diff --git a/3rdparty/glslang/SPIRV/GlslangToSpv.cpp b/3rdparty/glslang/SPIRV/GlslangToSpv.cpp index 6a28a1c1c..dab988462 100755 --- a/3rdparty/glslang/SPIRV/GlslangToSpv.cpp +++ b/3rdparty/glslang/SPIRV/GlslangToSpv.cpp @@ -7007,6 +7007,7 @@ void GlslangToSpv(const glslang::TIntermediate& intermediate, std::vector nul + - set PATH=C:\ninja;%PATH% + +before_build: + - git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers.git external/spirv-headers + - git clone --depth=1 https://github.com/google/googletest.git external/googletest + - git clone --depth=1 https://github.com/google/effcee.git external/effcee + - git clone --depth=1 https://github.com/google/re2.git external/re2 + # Set path and environment variables for the current Visual Studio version + - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2013" (call "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat" x86_amd64) + - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" (call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" x86_amd64) + +build: + parallel: true # enable MSBuild parallel builds + verbosity: minimal + +build_script: + - mkdir build && cd build + - cmake -GNinja -DSPIRV_BUILD_COMPRESSION=ON -DCMAKE_BUILD_TYPE=%CONFIGURATION% -DCMAKE_INSTALL_PREFIX=install -DRE2_BUILD_TESTING=OFF .. + - ninja install + +test_script: + - ctest -C %CONFIGURATION% --output-on-failure --timeout 300 + +after_test: + # Zip build artifacts for uploading and deploying + - cd install + - 7z a SPIRV-Tools-master-windows-"%PLATFORM%"-"%CONFIGURATION%".zip *\* + +artifacts: + - path: build\install\*.zip + name: artifacts-zip + +deploy: + - provider: GitHub + auth_token: + secure: TMfcScKzzFIm1YgeV/PwCRXFDCw8Xm0wY2Vb2FU6WKlbzb5eUITTpr6I5vHPnAxS + release: master-tot + description: "Continuous build of the latest master branch by Appveyor and Travis CI" + artifact: artifacts-zip + draft: false + prerelease: false + force_update: true + on: + branch: master + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 diff --git a/3rdparty/spirv-tools/.clang-format b/3rdparty/spirv-tools/.clang-format new file mode 100644 index 000000000..2b5d4a50b --- /dev/null +++ b/3rdparty/spirv-tools/.clang-format @@ -0,0 +1,5 @@ +--- +Language: Cpp +BasedOnStyle: Google +DerivePointerAlignment: false +... diff --git a/3rdparty/spirv-tools/.gitignore b/3rdparty/spirv-tools/.gitignore new file mode 100644 index 000000000..e5d8db4b1 --- /dev/null +++ b/3rdparty/spirv-tools/.gitignore @@ -0,0 +1,15 @@ +/build* +.ycm_extra_conf.py* +compile_commands.json +/external/googletest +/external/SPIRV-Headers +/external/spirv-headers +/external/effcee +/external/re2 +/TAGS +/.clang_complete +/utils/clang-format-diff.py + +# Vim +[._]*.s[a-w][a-z] +*~ diff --git a/3rdparty/spirv-tools/.travis.yml b/3rdparty/spirv-tools/.travis.yml new file mode 100644 index 000000000..b4f87cd7b --- /dev/null +++ b/3rdparty/spirv-tools/.travis.yml @@ -0,0 +1,133 @@ +# Linux Build Configuration for Travis + +language: cpp + +os: + - linux + - osx + +# Use Ubuntu 14.04 LTS (Trusty) as the Linux testing environment. +dist: trusty +sudo: false + +env: + global: + - secure: IoR/Xe9E+NnLAeI23WrmUsGQn5rocz+XRYUk+BbaoKiIRYm4q72GKyypRoOGLu7wImOXFSvnN/dpdnqIpx4W0NfsSvNdlXyhDy+wvT1kzTt77dJGnkGZTZ2SBOtC9AECLy4sqM9HG0rYRR6WfXcnP2GlrE5f2aF07aISQbOUsQMvyyhtCmVAzIigK1zIUto5I0pNenvo/Y+ur+mEvTh+FtaoDIGepCbZlCc+OxqRXwXNlI7mDXbzLPmTB1FWTGsrZdRX8czF9tN9Y+T79DQjB4Lcyyeow8yU9NBVlgzZJcp1xI0UIskRT8gVrXmBYL2dMeHnDQuhxjEg9n7jfr3ptA9rgwMaSsgdaLwuBXgtPuqVgUYDpE1cP8WI8q38MXX0I6psTs/WHu+z+5UwfjzpPOHmGdVt48o8ymFTapvD5Cf1+uJyk73QkyStnPIdBF1N9Yx5sD7HN28K6/Ro12sCCePHUZ9Uz1DdZI6XxkgCNKNwao0csAyvODxD6Ee43mkExtviB8BJY5jWLIMTdGhgEGH2sRqils8IDW0p8AOTPM4UC7iA7hdg3pA+XMvBHvP9ixsY7tuB+yR2AfnFaSw2DVbwI5GgFdFMNHXYuL+9V9Wuh3keBKYQT/Hy1YvxjQ/t9UouYHqEsyVFUl3R4lEAM9+qSRsRu+EKmcSO2QtCsWc= + matrix: + # Each line is a set of environment variables set before a build. + # Thus each line represents a different build configuration. + - BUILD_TYPE=RelWithDebInfo + - BUILD_TYPE=Debug + +compiler: + - clang + - gcc + +matrix: + fast_finish: true + include: + # Additional build using Android NDK with android-cmake + - env: BUILD_ANDROID_CMAKE=ON + # Additional build using Android NDK with Android.mk + - env: BUILD_ANDROID_MK=ON + # Additional check over format + - env: CHECK_FORMAT=ON + exclude: + # Skip GCC builds on macOS. + - os: osx + compiler: gcc + +cache: + apt: true + +git: + depth: 1 + +branches: + only: + - master + +before_install: + - if [[ "$BUILD_ANDROID_CMAKE" == "ON" ]] || [[ "$BUILD_ANDROID_MK" == "ON" ]]; then + git clone --depth=1 https://github.com/urho3d/android-ndk.git $HOME/android-ndk; + export ANDROID_NDK=$HOME/android-ndk; + git clone --depth=1 https://github.com/taka-no-me/android-cmake.git $HOME/android-cmake; + export TOOLCHAIN_PATH=$HOME/android-cmake/android.toolchain.cmake; + fi + - if [[ "$CHECK_FORMAT" == "ON" ]]; then + curl -L http://llvm.org/svn/llvm-project/cfe/trunk/tools/clang-format/clang-format-diff.py -o utils/clang-format-diff.py; + fi + +before_script: + - git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers external/spirv-headers + - git clone --depth=1 https://github.com/google/googletest external/googletest + - git clone --depth=1 https://github.com/google/effcee external/effcee + - git clone --depth=1 https://github.com/google/re2 external/re2 + +script: + # Due to the limitation of Travis platform, we cannot start too many concurrent jobs. + # Otherwise GCC will panic with internal error, possibility because of memory issues. + # ctest with the current tests doesn't profit from using more than 4 threads. + - export NPROC=4 + - mkdir build && cd build + - if [[ "$BUILD_ANDROID_MK" == "ON" ]]; then + export BUILD_DIR=$(pwd); + mkdir ${BUILD_DIR}/libs; + mkdir ${BUILD_DIR}/app; + $ANDROID_NDK/ndk-build -C ../android_test NDK_PROJECT_PATH=. + NDK_LIBS_OUT=${BUILD_DIR}/libs + NDK_APP_OUT=${BUILD_DIR}/app -j${NPROC}; + elif [[ "$BUILD_ANDROID_CMAKE" == "ON" ]]; then + cmake -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_PATH} + -DANDROID_NATIVE_API_LEVEL=android-9 + -DCMAKE_BUILD_TYPE=Release + -DANDROID_ABI="armeabi-v7a with NEON" + -DSPIRV_BUILD_COMPRESSION=ON + -DSPIRV_SKIP_TESTS=ON ..; + make -j${NPROC}; + elif [[ "$CHECK_FORMAT" == "ON" ]]; then + cd ..; + ./utils/check_code_format.sh; + else + cmake -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DSPIRV_BUILD_COMPRESSION=ON -DCMAKE_INSTALL_PREFIX=install ..; + make -j${NPROC} install; + ctest -j${NPROC} --output-on-failure --timeout 300; + fi + +after_success: + # Create tarball for deployment + - if [[ "${CC}" == "clang" && "${BUILD_ANDROID_MK}" != "ON" && "${BUILD_ANDROID_CMAKE}" != "ON" && "${CHECK_FORMAT}" != "ON" ]]; then + cd install; + export TARBALL=SPIRV-Tools-master-${TRAVIS_OS_NAME}-${BUILD_TYPE}.zip; + find . -print | zip -@ ${TARBALL}; + fi + +before_deploy: + # Tag the current master top of the tree as "master-tot". + # Travis CI relies on the tag name to push to the correct release. + - git config --global user.name "Travis CI" + - git config --global user.email "builds@travis-ci.org" + - git tag -f master-tot + - git push -q -f https://${spirvtoken}@github.com/KhronosGroup/SPIRV-Tools --tags + +deploy: + provider: releases + api_key: ${spirvtoken} + on: + branch: master + condition: ${CC} == clang && ${BUILD_ANDROID_MK} != ON && ${BUILD_ANDROID_CMAKE} != ON && ${CHECK_FORMAT} != ON + file: ${TARBALL} + skip_cleanup: true + overwrite: true + +notifications: + email: + recipients: + - andreyt@google.com + - antiagainst@google.com + - awoloszyn@google.com + - dneto@google.com + - ehsann@google.com + - qining@google.com + on_success: change + on_failure: always diff --git a/3rdparty/spirv-tools/Android.mk b/3rdparty/spirv-tools/Android.mk new file mode 100644 index 000000000..b775541f3 --- /dev/null +++ b/3rdparty/spirv-tools/Android.mk @@ -0,0 +1,315 @@ +LOCAL_PATH := $(call my-dir) +SPVTOOLS_OUT_PATH=$(if $(call host-path-is-absolute,$(TARGET_OUT)),$(TARGET_OUT),$(abspath $(TARGET_OUT))) +SPVHEADERS_LOCAL_PATH := $(LOCAL_PATH)/external/spirv-headers + +SPVTOOLS_SRC_FILES := \ + source/assembly_grammar.cpp \ + source/binary.cpp \ + source/diagnostic.cpp \ + source/disassemble.cpp \ + source/ext_inst.cpp \ + source/enum_string_mapping.cpp \ + source/extensions.cpp \ + source/id_descriptor.cpp \ + source/libspirv.cpp \ + source/name_mapper.cpp \ + source/opcode.cpp \ + source/operand.cpp \ + source/parsed_operand.cpp \ + source/print.cpp \ + source/software_version.cpp \ + source/spirv_endian.cpp \ + source/spirv_target_env.cpp \ + source/spirv_validator_options.cpp \ + source/table.cpp \ + source/text.cpp \ + source/text_handler.cpp \ + source/util/bit_stream.cpp \ + source/util/parse_number.cpp \ + source/util/string_utils.cpp \ + source/util/timer.cpp \ + source/val/basic_block.cpp \ + source/val/construct.cpp \ + source/val/function.cpp \ + source/val/instruction.cpp \ + source/val/validation_state.cpp \ + source/validate.cpp \ + source/validate_adjacency.cpp \ + source/validate_arithmetics.cpp \ + source/validate_atomics.cpp \ + source/validate_barriers.cpp \ + source/validate_bitwise.cpp \ + source/validate_builtins.cpp \ + source/validate_capability.cpp \ + source/validate_cfg.cpp \ + source/validate_composites.cpp \ + source/validate_conversion.cpp \ + source/validate_datarules.cpp \ + source/validate_decorations.cpp \ + source/validate_derivatives.cpp \ + source/validate_ext_inst.cpp \ + source/validate_id.cpp \ + source/validate_image.cpp \ + source/validate_instruction.cpp \ + source/validate_layout.cpp \ + source/validate_literals.cpp \ + source/validate_logicals.cpp \ + source/validate_primitives.cpp \ + source/validate_type_unique.cpp + +SPVTOOLS_OPT_SRC_FILES := \ + source/opt/aggressive_dead_code_elim_pass.cpp \ + source/opt/basic_block.cpp \ + source/opt/block_merge_pass.cpp \ + source/opt/build_module.cpp \ + source/opt/cfg.cpp \ + source/opt/cfg_cleanup_pass.cpp \ + source/opt/ccp_pass.cpp \ + source/opt/common_uniform_elim_pass.cpp \ + source/opt/compact_ids_pass.cpp \ + source/opt/composite.cpp \ + source/opt/const_folding_rules.cpp \ + source/opt/constants.cpp \ + source/opt/copy_prop_arrays.cpp \ + source/opt/dead_branch_elim_pass.cpp \ + source/opt/dead_insert_elim_pass.cpp \ + source/opt/dead_variable_elimination.cpp \ + source/opt/decoration_manager.cpp \ + source/opt/def_use_manager.cpp \ + source/opt/dominator_analysis.cpp \ + source/opt/dominator_tree.cpp \ + source/opt/eliminate_dead_constant_pass.cpp \ + source/opt/eliminate_dead_functions_pass.cpp \ + source/opt/feature_manager.cpp \ + source/opt/flatten_decoration_pass.cpp \ + source/opt/fold.cpp \ + source/opt/folding_rules.cpp \ + source/opt/fold_spec_constant_op_and_composite_pass.cpp \ + source/opt/freeze_spec_constant_value_pass.cpp \ + source/opt/function.cpp \ + source/opt/if_conversion.cpp \ + source/opt/inline_pass.cpp \ + source/opt/inline_exhaustive_pass.cpp \ + source/opt/inline_opaque_pass.cpp \ + source/opt/insert_extract_elim.cpp \ + source/opt/instruction.cpp \ + source/opt/instruction_list.cpp \ + source/opt/ir_context.cpp \ + source/opt/ir_loader.cpp \ + source/opt/licm_pass.cpp \ + source/opt/local_access_chain_convert_pass.cpp \ + source/opt/local_redundancy_elimination.cpp \ + source/opt/local_single_block_elim_pass.cpp \ + source/opt/local_single_store_elim_pass.cpp \ + source/opt/local_ssa_elim_pass.cpp \ + source/opt/loop_descriptor.cpp \ + source/opt/loop_peeling.cpp \ + source/opt/loop_unroller.cpp \ + source/opt/loop_unswitch_pass.cpp \ + source/opt/loop_utils.cpp \ + source/opt/mem_pass.cpp \ + source/opt/merge_return_pass.cpp \ + source/opt/module.cpp \ + source/opt/optimizer.cpp \ + source/opt/pass.cpp \ + source/opt/pass_manager.cpp \ + source/opt/private_to_local_pass.cpp \ + source/opt/propagator.cpp \ + source/opt/redundancy_elimination.cpp \ + source/opt/remove_duplicates_pass.cpp \ + source/opt/replace_invalid_opc.cpp \ + source/opt/scalar_analysis.cpp \ + source/opt/scalar_analysis_simplification.cpp \ + source/opt/scalar_replacement_pass.cpp \ + source/opt/set_spec_constant_default_value_pass.cpp \ + source/opt/simplification_pass.cpp \ + source/opt/ssa_rewrite_pass.cpp \ + source/opt/strength_reduction_pass.cpp \ + source/opt/strip_debug_info_pass.cpp \ + source/opt/strip_reflect_info_pass.cpp \ + source/opt/type_manager.cpp \ + source/opt/types.cpp \ + source/opt/unify_const_pass.cpp \ + source/opt/value_number_table.cpp \ + source/opt/workaround1209.cpp + +# Locations of grammar files. +# +# TODO(dneto): Build a single set of tables that embeds versioning differences on +# a per-item basis. That must happen before SPIR-V 1.4, etc. +# https://github.com/KhronosGroup/SPIRV-Tools/issues/1195 +SPV_CORE10_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/1.0/spirv.core.grammar.json +SPV_CORE11_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/1.1/spirv.core.grammar.json +SPV_CORE12_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/1.2/spirv.core.grammar.json +SPV_COREUNIFIED1_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/unified1/spirv.core.grammar.json +SPV_CORELATEST_GRAMMAR=$(SPV_COREUNIFIED1_GRAMMAR) +SPV_GLSL_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/1.2/extinst.glsl.std.450.grammar.json +SPV_OPENCL_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/1.2/extinst.opencl.std.100.grammar.json +# TODO(dneto): I expect the DebugInfo grammar file to eventually migrate to SPIRV-Headers +SPV_DEBUGINFO_GRAMMAR=$(LOCAL_PATH)/source/extinst.debuginfo.grammar.json + +define gen_spvtools_grammar_tables +$(call generate-file-dir,$(1)/core.insts-1.0.inc) +$(1)/core.insts-1.0.inc $(1)/operand.kinds-1.0.inc $(1)/glsl.std.450.insts.inc $(1)/opencl.std.insts.inc: \ + $(LOCAL_PATH)/utils/generate_grammar_tables.py \ + $(SPV_CORE10_GRAMMAR) \ + $(SPV_GLSL_GRAMMAR) \ + $(SPV_OPENCL_GRAMMAR) \ + $(SPV_DEBUGINFO_GRAMMAR) + @$(HOST_PYTHON) $(LOCAL_PATH)/utils/generate_grammar_tables.py \ + --spirv-core-grammar=$(SPV_CORE10_GRAMMAR) \ + --extinst-glsl-grammar=$(SPV_GLSL_GRAMMAR) \ + --extinst-opencl-grammar=$(SPV_OPENCL_GRAMMAR) \ + --extinst-debuginfo-grammar=$(SPV_DEBUGINFO_GRAMMAR) \ + --core-insts-output=$(1)/core.insts-1.0.inc \ + --glsl-insts-output=$(1)/glsl.std.450.insts.inc \ + --opencl-insts-output=$(1)/opencl.std.insts.inc \ + --operand-kinds-output=$(1)/operand.kinds-1.0.inc + @echo "[$(TARGET_ARCH_ABI)] Grammar v1.0 : instructions & operands <= grammar JSON files" +$(1)/core.insts-1.1.inc $(1)/operand.kinds-1.1.inc: \ + $(LOCAL_PATH)/utils/generate_grammar_tables.py \ + $(SPV_CORE11_GRAMMAR) \ + $(SPV_DEBUGINFO_GRAMMAR) + @$(HOST_PYTHON) $(LOCAL_PATH)/utils/generate_grammar_tables.py \ + --spirv-core-grammar=$(SPV_CORE11_GRAMMAR) \ + --extinst-debuginfo-grammar=$(SPV_DEBUGINFO_GRAMMAR) \ + --core-insts-output=$(1)/core.insts-1.1.inc \ + --operand-kinds-output=$(1)/operand.kinds-1.1.inc + @echo "[$(TARGET_ARCH_ABI)] Grammar v1.1 : instructions & operands <= grammar JSON files" +$(1)/core.insts-1.2.inc $(1)/operand.kinds-1.2.inc: \ + $(LOCAL_PATH)/utils/generate_grammar_tables.py \ + $(SPV_CORE12_GRAMMAR) \ + $(SPV_DEBUGINFO_GRAMMAR) + @$(HOST_PYTHON) $(LOCAL_PATH)/utils/generate_grammar_tables.py \ + --spirv-core-grammar=$(SPV_CORE12_GRAMMAR) \ + --extinst-debuginfo-grammar=$(SPV_DEBUGINFO_GRAMMAR) \ + --core-insts-output=$(1)/core.insts-1.2.inc \ + --operand-kinds-output=$(1)/operand.kinds-1.2.inc + @echo "[$(TARGET_ARCH_ABI)] Grammar v1.2 : instructions & operands <= grammar JSON files" +$(1)/core.insts-unified1.inc $(1)/operand.kinds-unified1.inc: \ + $(LOCAL_PATH)/utils/generate_grammar_tables.py \ + $(SPV_COREUNIFIED1_GRAMMAR) \ + $(SPV_DEBUGINFO_GRAMMAR) + @$(HOST_PYTHON) $(LOCAL_PATH)/utils/generate_grammar_tables.py \ + --spirv-core-grammar=$(SPV_COREUNIFIED1_GRAMMAR) \ + --extinst-debuginfo-grammar=$(SPV_DEBUGINFO_GRAMMAR) \ + --core-insts-output=$(1)/core.insts-unified1.inc \ + --operand-kinds-output=$(1)/operand.kinds-unified1.inc + @echo "[$(TARGET_ARCH_ABI)] Grammar v1.3 (from unified1) : instructions & operands <= grammar JSON files" +$(LOCAL_PATH)/source/opcode.cpp: $(1)/core.insts-1.0.inc $(1)/core.insts-1.1.inc $(1)/core.insts-1.2.inc $(1)/core.insts-unified1.inc +$(LOCAL_PATH)/source/operand.cpp: $(1)/operand.kinds-1.0.inc $(1)/operand.kinds-1.1.inc $(1)/operand.kinds-1.2.inc $(1)/operand.kinds-unified1.inc +$(LOCAL_PATH)/source/ext_inst.cpp: \ + $(1)/glsl.std.450.insts.inc \ + $(1)/opencl.std.insts.inc \ + $(1)/debuginfo.insts.inc \ + $(1)/spv-amd-gcn-shader.insts.inc \ + $(1)/spv-amd-shader-ballot.insts.inc \ + $(1)/spv-amd-shader-explicit-vertex-parameter.insts.inc \ + $(1)/spv-amd-shader-trinary-minmax.insts.inc +endef +$(eval $(call gen_spvtools_grammar_tables,$(SPVTOOLS_OUT_PATH))) + + +define gen_spvtools_lang_headers +# Generate language-specific headers. So far we only generate C headers +# $1 is the output directory. +# $2 is the base name of the header file, e.g. "DebugInfo". +# $3 is the grammar file containing token definitions. +$(call generate-file-dir,$(1)/$(2).h) +$(1)/$(2).h : \ + $(LOCAL_PATH)/utils/generate_language_headers.py \ + $(3) + @$(HOST_PYTHON) $(LOCAL_PATH)/utils/generate_language_headers.py \ + --extinst-name=$(2) \ + --extinst-grammar=$(3) \ + --extinst-output-base=$(1)/$(2) + @echo "[$(TARGET_ARCH_ABI)] Generate language specific header for $(2): headers <= grammar" +$(LOCAL_PATH)/source/ext_inst.cpp: $(1)/$(2).h +endef +# We generate language-specific headers for DebugInfo +$(eval $(call gen_spvtools_lang_headers,$(SPVTOOLS_OUT_PATH),DebugInfo,$(SPV_DEBUGINFO_GRAMMAR))) + + +define gen_spvtools_vendor_tables +$(call generate-file-dir,$(1)/$(2).insts.inc) +$(1)/$(2).insts.inc : \ + $(LOCAL_PATH)/utils/generate_grammar_tables.py \ + $(LOCAL_PATH)/source/extinst.$(2).grammar.json + @$(HOST_PYTHON) $(LOCAL_PATH)/utils/generate_grammar_tables.py \ + --extinst-vendor-grammar=$(LOCAL_PATH)/source/extinst.$(2).grammar.json \ + --vendor-insts-output=$(1)/$(2).insts.inc + @echo "[$(TARGET_ARCH_ABI)] Vendor extended instruction set: $(2) tables <= grammar" +$(LOCAL_PATH)/source/ext_inst.cpp: $(1)/$(2).insts.inc +endef +# Vendor extended instruction sets, with grammars from SPIRV-Tools source tree. +SPV_NONSTANDARD_EXTINST_GRAMMARS=$(foreach F,$(wildcard $(LOCAL_PATH)/source/extinst.*.grammar.json),$(patsubst extinst.%.grammar.json,%,$(notdir $F))) +$(foreach E,$(SPV_NONSTANDARD_EXTINST_GRAMMARS),$(eval $(call gen_spvtools_vendor_tables,$(SPVTOOLS_OUT_PATH),$E))) + +define gen_spvtools_enum_string_mapping +$(call generate-file-dir,$(1)/extension_enum.inc.inc) +$(1)/extension_enum.inc $(1)/enum_string_mapping.inc: \ + $(LOCAL_PATH)/utils/generate_grammar_tables.py \ + $(SPV_CORELATEST_GRAMMAR) + @$(HOST_PYTHON) $(LOCAL_PATH)/utils/generate_grammar_tables.py \ + --spirv-core-grammar=$(SPV_CORELATEST_GRAMMAR) \ + --extinst-debuginfo-grammar=$(SPV_DEBUGINFO_GRAMMAR) \ + --extension-enum-output=$(1)/extension_enum.inc \ + --enum-string-mapping-output=$(1)/enum_string_mapping.inc + @echo "[$(TARGET_ARCH_ABI)] Generate enum<->string mapping <= grammar JSON files" +# Generated header extension_enum.inc is transitively included by table.h, which is +# used pervasively. Capture the pervasive dependency. +$(foreach F,$(SPVTOOLS_SRC_FILES) $(SPVTOOLS_OPT_SRC_FILES),$(LOCAL_PATH)/$F ) \ + : $(1)/extension_enum.inc +$(LOCAL_PATH)/source/enum_string_mapping.cpp: $(1)/enum_string_mapping.inc +endef +$(eval $(call gen_spvtools_enum_string_mapping,$(SPVTOOLS_OUT_PATH))) + +define gen_spvtools_build_version_inc +$(call generate-file-dir,$(1)/dummy_filename) +$(1)/build-version.inc: \ + $(LOCAL_PATH)/utils/update_build_version.py \ + $(LOCAL_PATH)/CHANGES + @$(HOST_PYTHON) $(LOCAL_PATH)/utils/update_build_version.py \ + $(LOCAL_PATH) $(1)/build-version.inc + @echo "[$(TARGET_ARCH_ABI)] Generate : build-version.inc <= CHANGES" +$(LOCAL_PATH)/source/software_version.cpp: $(1)/build-version.inc +endef +$(eval $(call gen_spvtools_build_version_inc,$(SPVTOOLS_OUT_PATH))) + +define gen_spvtools_generators_inc +$(call generate-file-dir,$(1)/dummy_filename) +$(1)/generators.inc: \ + $(LOCAL_PATH)/utils/generate_registry_tables.py \ + $(SPVHEADERS_LOCAL_PATH)/include/spirv/spir-v.xml + @$(HOST_PYTHON) $(LOCAL_PATH)/utils/generate_registry_tables.py \ + --xml=$(SPVHEADERS_LOCAL_PATH)/include/spirv/spir-v.xml \ + --generator-output=$(1)/generators.inc + @echo "[$(TARGET_ARCH_ABI)] Generate : generators.inc <= spir-v.xml" +$(LOCAL_PATH)/source/opcode.cpp: $(1)/generators.inc +endef +$(eval $(call gen_spvtools_generators_inc,$(SPVTOOLS_OUT_PATH))) + +include $(CLEAR_VARS) +LOCAL_MODULE := SPIRV-Tools +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/include \ + $(LOCAL_PATH)/source \ + $(LOCAL_PATH)/external/spirv-headers/include \ + $(SPVTOOLS_OUT_PATH) +LOCAL_EXPORT_C_INCLUDES := \ + $(LOCAL_PATH)/include +LOCAL_CXXFLAGS:=-std=c++11 -fno-exceptions -fno-rtti -Werror +LOCAL_SRC_FILES:= $(SPVTOOLS_SRC_FILES) +include $(BUILD_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := SPIRV-Tools-opt +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/include \ + $(LOCAL_PATH)/source \ + $(LOCAL_PATH)/external/spirv-headers/include \ + $(SPVTOOLS_OUT_PATH) +LOCAL_CXXFLAGS:=-std=c++11 -fno-exceptions -fno-rtti -Werror +LOCAL_STATIC_LIBRARIES:=SPIRV-Tools +LOCAL_SRC_FILES:= $(SPVTOOLS_OPT_SRC_FILES) +include $(BUILD_STATIC_LIBRARY) diff --git a/3rdparty/spirv-tools/CHANGES b/3rdparty/spirv-tools/CHANGES new file mode 100644 index 000000000..9285dbfb9 --- /dev/null +++ b/3rdparty/spirv-tools/CHANGES @@ -0,0 +1,483 @@ +Revision history for SPIRV-Tools + +v2018.3-dev 2018-04-06 + - General: + - Support SPV_EXT_descriptor_indexing + - Support SPV_GOOGLE_decorate_string + - Support SPV_GOOGLE_hlsl_functionality1 + - Support SPV_NV_shader_subgroup_partitioned + - Use "unified1" grammar from SPIRV-Headers + - Simplify support for new extensions. Assembler, disassembler, and simple validation + support is automatic if new tokens are introduced with appropriate extension + attributes in the "unified1" SPIR-V core grammar. + - Disassembler: Emit more digits on floating point, to reliably reproduce all + significand bits. (Use std::max_digits10 instead of std::digits10) + - Optimizer: + - Add --strip-reflect + - Add --time-report + - Merge-return now works with structured control flow. + - New (faster) SSA rewriter to convert local loads and stores to SSA IDs and phis. + Can replace load/store elimination passes. + - Fix instruction folding case: insertion that feeds and extract, when the extract + remains. + - Copy propagate arrays, in simple cases. + - Better handling of OpImageTexelPointer + - Add loop peeling internal utility. + - Initial utilities for scalar evolution. + - Validator: + - Check Vulkan built-in variables + - Check Vulkan-specific atomic result type rule. + - Relax control barrier check for SPIR-V 1.3. Fixes #1427 + - Check OpPhi. + - Stop checking sizes derived from spec-constants. + - Re-enable checks for OpUConvert. + - Fixes: + #898: Linker properly removes FuncParamAttr from imported symbols. + #924, #1174: Fix handling of decoration groups in optimizer, linker. + #1404: Don't optimize away the compute compute workgroup size constant. + #1407: Remove a bad assertion + #1456: Fix bug in SSA rewriter related to variables updated in loops. + +v2018.2 2018-03-07 + - General: + - Support SPIR-V 1.3 and Vulkan 1.1. + - Default target environment is now SPIR-V 1.3. For command-line tools, + use the --target-env option to override the default. Examples: + # Generate a SPIR-V 1.0 binary instead of SPIR-V 1.3 + spirv-as --target-env spv1.0 a.spvasm -o a.spv + spirv-as --target-env vulkan1.0 a.spvasm -o a.spv + # Validate as Vulkan 1.0 + spirv-val --target-env vulkan1.0 a.spv + - Support SPV_GOOGLE_decorate_string and SPV_GOOGLE_hlsl_functionality1 + - Fixes: + - Fix Android.mk build. Compilation was failing due to missing definitions of + SpvCapabilityFloat16ImageAMD and other enumerated values. + - Optimizer: Avoid generating duplicate names when merging types. + - #1375: Validator: SPV_AMD_gpu_shaer_half_float implicitly allows declaration + of the 16-bit floating point type. + - #1376: Optimizer: Avoid folding half-precision float. + +v2018.1 2018-03-02 + - General: + - Support Visual Studio 2013 again. (Continue support for VS 2015 and VS 2017.) + - Support building SPIRV-Tools as a shared library. + - Improve the HLSL legalization optimization recipe. #1311 + - Optimizer: + - General speedups. + - Remove generic dead code elimination functionality from transforms: + --eliminate-local-single-block + --eliminate-local-single-store + --eliminate-local-multi-store + To recover the previous behaviour, a recipe using those transforms should now + also invoke the --eliminate-dead-code-aggressive transform. + - Improve folding, including coverage for floating point, OpSelect, and arithmetic + with non-trivial constant operands. + - Add loop-invariant code motion pass. + - Add loop-unrolling pass, for honouring unroll hits. + - Add loop-unswitch pass. + - Add instruction simplification pass. + - Aggressive dead code elimination: Understands capability hierarchy when finding + instructions it can eliminate (combinators). (PR #1268) + - CCP can now fold floating point arithmetic. #1311 + - Validator: + - Validate barrier instructions. + - Check Vulkan-specific rules for atomics. + - Check Vulkan prohibition of Location or Component decorations on BuiltIn variables. + - Linker: + - Add --verify-ids option + - Add option to allow a resulting module to be partially linked. + - Handle OpModuleProcessed (instructions in SPIR-V layout section 7c) + - Fixes: + - #1265: Optimizer: Fix use-after free bug in if-conversion. (Fix object lifecycle bug + in type manager.) + - #1282: Fix new warnings found by GCC 8.0.1. + - #1285: Optimizer: Fix random failures during inlining. (Dangling references in DefUseManager) + - #1295: Optimizer: Fix incorrect handling of Phi nodes in CCP. + - #1300: Fix CCP: avoid bad CCP transitions and unsettled values. + - #1304: Avoid static-duration variables of class type (with constructors). + - #1323: Fix folding of an insert composite feeding a composite extract. + - #1339: Fix CCP: Handle OpConstantNull boolean values as conditions. + - #1341: DCEInst: Keep atomic instructions (and some others with side effects). + - #1354: Don't fold integer division. + - #1357: Support OpConstantNull in folding. + - #1361: CCP: Fix handling of non-constant module-scope values + +v2018.0 2018-02-02 + - General + - VisualStudio 2013 is no longer supported. VisualStudio 2015 is supported. + - Use "include/unified1" directory from SPIRV-Headers. Requires recent SPIRV-Headers source. + - Disassembler: spirv-dis adds --color option to force color disassembly. + - Optimizer: + - Add pass to eliminate dead insertions. + - Aggressive dead code elimination now removes OpSwitch constructs. + - Block merging occurs in more cases. + - Add driver workaround transform: replace OpUnreachable with harmless branch to merge. + - Improve instruction folding framework. + - Add loop analysis. + - Add scalar replacement of aggregates to size-optimization recipe. + - Add pass to replace instructions invalid for a shader stage, with a harmless value. + This changes the semantics of the program! Not for general use! + - Rearragne and add passes to performance-optimization recipe, to produce better results. + - Validator: + - Validate OpenCL extended instructions. + - Shaders can't perform atomics on floats. + - Validate memory semantics values in atomics. + - Validate instruction-adjacency constraints, e.g. OpPhi predecessors, merge instructions + immediately precede branches. + - Fixes: + - PR 1198: Optimizer: Fix CCP in presence of matrix constants. + - #1199: Optimizer: Fix CCP: don't propagate spec constants. + - #1203: Optimizer: Fix common uniform elim bug introduced by refactoring. + - #1210: Optimizer: Aggressive dead code elimination: Fix 'break' identification. + - #1212: Optimizer: Aggressive dead code elimination: Was skipping too many instructions. + - #1214: Optimizer: Aggressive dead code elimination: Fix infinite loop. + - #1228: Optimizer: Fix CCP: Handling of varying Phi nodes; was resulting in infinite loop. + - #1245: Optimizer: Dead branch elimination: Avoid a null pointer dereference. + - #1250: Optimizer: Dead branch elimination: Avoid spuriously reporting a change. + +v2017.3 2018-01-12 + - General: + - Support DebugInfo extended instruction set, targeted at OpenCL environments. + See the SPIR-V Registry. + - Generate a SPIRV-Tools.pc file for pkg-config. + - Optimizer: + - Progress for legalization of code generated from HLSL (issue #1118): + - Add --legalize-hlsl option to run transforms used to transform intermediate + code generated by HLSL to SPIR-V for Vulkan compilers. Those compilers + normally run these transforms automatically. This option is used for developing + those transforms. + - Add Private-to-Function variable conversion for modules with logical + addressing. + - Add --ccp: SSA Conditional Constant Propagation (CCP) + - Add --print-all to show disassembly for each optimization pass. + - Internal: Add loop descriptors and post-order tree iterator. + - Generalized dead branch elimination + - Aggressive dead code elimination (ADCE) now removes dead functions and + module-scope variables. + - Vector extract/insert elimination now optimizes through some cases of + VectorShuffle, and GLSL.std.450 Mix extended instruction. + - Validator: + - Add validation for GLSL.std.450 extended instruction set. + - Check out of bounds composite accesses, where that's statically computable. + Fixes #1112. + - Check upper bits of literal numbers that aren't a multiple of 32-bits wide. + - More validation of primitive instructions + - Add optional "relaxed" checking logical addressing mode to permit some + cases of pointer-to-pointer. Contributes to HLSL legalization (issue #1118). + - Fixes: + #1100: Validator: Image operand Sample can be used with OpImageSparseFetch, + OpImageSparseRead. + #1108: Remove duplicates transform was incorrectly removing non-duplicate + decorations. + #1111: Optimizer's type manager could reference deleted memory. + #1112: Fix decoration equality check, e.g. it is now symmetric. + #1129: Validator now disallows Dim=SupbassData for OpImageSparseRead. + #1143: Fix CCP: Was generating incorrect code for loops. + #1153: Fix CCP crash. + #1154: Optimizer's internal instruction-to-block mappings were sometimes + inconsistent. + #1159: Fix CCP infinite loop. + #1168: Fix dead branch elimination intermittently generating incorrect code. + Fixes https://github.com/KhronosGroup/glslang/issues/1205 + #1186: Fix validation of PackDouble2x32 and UnpackDouble2x32 + +v2017.2 2017-12-15 + - General: + - Support OpenCL 1.2, 2.0 target environments, including embedded profiles + - Add CONTRIBUTING.md + - Fix exit status code for spirv-link + - Disassember: Enable emitting ANSI colour codes to a string + - Library avoids polluting global namespace. The libraries can export C and C++ + symbols starting with "spv", or in a C++ namespace. Add a test for this. + - Linux release builds include debug information, for easier profiling + - Build bots no longer test VisualStudio 2013 + - Testing dependency RE2 requires VisualStudio 2015 or later + - Build bots check code formatting + - Optimizer: + - Add --skip-validation to spirv-opt + - Add dominance tree analysis + - Add generic value propagation engine + - Add global redundancy elimination within a function + - Add scalar replacement of function-scope variables of composite type + - Aggressive dead code elimination: Remove empty loops + - Killing an instruction notifies the IRContext + - IRContext::KillInst deletes the instruction + - Move CFG analysis to IRContext + - Add constant manager + - Fix: Don't consider derivative instructions as combinators. + - Fix: Don't delete an instruction twice in local dead-code-elimination + - Fix: Don't consider derivative instructions as combinators. + - Validator: + - Finish checking of image instructions (Section 3.32.10) + - Check sparse image instructions + - Check OpTypeImage, OpTypeSampleImage + - Check composite instructions (Section 3.32.12) + - Check atomic instructions (Section 3.32.18) + - Check OpEmitStreamVertex, OpEndStreamPrimitive instructions + - Re-enable validation of OpCopyObject + - OpKill, image ImplicitLod and QueryLod instructions can only be used in Fragment + shaders. + - Fixes for image instruction validation: + - Lod image operand only usable with ExplicitLod and OpImageFetch + - ExplicitLod Lod image operand must be float scalar + - OpImageFectch Lod image operand must be int scalar + - OpImageGather component operand must be 32-bits (integer scalar) + - OpImageQuerySizeLod Lod must be integer scalar + - Fixes: + #622: Remove names and decorations when inlining + #989: Aggressive dead code elim: Don't optimize away live breaks from a loop + #991: Fix validation of SPV_AMD_shader_ballot + #1004: Use after free of an instruction, in remove-duplicates transform + #1007: OpImageRead not required to return 4-component vector + #1009: OpImageRead can return scalar int/float types + #1011: OpImageWrite should allow scalar int/float texel types + #1012: Fix validat Dref type check + #1017: Load-store elimination considers variable initializations + #1034: Fix Windows debug build: operator< should be a weak ordering + #1083: Inlining: Set parent (function) for each inlined basic block. + #1075: Aggressive dead code elimination: Was leaving dangling references to + removed blocks. + +v2017.1 2017-11-23 + - Update README with details on the public_spirv_tools_dev@khronos.org mailing list. + - General: + - Automatically deploy built artifacts to GitHub Releases + - Add a Linker (module combiner). Under development. + - Add Android.mk for Android NDK builds. + - Add the 'effcee' library as an optional dependency for use in tests. + Eventually it will be a required dependency, once downstream projects have + a chance to adjust. Requires 're2' library. + - Avoid static-duration variables of class type (with constructors). + - Hack around bugs in gcc-4.8.1 template handling + - Faster opcode lookup + - Validator: + - Recognize extensions listed on SPIR-V registry, + through #25 SPV_AMD_shader_fragment_mask + - Validator issues an info message when it sees an unrecognized extension. + - Type check basic arithmetic operations + - Type check carry/extended arithmetic operations + - Type check vector arithmetic operations + - Type check Relational and Logical instructions + - Type check Bit instructions + - Check type uniqueness rules + - Check conversion instructions + - Check image instructions + - Check derivative instructions + - Check OpVectorShuffle + - Check OpBranchConditional + - OpModuleProcessed is only allowed after debug names section and before annotations + section. + - Checks the right kind of return is called for each function (void or non-void). + - Add option to relax type check when storing structs (--relax-store-struct) + - Optimizer: + - Refactoring internal representation of the module, including: + - IRContext: owns a module and manages analyses + - Instructions are owned by intrusive lists, and have unique IDs + - BasicBlock owns its instruction list. + - DefUseManager: change representation of uses, for faster processing + on large modules. + - Add high level recipes: -O, -Os, and -Oconfig + Recipes for -O and -Os are under development. + - Add eliminate-dead-function transform + - Add strength reduction transform: For now, convert multiply by power of 2 + to a bit shift. + - Add CFG cleanup transform + - Add removal of dead module-scope variables + - Add merge-return transform for modules without structured control flow + - Add redundancy elimination within a basic block (local value numbering) + - Extract-insert elimination: + - Recognize the case where the first instruction in the sequence is an + OpCompositeConstruct or OpConstantComposite + - Handle some cases of nested structs + - Dead branch elimination now can eliminate entire selection constructs + when all arms are dead. + - Compressing codec: + - Updated algorithm to 1.01, 1.02, 1.03 + - Not built by default. Use -DSPIRV_BUILD_COMPRESSION=ON to build. + - Codec can be parameterized by a customized model. + - Fixes: + #728: Fix decoration of inlined functions + #798: spirv-as should fail when given unrecognized long option + #800: Inliner: Fix inlining function into header of multi-block loop + #824: Eliminate-local-multi-store: Fix a crash + #826: Elimiante-local-multi-store: Fix a crash + #827: Fix crash when compact-ids transform runs before another transform. + #834: Add Cmake option to build the compressing codec. Off by default. + #911: Fix classification of Line and NoLine instructions + +v2017.0 2017-09-01 + - Update README to describe that assembler, disassembler, and binary parser support + are based on grammar files from the SPIRV-Headers repository. + +v2016.7 2017-09-01 + - Add SPIR-V 1.2 + - OpenCL 2.2 support is now based on SPIR-V 1.2 + - Support AMD extensions in assembler, disassembler: + SPV_AMD_gcn_shader + SPV_AMD_shader_ballot + SPV_AMD_shader_explicit_vertex_parameter + SPV_AMD_shader_trinary_minmax + SPV_AMD_gpu_shader_half_float + SPV_AMD_texture_gather_bias_lod + SPV_AMD_gpu_shader_int16 + - Optimizer: Add support for: + - Inline all function calls in entry points. + - Flatten decoration groups. Fixes #602 + - Id compaction (minimize Id bound). Fixes #624 + - Eliminate redundant composite insert followed by extract + - Simplify access chains to local variables + - Eliminate local variables with a single store, if possible + - Eliminate local variables with a several stores, if possible + - Eliminate loads and stores in same block to local variables + - Eliminate redundant insert/extract to composite values + - Aggressive dead instruction elimination + - Eliminate dead branches + - Merge blocks when the second can only be preceded by the first + - Eliminate ommon uniform loads + - Assembler: Add option to preserve numeric ids. Fixes #625 + - Add build target spirv-tools-vimsyntax to generate spvasm.vim, a SPIR-V + assembly syntax file for Vim. + - Version string: Allow overriding of wall clock timestamp with contents + of environment variable SOURCE_DATE_EPOCH. + - Validator implements relaxed rules for SPV_KHR_16bit_storage. + - CMake installation rules use GNUInstallDirs. For example, libraries + will be installed into a lib64 directory if that's the norm for the + current system. + - Fixes: + #500: Parameterize validator limit checks + #508: Support compilation under CYGWIN + #517: Fix validation when continue (or case) contstruct is also the head of a + nested control construct. + #551: If a merge block is reachable, it must be *strictly* dominated by its + header. + #548: Validator: Error when the reserved OpImageSparseSampleProj* opcodes + are used. + #611: spvtools::Optimizer was failing to save the module to the output + binary vector when all passes succeded without changes. + #629: The inline-entry-points-all optimization could generate invalidly + structured code when the inlined function had early returns. + #697: Optimizer's Instruction::ForEachInId method was skipping semantics-id + and scope-id. + #755: Inliner: Fix inlining of callee with single Return appearing before + the end of the function. + #776: Fix dead branch elimination in presence of complex but dead control + flow. + #781: SPV_KHR_variable_pointers allows duplicate pointer types + #782: Inliner: Fix remapping of non-label forward references in callee + #787: Inliner: Fix remapping of inlined entry block when called from + single block loop. + #790: Inliner: Fix remapping of inlined entry block when callee has + multiple returns. + +v2016.6 2016-12-13 + - Published the C++ interface for assembling, disassembling, validation, and + optimization. + - Support SPV_KHR_shader_draw_parameters in assembler, disassembler, parser. + - Validator: + - Add validator API accepting raw binary words + - Increased coverage: + - Checks "Data rules" in Universal Validation Rules, section 2.16.1 + - WIP: Universal Limits. + - The minimum mandated upper bounds are checked. + - TODO: Parameterize the validator to allow larger limits accepted by + a more than minimally capable implementation. + - OpSampledImage checks + - OpConstantComposite checks + - Id bound check + - Disasssembler: + - Generates friendly GLSL-based names for more builtin variables + - Generates friendly names for numeric OpConstant values + - Vendor tool info extracted from SPIR-V XML registry file. + - Fixes issues: + #429: Validator: Allow OpTypeForwardPointer and OpTypeStruct to reference + undefined IDs + #482: Validator: OpVariable initializer can be an ID of a module-scope variable + +v2016.5 2016-09-16 + - Support SPV_KHR_shader_ballot in assembler, disassembler, parser. + - Disassembler: Generate friendly names for built-in variables. + - Partial fixes: + #359: Add Emacs helper for automatically diassembling/assembling a SPIR-V + binary on file load/save. + - Fixes: + #414: Validator: Allow OpUndef for composite constants + #415: Validator: Phi can use its own value in some cases. + +v2016.4 2016-09-01 + - Relicensed under Apache 2.0 + - Add optimization passes (in API and spirv-opt command) + - Fold spec constants defined with OpSpecConstantOp and + OpSpecConstantComposite to normal constants with fixed value(s). + - Fixes issues: + #318: Relicensed under Apache 2.0 + +v2016.3 2016-08-24 + - Add target environment enums for OpenCL 2.1, OpenCL 2.2, + OpenGL 4.0, OpenGL 4.1, OpenGL 4.2, OpenGL 4.3, OpenGL 4.5. + - Add spirv-cfg, an experimental tool to dump the control flow graph + as a GraphiViz "dot" graph + - Add optimization pass: Eliminate dead constants. + - Add spirv-lesspipe.sh filter utility + - Fixes issues: + #288: Check def-use dominance rules for OpPhi (variable,parent) operands + #339: Allow OpUndef in types-constants-global-vars section, as required + by SPIR-V 1.0 Rev7, 1.1 Rev 3. + #340: Avoid race on mkdir during build + #365: Relax PointSize, ClipDistance, CullDistance capability check in all + environments not just Vulkan 1.0. + +v2016.2 2016-08-05 + - Validator is incomplete + - Checks ID use block is dominated by definition block + - Add optimization passes (in API and spirv-opt command) + - Strip debug info instructions + - Freeze spec constant to their default values + - Allow INotEqual as operation for OpSpecConstantOp + - Fixes bugs: + #270: validator: crash when continue construct is unreachable + #279: validator: infinite loop when analyzing some degenerate control + flow graphs + #286: validator: don't incorrectly generate def-use error for + (variable,parent) parameters to OpPhi + #290: disassembler: never generate bare % for an identifier + #295: validator: def-use dominance check should ignore unreachable uses + #276: validator: allow unreachable continue constructs + #297: validator: allow an unreachable block to branch to a reachable + merge block + +v2016.1 2016-07-19 + - Fix https://github.com/KhronosGroup/SPIRV-Tools/issues/261 + Turn off ClipDistance and CullDistance capability checks for Vulkan. + - The disassembler can emit friendly names based on debug info (OpName + instructions), and will infer somewhat friendly names for most types. + This is turned on by default for the spirv-dis command line tool. + - Updated to support SPIR-V 1.1 rev 2 + - Input StorageClass, Sampled1D capability, and SampledBuffer capability + do not require Shader capability anymore. + +v2016.0 2016-07-04 + + - Adds v. versioning, with "-dev" indicating + work in progress. The intent is to more easly report + and summarize functionality when SPIRV-Tools is incorporated + in downstream projects. + + - Summary of functionality (See the README.md for more): + - Supports SPIR-V 1.1 Rev 1 + - Supports SPIR-V 1.0 Rev 5 + - Supports GLSL std450 extended instructions 1.0 Rev 3 + - Supports OpenCL extended instructions 1.0 Rev 2 + - Assembler, disassembler are complete + - Supports floating point widths of 16, 32, 64 bits + - Supports integer widths up to 64 bits + - Validator is incomplete + - Checks capability requirements in most cases + - Checks module layout constraints + - Checks ID use-definition ordering constraints, + ignoring control flow + - Checks some control flow graph rules + - Optimizer is introduced, with few available transforms. + - Supported on Linux, OSX, Android, Windows + + - Fixes bugs: + - #143: OpenCL pow and pown arguments diff --git a/3rdparty/spirv-tools/CMakeLists.txt b/3rdparty/spirv-tools/CMakeLists.txt new file mode 100644 index 000000000..8e61db1ea --- /dev/null +++ b/3rdparty/spirv-tools/CMakeLists.txt @@ -0,0 +1,268 @@ +# Copyright (c) 2015-2016 The Khronos Group Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required(VERSION 2.8.12) +if (POLICY CMP0048) + cmake_policy(SET CMP0048 NEW) +endif() +if (POLICY CMP0054) + # Avoid dereferencing variables or interpret keywords that have been + # quoted or bracketed. + # https://cmake.org/cmake/help/v3.1/policy/CMP0054.html + cmake_policy(SET CMP0054 NEW) +endif() +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +project(spirv-tools) +enable_testing() +set(SPIRV_TOOLS "SPIRV-Tools") + +include(GNUInstallDirs) + +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") + add_definitions(-DSPIRV_LINUX) + set(SPIRV_TIMER_ENABLED ON) +elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows") + add_definitions(-DSPIRV_WINDOWS) +elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "CYGWIN") + add_definitions(-DSPIRV_WINDOWS) +elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") + add_definitions(-DSPIRV_MAC) +elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Android") + add_definitions(-DSPIRV_ANDROID) + set(SPIRV_TIMER_ENABLED ON) +elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "FreeBSD") + add_definitions(-DSPIRV_FREEBSD) +else() + message(FATAL_ERROR "Your platform '${CMAKE_SYSTEM_NAME}' is not supported!") +endif() + +if (${SPIRV_TIMER_ENABLED}) + add_definitions(-DSPIRV_TIMER_ENABLED) +endif() + +if ("${CMAKE_BUILD_TYPE}" STREQUAL "") + message(STATUS "No build type selected, default to Debug") + set(CMAKE_BUILD_TYPE "Debug") +endif() + +option(SKIP_SPIRV_TOOLS_INSTALL "Skip installation" ${SKIP_SPIRV_TOOLS_INSTALL}) +if(NOT ${SKIP_SPIRV_TOOLS_INSTALL}) + set(ENABLE_SPIRV_TOOLS_INSTALL ON) +endif() + +option(SPIRV_BUILD_COMPRESSION "Build SPIR-V compressing codec" OFF) + +option(SPIRV_WERROR "Enable error on warning" ON) +if(("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")) + set(COMPILER_IS_LIKE_GNU TRUE) +endif() +if(${COMPILER_IS_LIKE_GNU}) + set(SPIRV_WARNINGS -Wall -Wextra -Wnon-virtual-dtor -Wno-missing-field-initializers) + + option(SPIRV_WARN_EVERYTHING "Enable -Weverything" ${SPIRV_WARN_EVERYTHING}) + if(${SPIRV_WARN_EVERYTHING}) + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + set(SPIRV_WARNINGS ${SPIRV_WARNINGS} + -Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-padded) + elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + set(SPIRV_WARNINGS ${SPIRV_WARNINGS} -Wpedantic -pedantic-errors) + else() + message(STATUS "Unknown compiler ${CMAKE_CXX_COMPILER_ID}, " + "so SPIRV_WARN_EVERYTHING has no effect") + endif() + endif() + + if(${SPIRV_WERROR}) + set(SPIRV_WARNINGS ${SPIRV_WARNINGS} -Werror) + endif() +elseif(MSVC) + set(SPIRV_WARNINGS -D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS /wd4800) + + if(${SPIRV_WERROR}) + set(SPIRV_WARNINGS ${SPIRV_WARNINGS} /WX) + endif() +endif() + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/source) + +option(SPIRV_COLOR_TERMINAL "Enable color terminal output" ON) +if(${SPIRV_COLOR_TERMINAL}) + add_definitions(-DSPIRV_COLOR_TERMINAL) +endif() + +option(SPIRV_LOG_DEBUG "Enable excessive debug output" OFF) +if(${SPIRV_LOG_DEBUG}) + add_definitions(-DSPIRV_LOG_DEBUG) +endif() + +if (DEFINED SPIRV_TOOLS_EXTRA_DEFINITIONS) + add_definitions(${SPIRV_TOOLS_EXTRA_DEFINITIONS}) +endif() + +function(spvtools_default_compile_options TARGET) + target_compile_options(${TARGET} PRIVATE ${SPIRV_WARNINGS}) + + if (${COMPILER_IS_LIKE_GNU}) + target_compile_options(${TARGET} PRIVATE + -std=c++11 -fno-exceptions -fno-rtti) + target_compile_options(${TARGET} PRIVATE + -Wall -Wextra -Wno-long-long -Wshadow -Wundef -Wconversion + -Wno-sign-conversion) + # For good call stacks in profiles, keep the frame pointers. + if(NOT "${SPIRV_PERF}" STREQUAL "") + target_compile_options(${TARGET} PRIVATE -fno-omit-frame-pointer) + endif() + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + set(SPIRV_USE_SANITIZER "" CACHE STRING + "Use the clang sanitizer [address|memory|thread|...]") + if(NOT "${SPIRV_USE_SANITIZER}" STREQUAL "") + target_compile_options(${TARGET} PRIVATE + -fsanitize=${SPIRV_USE_SANITIZER}) + endif() + else() + target_compile_options(${TARGET} PRIVATE + -Wno-missing-field-initializers) + endif() + endif() + + if (MSVC) + # Specify /EHs for exception handling. This makes using SPIRV-Tools as + # dependencies in other projects easier. + target_compile_options(${TARGET} PRIVATE /EHs) + endif() + + # For MinGW cross compile, statically link to the C++ runtime. + # But it still depends on MSVCRT.dll. + if (${CMAKE_SYSTEM_NAME} MATCHES "Windows") + if (${CMAKE_CXX_COMPILER_ID} MATCHES "GNU") + set_target_properties(${TARGET} PROPERTIES + LINK_FLAGS -static -static-libgcc -static-libstdc++) + endif() + endif() +endfunction() + +if(NOT COMMAND find_host_package) + macro(find_host_package) + find_package(${ARGN}) + endmacro() +endif() +if(NOT COMMAND find_host_program) + macro(find_host_program) + find_program(${ARGN}) + endmacro() +endif() + +find_host_package(PythonInterp) + +if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") + macro(spvtools_check_symbol_exports TARGET) + add_test(NAME spirv-tools-symbol-exports-${TARGET} + COMMAND ${PYTHON_EXECUTABLE} + ${spirv-tools_SOURCE_DIR}/utils/check_symbol_exports.py "$") + endmacro() +else() + macro(spvtools_check_symbol_exports TARGET) + message("Skipping symbol exports test for ${TARGET}") + endmacro() +endif() + +# Defaults to OFF if the user didn't set it. +option(SPIRV_SKIP_EXECUTABLES + "Skip building the executable and tests along with the library" + ${SPIRV_SKIP_EXECUTABLES}) +option(SPIRV_SKIP_TESTS + "Skip building tests along with the library" ${SPIRV_SKIP_TESTS}) +if ("${SPIRV_SKIP_EXECUTABLES}") + set(SPIRV_SKIP_TESTS ON) +endif() + +# Defaults to ON. The checks can be time consuming. +# Turn off if they take too long. +option(SPIRV_CHECK_CONTEXT "In a debug build, check if the IR context is in a valid state." ON) +if (${SPIRV_CHECK_CONTEXT}) + add_definitions(-DSPIRV_CHECK_CONTEXT) +endif() + +add_subdirectory(external) + +if (TARGET effcee) + add_definitions(-DSPIRV_EFFCEE) +endif() + +add_subdirectory(source) +add_subdirectory(tools) + +add_subdirectory(test) +add_subdirectory(examples) + +if(ENABLE_SPIRV_TOOLS_INSTALL) + install( + FILES + ${CMAKE_CURRENT_SOURCE_DIR}/include/spirv-tools/libspirv.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/spirv-tools/libspirv.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/spirv-tools/optimizer.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/include/spirv-tools/linker.hpp + DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR}/spirv-tools/) +endif(ENABLE_SPIRV_TOOLS_INSTALL) + +if (NOT "${SPIRV_SKIP_TESTS}") + add_test(NAME spirv-tools-copyrights + COMMAND ${PYTHON_EXECUTABLE} utils/check_copyright.py + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) +endif() + +set(SPIRV_LIBRARIES "-lSPIRV-Tools -lSPIRV-Tools-link -lSPIRV-Tools-opt") +set(SPIRV_SHARED_LIBRARIES "-lSPIRV-Tools-shared") +if(SPIRV_BUILD_COMPRESSION) + set(SPIRV_LIBRARIES "${SPIRV_LIBRARIES} -lSPIRV-Tools-comp") +endif(SPIRV_BUILD_COMPRESSION) + +# Build pkg-config file +# Use a first-class target so it's regenerated when relevant files are updated. +add_custom_target(spirv-tools-pkg-config ALL + COMMAND ${CMAKE_COMMAND} + -DCHANGES_FILE=${CMAKE_CURRENT_SOURCE_DIR}/CHANGES + -DTEMPLATE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/cmake/SPIRV-Tools.pc.in + -DOUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/SPIRV-Tools.pc + -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} + -DCMAKE_INSTALL_LIBDIR=${CMAKE_INSTALL_LIBDIR} + -DCMAKE_INSTALL_INCLUDEDIR=${CMAKE_INSTALL_INCLUDEDIR} + -DSPIRV_LIBRARIES=${SPIRV_LIBRARIES} + -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/write_pkg_config.cmake + DEPENDS "CHANGES" "cmake/SPIRV-Tools.pc.in" "cmake/write_pkg_config.cmake") +add_custom_target(spirv-tools-shared-pkg-config ALL + COMMAND ${CMAKE_COMMAND} + -DCHANGES_FILE=${CMAKE_CURRENT_SOURCE_DIR}/CHANGES + -DTEMPLATE_FILE=${CMAKE_CURRENT_SOURCE_DIR}/cmake/SPIRV-Tools-shared.pc.in + -DOUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/SPIRV-Tools-shared.pc + -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} + -DCMAKE_INSTALL_LIBDIR=${CMAKE_INSTALL_LIBDIR} + -DCMAKE_INSTALL_INCLUDEDIR=${CMAKE_INSTALL_INCLUDEDIR} + -DSPIRV_SHARED_LIBRARIES=${SPIRV_SHARED_LIBRARIES} + -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/write_pkg_config.cmake + DEPENDS "CHANGES" "cmake/SPIRV-Tools-shared.pc.in" "cmake/write_pkg_config.cmake") + +# Install pkg-config file +if (ENABLE_SPIRV_TOOLS_INSTALL) + install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/SPIRV-Tools.pc + ${CMAKE_CURRENT_BINARY_DIR}/SPIRV-Tools-shared.pc + DESTINATION + ${CMAKE_INSTALL_LIBDIR}/pkgconfig) +endif() diff --git a/3rdparty/spirv-tools/CONTRIBUTING.md b/3rdparty/spirv-tools/CONTRIBUTING.md new file mode 100644 index 000000000..f945f88fa --- /dev/null +++ b/3rdparty/spirv-tools/CONTRIBUTING.md @@ -0,0 +1,189 @@ +# Contributing to SPIR-V Tools + +## For users: Reporting bugs and requesting features + +We organize known future work in GitHub projects. See [Tracking SPIRV-Tools work +with GitHub +projects](https://github.com/KhronosGroup/SPIRV-Tools/blob/master/projects.md) +for more. + +To report a new bug or request a new feature, please file a GitHub issue. Please +ensure the bug has not already been reported by searching +[issues](https://github.com/KhronosGroup/SPIRV-Tools/issues) and +[projects](https://github.com/KhronosGroup/SPIRV-Tools/projects). If the bug has +not already been reported open a new one +[here](https://github.com/KhronosGroup/SPIRV-Tools/issues/new). + +When opening a new issue for a bug, make sure you provide the following: + +* A clear and descriptive title. + * We want a title that will make it easy for people to remember what the + issue is about. Simply using "Segfault in spirv-opt" is not helpful + because there could be (but hopefully aren't) multiple bugs with + segmentation faults with different causes. +* A test case that exposes the bug, with the steps and commands to reproduce + it. + * The easier it is for a developer to reproduce the problem, the quicker a + fix can be found and verified. It will also make it easier for someone + to possibly realize the bug is related to another issue. + +For feature requests, we use +[issues](https://github.com/KhronosGroup/SPIRV-Tools/issues) as well. Please +create a new issue, as with bugs. In the issue provide + +* A description of the problem that needs to be solved. +* Examples that demonstrate the problem. + +## For developers: Contributing a patch + +Before we can use your code, you must sign the [Khronos Open Source Contributor +License Agreement](https://cla-assistant.io/KhronosGroup/SPIRV-Tools) (CLA), +which you can do online. The CLA is necessary mainly because you own the +copyright to your changes, even after your contribution becomes part of our +codebase, so we need your permission to use and distribute your code. We also +need to be sure of various other things -- for instance that you'll tell us if +you know that your code infringes on other people's patents. You don't have to +sign the CLA until after you've submitted your code for review and a member has +approved it, but you must do it before we can put your code into our codebase. + +See +[README.md](https://github.com/KhronosGroup/SPIRV-Tools/blob/master/README.md) +for instruction on how to get, build, and test the source. Once you have made +your changes: + +* Ensure the code follows the [Google C++ Style + Guide](https://google.github.io/styleguide/cppguide.html). Running + `clang-format -style=file -i [modified-files]` can help. +* Create a pull request (PR) with your patch. +* Make sure the PR description clearly identified the problem, explains the + solution, and references the issue if applicable. +* If your patch completely fixes bug 1234, the commit message should say + `Fixes https://github.com/KhronosGroup/SPIRV-Tools/issues/1234` + When you do this, the issue will be closed automatically when the commit + goes into master. Also, this helps us update the [CHANGES](CHANGES) file. +* Watch the continuous builds to make sure they pass. +* Request a code review. + +The reviewer can either approve your PR or request changes. If changes are +requested: + +* Please add new commits to your branch, instead of amending your commit. + Adding new commits makes it easier for the reviewer to see what has changed + since the last review. +* Once you are ready for another round of reviews, add a comment at the + bottom, such as "Ready for review" or "Please take a look" (or "PTAL"). This + explicit handoff is useful when responding with multiple small commits. + +After the PR has been reviewed it is the job of the reviewer to merge the PR. +Instructions for this are given below. + +## For maintainers: Reviewing a PR + +The formal code reviews are done on GitHub. Reviewers are to look for all of the +usual things: + +* Coding style follows the [Google C++ Style + Guide](https://google.github.io/styleguide/cppguide.html) +* Identify potential functional problems. +* Identify code duplication. +* Ensure the unit tests have enough coverage. + +When looking for functional problems, there are some common problems reviewers +should pay particular attention to: + +* Does the code work for both Shader (Vulkan and OpenGL) and Kernel (OpenCL) + scenarios? The respective SPIR-V dialects are slightly different. +* Changes are made to a container while iterating through it. You have to be + careful that iterators are not invalidated or that elements are not skipped. +* C++11 and VS2013. We generally assume that we have a C++11 compliant + compiler. However, on Windows, we still support Visual Studio 2013, which is + not fully C++11 compliant. See + [here](https://msdn.microsoft.com/en-us/library/hh567368.aspx). In + particular, note that it does not provide default move-constructors or + move-assignments for classes. In general, r-value references do not work the + way you might assume they do. +* For SPIR-V transforms: The module is changed, but the analyses are not + updated. For example, a new instruction is added, but the def-use manager is + not updated. Later on, it is possible that the def-use manager will be used, + and give wrong results. + +## For maintainers: Merging a PR + +We intend to maintain a linear history on the GitHub master branch, and the +build and its tests should pass at each commit in that history. A linear +always-working history is easier to understand and to bisect in case we want to +find which commit introduced a bug. + +### Initial merge setup + +The following steps should be done exactly once (when you are about to merge a +PR for the first time): + +* It is assumed that upstream points to + [git@github.com](mailto:git@github.com):KhronosGroup/SPIRV-Tools.git or + https://github.com/KhronosGroup/SPIRV-Tools.git. + +* Find out the local name for the main github repo in your git configuration. + For example, in this configuration, it is labeled `upstream`. + + ``` + git remote -v + [ ... ] + upstream https://github.com/KhronosGroup/SPIRV-Tools.git (fetch) + upstream https://github.com/KhronosGroup/SPIRV-Tools.git (push) + ``` + +* Make sure that the `upstream` remote is set to fetch from the `refs/pull` + namespace: + + ``` + git config --get-all remote.upstream.fetch + +refs/heads/*:refs/remotes/upstream/* + +refs/pull/*/head:refs/remotes/upstream/pr/* + ``` + +* If the line `+refs/pull/*/head:refs/remotes/upstream/pr/*` is not present in + your configuration, you can add it with the command: + + ``` + git config --local --add remote.upstream.fetch '+refs/pull/*/head:refs/remotes/upstream/pr/*' + ``` + +### Merge workflow + +The following steps should be done for every PR that you intend to merge: + +* Make sure your local copy of the master branch is up to date: + + ``` + git checkout master + git pull + ``` + +* Fetch all pull requests refs: + + ``` + git fetch upstream + ``` + +* Checkout the particular pull request you are going to review: + + ``` + git checkout pr/1048 + ``` + +* Rebase the PR on top of the master branch. If there are conflicts, send it + back to the author and ask them to rebase. During the interactive rebase be + sure to squash all of the commits down to a single commit. + + ``` + git rebase -i master + ``` + +* **Build and test the PR.** + +* If all of the tests pass, push the commit `git push upstream HEAD:master` + +* Close the PR and add a comment saying it was push using the commit that you + just pushed. See https://github.com/KhronosGroup/SPIRV-Tools/pull/935 as an + example. diff --git a/3rdparty/spirv-tools/LICENSE b/3rdparty/spirv-tools/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/3rdparty/spirv-tools/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/3rdparty/spirv-tools/README.md b/3rdparty/spirv-tools/README.md new file mode 100644 index 000000000..1c2830de3 --- /dev/null +++ b/3rdparty/spirv-tools/README.md @@ -0,0 +1,509 @@ +# SPIR-V Tools + +[![Build Status](https://travis-ci.org/KhronosGroup/SPIRV-Tools.svg?branch=master)](https://travis-ci.org/KhronosGroup/SPIRV-Tools) +[![Build status](https://ci.appveyor.com/api/projects/status/gpue87cesrx3pi0d/branch/master?svg=true)](https://ci.appveyor.com/project/Khronoswebmaster/spirv-tools/branch/master) + +## Overview + +The SPIR-V Tools project provides an API and commands for processing SPIR-V +modules. + +The project includes an assembler, binary module parser, disassembler, +validator, and optimizer for SPIR-V. Except for the optimizer, all are based +on a common static library. The library contains all of the implementation +details, and is used in the standalone tools whilst also enabling integration +into other code bases directly. The optimizer implementation resides in its +own library, which depends on the core library. + +The interfaces have stabilized: +We don't anticipate making a breaking change for existing features. + +SPIR-V is defined by the Khronos Group Inc. +See the [SPIR-V Registry][spirv-registry] for the SPIR-V specification, +headers, and XML registry. + +## Versioning SPIRV-Tools + +See [`CHANGES`](CHANGES) for a high level summary of recent changes, by version. + +SPIRV-Tools project version numbers are of the form `v`*year*`.`*index* and with +an optional `-dev` suffix to indicate work in progress. For exampe, the +following versions are ordered from oldest to newest: + +* `v2016.0` +* `v2016.1-dev` +* `v2016.1` +* `v2016.2-dev` +* `v2016.2` + +Use the `--version` option on each command line tool to see the software +version. An API call reports the software version as a C-style string. + +## Supported features + +### Assembler, binary parser, and disassembler + +* Support for SPIR-V 1.0, 1.1, 1.2, and 1.3 + * Based on SPIR-V syntax described by JSON grammar files in the + [SPIRV-Headers](spirv-headers) repository. +* Support for extended instruction sets: + * GLSL std450 version 1.0 Rev 3 + * OpenCL version 1.0 Rev 2 +* Assembler only does basic syntax checking. No cross validation of + IDs or types is performed, except to check literal arguments to + `OpConstant`, `OpSpecConstant`, and `OpSwitch`. + +See [`syntax.md`](syntax.md) for the assembly language syntax. + +### Validator + +The validator checks validation rules described by the SPIR-V specification. + +Khronos recommends that tools that create or transform SPIR-V modules use the +validator to ensure their outputs are valid, and that tools that consume SPIR-V +modules optionally use the validator to protect themselves from bad inputs. +This is especially encouraged for debug and development scenarios. + +The validator has one-sided error: it will only return an error when it has +implemented a rule check and the module violates that rule. + +The validator is incomplete. +See the [CHANGES](CHANGES) file for reports on completed work, and +the [Validator +sub-project](https://github.com/KhronosGroup/SPIRV-Tools/projects/1) for planned +and in-progress work. + +*Note*: The validator checks some Universal Limits, from section 2.17 of the SPIR-V spec. +The validator will fail on a module that exceeds those minimum upper bound limits. +It is [future work](https://github.com/KhronosGroup/SPIRV-Tools/projects/1#card-1052403) +to parameterize the validator to allow larger +limits accepted by a more than minimally capable SPIR-V consumer. + + +### Optimizer + +*Note:* The optimizer is still under development. + +Currently supported optimizations: +* General + * Strip debug info +* Specialization Constants + * Set spec constant default value + * Freeze spec constant + * Fold `OpSpecConstantOp` and `OpSpecConstantComposite` + * Unify constants + * Eliminate dead constant +* Code Reduction + * Inline all function calls exhaustively + * Convert local access chains to inserts/extracts + * Eliminate local load/store in single block + * Eliminate local load/store with single store + * Eliminate local load/store with multiple stores + * Eliminate local extract from insert + * Eliminate dead instructions (aggressive) + * Eliminate dead branches + * Merge single successor / single predecessor block pairs + * Eliminate common uniform loads + * Remove duplicates: Capabilities, extended instruction imports, types, and + decorations. + +For the latest list with detailed documentation, please refer to +[`include/spirv-tools/optimizer.hpp`](include/spirv-tools/optimizer.hpp). + +For suggestions on using the code reduction options, please refer to this [white paper](https://www.lunarg.com/shader-compiler-technologies/white-paper-spirv-opt/). + + +### Linker + +*Note:* The linker is still under development. + +Current features: +* Combine multiple SPIR-V binary modules together. +* Combine into a library (exports are retained) or an executable (no symbols + are exported). + +See the [CHANGES](CHANGES) file for reports on completed work, and the [General +sub-project](https://github.com/KhronosGroup/SPIRV-Tools/projects/2) for +planned and in-progress work. + +### Extras + +* [Utility filters](#utility-filters) +* Build target `spirv-tools-vimsyntax` generates file `spvasm.vim`. + Copy that file into your `$HOME/.vim/syntax` directory to get SPIR-V assembly syntax + highlighting in Vim. This build target is not built by default. + +## Contributing + +The SPIR-V Tools project is maintained by members of the The Khronos Group Inc., +and is hosted at https://github.com/KhronosGroup/SPIRV-Tools. + +Consider joining the `public_spirv_tools_dev@khronos.org` mailing list, via +[https://www.khronos.org/spir/spirv-tools-mailing-list/](https://www.khronos.org/spir/spirv-tools-mailing-list/). +The mailing list is used to discuss development plans for the SPIRV-Tools as an open source project. +Once discussion is resolved, +specific work is tracked via issues and sometimes in one of the +[projects][spirv-tools-projects]. + +(To provide feedback on the SPIR-V _specification_, file an issue on the +[SPIRV-Headers][spirv-headers] GitHub repository.) + +See [`projects.md`](projects.md) to see how we use the +[GitHub Project +feature](https://help.github.com/articles/tracking-the-progress-of-your-work-with-projects/) +to organize planned and in-progress work. + +Contributions via merge request are welcome. Changes should: +* Be provided under the [Apache 2.0](#license). +* You'll be prompted with a one-time "click-through" + [Khronos Open Source Contributor License Agreement][spirv-tools-cla] + (CLA) dialog as part of submitting your pull request or + other contribution to GitHub. +* Include tests to cover updated functionality. +* C++ code should follow the [Google C++ Style Guide][cpp-style-guide]. +* Code should be formatted with `clang-format`. Settings are defined by + the included [.clang-format](.clang-format) file. + +We intend to maintain a linear history on the GitHub `master` branch. + +### Source code organization + +* `example`: demo code of using SPIRV-Tools APIs +* `external/googletest`: Intended location for the + [googletest][googletest] sources, not provided +* `external/effcee`: Location of [Effcee][effcee] sources, if the `effcee` library + is not already configured by an enclosing project. +* `external/re2`: Location of [RE2][re2] sources, if the `effcee` library is not already + configured by an enclosing project. + (The Effcee project already requires RE2.) +* `include/`: API clients should add this directory to the include search path +* `external/spirv-headers`: Intended location for + [SPIR-V headers][spirv-headers], not provided +* `include/spirv-tools/libspirv.h`: C API public interface +* `source/`: API implementation +* `test/`: Tests, using the [googletest][googletest] framework +* `tools/`: Command line executables + +Example of getting sources, assuming SPIRV-Tools is configured as a standalone project: + + git clone https://github.com/KhronosGroup/SPIRV-Tools.git spirv-tools + git clone https://github.com/KhronosGroup/SPIRV-Headers.git spirv-tools/external/spirv-headers + git clone https://github.com/google/googletest.git spirv-tools/external/googletest + git clone https://github.com/google/effcee.git spirv-tools/external/effcee + git clone https://github.com/google/re2.git spirv-tools/external/re2 + +### Tests + +The project contains a number of tests, used to drive development +and ensure correctness. The tests are written using the +[googletest][googletest] framework. The `googletest` +source is not provided with this project. There are two ways to enable +tests: +* If SPIR-V Tools is configured as part of an enclosing project, then the + enclosing project should configure `googletest` before configuring SPIR-V Tools. +* If SPIR-V Tools is configured as a standalone project, then download the + `googletest` source into the `/external/googletest` directory before + configuring and building the project. + +*Note*: You must use a version of googletest that includes +[a fix][googletest-pull-612] for [googletest issue 610][googletest-issue-610]. +The fix is included on the googletest master branch any time after 2015-11-10. +In particular, googletest must be newer than version 1.7.0. + +### Optional dependency on Effcee + +Some tests depend on the [Effcee][effcee] library for stateful matching. +Effcee itself depends on [RE2][re2]. + +* If SPIRV-Tools is configured as part of a larger project that already uses + Effcee, then that project should include Effcee before SPIRV-Tools. +* Otherwise, SPIRV-Tools expects Effcee sources to appear in `external/effcee` + and RE2 sources to appear in `external/re2`. + +Currently Effcee is an optional dependency, but soon it will be required. + +## Build + +Instead of building manually, you can also download the binaries for your +platform directly from the [master-tot release][master-tot-release] on GitHub. +Those binaries are automatically uploaded by the buildbots after successful +testing and they always reflect the current top of the tree of the master +branch. + +The project uses [CMake][cmake] to generate platform-specific build +configurations. Assume that `` is the root directory of the checked +out code: + +```sh +cd +git clone https://github.com/KhronosGroup/SPIRV-Headers.git external/spirv-headers +git clone https://github.com/google/googletest.git external/googletest # optional + +mkdir build && cd build +cmake [-G ] +``` + +Once the build files have been generated, build using your preferred +development environment. + +### CMake options + +The following CMake options are supported: + +* `SPIRV_COLOR_TERMINAL={ON|OFF}`, default `ON` - Enables color console output. +* `SPIRV_SKIP_TESTS={ON|OFF}`, default `OFF`- Build only the library and + the command line tools. This will prevent the tests from being built. +* `SPIRV_SKIP_EXECUTABLES={ON|OFF}`, default `OFF`- Build only the library, not + the command line tools and tests. +* `SPIRV_BUILD_COMPRESSION={ON|OFF}`, default `OFF`- Build SPIR-V compressing + codec. +* `SPIRV_USE_SANITIZER=`, default is no sanitizing - On UNIX + platforms with an appropriate version of `clang` this option enables the use + of the sanitizers documented [here][clang-sanitizers]. + This should only be used with a debug build. +* `SPIRV_WARN_EVERYTHING={ON|OFF}`, default `OFF` - On UNIX platforms enable + more strict warnings. The code might not compile with this option enabled. + For Clang, enables `-Weverything`. For GCC, enables `-Wpedantic`. + See [`CMakeLists.txt`](CMakeLists.txt) for details. +* `SPIRV_WERROR={ON|OFF}`, default `ON` - Forces a compilation error on any + warnings encountered by enabling the compiler-specific compiler front-end + option. + +Additionally, you can pass additional C preprocessor definitions to SPIRV-Tools +via setting `SPIRV_TOOLS_EXTRA_DEFINITIONS`. For example, by setting it to +`/D_ITERATOR_DEBUG_LEVEL=0` on Windows, you can disable checked iterators and +iterator debugging. + +## Library + +### Usage + +The internals of the library use C++11 features, and are exposed via both a C +and C++ API. + +In order to use the library from an application, the include path should point +to `/include`, which will enable the application to include the +header `/include/spirv-tools/libspirv.h{|pp}` then linking against +the static library in `/source/libSPIRV-Tools.a` or +`/source/SPIRV-Tools.lib`. +For optimization, the header file is +`/include/spirv-tools/optimizer.hpp`, and the static library is +`/source/libSPIRV-Tools-opt.a` or +`/source/SPIRV-Tools-opt.lib`. + +* `SPIRV-Tools` CMake target: Creates the static library: + * `/source/libSPIRV-Tools.a` on Linux and OS X. + * `/source/libSPIRV-Tools.lib` on Windows. +* `SPIRV-Tools-opt` CMake target: Creates the static library: + * `/source/libSPIRV-Tools-opt.a` on Linux and OS X. + * `/source/libSPIRV-Tools-opt.lib` on Windows. + +#### Entry points + +The interfaces are still under development, and are expected to change. + +There are five main entry points into the library in the C interface: + +* `spvTextToBinary`: An assembler, translating text to a binary SPIR-V module. +* `spvBinaryToText`: A disassembler, translating a binary SPIR-V module to + text. +* `spvBinaryParse`: The entry point to a binary parser API. It issues callbacks + for the header and each parsed instruction. The disassembler is implemented + as a client of `spvBinaryParse`. +* `spvValidate` implements the validator functionality. *Incomplete* +* `spvValidateBinary` implements the validator functionality. *Incomplete* + +The C++ interface is comprised of three classes, `SpirvTools`, `Optimizer` and +`Linker`, all in the `spvtools` namespace. +* `SpirvTools` provides `Assemble`, `Disassemble`, and `Validate` methods. +* `Optimizer` provides methods for registering and running optimization passes. +* `Linker` provides methods for combining together multiple binaries. + +## Command line tools + +Command line tools, which wrap the above library functions, are provided to +assemble or disassemble shader files. It's a convention to name SPIR-V +assembly and binary files with suffix `.spvasm` and `.spv`, respectively. + +### Assembler tool + +The assembler reads the assembly language text, and emits the binary form. + +The standalone assembler is the exectuable called `spirv-as`, and is located in +`/tools/spirv-as`. The functionality of the assembler is implemented +by the `spvTextToBinary` library function. + +* `spirv-as` - the standalone assembler + * `/tools/as` + +Use option `-h` to print help. + +### Disassembler tool + +The disassembler reads the binary form, and emits assembly language text. + +The standalone disassembler is the executable called `spirv-dis`, and is located in +`/tools/spirv-dis`. The functionality of the disassembler is implemented +by the `spvBinaryToText` library function. + +* `spirv-dis` - the standalone disassembler + * `/tools/dis` + +Use option `-h` to print help. + +The output includes syntax colouring when printing to the standard output stream, +on Linux, Windows, and OS X. + +### Linker tool + +The linker combines multiple SPIR-V binary modules together, resulting in a single +binary module as output. + +This is a work in progress. +The linker does not support OpenCL program linking options related to math +flags. (See section 5.6.5.2 in OpenCL 1.2) + +* `spirv-link` - the standalone linker + * `/tools/link` + +### Optimizer tool + +The optimizer processes a SPIR-V binary module, applying transformations +in the specified order. + +This is a work in progress, with initially only few available transformations. + +* `spirv-opt` - the standalone optimizer + * `/tools/opt` + +### Validator tool + +*Warning:* This functionality is under development, and is incomplete. + +The standalone validator is the executable called `spirv-val`, and is located in +`/tools/spirv-val`. The functionality of the validator is implemented +by the `spvValidate` library function. + +The validator operates on the binary form. + +* `spirv-val` - the standalone validator + * `/tools/val` + +### Control flow dumper tool + +The control flow dumper prints the control flow graph for a SPIR-V module as a +[GraphViz](http://www.graphviz.org/) graph. + +This is experimental. + +* `spirv-cfg` - the control flow graph dumper + * `/tools/cfg` + +### Utility filters + +* `spirv-lesspipe.sh` - Automatically disassembles `.spv` binary files for the + `less` program, on compatible systems. For example, set the `LESSOPEN` + environment variable as follows, assuming both `spirv-lesspipe.sh` and + `spirv-dis` are on your executable search path: + ``` + export LESSOPEN='| spirv-lesspipe.sh "%s"' + ``` + Then you page through a disassembled module as follows: + ``` + less foo.spv + ``` + * The `spirv-lesspipe.sh` script will pass through any extra arguments to + `spirv-dis`. So, for example, you can turn off colours and friendly ID + naming as follows: + ``` + export LESSOPEN='| spirv-lesspipe.sh "%s" --no-color --raw-id' + ``` + +* [vim-spirv](https://github.com/kbenzie/vim-spirv) - A vim plugin which + supports automatic disassembly of `.spv` files using the `:edit` command and + assembly using the `:write` command. The plugin also provides additional + features which include; syntax highlighting; highlighting of all ID's matching + the ID under the cursor; and highlighting errors where the `Instruction` + operand of `OpExtInst` is used without an appropriate `OpExtInstImport`. + +* `50spirv-tools.el` - Automatically disassembles '.spv' binary files when + loaded into the emacs text editor, and re-assembles them when saved, + provided any modifications to the file are valid. This functionality + must be explicitly requested by defining the symbol + SPIRV_TOOLS_INSTALL_EMACS_HELPERS as follows: + ``` + cmake -DSPIRV_TOOLS_INSTALL_EMACS_HELPERS=true ... + ``` + + In addition, this helper is only installed if the directory /etc/emacs/site-start.d + exists, which is typically true if emacs is installed on the system. + + Note that symbol IDs are not currently preserved through a load/edit/save operation. + This may change if the ability is added to spirv-as. + + +### Tests + +Tests are only built when googletest is found. Use `ctest` to run all the +tests. + +## Future Work + + +_See the [projects pages](https://github.com/KhronosGroup/SPIRV-Tools/projects) +for more information._ + +### Assembler and disassembler + +* The disassembler could emit helpful annotations in comments. For example: + * Use variable name information from debug instructions to annotate + key operations on variables. + * Show control flow information by annotating `OpLabel` instructions with + that basic block's predecessors. +* Error messages could be improved. + +### Validator + +This is a work in progress. + +### Linker + +* The linker could accept math transformations such as allowing MADs, or other + math flags passed at linking-time in OpenCL. +* Linkage attributes can not be applied through a group. +* Check decorations of linked functions attributes. +* Remove dead instructions, such as OpName targeting imported symbols. + +## Licence + +Full license terms are in [LICENSE](LICENSE) +``` +Copyright (c) 2015-2016 The Khronos Group Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` + +[spirv-tools-cla]: https://cla-assistant.io/KhronosGroup/SPIRV-Tools +[spirv-tools-projects]: https://github.com/KhronosGroup/SPIRV-Tools/projects +[spirv-tools-mailing-list]: https://www.khronos.org/spir/spirv-tools-mailing-list +[spirv-registry]: https://www.khronos.org/registry/spir-v/ +[spirv-headers]: https://github.com/KhronosGroup/SPIRV-Headers +[googletest]: https://github.com/google/googletest +[googletest-pull-612]: https://github.com/google/googletest/pull/612 +[googletest-issue-610]: https://github.com/google/googletest/issues/610 +[effcee]: https://github.com/google/effcee +[re2]: https://github.com/google/re2 +[CMake]: https://cmake.org/ +[cpp-style-guide]: https://google.github.io/styleguide/cppguide.html +[clang-sanitizers]: http://clang.llvm.org/docs/UsersManual.html#controlling-code-generation +[master-tot-release]: https://github.com/KhronosGroup/SPIRV-Tools/releases/tag/master-tot diff --git a/3rdparty/spirv-tools/android_test/Android.mk b/3rdparty/spirv-tools/android_test/Android.mk new file mode 100644 index 000000000..dbaf93ba9 --- /dev/null +++ b/3rdparty/spirv-tools/android_test/Android.mk @@ -0,0 +1,12 @@ +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_CPP_EXTENSION := .cc .cpp .cxx +LOCAL_SRC_FILES:=test.cpp +LOCAL_MODULE:=spirvtools_test +LOCAL_LDLIBS:=-landroid +LOCAL_CXXFLAGS:=-std=c++11 -fno-exceptions -fno-rtti -Werror +LOCAL_STATIC_LIBRARIES=SPIRV-Tools SPIRV-Tools-opt +include $(BUILD_SHARED_LIBRARY) + +include $(LOCAL_PATH)/../Android.mk diff --git a/3rdparty/spirv-tools/android_test/jni/Application.mk b/3rdparty/spirv-tools/android_test/jni/Application.mk new file mode 100644 index 000000000..d7ccd349a --- /dev/null +++ b/3rdparty/spirv-tools/android_test/jni/Application.mk @@ -0,0 +1,5 @@ +APP_ABI := all +APP_BUILD_SCRIPT := Android.mk +APP_STL := gnustl_static +APP_PLATFORM := android-9 +NDK_TOOLCHAIN_VERSION := 4.9 diff --git a/3rdparty/spirv-tools/android_test/test.cpp b/3rdparty/spirv-tools/android_test/test.cpp new file mode 100644 index 000000000..e6a57c127 --- /dev/null +++ b/3rdparty/spirv-tools/android_test/test.cpp @@ -0,0 +1,22 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include "spirv-tools/libspirv.hpp" +#include "spirv-tools/optimizer.hpp" + +void android_main(struct android_app* /*state*/) { + spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_2); + spvtools::Optimizer optimizer(SPV_ENV_UNIVERSAL_1_2); +} diff --git a/3rdparty/spirv-tools/cmake/SPIRV-Tools-shared.pc.in b/3rdparty/spirv-tools/cmake/SPIRV-Tools-shared.pc.in new file mode 100644 index 000000000..0dcaa2764 --- /dev/null +++ b/3rdparty/spirv-tools/cmake/SPIRV-Tools-shared.pc.in @@ -0,0 +1,12 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ +includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ + +Name: SPIRV-Tools +Description: Tools for SPIR-V +Version: @CURRENT_VERSION@ +URL: https://github.com/KhronosGroup/SPIRV-Tools + +Libs: -L${libdir} @SPIRV_SHARED_LIBRARIES@ +Cflags: -I${includedir} diff --git a/3rdparty/spirv-tools/cmake/SPIRV-Tools.pc.in b/3rdparty/spirv-tools/cmake/SPIRV-Tools.pc.in new file mode 100644 index 000000000..2984dc57f --- /dev/null +++ b/3rdparty/spirv-tools/cmake/SPIRV-Tools.pc.in @@ -0,0 +1,12 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ +includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ + +Name: SPIRV-Tools +Description: Tools for SPIR-V +Version: @CURRENT_VERSION@ +URL: https://github.com/KhronosGroup/SPIRV-Tools + +Libs: -L${libdir} @SPIRV_LIBRARIES@ +Cflags: -I${includedir} diff --git a/3rdparty/spirv-tools/cmake/write_pkg_config.cmake b/3rdparty/spirv-tools/cmake/write_pkg_config.cmake new file mode 100644 index 000000000..d367ce3e4 --- /dev/null +++ b/3rdparty/spirv-tools/cmake/write_pkg_config.cmake @@ -0,0 +1,31 @@ +# Copyright (c) 2017 Pierre Moreau +# +# 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. + +# First, retrieve the current version from CHANGES +file(STRINGS ${CHANGES_FILE} CHANGES_CONTENT) +string( +REGEX + MATCH "v[0-9]+(.[0-9]+)?(-dev)? [0-9]+-[0-9]+-[0-9]+" + FIRST_VERSION_LINE + ${CHANGES_CONTENT}) +string( +REGEX + REPLACE "^v([^ ]+) .+$" "\\1" + CURRENT_VERSION + "${FIRST_VERSION_LINE}") +# If this is a development version, replace "-dev" by ".0" as pkg-config nor +# CMake support "-dev" in the version. +# If it's not a "-dev" version then ensure it ends with ".1" +string(REGEX REPLACE "-dev.1" ".0" CURRENT_VERSION "${CURRENT_VERSION}.1") +configure_file(${TEMPLATE_FILE} ${OUT_FILE} @ONLY) diff --git a/3rdparty/spirv-tools/examples/CMakeLists.txt b/3rdparty/spirv-tools/examples/CMakeLists.txt new file mode 100644 index 000000000..fd627cbda --- /dev/null +++ b/3rdparty/spirv-tools/examples/CMakeLists.txt @@ -0,0 +1,35 @@ +# Copyright (c) 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Add a SPIR-V Tools example. Signature: +# add_spvtools_example( +# TARGET target_name +# SRCS src_file1.cpp src_file2.cpp +# LIBS lib_target1 lib_target2 +# ) +function(add_spvtools_example) + if (NOT ${SPIRV_SKIP_EXECUTABLES}) + set(one_value_args TARGET) + set(multi_value_args SRCS LIBS) + cmake_parse_arguments( + ARG "" "${one_value_args}" "${multi_value_args}" ${ARGN}) + + add_executable(${ARG_TARGET} ${ARG_SRCS}) + spvtools_default_compile_options(${ARG_TARGET}) + target_link_libraries(${ARG_TARGET} PRIVATE ${ARG_LIBS}) + set_property(TARGET ${ARG_TARGET} PROPERTY FOLDER "SPIRV-Tools examples") + endif() +endfunction() + +add_subdirectory(cpp-interface) diff --git a/3rdparty/spirv-tools/examples/cpp-interface/CMakeLists.txt b/3rdparty/spirv-tools/examples/cpp-interface/CMakeLists.txt new file mode 100644 index 000000000..d050b0759 --- /dev/null +++ b/3rdparty/spirv-tools/examples/cpp-interface/CMakeLists.txt @@ -0,0 +1,19 @@ +# Copyright (c) 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +add_spvtools_example( + TARGET spirv-tools-cpp-example + SRCS main.cpp + LIBS SPIRV-Tools-opt +) \ No newline at end of file diff --git a/3rdparty/spirv-tools/examples/cpp-interface/main.cpp b/3rdparty/spirv-tools/examples/cpp-interface/main.cpp new file mode 100644 index 000000000..c5354b8bd --- /dev/null +++ b/3rdparty/spirv-tools/examples/cpp-interface/main.cpp @@ -0,0 +1,64 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This program demonstrates basic SPIR-V module processing using +// SPIRV-Tools C++ API: +// * Assembling +// * Validating +// * Optimizing +// * Disassembling + +#include +#include +#include + +#include "spirv-tools/libspirv.hpp" +#include "spirv-tools/optimizer.hpp" + +int main() { + const std::string source = + " OpCapability Shader " + " OpMemoryModel Logical GLSL450 " + " OpSource GLSL 450 " + " OpDecorate %spec SpecId 1 " + " %int = OpTypeInt 32 1 " + " %spec = OpSpecConstant %int 0 " + "%const = OpConstant %int 42"; + + spvtools::SpirvTools core(SPV_ENV_VULKAN_1_0); + spvtools::Optimizer opt(SPV_ENV_VULKAN_1_0); + + auto print_msg_to_stderr = [](spv_message_level_t, const char*, + const spv_position_t&, const char* m) { + std::cerr << "error: " << m << std::endl; + }; + core.SetMessageConsumer(print_msg_to_stderr); + opt.SetMessageConsumer(print_msg_to_stderr); + + std::vector spirv; + if (!core.Assemble(source, &spirv)) return 1; + if (!core.Validate(spirv)) return 1; + + opt.RegisterPass(spvtools::CreateSetSpecConstantDefaultValuePass({{1, "42"}})) + .RegisterPass(spvtools::CreateFreezeSpecConstantValuePass()) + .RegisterPass(spvtools::CreateUnifyConstantPass()) + .RegisterPass(spvtools::CreateStripDebugInfoPass()); + if (!opt.Run(spirv.data(), spirv.size(), &spirv)) return 1; + + std::string disassembly; + if (!core.Disassemble(spirv, &disassembly)) return 1; + std::cout << disassembly << "\n"; + + return 0; +} diff --git a/3rdparty/spirv-tools/external/CMakeLists.txt b/3rdparty/spirv-tools/external/CMakeLists.txt new file mode 100644 index 000000000..da0b2e649 --- /dev/null +++ b/3rdparty/spirv-tools/external/CMakeLists.txt @@ -0,0 +1,122 @@ +# Copyright (c) 2015-2016 The Khronos Group Inc. +# +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +if (DEFINED SPIRV-Headers_SOURCE_DIR) + # This allows flexible position of the SPIRV-Headers repo. + set(SPIRV_HEADER_DIR ${SPIRV-Headers_SOURCE_DIR}) +else() + if (IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/SPIRV-Headers) + set(SPIRV_HEADER_DIR ${CMAKE_CURRENT_SOURCE_DIR}/SPIRV-Headers) + else() + set(SPIRV_HEADER_DIR ${CMAKE_CURRENT_SOURCE_DIR}/spirv-headers) + endif() +endif() + +if (IS_DIRECTORY ${SPIRV_HEADER_DIR}) + set(SPIRV_HEADER_INCLUDE_DIR ${SPIRV_HEADER_DIR}/include PARENT_SCOPE) +else() + message(FATAL_ERROR + "SPIRV-Headers was not found - please checkout a copy under external/.") +endif() + +if (NOT ${SPIRV_SKIP_TESTS}) + # Find gmock if we can. If it's not already configured, then try finding + # it in external/googletest. + if (TARGET gmock) + message(STATUS "Google Mock already configured") + else() + set(GMOCK_DIR ${CMAKE_CURRENT_SOURCE_DIR}/googletest/googlemock) + if(EXISTS ${GMOCK_DIR}) + if(MSVC) + # Our tests use ::testing::Combine. Work around a compiler + # detection problem in googletest, where that template is + # accidentally disabled for VS 2017. + # See https://github.com/google/googletest/issues/1352 + add_definitions(-DGTEST_HAS_COMBINE=1) + endif() + if(WIN32) + option(gtest_force_shared_crt + "Use shared (DLL) run-time lib even when Google Test is built as static lib." + ON) + endif() + add_subdirectory(${GMOCK_DIR} EXCLUDE_FROM_ALL) + endif() + endif() + if (TARGET gmock) + set(GTEST_TARGETS + gtest + gtest_main + gmock + gmock_main + ) + foreach(target ${GTEST_TARGETS}) + set_property(TARGET ${target} PROPERTY FOLDER GoogleTest) + endforeach() + endif() + + set(SPIRV_ENABLE_EFFCEE ON) + if (MSVC) + if (MSVC_VERSION LESS 1900) + message(STATUS "SPIRV-Tools: Need Visual Studio 2015 or later for Effcee and RE2") + set(SPIRV_ENABLE_EFFCEE OFF) + endif() + endif() + + if (SPIRV_ENABLE_EFFCEE) + # Find Effcee and RE2, for testing. + # Optional for now, but eventually we'll make this required. + + # First find RE2, since Effcee depends on it. + # If already configured, then use that. Otherwise, prefer to find it under 're2' + # in this directory. + if (NOT TARGET re2) + # If we are configuring RE2, then turn off its testing. It takes a long time and + # does not add much value for us. If an enclosing project configured RE2, then it + # has already chosen whether to enable RE2 tesitng. + set(RE2_BUILD_TESTING OFF CACHE STRING "Run RE2 Tests") + if (NOT RE2_SOURCE_DIR) + if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/re2) + set(RE2_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/re2" CACHE STRING "RE2 source dir" ) + endif() + endif() + endif() + + if (NOT TARGET effcee) + # Expect to find effcee in this directory. + if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/effcee) + # If we're configuring RE2 (via Effcee), then turn off RE2 testing. + if (NOT TARGET re2) + set(RE2_BUILD_TESTING OFF) + endif() + if (MSVC) + # SPIRV-Tools uses the shared CRT with MSVC. Tell Effcee to do the same. + set(EFFCEE_ENABLE_SHARED_CRT ON) + endif() + add_subdirectory(effcee) + set_property(TARGET effcee PROPERTY FOLDER Effcee) + # Turn off warnings for effcee and re2 + set_property(TARGET effcee APPEND PROPERTY COMPILE_OPTIONS -w) + set_property(TARGET re2 APPEND PROPERTY COMPILE_OPTIONS -w) + endif() + endif() + # TODO(dneto): Eventually, require this. + endif() + if (TARGET effcee) + message(STATUS "SPIRV-Tools: Effcee is configured") + else() + message(STATUS "SPIRV-Tools: Effcee is not configured. Skipping Effcee-based tests.") + endif() + +endif() diff --git a/3rdparty/spirv-tools/include/generated/DebugInfo.h b/3rdparty/spirv-tools/include/generated/DebugInfo.h new file mode 100644 index 000000000..9639ea107 --- /dev/null +++ b/3rdparty/spirv-tools/include/generated/DebugInfo.h @@ -0,0 +1,137 @@ +// Copyright (c) 2017 The Khronos Group Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and/or associated documentation files (the "Materials"), +// to deal in the Materials without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Materials, and to permit persons to whom the +// Materials are furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Materials. +// +// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS +// STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND +// HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ +// +// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS +// IN THE MATERIALS. + +#ifndef SPIRV_EXTINST_DebugInfo_H_ +#define SPIRV_EXTINST_DebugInfo_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +enum { DebugInfoVersion = 100, DebugInfoVersion_BitWidthPadding = 0x7fffffff }; +enum { DebugInfoRevision = 1, DebugInfoRevision_BitWidthPadding = 0x7fffffff }; + +enum DebugInfoInstructions { + DebugInfoDebugInfoNone = 0, + DebugInfoDebugCompilationUnit = 1, + DebugInfoDebugTypeBasic = 2, + DebugInfoDebugTypePointer = 3, + DebugInfoDebugTypeQualifier = 4, + DebugInfoDebugTypeArray = 5, + DebugInfoDebugTypeVector = 6, + DebugInfoDebugTypedef = 7, + DebugInfoDebugTypeFunction = 8, + DebugInfoDebugTypeEnum = 9, + DebugInfoDebugTypeComposite = 10, + DebugInfoDebugTypeMember = 11, + DebugInfoDebugTypeInheritance = 12, + DebugInfoDebugTypePtrToMember = 13, + DebugInfoDebugTypeTemplate = 14, + DebugInfoDebugTypeTemplateParameter = 15, + DebugInfoDebugTypeTemplateTemplateParameter = 16, + DebugInfoDebugTypeTemplateParameterPack = 17, + DebugInfoDebugGlobalVariable = 18, + DebugInfoDebugFunctionDeclaration = 19, + DebugInfoDebugFunction = 20, + DebugInfoDebugLexicalBlock = 21, + DebugInfoDebugLexicalBlockDiscriminator = 22, + DebugInfoDebugScope = 23, + DebugInfoDebugNoScope = 24, + DebugInfoDebugInlinedAt = 25, + DebugInfoDebugLocalVariable = 26, + DebugInfoDebugInlinedVariable = 27, + DebugInfoDebugDeclare = 28, + DebugInfoDebugValue = 29, + DebugInfoDebugOperation = 30, + DebugInfoDebugExpression = 31, + DebugInfoDebugMacroDef = 32, + DebugInfoDebugMacroUndef = 33, + DebugInfoInstructionsMax = 0x7ffffff +}; + + +enum DebugInfoDebugInfoFlags { + DebugInfoFlagIsProtected = 0x01, + DebugInfoFlagIsPrivate = 0x02, + DebugInfoFlagIsPublic = 0x03, + DebugInfoFlagIsLocal = 0x04, + DebugInfoFlagIsDefinition = 0x08, + DebugInfoFlagFwdDecl = 0x10, + DebugInfoFlagArtificial = 0x20, + DebugInfoFlagExplicit = 0x40, + DebugInfoFlagPrototyped = 0x80, + DebugInfoFlagObjectPointer = 0x100, + DebugInfoFlagStaticMember = 0x200, + DebugInfoFlagIndirectVariable = 0x400, + DebugInfoFlagLValueReference = 0x800, + DebugInfoFlagRValueReference = 0x1000, + DebugInfoFlagIsOptimized = 0x2000, + DebugInfoDebugInfoFlagsMax = 0x7ffffff +}; + +enum DebugInfoDebugBaseTypeAttributeEncoding { + DebugInfoUnspecified = 0, + DebugInfoAddress = 1, + DebugInfoBoolean = 2, + DebugInfoFloat = 4, + DebugInfoSigned = 5, + DebugInfoSignedChar = 6, + DebugInfoUnsigned = 7, + DebugInfoUnsignedChar = 8, + DebugInfoDebugBaseTypeAttributeEncodingMax = 0x7ffffff +}; + +enum DebugInfoDebugCompositeType { + DebugInfoClass = 0, + DebugInfoStructure = 1, + DebugInfoUnion = 2, + DebugInfoDebugCompositeTypeMax = 0x7ffffff +}; + +enum DebugInfoDebugTypeQualifier { + DebugInfoConstType = 0, + DebugInfoVolatileType = 1, + DebugInfoRestrictType = 2, + DebugInfoDebugTypeQualifierMax = 0x7ffffff +}; + +enum DebugInfoDebugOperation { + DebugInfoDeref = 0, + DebugInfoPlus = 1, + DebugInfoMinus = 2, + DebugInfoPlusUconst = 3, + DebugInfoBitPiece = 4, + DebugInfoSwap = 5, + DebugInfoXderef = 6, + DebugInfoStackValue = 7, + DebugInfoConstu = 8, + DebugInfoDebugOperationMax = 0x7ffffff +}; + + +#ifdef __cplusplus +} +#endif + +#endif // SPIRV_EXTINST_DebugInfo_H_ diff --git a/3rdparty/spirv-tools/include/generated/build-version.inc b/3rdparty/spirv-tools/include/generated/build-version.inc new file mode 100644 index 000000000..f6329caa0 --- /dev/null +++ b/3rdparty/spirv-tools/include/generated/build-version.inc @@ -0,0 +1 @@ +"v2018.3-dev", "SPIRV-Tools v2018.3-dev v2018.2-56-g3020104" diff --git a/3rdparty/spirv-tools/include/generated/core.insts-unified1.inc b/3rdparty/spirv-tools/include/generated/core.insts-unified1.inc new file mode 100644 index 000000000..1d686309e --- /dev/null +++ b/3rdparty/spirv-tools/include/generated/core.insts-unified1.inc @@ -0,0 +1,410 @@ +static const SpvCapability pygen_variable_caps_Addresses[] = {SpvCapabilityAddresses}; +static const SpvCapability pygen_variable_caps_AddressesVariablePointersVariablePointersStorageBuffer[] = {SpvCapabilityAddresses, SpvCapabilityVariablePointers, SpvCapabilityVariablePointersStorageBuffer}; +static const SpvCapability pygen_variable_caps_DerivativeControl[] = {SpvCapabilityDerivativeControl}; +static const SpvCapability pygen_variable_caps_DeviceEnqueue[] = {SpvCapabilityDeviceEnqueue}; +static const SpvCapability pygen_variable_caps_FragmentMaskAMD[] = {SpvCapabilityFragmentMaskAMD}; +static const SpvCapability pygen_variable_caps_Geometry[] = {SpvCapabilityGeometry}; +static const SpvCapability pygen_variable_caps_GeometryStreams[] = {SpvCapabilityGeometryStreams}; +static const SpvCapability pygen_variable_caps_GroupNonUniform[] = {SpvCapabilityGroupNonUniform}; +static const SpvCapability pygen_variable_caps_GroupNonUniformArithmeticGroupNonUniformClustered[] = {SpvCapabilityGroupNonUniformArithmetic, SpvCapabilityGroupNonUniformClustered}; +static const SpvCapability pygen_variable_caps_GroupNonUniformBallot[] = {SpvCapabilityGroupNonUniformBallot}; +static const SpvCapability pygen_variable_caps_GroupNonUniformPartitionedNV[] = {SpvCapabilityGroupNonUniformPartitionedNV}; +static const SpvCapability pygen_variable_caps_GroupNonUniformQuad[] = {SpvCapabilityGroupNonUniformQuad}; +static const SpvCapability pygen_variable_caps_GroupNonUniformShuffle[] = {SpvCapabilityGroupNonUniformShuffle}; +static const SpvCapability pygen_variable_caps_GroupNonUniformShuffleRelative[] = {SpvCapabilityGroupNonUniformShuffleRelative}; +static const SpvCapability pygen_variable_caps_GroupNonUniformVote[] = {SpvCapabilityGroupNonUniformVote}; +static const SpvCapability pygen_variable_caps_Groups[] = {SpvCapabilityGroups}; +static const SpvCapability pygen_variable_caps_ImageQuery[] = {SpvCapabilityImageQuery}; +static const SpvCapability pygen_variable_caps_Kernel[] = {SpvCapabilityKernel}; +static const SpvCapability pygen_variable_caps_KernelImageQuery[] = {SpvCapabilityKernel, SpvCapabilityImageQuery}; +static const SpvCapability pygen_variable_caps_LiteralSampler[] = {SpvCapabilityLiteralSampler}; +static const SpvCapability pygen_variable_caps_Matrix[] = {SpvCapabilityMatrix}; +static const SpvCapability pygen_variable_caps_NamedBarrier[] = {SpvCapabilityNamedBarrier}; +static const SpvCapability pygen_variable_caps_PipeStorage[] = {SpvCapabilityPipeStorage}; +static const SpvCapability pygen_variable_caps_Pipes[] = {SpvCapabilityPipes}; +static const SpvCapability pygen_variable_caps_Shader[] = {SpvCapabilityShader}; +static const SpvCapability pygen_variable_caps_SparseResidency[] = {SpvCapabilitySparseResidency}; +static const SpvCapability pygen_variable_caps_SubgroupBallotKHR[] = {SpvCapabilitySubgroupBallotKHR}; +static const SpvCapability pygen_variable_caps_SubgroupBufferBlockIOINTEL[] = {SpvCapabilitySubgroupBufferBlockIOINTEL}; +static const SpvCapability pygen_variable_caps_SubgroupDispatch[] = {SpvCapabilitySubgroupDispatch}; +static const SpvCapability pygen_variable_caps_SubgroupImageBlockIOINTEL[] = {SpvCapabilitySubgroupImageBlockIOINTEL}; +static const SpvCapability pygen_variable_caps_SubgroupShuffleINTEL[] = {SpvCapabilitySubgroupShuffleINTEL}; +static const SpvCapability pygen_variable_caps_SubgroupVoteKHR[] = {SpvCapabilitySubgroupVoteKHR}; + +static const libspirv::Extension pygen_variable_exts_SPV_AMD_shader_ballot[] = {libspirv::Extension::kSPV_AMD_shader_ballot}; +static const libspirv::Extension pygen_variable_exts_SPV_AMD_shader_fragment_mask[] = {libspirv::Extension::kSPV_AMD_shader_fragment_mask}; +static const libspirv::Extension pygen_variable_exts_SPV_GOOGLE_decorate_string[] = {libspirv::Extension::kSPV_GOOGLE_decorate_string}; +static const libspirv::Extension pygen_variable_exts_SPV_GOOGLE_hlsl_functionality1[] = {libspirv::Extension::kSPV_GOOGLE_hlsl_functionality1}; +static const libspirv::Extension pygen_variable_exts_SPV_KHR_shader_ballot[] = {libspirv::Extension::kSPV_KHR_shader_ballot}; +static const libspirv::Extension pygen_variable_exts_SPV_KHR_subgroup_vote[] = {libspirv::Extension::kSPV_KHR_subgroup_vote}; +static const libspirv::Extension pygen_variable_exts_SPV_NV_shader_subgroup_partitioned[] = {libspirv::Extension::kSPV_NV_shader_subgroup_partitioned}; + +static const spv_opcode_desc_t kOpcodeTableEntries[] = { + {"Nop", SpvOpNop, 0, nullptr, 0, {}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Undef", SpvOpUndef, 0, nullptr, 2, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SourceContinued", SpvOpSourceContinued, 0, nullptr, 1, {SPV_OPERAND_TYPE_LITERAL_STRING}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Source", SpvOpSource, 0, nullptr, 4, {SPV_OPERAND_TYPE_SOURCE_LANGUAGE, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_OPTIONAL_ID, SPV_OPERAND_TYPE_OPTIONAL_LITERAL_STRING}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SourceExtension", SpvOpSourceExtension, 0, nullptr, 1, {SPV_OPERAND_TYPE_LITERAL_STRING}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Name", SpvOpName, 0, nullptr, 2, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_STRING}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"MemberName", SpvOpMemberName, 0, nullptr, 3, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_STRING}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"String", SpvOpString, 0, nullptr, 2, {SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_LITERAL_STRING}, 1, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Line", SpvOpLine, 0, nullptr, 3, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Extension", SpvOpExtension, 0, nullptr, 1, {SPV_OPERAND_TYPE_LITERAL_STRING}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ExtInstImport", SpvOpExtInstImport, 0, nullptr, 2, {SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_LITERAL_STRING}, 1, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ExtInst", SpvOpExtInst, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"MemoryModel", SpvOpMemoryModel, 0, nullptr, 2, {SPV_OPERAND_TYPE_ADDRESSING_MODEL, SPV_OPERAND_TYPE_MEMORY_MODEL}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"EntryPoint", SpvOpEntryPoint, 0, nullptr, 4, {SPV_OPERAND_TYPE_EXECUTION_MODEL, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_STRING, SPV_OPERAND_TYPE_VARIABLE_ID}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ExecutionMode", SpvOpExecutionMode, 0, nullptr, 2, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_EXECUTION_MODE}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Capability", SpvOpCapability, 0, nullptr, 1, {SPV_OPERAND_TYPE_CAPABILITY}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"TypeVoid", SpvOpTypeVoid, 0, nullptr, 1, {SPV_OPERAND_TYPE_RESULT_ID}, 1, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"TypeBool", SpvOpTypeBool, 0, nullptr, 1, {SPV_OPERAND_TYPE_RESULT_ID}, 1, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"TypeInt", SpvOpTypeInt, 0, nullptr, 3, {SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER}, 1, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"TypeFloat", SpvOpTypeFloat, 0, nullptr, 2, {SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER}, 1, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"TypeVector", SpvOpTypeVector, 0, nullptr, 3, {SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER}, 1, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"TypeMatrix", SpvOpTypeMatrix, 1, pygen_variable_caps_Matrix, 3, {SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER}, 1, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"TypeImage", SpvOpTypeImage, 0, nullptr, 9, {SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_DIMENSIONALITY, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_SAMPLER_IMAGE_FORMAT, SPV_OPERAND_TYPE_OPTIONAL_ACCESS_QUALIFIER}, 1, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"TypeSampler", SpvOpTypeSampler, 0, nullptr, 1, {SPV_OPERAND_TYPE_RESULT_ID}, 1, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"TypeSampledImage", SpvOpTypeSampledImage, 0, nullptr, 2, {SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"TypeArray", SpvOpTypeArray, 0, nullptr, 3, {SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"TypeRuntimeArray", SpvOpTypeRuntimeArray, 1, pygen_variable_caps_Shader, 2, {SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"TypeStruct", SpvOpTypeStruct, 0, nullptr, 2, {SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_VARIABLE_ID}, 1, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"TypeOpaque", SpvOpTypeOpaque, 1, pygen_variable_caps_Kernel, 2, {SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_LITERAL_STRING}, 1, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"TypePointer", SpvOpTypePointer, 0, nullptr, 3, {SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_STORAGE_CLASS, SPV_OPERAND_TYPE_ID}, 1, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"TypeFunction", SpvOpTypeFunction, 0, nullptr, 3, {SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_VARIABLE_ID}, 1, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"TypeEvent", SpvOpTypeEvent, 1, pygen_variable_caps_Kernel, 1, {SPV_OPERAND_TYPE_RESULT_ID}, 1, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"TypeDeviceEvent", SpvOpTypeDeviceEvent, 1, pygen_variable_caps_DeviceEnqueue, 1, {SPV_OPERAND_TYPE_RESULT_ID}, 1, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"TypeReserveId", SpvOpTypeReserveId, 1, pygen_variable_caps_Pipes, 1, {SPV_OPERAND_TYPE_RESULT_ID}, 1, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"TypeQueue", SpvOpTypeQueue, 1, pygen_variable_caps_DeviceEnqueue, 1, {SPV_OPERAND_TYPE_RESULT_ID}, 1, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"TypePipe", SpvOpTypePipe, 1, pygen_variable_caps_Pipes, 2, {SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ACCESS_QUALIFIER}, 1, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"TypeForwardPointer", SpvOpTypeForwardPointer, 1, pygen_variable_caps_Addresses, 2, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_STORAGE_CLASS}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ConstantTrue", SpvOpConstantTrue, 0, nullptr, 2, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ConstantFalse", SpvOpConstantFalse, 0, nullptr, 2, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Constant", SpvOpConstant, 0, nullptr, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ConstantComposite", SpvOpConstantComposite, 0, nullptr, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_VARIABLE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ConstantSampler", SpvOpConstantSampler, 1, pygen_variable_caps_LiteralSampler, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SAMPLER_ADDRESSING_MODE, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_SAMPLER_FILTER_MODE}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ConstantNull", SpvOpConstantNull, 0, nullptr, 2, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SpecConstantTrue", SpvOpSpecConstantTrue, 0, nullptr, 2, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SpecConstantFalse", SpvOpSpecConstantFalse, 0, nullptr, 2, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SpecConstant", SpvOpSpecConstant, 0, nullptr, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SpecConstantComposite", SpvOpSpecConstantComposite, 0, nullptr, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_VARIABLE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SpecConstantOp", SpvOpSpecConstantOp, 0, nullptr, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Function", SpvOpFunction, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_FUNCTION_CONTROL, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FunctionParameter", SpvOpFunctionParameter, 0, nullptr, 2, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FunctionEnd", SpvOpFunctionEnd, 0, nullptr, 0, {}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FunctionCall", SpvOpFunctionCall, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_VARIABLE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Variable", SpvOpVariable, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_STORAGE_CLASS, SPV_OPERAND_TYPE_OPTIONAL_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageTexelPointer", SpvOpImageTexelPointer, 0, nullptr, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Load", SpvOpLoad, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Store", SpvOpStore, 0, nullptr, 3, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"CopyMemory", SpvOpCopyMemory, 0, nullptr, 3, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"CopyMemorySized", SpvOpCopyMemorySized, 1, pygen_variable_caps_Addresses, 4, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"AccessChain", SpvOpAccessChain, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_VARIABLE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"InBoundsAccessChain", SpvOpInBoundsAccessChain, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_VARIABLE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"PtrAccessChain", SpvOpPtrAccessChain, 3, pygen_variable_caps_AddressesVariablePointersVariablePointersStorageBuffer, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_VARIABLE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ArrayLength", SpvOpArrayLength, 1, pygen_variable_caps_Shader, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GenericPtrMemSemantics", SpvOpGenericPtrMemSemantics, 1, pygen_variable_caps_Kernel, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"InBoundsPtrAccessChain", SpvOpInBoundsPtrAccessChain, 1, pygen_variable_caps_Addresses, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_VARIABLE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Decorate", SpvOpDecorate, 0, nullptr, 2, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_DECORATION}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"MemberDecorate", SpvOpMemberDecorate, 0, nullptr, 3, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_DECORATION}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"DecorationGroup", SpvOpDecorationGroup, 0, nullptr, 1, {SPV_OPERAND_TYPE_RESULT_ID}, 1, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GroupDecorate", SpvOpGroupDecorate, 0, nullptr, 2, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_VARIABLE_ID}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GroupMemberDecorate", SpvOpGroupMemberDecorate, 0, nullptr, 2, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_VARIABLE_ID_LITERAL_INTEGER}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"VectorExtractDynamic", SpvOpVectorExtractDynamic, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"VectorInsertDynamic", SpvOpVectorInsertDynamic, 0, nullptr, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"VectorShuffle", SpvOpVectorShuffle, 0, nullptr, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_VARIABLE_LITERAL_INTEGER}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"CompositeConstruct", SpvOpCompositeConstruct, 0, nullptr, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_VARIABLE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"CompositeExtract", SpvOpCompositeExtract, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_VARIABLE_LITERAL_INTEGER}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"CompositeInsert", SpvOpCompositeInsert, 0, nullptr, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_VARIABLE_LITERAL_INTEGER}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"CopyObject", SpvOpCopyObject, 0, nullptr, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Transpose", SpvOpTranspose, 1, pygen_variable_caps_Matrix, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SampledImage", SpvOpSampledImage, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageSampleImplicitLod", SpvOpImageSampleImplicitLod, 1, pygen_variable_caps_Shader, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_IMAGE}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageSampleExplicitLod", SpvOpImageSampleExplicitLod, 0, nullptr, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_IMAGE}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageSampleDrefImplicitLod", SpvOpImageSampleDrefImplicitLod, 1, pygen_variable_caps_Shader, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_IMAGE}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageSampleDrefExplicitLod", SpvOpImageSampleDrefExplicitLod, 1, pygen_variable_caps_Shader, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_IMAGE}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageSampleProjImplicitLod", SpvOpImageSampleProjImplicitLod, 1, pygen_variable_caps_Shader, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_IMAGE}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageSampleProjExplicitLod", SpvOpImageSampleProjExplicitLod, 1, pygen_variable_caps_Shader, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_IMAGE}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageSampleProjDrefImplicitLod", SpvOpImageSampleProjDrefImplicitLod, 1, pygen_variable_caps_Shader, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_IMAGE}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageSampleProjDrefExplicitLod", SpvOpImageSampleProjDrefExplicitLod, 1, pygen_variable_caps_Shader, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_IMAGE}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageFetch", SpvOpImageFetch, 0, nullptr, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_IMAGE}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageGather", SpvOpImageGather, 1, pygen_variable_caps_Shader, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_IMAGE}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageDrefGather", SpvOpImageDrefGather, 1, pygen_variable_caps_Shader, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_IMAGE}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageRead", SpvOpImageRead, 0, nullptr, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_IMAGE}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageWrite", SpvOpImageWrite, 0, nullptr, 4, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_IMAGE}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Image", SpvOpImage, 0, nullptr, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageQueryFormat", SpvOpImageQueryFormat, 1, pygen_variable_caps_Kernel, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageQueryOrder", SpvOpImageQueryOrder, 1, pygen_variable_caps_Kernel, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageQuerySizeLod", SpvOpImageQuerySizeLod, 2, pygen_variable_caps_KernelImageQuery, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageQuerySize", SpvOpImageQuerySize, 2, pygen_variable_caps_KernelImageQuery, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageQueryLod", SpvOpImageQueryLod, 1, pygen_variable_caps_ImageQuery, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageQueryLevels", SpvOpImageQueryLevels, 2, pygen_variable_caps_KernelImageQuery, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageQuerySamples", SpvOpImageQuerySamples, 2, pygen_variable_caps_KernelImageQuery, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ConvertFToU", SpvOpConvertFToU, 0, nullptr, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ConvertFToS", SpvOpConvertFToS, 0, nullptr, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ConvertSToF", SpvOpConvertSToF, 0, nullptr, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ConvertUToF", SpvOpConvertUToF, 0, nullptr, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"UConvert", SpvOpUConvert, 0, nullptr, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SConvert", SpvOpSConvert, 0, nullptr, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FConvert", SpvOpFConvert, 0, nullptr, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"QuantizeToF16", SpvOpQuantizeToF16, 0, nullptr, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ConvertPtrToU", SpvOpConvertPtrToU, 1, pygen_variable_caps_Addresses, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SatConvertSToU", SpvOpSatConvertSToU, 1, pygen_variable_caps_Kernel, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SatConvertUToS", SpvOpSatConvertUToS, 1, pygen_variable_caps_Kernel, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ConvertUToPtr", SpvOpConvertUToPtr, 1, pygen_variable_caps_Addresses, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"PtrCastToGeneric", SpvOpPtrCastToGeneric, 1, pygen_variable_caps_Kernel, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GenericCastToPtr", SpvOpGenericCastToPtr, 1, pygen_variable_caps_Kernel, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GenericCastToPtrExplicit", SpvOpGenericCastToPtrExplicit, 1, pygen_variable_caps_Kernel, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_STORAGE_CLASS}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Bitcast", SpvOpBitcast, 0, nullptr, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SNegate", SpvOpSNegate, 0, nullptr, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FNegate", SpvOpFNegate, 0, nullptr, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"IAdd", SpvOpIAdd, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FAdd", SpvOpFAdd, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ISub", SpvOpISub, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FSub", SpvOpFSub, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"IMul", SpvOpIMul, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FMul", SpvOpFMul, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"UDiv", SpvOpUDiv, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SDiv", SpvOpSDiv, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FDiv", SpvOpFDiv, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"UMod", SpvOpUMod, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SRem", SpvOpSRem, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SMod", SpvOpSMod, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FRem", SpvOpFRem, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FMod", SpvOpFMod, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"VectorTimesScalar", SpvOpVectorTimesScalar, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"MatrixTimesScalar", SpvOpMatrixTimesScalar, 1, pygen_variable_caps_Matrix, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"VectorTimesMatrix", SpvOpVectorTimesMatrix, 1, pygen_variable_caps_Matrix, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"MatrixTimesVector", SpvOpMatrixTimesVector, 1, pygen_variable_caps_Matrix, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"MatrixTimesMatrix", SpvOpMatrixTimesMatrix, 1, pygen_variable_caps_Matrix, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"OuterProduct", SpvOpOuterProduct, 1, pygen_variable_caps_Matrix, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Dot", SpvOpDot, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"IAddCarry", SpvOpIAddCarry, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ISubBorrow", SpvOpISubBorrow, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"UMulExtended", SpvOpUMulExtended, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SMulExtended", SpvOpSMulExtended, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Any", SpvOpAny, 0, nullptr, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"All", SpvOpAll, 0, nullptr, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"IsNan", SpvOpIsNan, 0, nullptr, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"IsInf", SpvOpIsInf, 0, nullptr, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"IsFinite", SpvOpIsFinite, 1, pygen_variable_caps_Kernel, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"IsNormal", SpvOpIsNormal, 1, pygen_variable_caps_Kernel, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SignBitSet", SpvOpSignBitSet, 1, pygen_variable_caps_Kernel, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"LessOrGreater", SpvOpLessOrGreater, 1, pygen_variable_caps_Kernel, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Ordered", SpvOpOrdered, 1, pygen_variable_caps_Kernel, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Unordered", SpvOpUnordered, 1, pygen_variable_caps_Kernel, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"LogicalEqual", SpvOpLogicalEqual, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"LogicalNotEqual", SpvOpLogicalNotEqual, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"LogicalOr", SpvOpLogicalOr, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"LogicalAnd", SpvOpLogicalAnd, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"LogicalNot", SpvOpLogicalNot, 0, nullptr, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Select", SpvOpSelect, 0, nullptr, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"IEqual", SpvOpIEqual, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"INotEqual", SpvOpINotEqual, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"UGreaterThan", SpvOpUGreaterThan, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SGreaterThan", SpvOpSGreaterThan, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"UGreaterThanEqual", SpvOpUGreaterThanEqual, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SGreaterThanEqual", SpvOpSGreaterThanEqual, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ULessThan", SpvOpULessThan, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SLessThan", SpvOpSLessThan, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ULessThanEqual", SpvOpULessThanEqual, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SLessThanEqual", SpvOpSLessThanEqual, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FOrdEqual", SpvOpFOrdEqual, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FUnordEqual", SpvOpFUnordEqual, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FOrdNotEqual", SpvOpFOrdNotEqual, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FUnordNotEqual", SpvOpFUnordNotEqual, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FOrdLessThan", SpvOpFOrdLessThan, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FUnordLessThan", SpvOpFUnordLessThan, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FOrdGreaterThan", SpvOpFOrdGreaterThan, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FUnordGreaterThan", SpvOpFUnordGreaterThan, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FOrdLessThanEqual", SpvOpFOrdLessThanEqual, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FUnordLessThanEqual", SpvOpFUnordLessThanEqual, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FOrdGreaterThanEqual", SpvOpFOrdGreaterThanEqual, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FUnordGreaterThanEqual", SpvOpFUnordGreaterThanEqual, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ShiftRightLogical", SpvOpShiftRightLogical, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ShiftRightArithmetic", SpvOpShiftRightArithmetic, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ShiftLeftLogical", SpvOpShiftLeftLogical, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"BitwiseOr", SpvOpBitwiseOr, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"BitwiseXor", SpvOpBitwiseXor, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"BitwiseAnd", SpvOpBitwiseAnd, 0, nullptr, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Not", SpvOpNot, 0, nullptr, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"BitFieldInsert", SpvOpBitFieldInsert, 1, pygen_variable_caps_Shader, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"BitFieldSExtract", SpvOpBitFieldSExtract, 1, pygen_variable_caps_Shader, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"BitFieldUExtract", SpvOpBitFieldUExtract, 1, pygen_variable_caps_Shader, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"BitReverse", SpvOpBitReverse, 1, pygen_variable_caps_Shader, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"BitCount", SpvOpBitCount, 0, nullptr, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"DPdx", SpvOpDPdx, 1, pygen_variable_caps_Shader, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"DPdy", SpvOpDPdy, 1, pygen_variable_caps_Shader, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Fwidth", SpvOpFwidth, 1, pygen_variable_caps_Shader, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"DPdxFine", SpvOpDPdxFine, 1, pygen_variable_caps_DerivativeControl, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"DPdyFine", SpvOpDPdyFine, 1, pygen_variable_caps_DerivativeControl, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FwidthFine", SpvOpFwidthFine, 1, pygen_variable_caps_DerivativeControl, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"DPdxCoarse", SpvOpDPdxCoarse, 1, pygen_variable_caps_DerivativeControl, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"DPdyCoarse", SpvOpDPdyCoarse, 1, pygen_variable_caps_DerivativeControl, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FwidthCoarse", SpvOpFwidthCoarse, 1, pygen_variable_caps_DerivativeControl, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"EmitVertex", SpvOpEmitVertex, 1, pygen_variable_caps_Geometry, 0, {}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"EndPrimitive", SpvOpEndPrimitive, 1, pygen_variable_caps_Geometry, 0, {}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"EmitStreamVertex", SpvOpEmitStreamVertex, 1, pygen_variable_caps_GeometryStreams, 1, {SPV_OPERAND_TYPE_ID}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"EndStreamPrimitive", SpvOpEndStreamPrimitive, 1, pygen_variable_caps_GeometryStreams, 1, {SPV_OPERAND_TYPE_ID}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ControlBarrier", SpvOpControlBarrier, 0, nullptr, 3, {SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"MemoryBarrier", SpvOpMemoryBarrier, 0, nullptr, 2, {SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"AtomicLoad", SpvOpAtomicLoad, 0, nullptr, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"AtomicStore", SpvOpAtomicStore, 0, nullptr, 4, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID, SPV_OPERAND_TYPE_ID}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"AtomicExchange", SpvOpAtomicExchange, 0, nullptr, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"AtomicCompareExchange", SpvOpAtomicCompareExchange, 0, nullptr, 8, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID, SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"AtomicCompareExchangeWeak", SpvOpAtomicCompareExchangeWeak, 1, pygen_variable_caps_Kernel, 8, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID, SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"AtomicIIncrement", SpvOpAtomicIIncrement, 0, nullptr, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"AtomicIDecrement", SpvOpAtomicIDecrement, 0, nullptr, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"AtomicIAdd", SpvOpAtomicIAdd, 0, nullptr, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"AtomicISub", SpvOpAtomicISub, 0, nullptr, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"AtomicSMin", SpvOpAtomicSMin, 0, nullptr, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"AtomicUMin", SpvOpAtomicUMin, 0, nullptr, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"AtomicSMax", SpvOpAtomicSMax, 0, nullptr, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"AtomicUMax", SpvOpAtomicUMax, 0, nullptr, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"AtomicAnd", SpvOpAtomicAnd, 0, nullptr, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"AtomicOr", SpvOpAtomicOr, 0, nullptr, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"AtomicXor", SpvOpAtomicXor, 0, nullptr, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Phi", SpvOpPhi, 0, nullptr, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_VARIABLE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"LoopMerge", SpvOpLoopMerge, 0, nullptr, 3, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LOOP_CONTROL}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SelectionMerge", SpvOpSelectionMerge, 0, nullptr, 2, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_SELECTION_CONTROL}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Label", SpvOpLabel, 0, nullptr, 1, {SPV_OPERAND_TYPE_RESULT_ID}, 1, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Branch", SpvOpBranch, 0, nullptr, 1, {SPV_OPERAND_TYPE_ID}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"BranchConditional", SpvOpBranchConditional, 0, nullptr, 4, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_VARIABLE_LITERAL_INTEGER}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Switch", SpvOpSwitch, 0, nullptr, 3, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_VARIABLE_LITERAL_INTEGER_ID}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Kill", SpvOpKill, 1, pygen_variable_caps_Shader, 0, {}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Return", SpvOpReturn, 0, nullptr, 0, {}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ReturnValue", SpvOpReturnValue, 0, nullptr, 1, {SPV_OPERAND_TYPE_ID}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Unreachable", SpvOpUnreachable, 0, nullptr, 0, {}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"LifetimeStart", SpvOpLifetimeStart, 1, pygen_variable_caps_Kernel, 2, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"LifetimeStop", SpvOpLifetimeStop, 1, pygen_variable_caps_Kernel, 2, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GroupAsyncCopy", SpvOpGroupAsyncCopy, 1, pygen_variable_caps_Kernel, 8, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GroupWaitEvents", SpvOpGroupWaitEvents, 1, pygen_variable_caps_Kernel, 3, {SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GroupAll", SpvOpGroupAll, 1, pygen_variable_caps_Groups, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GroupAny", SpvOpGroupAny, 1, pygen_variable_caps_Groups, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GroupBroadcast", SpvOpGroupBroadcast, 1, pygen_variable_caps_Groups, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GroupIAdd", SpvOpGroupIAdd, 1, pygen_variable_caps_Groups, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GroupFAdd", SpvOpGroupFAdd, 1, pygen_variable_caps_Groups, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GroupFMin", SpvOpGroupFMin, 1, pygen_variable_caps_Groups, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GroupUMin", SpvOpGroupUMin, 1, pygen_variable_caps_Groups, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GroupSMin", SpvOpGroupSMin, 1, pygen_variable_caps_Groups, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GroupFMax", SpvOpGroupFMax, 1, pygen_variable_caps_Groups, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GroupUMax", SpvOpGroupUMax, 1, pygen_variable_caps_Groups, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GroupSMax", SpvOpGroupSMax, 1, pygen_variable_caps_Groups, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ReadPipe", SpvOpReadPipe, 1, pygen_variable_caps_Pipes, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"WritePipe", SpvOpWritePipe, 1, pygen_variable_caps_Pipes, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ReservedReadPipe", SpvOpReservedReadPipe, 1, pygen_variable_caps_Pipes, 8, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ReservedWritePipe", SpvOpReservedWritePipe, 1, pygen_variable_caps_Pipes, 8, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ReserveReadPipePackets", SpvOpReserveReadPipePackets, 1, pygen_variable_caps_Pipes, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ReserveWritePipePackets", SpvOpReserveWritePipePackets, 1, pygen_variable_caps_Pipes, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"CommitReadPipe", SpvOpCommitReadPipe, 1, pygen_variable_caps_Pipes, 4, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"CommitWritePipe", SpvOpCommitWritePipe, 1, pygen_variable_caps_Pipes, 4, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"IsValidReserveId", SpvOpIsValidReserveId, 1, pygen_variable_caps_Pipes, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GetNumPipePackets", SpvOpGetNumPipePackets, 1, pygen_variable_caps_Pipes, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GetMaxPipePackets", SpvOpGetMaxPipePackets, 1, pygen_variable_caps_Pipes, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GroupReserveReadPipePackets", SpvOpGroupReserveReadPipePackets, 1, pygen_variable_caps_Pipes, 7, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GroupReserveWritePipePackets", SpvOpGroupReserveWritePipePackets, 1, pygen_variable_caps_Pipes, 7, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GroupCommitReadPipe", SpvOpGroupCommitReadPipe, 1, pygen_variable_caps_Pipes, 5, {SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GroupCommitWritePipe", SpvOpGroupCommitWritePipe, 1, pygen_variable_caps_Pipes, 5, {SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"EnqueueMarker", SpvOpEnqueueMarker, 1, pygen_variable_caps_DeviceEnqueue, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"EnqueueKernel", SpvOpEnqueueKernel, 1, pygen_variable_caps_DeviceEnqueue, 13, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_VARIABLE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GetKernelNDrangeSubGroupCount", SpvOpGetKernelNDrangeSubGroupCount, 1, pygen_variable_caps_DeviceEnqueue, 7, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GetKernelNDrangeMaxSubGroupSize", SpvOpGetKernelNDrangeMaxSubGroupSize, 1, pygen_variable_caps_DeviceEnqueue, 7, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GetKernelWorkGroupSize", SpvOpGetKernelWorkGroupSize, 1, pygen_variable_caps_DeviceEnqueue, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GetKernelPreferredWorkGroupSizeMultiple", SpvOpGetKernelPreferredWorkGroupSizeMultiple, 1, pygen_variable_caps_DeviceEnqueue, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"RetainEvent", SpvOpRetainEvent, 1, pygen_variable_caps_DeviceEnqueue, 1, {SPV_OPERAND_TYPE_ID}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ReleaseEvent", SpvOpReleaseEvent, 1, pygen_variable_caps_DeviceEnqueue, 1, {SPV_OPERAND_TYPE_ID}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"CreateUserEvent", SpvOpCreateUserEvent, 1, pygen_variable_caps_DeviceEnqueue, 2, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"IsValidEvent", SpvOpIsValidEvent, 1, pygen_variable_caps_DeviceEnqueue, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SetUserEventStatus", SpvOpSetUserEventStatus, 1, pygen_variable_caps_DeviceEnqueue, 2, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"CaptureEventProfilingInfo", SpvOpCaptureEventProfilingInfo, 1, pygen_variable_caps_DeviceEnqueue, 3, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GetDefaultQueue", SpvOpGetDefaultQueue, 1, pygen_variable_caps_DeviceEnqueue, 2, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"BuildNDRange", SpvOpBuildNDRange, 1, pygen_variable_caps_DeviceEnqueue, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageSparseSampleImplicitLod", SpvOpImageSparseSampleImplicitLod, 1, pygen_variable_caps_SparseResidency, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_IMAGE}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageSparseSampleExplicitLod", SpvOpImageSparseSampleExplicitLod, 1, pygen_variable_caps_SparseResidency, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_IMAGE}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageSparseSampleDrefImplicitLod", SpvOpImageSparseSampleDrefImplicitLod, 1, pygen_variable_caps_SparseResidency, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_IMAGE}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageSparseSampleDrefExplicitLod", SpvOpImageSparseSampleDrefExplicitLod, 1, pygen_variable_caps_SparseResidency, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_IMAGE}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageSparseSampleProjImplicitLod", SpvOpImageSparseSampleProjImplicitLod, 1, pygen_variable_caps_SparseResidency, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_IMAGE}, 1, 1, 0, nullptr, 0xffffffffu}, + {"ImageSparseSampleProjExplicitLod", SpvOpImageSparseSampleProjExplicitLod, 1, pygen_variable_caps_SparseResidency, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_IMAGE}, 1, 1, 0, nullptr, 0xffffffffu}, + {"ImageSparseSampleProjDrefImplicitLod", SpvOpImageSparseSampleProjDrefImplicitLod, 1, pygen_variable_caps_SparseResidency, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_IMAGE}, 1, 1, 0, nullptr, 0xffffffffu}, + {"ImageSparseSampleProjDrefExplicitLod", SpvOpImageSparseSampleProjDrefExplicitLod, 1, pygen_variable_caps_SparseResidency, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_IMAGE}, 1, 1, 0, nullptr, 0xffffffffu}, + {"ImageSparseFetch", SpvOpImageSparseFetch, 1, pygen_variable_caps_SparseResidency, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_IMAGE}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageSparseGather", SpvOpImageSparseGather, 1, pygen_variable_caps_SparseResidency, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_IMAGE}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageSparseDrefGather", SpvOpImageSparseDrefGather, 1, pygen_variable_caps_SparseResidency, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_IMAGE}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageSparseTexelsResident", SpvOpImageSparseTexelsResident, 1, pygen_variable_caps_SparseResidency, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"NoLine", SpvOpNoLine, 0, nullptr, 0, {}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"AtomicFlagTestAndSet", SpvOpAtomicFlagTestAndSet, 1, pygen_variable_caps_Kernel, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"AtomicFlagClear", SpvOpAtomicFlagClear, 1, pygen_variable_caps_Kernel, 3, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageSparseRead", SpvOpImageSparseRead, 1, pygen_variable_caps_SparseResidency, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_IMAGE}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SizeOf", SpvOpSizeOf, 1, pygen_variable_caps_Addresses, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,1)}, + {"TypePipeStorage", SpvOpTypePipeStorage, 1, pygen_variable_caps_PipeStorage, 1, {SPV_OPERAND_TYPE_RESULT_ID}, 1, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,1)}, + {"ConstantPipeStorage", SpvOpConstantPipeStorage, 1, pygen_variable_caps_PipeStorage, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,1)}, + {"CreatePipeFromPipeStorage", SpvOpCreatePipeFromPipeStorage, 1, pygen_variable_caps_PipeStorage, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,1)}, + {"GetKernelLocalSizeForSubgroupCount", SpvOpGetKernelLocalSizeForSubgroupCount, 1, pygen_variable_caps_SubgroupDispatch, 7, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,1)}, + {"GetKernelMaxNumSubgroups", SpvOpGetKernelMaxNumSubgroups, 1, pygen_variable_caps_SubgroupDispatch, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,1)}, + {"TypeNamedBarrier", SpvOpTypeNamedBarrier, 1, pygen_variable_caps_NamedBarrier, 1, {SPV_OPERAND_TYPE_RESULT_ID}, 1, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,1)}, + {"NamedBarrierInitialize", SpvOpNamedBarrierInitialize, 1, pygen_variable_caps_NamedBarrier, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,1)}, + {"MemoryNamedBarrier", SpvOpMemoryNamedBarrier, 1, pygen_variable_caps_NamedBarrier, 3, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,1)}, + {"ModuleProcessed", SpvOpModuleProcessed, 0, nullptr, 1, {SPV_OPERAND_TYPE_LITERAL_STRING}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,1)}, + {"ExecutionModeId", SpvOpExecutionModeId, 0, nullptr, 2, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_EXECUTION_MODE}, 0, 0, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,2)}, + {"DecorateId", SpvOpDecorateId, 0, nullptr, 2, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_DECORATION}, 0, 0, 1, pygen_variable_exts_SPV_GOOGLE_hlsl_functionality1, SPV_SPIRV_VERSION_WORD(1,2)}, + {"GroupNonUniformElect", SpvOpGroupNonUniformElect, 1, pygen_variable_caps_GroupNonUniform, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformAll", SpvOpGroupNonUniformAll, 1, pygen_variable_caps_GroupNonUniformVote, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformAny", SpvOpGroupNonUniformAny, 1, pygen_variable_caps_GroupNonUniformVote, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformAllEqual", SpvOpGroupNonUniformAllEqual, 1, pygen_variable_caps_GroupNonUniformVote, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformBroadcast", SpvOpGroupNonUniformBroadcast, 1, pygen_variable_caps_GroupNonUniformBallot, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformBroadcastFirst", SpvOpGroupNonUniformBroadcastFirst, 1, pygen_variable_caps_GroupNonUniformBallot, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformBallot", SpvOpGroupNonUniformBallot, 1, pygen_variable_caps_GroupNonUniformBallot, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformInverseBallot", SpvOpGroupNonUniformInverseBallot, 1, pygen_variable_caps_GroupNonUniformBallot, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformBallotBitExtract", SpvOpGroupNonUniformBallotBitExtract, 1, pygen_variable_caps_GroupNonUniformBallot, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformBallotBitCount", SpvOpGroupNonUniformBallotBitCount, 1, pygen_variable_caps_GroupNonUniformBallot, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformBallotFindLSB", SpvOpGroupNonUniformBallotFindLSB, 1, pygen_variable_caps_GroupNonUniformBallot, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformBallotFindMSB", SpvOpGroupNonUniformBallotFindMSB, 1, pygen_variable_caps_GroupNonUniformBallot, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformShuffle", SpvOpGroupNonUniformShuffle, 1, pygen_variable_caps_GroupNonUniformShuffle, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformShuffleXor", SpvOpGroupNonUniformShuffleXor, 1, pygen_variable_caps_GroupNonUniformShuffle, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformShuffleUp", SpvOpGroupNonUniformShuffleUp, 1, pygen_variable_caps_GroupNonUniformShuffleRelative, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformShuffleDown", SpvOpGroupNonUniformShuffleDown, 1, pygen_variable_caps_GroupNonUniformShuffleRelative, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformIAdd", SpvOpGroupNonUniformIAdd, 2, pygen_variable_caps_GroupNonUniformArithmeticGroupNonUniformClustered, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformFAdd", SpvOpGroupNonUniformFAdd, 2, pygen_variable_caps_GroupNonUniformArithmeticGroupNonUniformClustered, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformIMul", SpvOpGroupNonUniformIMul, 2, pygen_variable_caps_GroupNonUniformArithmeticGroupNonUniformClustered, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformFMul", SpvOpGroupNonUniformFMul, 2, pygen_variable_caps_GroupNonUniformArithmeticGroupNonUniformClustered, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformSMin", SpvOpGroupNonUniformSMin, 2, pygen_variable_caps_GroupNonUniformArithmeticGroupNonUniformClustered, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformUMin", SpvOpGroupNonUniformUMin, 2, pygen_variable_caps_GroupNonUniformArithmeticGroupNonUniformClustered, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformFMin", SpvOpGroupNonUniformFMin, 2, pygen_variable_caps_GroupNonUniformArithmeticGroupNonUniformClustered, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformSMax", SpvOpGroupNonUniformSMax, 2, pygen_variable_caps_GroupNonUniformArithmeticGroupNonUniformClustered, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformUMax", SpvOpGroupNonUniformUMax, 2, pygen_variable_caps_GroupNonUniformArithmeticGroupNonUniformClustered, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformFMax", SpvOpGroupNonUniformFMax, 2, pygen_variable_caps_GroupNonUniformArithmeticGroupNonUniformClustered, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformBitwiseAnd", SpvOpGroupNonUniformBitwiseAnd, 2, pygen_variable_caps_GroupNonUniformArithmeticGroupNonUniformClustered, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformBitwiseOr", SpvOpGroupNonUniformBitwiseOr, 2, pygen_variable_caps_GroupNonUniformArithmeticGroupNonUniformClustered, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformBitwiseXor", SpvOpGroupNonUniformBitwiseXor, 2, pygen_variable_caps_GroupNonUniformArithmeticGroupNonUniformClustered, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformLogicalAnd", SpvOpGroupNonUniformLogicalAnd, 2, pygen_variable_caps_GroupNonUniformArithmeticGroupNonUniformClustered, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformLogicalOr", SpvOpGroupNonUniformLogicalOr, 2, pygen_variable_caps_GroupNonUniformArithmeticGroupNonUniformClustered, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformLogicalXor", SpvOpGroupNonUniformLogicalXor, 2, pygen_variable_caps_GroupNonUniformArithmeticGroupNonUniformClustered, 6, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformQuadBroadcast", SpvOpGroupNonUniformQuadBroadcast, 1, pygen_variable_caps_GroupNonUniformQuad, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformQuadSwap", SpvOpGroupNonUniformQuadSwap, 1, pygen_variable_caps_GroupNonUniformQuad, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, SPV_SPIRV_VERSION_WORD(1,3)}, + {"SubgroupBallotKHR", SpvOpSubgroupBallotKHR, 1, pygen_variable_caps_SubgroupBallotKHR, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 1, pygen_variable_exts_SPV_KHR_shader_ballot, 0xffffffffu}, + {"SubgroupFirstInvocationKHR", SpvOpSubgroupFirstInvocationKHR, 1, pygen_variable_caps_SubgroupBallotKHR, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 1, pygen_variable_exts_SPV_KHR_shader_ballot, 0xffffffffu}, + {"SubgroupAllKHR", SpvOpSubgroupAllKHR, 1, pygen_variable_caps_SubgroupVoteKHR, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 1, pygen_variable_exts_SPV_KHR_subgroup_vote, 0xffffffffu}, + {"SubgroupAnyKHR", SpvOpSubgroupAnyKHR, 1, pygen_variable_caps_SubgroupVoteKHR, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 1, pygen_variable_exts_SPV_KHR_subgroup_vote, 0xffffffffu}, + {"SubgroupAllEqualKHR", SpvOpSubgroupAllEqualKHR, 1, pygen_variable_caps_SubgroupVoteKHR, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 1, pygen_variable_exts_SPV_KHR_subgroup_vote, 0xffffffffu}, + {"SubgroupReadInvocationKHR", SpvOpSubgroupReadInvocationKHR, 1, pygen_variable_caps_SubgroupBallotKHR, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 1, pygen_variable_exts_SPV_KHR_shader_ballot, 0xffffffffu}, + {"GroupIAddNonUniformAMD", SpvOpGroupIAddNonUniformAMD, 1, pygen_variable_caps_Groups, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID}, 1, 1, 1, pygen_variable_exts_SPV_AMD_shader_ballot, 0xffffffffu}, + {"GroupFAddNonUniformAMD", SpvOpGroupFAddNonUniformAMD, 1, pygen_variable_caps_Groups, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID}, 1, 1, 1, pygen_variable_exts_SPV_AMD_shader_ballot, 0xffffffffu}, + {"GroupFMinNonUniformAMD", SpvOpGroupFMinNonUniformAMD, 1, pygen_variable_caps_Groups, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID}, 1, 1, 1, pygen_variable_exts_SPV_AMD_shader_ballot, 0xffffffffu}, + {"GroupUMinNonUniformAMD", SpvOpGroupUMinNonUniformAMD, 1, pygen_variable_caps_Groups, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID}, 1, 1, 1, pygen_variable_exts_SPV_AMD_shader_ballot, 0xffffffffu}, + {"GroupSMinNonUniformAMD", SpvOpGroupSMinNonUniformAMD, 1, pygen_variable_caps_Groups, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID}, 1, 1, 1, pygen_variable_exts_SPV_AMD_shader_ballot, 0xffffffffu}, + {"GroupFMaxNonUniformAMD", SpvOpGroupFMaxNonUniformAMD, 1, pygen_variable_caps_Groups, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID}, 1, 1, 1, pygen_variable_exts_SPV_AMD_shader_ballot, 0xffffffffu}, + {"GroupUMaxNonUniformAMD", SpvOpGroupUMaxNonUniformAMD, 1, pygen_variable_caps_Groups, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID}, 1, 1, 1, pygen_variable_exts_SPV_AMD_shader_ballot, 0xffffffffu}, + {"GroupSMaxNonUniformAMD", SpvOpGroupSMaxNonUniformAMD, 1, pygen_variable_caps_Groups, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_SCOPE_ID, SPV_OPERAND_TYPE_GROUP_OPERATION, SPV_OPERAND_TYPE_ID}, 1, 1, 1, pygen_variable_exts_SPV_AMD_shader_ballot, 0xffffffffu}, + {"FragmentMaskFetchAMD", SpvOpFragmentMaskFetchAMD, 1, pygen_variable_caps_FragmentMaskAMD, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 1, pygen_variable_exts_SPV_AMD_shader_fragment_mask, 0xffffffffu}, + {"FragmentFetchAMD", SpvOpFragmentFetchAMD, 1, pygen_variable_caps_FragmentMaskAMD, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 1, pygen_variable_exts_SPV_AMD_shader_fragment_mask, 0xffffffffu}, + {"GroupNonUniformPartitionNV", SpvOpGroupNonUniformPartitionNV, 1, pygen_variable_caps_GroupNonUniformPartitionedNV, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 1, pygen_variable_exts_SPV_NV_shader_subgroup_partitioned, 0xffffffffu}, + {"SubgroupShuffleINTEL", SpvOpSubgroupShuffleINTEL, 1, pygen_variable_caps_SubgroupShuffleINTEL, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, 0xffffffffu}, + {"SubgroupShuffleDownINTEL", SpvOpSubgroupShuffleDownINTEL, 1, pygen_variable_caps_SubgroupShuffleINTEL, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, 0xffffffffu}, + {"SubgroupShuffleUpINTEL", SpvOpSubgroupShuffleUpINTEL, 1, pygen_variable_caps_SubgroupShuffleINTEL, 5, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, 0xffffffffu}, + {"SubgroupShuffleXorINTEL", SpvOpSubgroupShuffleXorINTEL, 1, pygen_variable_caps_SubgroupShuffleINTEL, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, 0xffffffffu}, + {"SubgroupBlockReadINTEL", SpvOpSubgroupBlockReadINTEL, 1, pygen_variable_caps_SubgroupBufferBlockIOINTEL, 3, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, 0xffffffffu}, + {"SubgroupBlockWriteINTEL", SpvOpSubgroupBlockWriteINTEL, 1, pygen_variable_caps_SubgroupBufferBlockIOINTEL, 2, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 0, 0, 0, nullptr, 0xffffffffu}, + {"SubgroupImageBlockReadINTEL", SpvOpSubgroupImageBlockReadINTEL, 1, pygen_variable_caps_SubgroupImageBlockIOINTEL, 4, {SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 1, 1, 0, nullptr, 0xffffffffu}, + {"SubgroupImageBlockWriteINTEL", SpvOpSubgroupImageBlockWriteINTEL, 1, pygen_variable_caps_SubgroupImageBlockIOINTEL, 3, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, 0, 0, 0, nullptr, 0xffffffffu}, + {"DecorateStringGOOGLE", SpvOpDecorateStringGOOGLE, 0, nullptr, 2, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_DECORATION}, 0, 0, 1, pygen_variable_exts_SPV_GOOGLE_decorate_string, 0xffffffffu}, + {"MemberDecorateStringGOOGLE", SpvOpMemberDecorateStringGOOGLE, 0, nullptr, 3, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_DECORATION}, 0, 0, 1, pygen_variable_exts_SPV_GOOGLE_decorate_string, 0xffffffffu} +}; diff --git a/3rdparty/spirv-tools/include/generated/debuginfo.insts.inc b/3rdparty/spirv-tools/include/generated/debuginfo.insts.inc new file mode 100644 index 000000000..e28cc97d2 --- /dev/null +++ b/3rdparty/spirv-tools/include/generated/debuginfo.insts.inc @@ -0,0 +1,38 @@ + + +static const spv_ext_inst_desc_t debuginfo_entries[] = { + {"DebugInfoNone", 0, 0, nullptr, {SPV_OPERAND_TYPE_NONE}}, + {"DebugCompilationUnit", 1, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypeBasic", 2, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypePointer", 3, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_STORAGE_CLASS, SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypeQualifier", 4, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_DEBUG_TYPE_QUALIFIER, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypeArray", 5, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_VARIABLE_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypeVector", 6, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypedef", 7, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypeFunction", 8, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_VARIABLE_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypeEnum", 9, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS, SPV_OPERAND_TYPE_VARIABLE_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypeComposite", 10, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_DEBUG_COMPOSITE_TYPE, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS, SPV_OPERAND_TYPE_VARIABLE_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypeMember", 11, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS, SPV_OPERAND_TYPE_OPTIONAL_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypeInheritance", 12, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypePtrToMember", 13, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypeTemplate", 14, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_VARIABLE_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypeTemplateParameter", 15, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypeTemplateTemplateParameter", 16, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_NONE}}, + {"DebugTypeTemplateParameterPack", 17, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_VARIABLE_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugGlobalVariable", 18, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS, SPV_OPERAND_TYPE_OPTIONAL_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugFunctionDeclaration", 19, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS, SPV_OPERAND_TYPE_NONE}}, + {"DebugFunction", 20, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugLexicalBlock", 21, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugLexicalBlockDiscriminator", 22, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugScope", 23, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugNoScope", 24, 0, nullptr, {SPV_OPERAND_TYPE_NONE}}, + {"DebugInlinedAt", 25, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugLocalVariable", 26, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_LITERAL_INTEGER, SPV_OPERAND_TYPE_NONE}}, + {"DebugInlinedVariable", 27, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugDeclare", 28, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugValue", 29, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_VARIABLE_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugOperation", 30, 0, nullptr, {SPV_OPERAND_TYPE_DEBUG_OPERATION, SPV_OPERAND_TYPE_VARIABLE_LITERAL_INTEGER, SPV_OPERAND_TYPE_NONE}}, + {"DebugExpression", 31, 0, nullptr, {SPV_OPERAND_TYPE_VARIABLE_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugMacroDef", 32, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_OPTIONAL_ID, SPV_OPERAND_TYPE_NONE}}, + {"DebugMacroUndef", 33, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}} +}; diff --git a/3rdparty/spirv-tools/include/generated/enum_string_mapping.inc b/3rdparty/spirv-tools/include/generated/enum_string_mapping.inc new file mode 100644 index 000000000..a5b6274b2 --- /dev/null +++ b/3rdparty/spirv-tools/include/generated/enum_string_mapping.inc @@ -0,0 +1,317 @@ +const char* ExtensionToString(Extension extension) { + switch (extension) { + case Extension::kSPV_AMD_gcn_shader: + return "SPV_AMD_gcn_shader"; + case Extension::kSPV_AMD_gpu_shader_half_float: + return "SPV_AMD_gpu_shader_half_float"; + case Extension::kSPV_AMD_gpu_shader_half_float_fetch: + return "SPV_AMD_gpu_shader_half_float_fetch"; + case Extension::kSPV_AMD_gpu_shader_int16: + return "SPV_AMD_gpu_shader_int16"; + case Extension::kSPV_AMD_shader_ballot: + return "SPV_AMD_shader_ballot"; + case Extension::kSPV_AMD_shader_explicit_vertex_parameter: + return "SPV_AMD_shader_explicit_vertex_parameter"; + case Extension::kSPV_AMD_shader_fragment_mask: + return "SPV_AMD_shader_fragment_mask"; + case Extension::kSPV_AMD_shader_image_load_store_lod: + return "SPV_AMD_shader_image_load_store_lod"; + case Extension::kSPV_AMD_shader_trinary_minmax: + return "SPV_AMD_shader_trinary_minmax"; + case Extension::kSPV_AMD_texture_gather_bias_lod: + return "SPV_AMD_texture_gather_bias_lod"; + case Extension::kSPV_EXT_descriptor_indexing: + return "SPV_EXT_descriptor_indexing"; + case Extension::kSPV_EXT_fragment_fully_covered: + return "SPV_EXT_fragment_fully_covered"; + case Extension::kSPV_EXT_shader_stencil_export: + return "SPV_EXT_shader_stencil_export"; + case Extension::kSPV_EXT_shader_viewport_index_layer: + return "SPV_EXT_shader_viewport_index_layer"; + case Extension::kSPV_GOOGLE_decorate_string: + return "SPV_GOOGLE_decorate_string"; + case Extension::kSPV_GOOGLE_hlsl_functionality1: + return "SPV_GOOGLE_hlsl_functionality1"; + case Extension::kSPV_INTEL_subgroups: + return "SPV_INTEL_subgroups"; + case Extension::kSPV_KHR_16bit_storage: + return "SPV_KHR_16bit_storage"; + case Extension::kSPV_KHR_device_group: + return "SPV_KHR_device_group"; + case Extension::kSPV_KHR_multiview: + return "SPV_KHR_multiview"; + case Extension::kSPV_KHR_post_depth_coverage: + return "SPV_KHR_post_depth_coverage"; + case Extension::kSPV_KHR_shader_atomic_counter_ops: + return "SPV_KHR_shader_atomic_counter_ops"; + case Extension::kSPV_KHR_shader_ballot: + return "SPV_KHR_shader_ballot"; + case Extension::kSPV_KHR_shader_draw_parameters: + return "SPV_KHR_shader_draw_parameters"; + case Extension::kSPV_KHR_storage_buffer_storage_class: + return "SPV_KHR_storage_buffer_storage_class"; + case Extension::kSPV_KHR_subgroup_vote: + return "SPV_KHR_subgroup_vote"; + case Extension::kSPV_KHR_variable_pointers: + return "SPV_KHR_variable_pointers"; + case Extension::kSPV_NVX_multiview_per_view_attributes: + return "SPV_NVX_multiview_per_view_attributes"; + case Extension::kSPV_NV_geometry_shader_passthrough: + return "SPV_NV_geometry_shader_passthrough"; + case Extension::kSPV_NV_sample_mask_override_coverage: + return "SPV_NV_sample_mask_override_coverage"; + case Extension::kSPV_NV_shader_subgroup_partitioned: + return "SPV_NV_shader_subgroup_partitioned"; + case Extension::kSPV_NV_stereo_view_rendering: + return "SPV_NV_stereo_view_rendering"; + case Extension::kSPV_NV_viewport_array2: + return "SPV_NV_viewport_array2"; + case Extension::kSPV_VALIDATOR_ignore_type_decl_unique: + return "SPV_VALIDATOR_ignore_type_decl_unique"; + }; + + return ""; +} + + + bool GetExtensionFromString(const char* str, Extension* extension) { + static const char* known_ext_strs[] = { "SPV_AMD_gcn_shader", "SPV_AMD_gpu_shader_half_float", "SPV_AMD_gpu_shader_half_float_fetch", "SPV_AMD_gpu_shader_int16", "SPV_AMD_shader_ballot", "SPV_AMD_shader_explicit_vertex_parameter", "SPV_AMD_shader_fragment_mask", "SPV_AMD_shader_image_load_store_lod", "SPV_AMD_shader_trinary_minmax", "SPV_AMD_texture_gather_bias_lod", "SPV_EXT_descriptor_indexing", "SPV_EXT_fragment_fully_covered", "SPV_EXT_shader_stencil_export", "SPV_EXT_shader_viewport_index_layer", "SPV_GOOGLE_decorate_string", "SPV_GOOGLE_hlsl_functionality1", "SPV_INTEL_subgroups", "SPV_KHR_16bit_storage", "SPV_KHR_device_group", "SPV_KHR_multiview", "SPV_KHR_post_depth_coverage", "SPV_KHR_shader_atomic_counter_ops", "SPV_KHR_shader_ballot", "SPV_KHR_shader_draw_parameters", "SPV_KHR_storage_buffer_storage_class", "SPV_KHR_subgroup_vote", "SPV_KHR_variable_pointers", "SPV_NVX_multiview_per_view_attributes", "SPV_NV_geometry_shader_passthrough", "SPV_NV_sample_mask_override_coverage", "SPV_NV_shader_subgroup_partitioned", "SPV_NV_stereo_view_rendering", "SPV_NV_viewport_array2", "SPV_VALIDATOR_ignore_type_decl_unique" }; + static const Extension known_ext_ids[] = { Extension::kSPV_AMD_gcn_shader, Extension::kSPV_AMD_gpu_shader_half_float, Extension::kSPV_AMD_gpu_shader_half_float_fetch, Extension::kSPV_AMD_gpu_shader_int16, Extension::kSPV_AMD_shader_ballot, Extension::kSPV_AMD_shader_explicit_vertex_parameter, Extension::kSPV_AMD_shader_fragment_mask, Extension::kSPV_AMD_shader_image_load_store_lod, Extension::kSPV_AMD_shader_trinary_minmax, Extension::kSPV_AMD_texture_gather_bias_lod, Extension::kSPV_EXT_descriptor_indexing, Extension::kSPV_EXT_fragment_fully_covered, Extension::kSPV_EXT_shader_stencil_export, Extension::kSPV_EXT_shader_viewport_index_layer, Extension::kSPV_GOOGLE_decorate_string, Extension::kSPV_GOOGLE_hlsl_functionality1, Extension::kSPV_INTEL_subgroups, Extension::kSPV_KHR_16bit_storage, Extension::kSPV_KHR_device_group, Extension::kSPV_KHR_multiview, Extension::kSPV_KHR_post_depth_coverage, Extension::kSPV_KHR_shader_atomic_counter_ops, Extension::kSPV_KHR_shader_ballot, Extension::kSPV_KHR_shader_draw_parameters, Extension::kSPV_KHR_storage_buffer_storage_class, Extension::kSPV_KHR_subgroup_vote, Extension::kSPV_KHR_variable_pointers, Extension::kSPV_NVX_multiview_per_view_attributes, Extension::kSPV_NV_geometry_shader_passthrough, Extension::kSPV_NV_sample_mask_override_coverage, Extension::kSPV_NV_shader_subgroup_partitioned, Extension::kSPV_NV_stereo_view_rendering, Extension::kSPV_NV_viewport_array2, Extension::kSPV_VALIDATOR_ignore_type_decl_unique }; + const auto b = std::begin(known_ext_strs); + const auto e = std::end(known_ext_strs); + const auto found = std::equal_range( + b, e, str, [](const char* str1, const char* str2) { + return std::strcmp(str1, str2) < 0; + }); + if (found.first == e || found.first == found.second) return false; + + *extension = known_ext_ids[found.first - b]; + return true; + } + + +const char* CapabilityToString(SpvCapability capability) { + switch (capability) { + case SpvCapabilityMatrix: + return "Matrix"; + case SpvCapabilityShader: + return "Shader"; + case SpvCapabilityGeometry: + return "Geometry"; + case SpvCapabilityTessellation: + return "Tessellation"; + case SpvCapabilityAddresses: + return "Addresses"; + case SpvCapabilityLinkage: + return "Linkage"; + case SpvCapabilityKernel: + return "Kernel"; + case SpvCapabilityVector16: + return "Vector16"; + case SpvCapabilityFloat16Buffer: + return "Float16Buffer"; + case SpvCapabilityFloat16: + return "Float16"; + case SpvCapabilityFloat64: + return "Float64"; + case SpvCapabilityInt64: + return "Int64"; + case SpvCapabilityInt64Atomics: + return "Int64Atomics"; + case SpvCapabilityImageBasic: + return "ImageBasic"; + case SpvCapabilityImageReadWrite: + return "ImageReadWrite"; + case SpvCapabilityImageMipmap: + return "ImageMipmap"; + case SpvCapabilityPipes: + return "Pipes"; + case SpvCapabilityGroups: + return "Groups"; + case SpvCapabilityDeviceEnqueue: + return "DeviceEnqueue"; + case SpvCapabilityLiteralSampler: + return "LiteralSampler"; + case SpvCapabilityAtomicStorage: + return "AtomicStorage"; + case SpvCapabilityInt16: + return "Int16"; + case SpvCapabilityTessellationPointSize: + return "TessellationPointSize"; + case SpvCapabilityGeometryPointSize: + return "GeometryPointSize"; + case SpvCapabilityImageGatherExtended: + return "ImageGatherExtended"; + case SpvCapabilityStorageImageMultisample: + return "StorageImageMultisample"; + case SpvCapabilityUniformBufferArrayDynamicIndexing: + return "UniformBufferArrayDynamicIndexing"; + case SpvCapabilitySampledImageArrayDynamicIndexing: + return "SampledImageArrayDynamicIndexing"; + case SpvCapabilityStorageBufferArrayDynamicIndexing: + return "StorageBufferArrayDynamicIndexing"; + case SpvCapabilityStorageImageArrayDynamicIndexing: + return "StorageImageArrayDynamicIndexing"; + case SpvCapabilityClipDistance: + return "ClipDistance"; + case SpvCapabilityCullDistance: + return "CullDistance"; + case SpvCapabilityImageCubeArray: + return "ImageCubeArray"; + case SpvCapabilitySampleRateShading: + return "SampleRateShading"; + case SpvCapabilityImageRect: + return "ImageRect"; + case SpvCapabilitySampledRect: + return "SampledRect"; + case SpvCapabilityGenericPointer: + return "GenericPointer"; + case SpvCapabilityInt8: + return "Int8"; + case SpvCapabilityInputAttachment: + return "InputAttachment"; + case SpvCapabilitySparseResidency: + return "SparseResidency"; + case SpvCapabilityMinLod: + return "MinLod"; + case SpvCapabilitySampled1D: + return "Sampled1D"; + case SpvCapabilityImage1D: + return "Image1D"; + case SpvCapabilitySampledCubeArray: + return "SampledCubeArray"; + case SpvCapabilitySampledBuffer: + return "SampledBuffer"; + case SpvCapabilityImageBuffer: + return "ImageBuffer"; + case SpvCapabilityImageMSArray: + return "ImageMSArray"; + case SpvCapabilityStorageImageExtendedFormats: + return "StorageImageExtendedFormats"; + case SpvCapabilityImageQuery: + return "ImageQuery"; + case SpvCapabilityDerivativeControl: + return "DerivativeControl"; + case SpvCapabilityInterpolationFunction: + return "InterpolationFunction"; + case SpvCapabilityTransformFeedback: + return "TransformFeedback"; + case SpvCapabilityGeometryStreams: + return "GeometryStreams"; + case SpvCapabilityStorageImageReadWithoutFormat: + return "StorageImageReadWithoutFormat"; + case SpvCapabilityStorageImageWriteWithoutFormat: + return "StorageImageWriteWithoutFormat"; + case SpvCapabilityMultiViewport: + return "MultiViewport"; + case SpvCapabilitySubgroupDispatch: + return "SubgroupDispatch"; + case SpvCapabilityNamedBarrier: + return "NamedBarrier"; + case SpvCapabilityPipeStorage: + return "PipeStorage"; + case SpvCapabilityGroupNonUniform: + return "GroupNonUniform"; + case SpvCapabilityGroupNonUniformVote: + return "GroupNonUniformVote"; + case SpvCapabilityGroupNonUniformArithmetic: + return "GroupNonUniformArithmetic"; + case SpvCapabilityGroupNonUniformBallot: + return "GroupNonUniformBallot"; + case SpvCapabilityGroupNonUniformShuffle: + return "GroupNonUniformShuffle"; + case SpvCapabilityGroupNonUniformShuffleRelative: + return "GroupNonUniformShuffleRelative"; + case SpvCapabilityGroupNonUniformClustered: + return "GroupNonUniformClustered"; + case SpvCapabilityGroupNonUniformQuad: + return "GroupNonUniformQuad"; + case SpvCapabilitySubgroupBallotKHR: + return "SubgroupBallotKHR"; + case SpvCapabilityDrawParameters: + return "DrawParameters"; + case SpvCapabilitySubgroupVoteKHR: + return "SubgroupVoteKHR"; + case SpvCapabilityStorageBuffer16BitAccess: + return "StorageBuffer16BitAccess"; + case SpvCapabilityUniformAndStorageBuffer16BitAccess: + return "UniformAndStorageBuffer16BitAccess"; + case SpvCapabilityStoragePushConstant16: + return "StoragePushConstant16"; + case SpvCapabilityStorageInputOutput16: + return "StorageInputOutput16"; + case SpvCapabilityDeviceGroup: + return "DeviceGroup"; + case SpvCapabilityMultiView: + return "MultiView"; + case SpvCapabilityVariablePointersStorageBuffer: + return "VariablePointersStorageBuffer"; + case SpvCapabilityVariablePointers: + return "VariablePointers"; + case SpvCapabilityAtomicStorageOps: + return "AtomicStorageOps"; + case SpvCapabilitySampleMaskPostDepthCoverage: + return "SampleMaskPostDepthCoverage"; + case SpvCapabilityFloat16ImageAMD: + return "Float16ImageAMD"; + case SpvCapabilityImageGatherBiasLodAMD: + return "ImageGatherBiasLodAMD"; + case SpvCapabilityFragmentMaskAMD: + return "FragmentMaskAMD"; + case SpvCapabilityStencilExportEXT: + return "StencilExportEXT"; + case SpvCapabilityImageReadWriteLodAMD: + return "ImageReadWriteLodAMD"; + case SpvCapabilitySampleMaskOverrideCoverageNV: + return "SampleMaskOverrideCoverageNV"; + case SpvCapabilityGeometryShaderPassthroughNV: + return "GeometryShaderPassthroughNV"; + case SpvCapabilityShaderViewportIndexLayerEXT: + return "ShaderViewportIndexLayerEXT"; + case SpvCapabilityShaderViewportMaskNV: + return "ShaderViewportMaskNV"; + case SpvCapabilityShaderStereoViewNV: + return "ShaderStereoViewNV"; + case SpvCapabilityPerViewAttributesNV: + return "PerViewAttributesNV"; + case SpvCapabilityFragmentFullyCoveredEXT: + return "FragmentFullyCoveredEXT"; + case SpvCapabilityShaderNonUniformEXT: + return "ShaderNonUniformEXT"; + case SpvCapabilityRuntimeDescriptorArrayEXT: + return "RuntimeDescriptorArrayEXT"; + case SpvCapabilityInputAttachmentArrayDynamicIndexingEXT: + return "InputAttachmentArrayDynamicIndexingEXT"; + case SpvCapabilityUniformTexelBufferArrayDynamicIndexingEXT: + return "UniformTexelBufferArrayDynamicIndexingEXT"; + case SpvCapabilityStorageTexelBufferArrayDynamicIndexingEXT: + return "StorageTexelBufferArrayDynamicIndexingEXT"; + case SpvCapabilityUniformBufferArrayNonUniformIndexingEXT: + return "UniformBufferArrayNonUniformIndexingEXT"; + case SpvCapabilitySampledImageArrayNonUniformIndexingEXT: + return "SampledImageArrayNonUniformIndexingEXT"; + case SpvCapabilityStorageBufferArrayNonUniformIndexingEXT: + return "StorageBufferArrayNonUniformIndexingEXT"; + case SpvCapabilityStorageImageArrayNonUniformIndexingEXT: + return "StorageImageArrayNonUniformIndexingEXT"; + case SpvCapabilityInputAttachmentArrayNonUniformIndexingEXT: + return "InputAttachmentArrayNonUniformIndexingEXT"; + case SpvCapabilityUniformTexelBufferArrayNonUniformIndexingEXT: + return "UniformTexelBufferArrayNonUniformIndexingEXT"; + case SpvCapabilityStorageTexelBufferArrayNonUniformIndexingEXT: + return "StorageTexelBufferArrayNonUniformIndexingEXT"; + case SpvCapabilitySubgroupShuffleINTEL: + return "SubgroupShuffleINTEL"; + case SpvCapabilitySubgroupBufferBlockIOINTEL: + return "SubgroupBufferBlockIOINTEL"; + case SpvCapabilitySubgroupImageBlockIOINTEL: + return "SubgroupImageBlockIOINTEL"; + case SpvCapabilityGroupNonUniformPartitionedNV: + return "GroupNonUniformPartitionedNV"; + case SpvCapabilityMax: + assert(0 && "Attempting to convert SpvCapabilityMax to string"); + return ""; + }; + + return ""; +} diff --git a/3rdparty/spirv-tools/include/generated/extension_enum.inc b/3rdparty/spirv-tools/include/generated/extension_enum.inc new file mode 100644 index 000000000..db2809338 --- /dev/null +++ b/3rdparty/spirv-tools/include/generated/extension_enum.inc @@ -0,0 +1,34 @@ +kSPV_AMD_gcn_shader, +kSPV_AMD_gpu_shader_half_float, +kSPV_AMD_gpu_shader_half_float_fetch, +kSPV_AMD_gpu_shader_int16, +kSPV_AMD_shader_ballot, +kSPV_AMD_shader_explicit_vertex_parameter, +kSPV_AMD_shader_fragment_mask, +kSPV_AMD_shader_image_load_store_lod, +kSPV_AMD_shader_trinary_minmax, +kSPV_AMD_texture_gather_bias_lod, +kSPV_EXT_descriptor_indexing, +kSPV_EXT_fragment_fully_covered, +kSPV_EXT_shader_stencil_export, +kSPV_EXT_shader_viewport_index_layer, +kSPV_GOOGLE_decorate_string, +kSPV_GOOGLE_hlsl_functionality1, +kSPV_INTEL_subgroups, +kSPV_KHR_16bit_storage, +kSPV_KHR_device_group, +kSPV_KHR_multiview, +kSPV_KHR_post_depth_coverage, +kSPV_KHR_shader_atomic_counter_ops, +kSPV_KHR_shader_ballot, +kSPV_KHR_shader_draw_parameters, +kSPV_KHR_storage_buffer_storage_class, +kSPV_KHR_subgroup_vote, +kSPV_KHR_variable_pointers, +kSPV_NVX_multiview_per_view_attributes, +kSPV_NV_geometry_shader_passthrough, +kSPV_NV_sample_mask_override_coverage, +kSPV_NV_shader_subgroup_partitioned, +kSPV_NV_stereo_view_rendering, +kSPV_NV_viewport_array2, +kSPV_VALIDATOR_ignore_type_decl_unique diff --git a/3rdparty/spirv-tools/include/generated/generators.inc b/3rdparty/spirv-tools/include/generated/generators.inc new file mode 100644 index 000000000..c186d0cf0 --- /dev/null +++ b/3rdparty/spirv-tools/include/generated/generators.inc @@ -0,0 +1,19 @@ +{0, "Khronos", "", "Khronos"}, +{1, "LunarG", "", "LunarG"}, +{2, "Valve", "", "Valve"}, +{3, "Codeplay", "", "Codeplay"}, +{4, "NVIDIA", "", "NVIDIA"}, +{5, "ARM", "", "ARM"}, +{6, "Khronos", "LLVM/SPIR-V Translator", "Khronos LLVM/SPIR-V Translator"}, +{7, "Khronos", "SPIR-V Tools Assembler", "Khronos SPIR-V Tools Assembler"}, +{8, "Khronos", "Glslang Reference Front End", "Khronos Glslang Reference Front End"}, +{9, "Qualcomm", "", "Qualcomm"}, +{10, "AMD", "", "AMD"}, +{11, "Intel", "", "Intel"}, +{12, "Imagination", "", "Imagination"}, +{13, "Google", "Shaderc over Glslang", "Google Shaderc over Glslang"}, +{14, "Google", "spiregg", "Google spiregg"}, +{15, "Google", "rspirv", "Google rspirv"}, +{16, "X-LEGEND", "Mesa-IR/SPIR-V Translator", "X-LEGEND Mesa-IR/SPIR-V Translator"}, +{17, "Khronos", "SPIR-V Tools Linker", "Khronos SPIR-V Tools Linker"}, +{18, "Wine", "VKD3D Shader Compiler", "Wine VKD3D Shader Compiler"}, diff --git a/3rdparty/spirv-tools/include/generated/glsl.std.450.insts.inc b/3rdparty/spirv-tools/include/generated/glsl.std.450.insts.inc new file mode 100644 index 000000000..6bb2e7662 --- /dev/null +++ b/3rdparty/spirv-tools/include/generated/glsl.std.450.insts.inc @@ -0,0 +1,86 @@ +static const SpvCapability pygen_variable_caps_Float64[] = {SpvCapabilityFloat64}; +static const SpvCapability pygen_variable_caps_InterpolationFunction[] = {SpvCapabilityInterpolationFunction}; + +static const spv_ext_inst_desc_t glsl_entries[] = { + {"Round", 1, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"RoundEven", 2, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Trunc", 3, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"FAbs", 4, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"SAbs", 5, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"FSign", 6, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"SSign", 7, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Floor", 8, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Ceil", 9, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Fract", 10, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Radians", 11, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Degrees", 12, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Sin", 13, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Cos", 14, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Tan", 15, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Asin", 16, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Acos", 17, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Atan", 18, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Sinh", 19, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Cosh", 20, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Tanh", 21, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Asinh", 22, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Acosh", 23, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Atanh", 24, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Atan2", 25, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Pow", 26, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Exp", 27, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Log", 28, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Exp2", 29, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Log2", 30, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Sqrt", 31, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"InverseSqrt", 32, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Determinant", 33, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"MatrixInverse", 34, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Modf", 35, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"ModfStruct", 36, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"FMin", 37, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"UMin", 38, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"SMin", 39, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"FMax", 40, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"UMax", 41, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"SMax", 42, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"FClamp", 43, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"UClamp", 44, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"SClamp", 45, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"FMix", 46, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"IMix", 47, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Step", 48, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"SmoothStep", 49, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Fma", 50, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Frexp", 51, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"FrexpStruct", 52, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Ldexp", 53, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"PackSnorm4x8", 54, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"PackUnorm4x8", 55, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"PackSnorm2x16", 56, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"PackUnorm2x16", 57, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"PackHalf2x16", 58, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"PackDouble2x32", 59, 1, pygen_variable_caps_Float64, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"UnpackSnorm2x16", 60, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"UnpackUnorm2x16", 61, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"UnpackHalf2x16", 62, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"UnpackSnorm4x8", 63, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"UnpackUnorm4x8", 64, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"UnpackDouble2x32", 65, 1, pygen_variable_caps_Float64, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Length", 66, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Distance", 67, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Cross", 68, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Normalize", 69, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"FaceForward", 70, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Reflect", 71, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"Refract", 72, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"FindILsb", 73, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"FindSMsb", 74, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"FindUMsb", 75, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"InterpolateAtCentroid", 76, 1, pygen_variable_caps_InterpolationFunction, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"InterpolateAtSample", 77, 1, pygen_variable_caps_InterpolationFunction, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"InterpolateAtOffset", 78, 1, pygen_variable_caps_InterpolationFunction, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"NMin", 79, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"NMax", 80, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"NClamp", 81, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}} +}; diff --git a/3rdparty/spirv-tools/include/generated/opencl.std.insts.inc b/3rdparty/spirv-tools/include/generated/opencl.std.insts.inc new file mode 100644 index 000000000..abc76efc3 --- /dev/null +++ b/3rdparty/spirv-tools/include/generated/opencl.std.insts.inc @@ -0,0 +1,166 @@ + + +static const spv_ext_inst_desc_t opencl_entries[] = { + {"acos", 0, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"acosh", 1, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"acospi", 2, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"asin", 3, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"asinh", 4, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"asinpi", 5, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"atan", 6, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"atan2", 7, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"atanh", 8, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"atanpi", 9, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"atan2pi", 10, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"cbrt", 11, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"ceil", 12, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"copysign", 13, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"cos", 14, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"cosh", 15, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"cospi", 16, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"erfc", 17, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"erf", 18, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"exp", 19, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"exp2", 20, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"exp10", 21, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"expm1", 22, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"fabs", 23, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"fdim", 24, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"floor", 25, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"fma", 26, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"fmax", 27, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"fmin", 28, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"fmod", 29, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"fract", 30, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"frexp", 31, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"hypot", 32, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"ilogb", 33, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"ldexp", 34, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"lgamma", 35, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"lgamma_r", 36, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"log", 37, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"log2", 38, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"log10", 39, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"log1p", 40, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"logb", 41, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"mad", 42, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"maxmag", 43, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"minmag", 44, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"modf", 45, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"nan", 46, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"nextafter", 47, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"pow", 48, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"pown", 49, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"powr", 50, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"remainder", 51, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"remquo", 52, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"rint", 53, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"rootn", 54, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"round", 55, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"rsqrt", 56, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"sin", 57, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"sincos", 58, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"sinh", 59, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"sinpi", 60, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"sqrt", 61, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"tan", 62, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"tanh", 63, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"tanpi", 64, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"tgamma", 65, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"trunc", 66, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"half_cos", 67, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"half_divide", 68, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"half_exp", 69, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"half_exp2", 70, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"half_exp10", 71, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"half_log", 72, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"half_log2", 73, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"half_log10", 74, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"half_powr", 75, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"half_recip", 76, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"half_rsqrt", 77, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"half_sin", 78, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"half_sqrt", 79, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"half_tan", 80, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"native_cos", 81, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"native_divide", 82, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"native_exp", 83, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"native_exp2", 84, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"native_exp10", 85, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"native_log", 86, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"native_log2", 87, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"native_log10", 88, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"native_powr", 89, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"native_recip", 90, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"native_rsqrt", 91, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"native_sin", 92, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"native_sqrt", 93, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"native_tan", 94, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"fclamp", 95, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"degrees", 96, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"fmax_common", 97, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"fmin_common", 98, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"mix", 99, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"radians", 100, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"step", 101, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"smoothstep", 102, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"sign", 103, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"cross", 104, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"distance", 105, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"length", 106, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"normalize", 107, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"fast_distance", 108, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"fast_length", 109, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"fast_normalize", 110, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"s_abs", 141, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"s_abs_diff", 142, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"s_add_sat", 143, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"u_add_sat", 144, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"s_hadd", 145, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"u_hadd", 146, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"s_rhadd", 147, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"u_rhadd", 148, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"s_clamp", 149, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"u_clamp", 150, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"clz", 151, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"ctz", 152, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"s_mad_hi", 153, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"u_mad_sat", 154, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"s_mad_sat", 155, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"s_max", 156, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"u_max", 157, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"s_min", 158, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"u_min", 159, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"s_mul_hi", 160, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"rotate", 161, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"s_sub_sat", 162, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"u_sub_sat", 163, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"u_upsample", 164, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"s_upsample", 165, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"popcount", 166, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"s_mad24", 167, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"u_mad24", 168, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"s_mul24", 169, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"u_mul24", 170, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"vloadn", 171, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_NONE}}, + {"vstoren", 172, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"vload_half", 173, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"vload_halfn", 174, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_NONE}}, + {"vstore_half", 175, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"vstore_half_r", 176, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_FP_ROUNDING_MODE, SPV_OPERAND_TYPE_NONE}}, + {"vstore_halfn", 177, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"vstore_halfn_r", 178, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_FP_ROUNDING_MODE, SPV_OPERAND_TYPE_NONE}}, + {"vloada_halfn", 179, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_NONE}}, + {"vstorea_halfn", 180, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"vstorea_halfn_r", 181, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_FP_ROUNDING_MODE, SPV_OPERAND_TYPE_NONE}}, + {"shuffle", 182, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"shuffle2", 183, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"printf", 184, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_VARIABLE_ID, SPV_OPERAND_TYPE_NONE}}, + {"prefetch", 185, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"bitselect", 186, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"select", 187, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"u_abs", 201, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"u_abs_diff", 202, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"u_mul_hi", 203, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"u_mad_hi", 204, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}} +}; diff --git a/3rdparty/spirv-tools/include/generated/operand.kinds-unified1.inc b/3rdparty/spirv-tools/include/generated/operand.kinds-unified1.inc new file mode 100644 index 000000000..991cfa837 --- /dev/null +++ b/3rdparty/spirv-tools/include/generated/operand.kinds-unified1.inc @@ -0,0 +1,744 @@ +static const SpvCapability pygen_variable_caps_Addresses[] = {SpvCapabilityAddresses}; +static const SpvCapability pygen_variable_caps_AtomicStorage[] = {SpvCapabilityAtomicStorage}; +static const SpvCapability pygen_variable_caps_ClipDistance[] = {SpvCapabilityClipDistance}; +static const SpvCapability pygen_variable_caps_CullDistance[] = {SpvCapabilityCullDistance}; +static const SpvCapability pygen_variable_caps_DeviceEnqueue[] = {SpvCapabilityDeviceEnqueue}; +static const SpvCapability pygen_variable_caps_DeviceGroup[] = {SpvCapabilityDeviceGroup}; +static const SpvCapability pygen_variable_caps_DrawParameters[] = {SpvCapabilityDrawParameters}; +static const SpvCapability pygen_variable_caps_FragmentFullyCoveredEXT[] = {SpvCapabilityFragmentFullyCoveredEXT}; +static const SpvCapability pygen_variable_caps_GenericPointer[] = {SpvCapabilityGenericPointer}; +static const SpvCapability pygen_variable_caps_Geometry[] = {SpvCapabilityGeometry}; +static const SpvCapability pygen_variable_caps_GeometryTessellation[] = {SpvCapabilityGeometry, SpvCapabilityTessellation}; +static const SpvCapability pygen_variable_caps_GeometryShaderPassthroughNV[] = {SpvCapabilityGeometryShaderPassthroughNV}; +static const SpvCapability pygen_variable_caps_GeometryStreams[] = {SpvCapabilityGeometryStreams}; +static const SpvCapability pygen_variable_caps_GroupNonUniform[] = {SpvCapabilityGroupNonUniform}; +static const SpvCapability pygen_variable_caps_GroupNonUniformClustered[] = {SpvCapabilityGroupNonUniformClustered}; +static const SpvCapability pygen_variable_caps_GroupNonUniformPartitionedNV[] = {SpvCapabilityGroupNonUniformPartitionedNV}; +static const SpvCapability pygen_variable_caps_ImageBasic[] = {SpvCapabilityImageBasic}; +static const SpvCapability pygen_variable_caps_ImageBuffer[] = {SpvCapabilityImageBuffer}; +static const SpvCapability pygen_variable_caps_ImageBufferShaderNonUniformEXT[] = {SpvCapabilityImageBuffer, SpvCapabilityShaderNonUniformEXT}; +static const SpvCapability pygen_variable_caps_ImageGatherExtended[] = {SpvCapabilityImageGatherExtended}; +static const SpvCapability pygen_variable_caps_InputAttachment[] = {SpvCapabilityInputAttachment}; +static const SpvCapability pygen_variable_caps_InputAttachmentShaderNonUniformEXT[] = {SpvCapabilityInputAttachment, SpvCapabilityShaderNonUniformEXT}; +static const SpvCapability pygen_variable_caps_Int64[] = {SpvCapabilityInt64}; +static const SpvCapability pygen_variable_caps_Kernel[] = {SpvCapabilityKernel}; +static const SpvCapability pygen_variable_caps_KernelGroupNonUniform[] = {SpvCapabilityKernel, SpvCapabilityGroupNonUniform}; +static const SpvCapability pygen_variable_caps_KernelGroupNonUniformSubgroupBallotKHR[] = {SpvCapabilityKernel, SpvCapabilityGroupNonUniform, SpvCapabilitySubgroupBallotKHR}; +static const SpvCapability pygen_variable_caps_KernelGroupNonUniformArithmeticGroupNonUniformBallot[] = {SpvCapabilityKernel, SpvCapabilityGroupNonUniformArithmetic, SpvCapabilityGroupNonUniformBallot}; +static const SpvCapability pygen_variable_caps_Linkage[] = {SpvCapabilityLinkage}; +static const SpvCapability pygen_variable_caps_Matrix[] = {SpvCapabilityMatrix}; +static const SpvCapability pygen_variable_caps_MinLod[] = {SpvCapabilityMinLod}; +static const SpvCapability pygen_variable_caps_MultiView[] = {SpvCapabilityMultiView}; +static const SpvCapability pygen_variable_caps_MultiViewport[] = {SpvCapabilityMultiViewport}; +static const SpvCapability pygen_variable_caps_PerViewAttributesNV[] = {SpvCapabilityPerViewAttributesNV}; +static const SpvCapability pygen_variable_caps_Pipes[] = {SpvCapabilityPipes}; +static const SpvCapability pygen_variable_caps_SampleMaskOverrideCoverageNV[] = {SpvCapabilitySampleMaskOverrideCoverageNV}; +static const SpvCapability pygen_variable_caps_SampleMaskPostDepthCoverage[] = {SpvCapabilitySampleMaskPostDepthCoverage}; +static const SpvCapability pygen_variable_caps_SampleRateShading[] = {SpvCapabilitySampleRateShading}; +static const SpvCapability pygen_variable_caps_Sampled1D[] = {SpvCapabilitySampled1D}; +static const SpvCapability pygen_variable_caps_SampledBuffer[] = {SpvCapabilitySampledBuffer}; +static const SpvCapability pygen_variable_caps_SampledBufferShaderNonUniformEXT[] = {SpvCapabilitySampledBuffer, SpvCapabilityShaderNonUniformEXT}; +static const SpvCapability pygen_variable_caps_SampledCubeArray[] = {SpvCapabilitySampledCubeArray}; +static const SpvCapability pygen_variable_caps_SampledRect[] = {SpvCapabilitySampledRect}; +static const SpvCapability pygen_variable_caps_Shader[] = {SpvCapabilityShader}; +static const SpvCapability pygen_variable_caps_ShaderKernel[] = {SpvCapabilityShader, SpvCapabilityKernel}; +static const SpvCapability pygen_variable_caps_ShaderNonUniformEXT[] = {SpvCapabilityShaderNonUniformEXT}; +static const SpvCapability pygen_variable_caps_ShaderStereoViewNV[] = {SpvCapabilityShaderStereoViewNV}; +static const SpvCapability pygen_variable_caps_ShaderViewportIndexLayerNV[] = {SpvCapabilityShaderViewportIndexLayerNV}; +static const SpvCapability pygen_variable_caps_ShaderViewportMaskNV[] = {SpvCapabilityShaderViewportMaskNV}; +static const SpvCapability pygen_variable_caps_StencilExportEXT[] = {SpvCapabilityStencilExportEXT}; +static const SpvCapability pygen_variable_caps_StorageBuffer16BitAccessStorageUniformBufferBlock16[] = {SpvCapabilityStorageBuffer16BitAccess, SpvCapabilityStorageUniformBufferBlock16}; +static const SpvCapability pygen_variable_caps_StorageImageExtendedFormats[] = {SpvCapabilityStorageImageExtendedFormats}; +static const SpvCapability pygen_variable_caps_SubgroupBallotKHRGroupNonUniformBallot[] = {SpvCapabilitySubgroupBallotKHR, SpvCapabilityGroupNonUniformBallot}; +static const SpvCapability pygen_variable_caps_SubgroupDispatch[] = {SpvCapabilitySubgroupDispatch}; +static const SpvCapability pygen_variable_caps_Tessellation[] = {SpvCapabilityTessellation}; +static const SpvCapability pygen_variable_caps_TransformFeedback[] = {SpvCapabilityTransformFeedback}; +static const SpvCapability pygen_variable_caps_VariablePointersStorageBuffer[] = {SpvCapabilityVariablePointersStorageBuffer}; + +static const libspirv::Extension pygen_variable_exts_SPV_AMD_gpu_shader_half_float_fetch[] = {libspirv::Extension::kSPV_AMD_gpu_shader_half_float_fetch}; +static const libspirv::Extension pygen_variable_exts_SPV_AMD_shader_explicit_vertex_parameter[] = {libspirv::Extension::kSPV_AMD_shader_explicit_vertex_parameter}; +static const libspirv::Extension pygen_variable_exts_SPV_AMD_shader_fragment_mask[] = {libspirv::Extension::kSPV_AMD_shader_fragment_mask}; +static const libspirv::Extension pygen_variable_exts_SPV_AMD_shader_image_load_store_lod[] = {libspirv::Extension::kSPV_AMD_shader_image_load_store_lod}; +static const libspirv::Extension pygen_variable_exts_SPV_AMD_texture_gather_bias_lod[] = {libspirv::Extension::kSPV_AMD_texture_gather_bias_lod}; +static const libspirv::Extension pygen_variable_exts_SPV_EXT_descriptor_indexing[] = {libspirv::Extension::kSPV_EXT_descriptor_indexing}; +static const libspirv::Extension pygen_variable_exts_SPV_EXT_fragment_fully_covered[] = {libspirv::Extension::kSPV_EXT_fragment_fully_covered}; +static const libspirv::Extension pygen_variable_exts_SPV_EXT_shader_stencil_export[] = {libspirv::Extension::kSPV_EXT_shader_stencil_export}; +static const libspirv::Extension pygen_variable_exts_SPV_EXT_shader_viewport_index_layer[] = {libspirv::Extension::kSPV_EXT_shader_viewport_index_layer}; +static const libspirv::Extension pygen_variable_exts_SPV_GOOGLE_hlsl_functionality1[] = {libspirv::Extension::kSPV_GOOGLE_hlsl_functionality1}; +static const libspirv::Extension pygen_variable_exts_SPV_INTEL_subgroups[] = {libspirv::Extension::kSPV_INTEL_subgroups}; +static const libspirv::Extension pygen_variable_exts_SPV_KHR_16bit_storage[] = {libspirv::Extension::kSPV_KHR_16bit_storage}; +static const libspirv::Extension pygen_variable_exts_SPV_KHR_device_group[] = {libspirv::Extension::kSPV_KHR_device_group}; +static const libspirv::Extension pygen_variable_exts_SPV_KHR_multiview[] = {libspirv::Extension::kSPV_KHR_multiview}; +static const libspirv::Extension pygen_variable_exts_SPV_KHR_post_depth_coverage[] = {libspirv::Extension::kSPV_KHR_post_depth_coverage}; +static const libspirv::Extension pygen_variable_exts_SPV_KHR_shader_atomic_counter_ops[] = {libspirv::Extension::kSPV_KHR_shader_atomic_counter_ops}; +static const libspirv::Extension pygen_variable_exts_SPV_KHR_shader_ballot[] = {libspirv::Extension::kSPV_KHR_shader_ballot}; +static const libspirv::Extension pygen_variable_exts_SPV_KHR_shader_draw_parameters[] = {libspirv::Extension::kSPV_KHR_shader_draw_parameters}; +static const libspirv::Extension pygen_variable_exts_SPV_KHR_storage_buffer_storage_classSPV_KHR_variable_pointers[] = {libspirv::Extension::kSPV_KHR_storage_buffer_storage_class, libspirv::Extension::kSPV_KHR_variable_pointers}; +static const libspirv::Extension pygen_variable_exts_SPV_KHR_subgroup_vote[] = {libspirv::Extension::kSPV_KHR_subgroup_vote}; +static const libspirv::Extension pygen_variable_exts_SPV_KHR_variable_pointers[] = {libspirv::Extension::kSPV_KHR_variable_pointers}; +static const libspirv::Extension pygen_variable_exts_SPV_NVX_multiview_per_view_attributes[] = {libspirv::Extension::kSPV_NVX_multiview_per_view_attributes}; +static const libspirv::Extension pygen_variable_exts_SPV_NV_geometry_shader_passthrough[] = {libspirv::Extension::kSPV_NV_geometry_shader_passthrough}; +static const libspirv::Extension pygen_variable_exts_SPV_NV_sample_mask_override_coverage[] = {libspirv::Extension::kSPV_NV_sample_mask_override_coverage}; +static const libspirv::Extension pygen_variable_exts_SPV_NV_shader_subgroup_partitioned[] = {libspirv::Extension::kSPV_NV_shader_subgroup_partitioned}; +static const libspirv::Extension pygen_variable_exts_SPV_NV_stereo_view_rendering[] = {libspirv::Extension::kSPV_NV_stereo_view_rendering}; +static const libspirv::Extension pygen_variable_exts_SPV_NV_viewport_array2[] = {libspirv::Extension::kSPV_NV_viewport_array2}; + +static const spv_operand_desc_t pygen_variable_ImageOperandsEntries[] = { + {"None", 0x0000, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Bias", 0x0001, 1, pygen_variable_caps_Shader, 0, nullptr, {SPV_OPERAND_TYPE_ID}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Lod", 0x0002, 0, nullptr, 0, nullptr, {SPV_OPERAND_TYPE_ID}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Grad", 0x0004, 0, nullptr, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ConstOffset", 0x0008, 0, nullptr, 0, nullptr, {SPV_OPERAND_TYPE_ID}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Offset", 0x0010, 1, pygen_variable_caps_ImageGatherExtended, 0, nullptr, {SPV_OPERAND_TYPE_ID}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ConstOffsets", 0x0020, 0, nullptr, 0, nullptr, {SPV_OPERAND_TYPE_ID}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Sample", 0x0040, 0, nullptr, 0, nullptr, {SPV_OPERAND_TYPE_ID}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"MinLod", 0x0080, 1, pygen_variable_caps_MinLod, 0, nullptr, {SPV_OPERAND_TYPE_ID}, SPV_SPIRV_VERSION_WORD(1, 0)} +}; + +static const spv_operand_desc_t pygen_variable_FPFastMathModeEntries[] = { + {"None", 0x0000, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"NotNaN", 0x0001, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"NotInf", 0x0002, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"NSZ", 0x0004, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"AllowRecip", 0x0008, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Fast", 0x0010, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)} +}; + +static const spv_operand_desc_t pygen_variable_SelectionControlEntries[] = { + {"None", 0x0000, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Flatten", 0x0001, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"DontFlatten", 0x0002, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)} +}; + +static const spv_operand_desc_t pygen_variable_LoopControlEntries[] = { + {"None", 0x0000, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Unroll", 0x0001, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"DontUnroll", 0x0002, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"DependencyInfinite", 0x0004, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1,1)}, + {"DependencyLength", 0x0008, 0, nullptr, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1,1)} +}; + +static const spv_operand_desc_t pygen_variable_FunctionControlEntries[] = { + {"None", 0x0000, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Inline", 0x0001, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"DontInline", 0x0002, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Pure", 0x0004, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Const", 0x0008, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)} +}; + +static const spv_operand_desc_t pygen_variable_MemorySemanticsEntries[] = { + {"None", 0x0000, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Relaxed", 0x0000, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Acquire", 0x0002, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Release", 0x0004, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"AcquireRelease", 0x0008, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SequentiallyConsistent", 0x0010, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"UniformMemory", 0x0040, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SubgroupMemory", 0x0080, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"WorkgroupMemory", 0x0100, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"CrossWorkgroupMemory", 0x0200, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"AtomicCounterMemory", 0x0400, 1, pygen_variable_caps_AtomicStorage, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageMemory", 0x0800, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)} +}; + +static const spv_operand_desc_t pygen_variable_MemoryAccessEntries[] = { + {"None", 0x0000, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Volatile", 0x0001, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Aligned", 0x0002, 0, nullptr, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Nontemporal", 0x0004, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)} +}; + +static const spv_operand_desc_t pygen_variable_KernelProfilingInfoEntries[] = { + {"None", 0x0000, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"CmdExecTime", 0x0001, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)} +}; + +static const spv_operand_desc_t pygen_variable_SourceLanguageEntries[] = { + {"Unknown", 0, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ESSL", 1, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GLSL", 2, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"OpenCL_C", 3, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"OpenCL_CPP", 4, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"HLSL", 5, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)} +}; + +static const spv_operand_desc_t pygen_variable_ExecutionModelEntries[] = { + {"Vertex", 0, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"TessellationControl", 1, 1, pygen_variable_caps_Tessellation, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"TessellationEvaluation", 2, 1, pygen_variable_caps_Tessellation, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Geometry", 3, 1, pygen_variable_caps_Geometry, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Fragment", 4, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GLCompute", 5, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Kernel", 6, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)} +}; + +static const spv_operand_desc_t pygen_variable_AddressingModelEntries[] = { + {"Logical", 0, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Physical32", 1, 1, pygen_variable_caps_Addresses, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Physical64", 2, 1, pygen_variable_caps_Addresses, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)} +}; + +static const spv_operand_desc_t pygen_variable_MemoryModelEntries[] = { + {"Simple", 0, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GLSL450", 1, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"OpenCL", 2, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)} +}; + +static const spv_operand_desc_t pygen_variable_ExecutionModeEntries[] = { + {"Invocations", 0, 1, pygen_variable_caps_Geometry, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SpacingEqual", 1, 1, pygen_variable_caps_Tessellation, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SpacingFractionalEven", 2, 1, pygen_variable_caps_Tessellation, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SpacingFractionalOdd", 3, 1, pygen_variable_caps_Tessellation, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"VertexOrderCw", 4, 1, pygen_variable_caps_Tessellation, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"VertexOrderCcw", 5, 1, pygen_variable_caps_Tessellation, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"PixelCenterInteger", 6, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"OriginUpperLeft", 7, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"OriginLowerLeft", 8, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"EarlyFragmentTests", 9, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"PointMode", 10, 1, pygen_variable_caps_Tessellation, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Xfb", 11, 1, pygen_variable_caps_TransformFeedback, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"DepthReplacing", 12, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"DepthGreater", 14, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"DepthLess", 15, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"DepthUnchanged", 16, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"LocalSize", 17, 0, nullptr, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"LocalSizeHint", 18, 1, pygen_variable_caps_Kernel, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"InputPoints", 19, 1, pygen_variable_caps_Geometry, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"InputLines", 20, 1, pygen_variable_caps_Geometry, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"InputLinesAdjacency", 21, 1, pygen_variable_caps_Geometry, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Triangles", 22, 2, pygen_variable_caps_GeometryTessellation, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"InputTrianglesAdjacency", 23, 1, pygen_variable_caps_Geometry, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Quads", 24, 1, pygen_variable_caps_Tessellation, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Isolines", 25, 1, pygen_variable_caps_Tessellation, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"OutputVertices", 26, 2, pygen_variable_caps_GeometryTessellation, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"OutputPoints", 27, 1, pygen_variable_caps_Geometry, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"OutputLineStrip", 28, 1, pygen_variable_caps_Geometry, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"OutputTriangleStrip", 29, 1, pygen_variable_caps_Geometry, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"VecTypeHint", 30, 1, pygen_variable_caps_Kernel, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ContractionOff", 31, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Initializer", 33, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1,1)}, + {"Finalizer", 34, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1,1)}, + {"SubgroupSize", 35, 1, pygen_variable_caps_SubgroupDispatch, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1,1)}, + {"SubgroupsPerWorkgroup", 36, 1, pygen_variable_caps_SubgroupDispatch, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1,1)}, + {"SubgroupsPerWorkgroupId", 37, 1, pygen_variable_caps_SubgroupDispatch, 0, nullptr, {SPV_OPERAND_TYPE_ID}, SPV_SPIRV_VERSION_WORD(1,2)}, + {"LocalSizeId", 38, 0, nullptr, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID}, SPV_SPIRV_VERSION_WORD(1,2)}, + {"LocalSizeHintId", 39, 1, pygen_variable_caps_Kernel, 0, nullptr, {SPV_OPERAND_TYPE_ID}, SPV_SPIRV_VERSION_WORD(1,2)}, + {"PostDepthCoverage", 4446, 1, pygen_variable_caps_SampleMaskPostDepthCoverage, 1, pygen_variable_exts_SPV_KHR_post_depth_coverage, {}, 0xffffffffu}, + {"StencilRefReplacingEXT", 5027, 1, pygen_variable_caps_StencilExportEXT, 1, pygen_variable_exts_SPV_EXT_shader_stencil_export, {}, 0xffffffffu} +}; + +static const spv_operand_desc_t pygen_variable_StorageClassEntries[] = { + {"UniformConstant", 0, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Input", 1, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Uniform", 2, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Output", 3, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Workgroup", 4, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"CrossWorkgroup", 5, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Private", 6, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Function", 7, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Generic", 8, 1, pygen_variable_caps_GenericPointer, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"PushConstant", 9, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"AtomicCounter", 10, 1, pygen_variable_caps_AtomicStorage, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Image", 11, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"StorageBuffer", 12, 1, pygen_variable_caps_Shader, 2, pygen_variable_exts_SPV_KHR_storage_buffer_storage_classSPV_KHR_variable_pointers, {}, SPV_SPIRV_VERSION_WORD(1,3)} +}; + +static const spv_operand_desc_t pygen_variable_DimEntries[] = { + {"1D", 0, 1, pygen_variable_caps_Sampled1D, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"2D", 1, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"3D", 2, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Cube", 3, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Rect", 4, 1, pygen_variable_caps_SampledRect, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Buffer", 5, 1, pygen_variable_caps_SampledBuffer, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SubpassData", 6, 1, pygen_variable_caps_InputAttachment, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)} +}; + +static const spv_operand_desc_t pygen_variable_SamplerAddressingModeEntries[] = { + {"None", 0, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ClampToEdge", 1, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Clamp", 2, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Repeat", 3, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"RepeatMirrored", 4, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)} +}; + +static const spv_operand_desc_t pygen_variable_SamplerFilterModeEntries[] = { + {"Nearest", 0, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Linear", 1, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)} +}; + +static const spv_operand_desc_t pygen_variable_ImageFormatEntries[] = { + {"Unknown", 0, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Rgba32f", 1, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Rgba16f", 2, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"R32f", 3, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Rgba8", 4, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Rgba8Snorm", 5, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Rg32f", 6, 1, pygen_variable_caps_StorageImageExtendedFormats, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Rg16f", 7, 1, pygen_variable_caps_StorageImageExtendedFormats, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"R11fG11fB10f", 8, 1, pygen_variable_caps_StorageImageExtendedFormats, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"R16f", 9, 1, pygen_variable_caps_StorageImageExtendedFormats, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Rgba16", 10, 1, pygen_variable_caps_StorageImageExtendedFormats, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Rgb10A2", 11, 1, pygen_variable_caps_StorageImageExtendedFormats, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Rg16", 12, 1, pygen_variable_caps_StorageImageExtendedFormats, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Rg8", 13, 1, pygen_variable_caps_StorageImageExtendedFormats, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"R16", 14, 1, pygen_variable_caps_StorageImageExtendedFormats, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"R8", 15, 1, pygen_variable_caps_StorageImageExtendedFormats, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Rgba16Snorm", 16, 1, pygen_variable_caps_StorageImageExtendedFormats, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Rg16Snorm", 17, 1, pygen_variable_caps_StorageImageExtendedFormats, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Rg8Snorm", 18, 1, pygen_variable_caps_StorageImageExtendedFormats, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"R16Snorm", 19, 1, pygen_variable_caps_StorageImageExtendedFormats, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"R8Snorm", 20, 1, pygen_variable_caps_StorageImageExtendedFormats, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Rgba32i", 21, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Rgba16i", 22, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Rgba8i", 23, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"R32i", 24, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Rg32i", 25, 1, pygen_variable_caps_StorageImageExtendedFormats, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Rg16i", 26, 1, pygen_variable_caps_StorageImageExtendedFormats, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Rg8i", 27, 1, pygen_variable_caps_StorageImageExtendedFormats, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"R16i", 28, 1, pygen_variable_caps_StorageImageExtendedFormats, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"R8i", 29, 1, pygen_variable_caps_StorageImageExtendedFormats, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Rgba32ui", 30, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Rgba16ui", 31, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Rgba8ui", 32, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"R32ui", 33, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Rgb10a2ui", 34, 1, pygen_variable_caps_StorageImageExtendedFormats, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Rg32ui", 35, 1, pygen_variable_caps_StorageImageExtendedFormats, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Rg16ui", 36, 1, pygen_variable_caps_StorageImageExtendedFormats, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Rg8ui", 37, 1, pygen_variable_caps_StorageImageExtendedFormats, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"R16ui", 38, 1, pygen_variable_caps_StorageImageExtendedFormats, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"R8ui", 39, 1, pygen_variable_caps_StorageImageExtendedFormats, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)} +}; + +static const spv_operand_desc_t pygen_variable_ImageChannelOrderEntries[] = { + {"R", 0, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"A", 1, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"RG", 2, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"RA", 3, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"RGB", 4, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"RGBA", 5, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"BGRA", 6, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ARGB", 7, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Intensity", 8, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Luminance", 9, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Rx", 10, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"RGx", 11, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"RGBx", 12, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Depth", 13, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"DepthStencil", 14, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"sRGB", 15, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"sRGBx", 16, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"sRGBA", 17, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"sBGRA", 18, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ABGR", 19, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)} +}; + +static const spv_operand_desc_t pygen_variable_ImageChannelDataTypeEntries[] = { + {"SnormInt8", 0, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SnormInt16", 1, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"UnormInt8", 2, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"UnormInt16", 3, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"UnormShort565", 4, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"UnormShort555", 5, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"UnormInt101010", 6, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SignedInt8", 7, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SignedInt16", 8, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SignedInt32", 9, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"UnsignedInt8", 10, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"UnsignedInt16", 11, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"UnsignedInt32", 12, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"HalfFloat", 13, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Float", 14, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"UnormInt24", 15, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"UnormInt101010_2", 16, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)} +}; + +static const spv_operand_desc_t pygen_variable_FPRoundingModeEntries[] = { + {"RTE", 0, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"RTZ", 1, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"RTP", 2, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"RTN", 3, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)} +}; + +static const spv_operand_desc_t pygen_variable_LinkageTypeEntries[] = { + {"Export", 0, 1, pygen_variable_caps_Linkage, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Import", 1, 1, pygen_variable_caps_Linkage, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)} +}; + +static const spv_operand_desc_t pygen_variable_AccessQualifierEntries[] = { + {"ReadOnly", 0, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"WriteOnly", 1, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ReadWrite", 2, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)} +}; + +static const spv_operand_desc_t pygen_variable_FunctionParameterAttributeEntries[] = { + {"Zext", 0, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Sext", 1, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ByVal", 2, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Sret", 3, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"NoAlias", 4, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"NoCapture", 5, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"NoWrite", 6, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"NoReadWrite", 7, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)} +}; + +static const spv_operand_desc_t pygen_variable_DecorationEntries[] = { + {"RelaxedPrecision", 0, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SpecId", 1, 2, pygen_variable_caps_ShaderKernel, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Block", 2, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"BufferBlock", 3, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"RowMajor", 4, 1, pygen_variable_caps_Matrix, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ColMajor", 5, 1, pygen_variable_caps_Matrix, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ArrayStride", 6, 1, pygen_variable_caps_Shader, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"MatrixStride", 7, 1, pygen_variable_caps_Matrix, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GLSLShared", 8, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GLSLPacked", 9, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"CPacked", 10, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"BuiltIn", 11, 0, nullptr, 0, nullptr, {SPV_OPERAND_TYPE_BUILT_IN}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"NoPerspective", 13, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Flat", 14, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Patch", 15, 1, pygen_variable_caps_Tessellation, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Centroid", 16, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Sample", 17, 1, pygen_variable_caps_SampleRateShading, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Invariant", 18, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Restrict", 19, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Aliased", 20, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Volatile", 21, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Constant", 22, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Coherent", 23, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"NonWritable", 24, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"NonReadable", 25, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Uniform", 26, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SaturatedConversion", 28, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Stream", 29, 1, pygen_variable_caps_GeometryStreams, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Location", 30, 1, pygen_variable_caps_Shader, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Component", 31, 1, pygen_variable_caps_Shader, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Index", 32, 1, pygen_variable_caps_Shader, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Binding", 33, 1, pygen_variable_caps_Shader, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"DescriptorSet", 34, 1, pygen_variable_caps_Shader, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Offset", 35, 1, pygen_variable_caps_Shader, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"XfbBuffer", 36, 1, pygen_variable_caps_TransformFeedback, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"XfbStride", 37, 1, pygen_variable_caps_TransformFeedback, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FuncParamAttr", 38, 1, pygen_variable_caps_Kernel, 0, nullptr, {SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FPRoundingMode", 39, 0, nullptr, 0, nullptr, {SPV_OPERAND_TYPE_FP_ROUNDING_MODE}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FPFastMathMode", 40, 1, pygen_variable_caps_Kernel, 0, nullptr, {SPV_OPERAND_TYPE_FP_FAST_MATH_MODE}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"LinkageAttributes", 41, 1, pygen_variable_caps_Linkage, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_STRING, SPV_OPERAND_TYPE_LINKAGE_TYPE}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"NoContraction", 42, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"InputAttachmentIndex", 43, 1, pygen_variable_caps_InputAttachment, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Alignment", 44, 1, pygen_variable_caps_Kernel, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"MaxByteOffset", 45, 1, pygen_variable_caps_Addresses, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1,1)}, + {"AlignmentId", 46, 1, pygen_variable_caps_Kernel, 0, nullptr, {SPV_OPERAND_TYPE_ID}, SPV_SPIRV_VERSION_WORD(1,2)}, + {"MaxByteOffsetId", 47, 1, pygen_variable_caps_Addresses, 0, nullptr, {SPV_OPERAND_TYPE_ID}, SPV_SPIRV_VERSION_WORD(1,2)}, + {"ExplicitInterpAMD", 4999, 0, nullptr, 1, pygen_variable_exts_SPV_AMD_shader_explicit_vertex_parameter, {}, 0xffffffffu}, + {"OverrideCoverageNV", 5248, 1, pygen_variable_caps_SampleMaskOverrideCoverageNV, 0, nullptr, {}, 0xffffffffu}, + {"PassthroughNV", 5250, 1, pygen_variable_caps_GeometryShaderPassthroughNV, 0, nullptr, {}, 0xffffffffu}, + {"ViewportRelativeNV", 5252, 1, pygen_variable_caps_ShaderViewportMaskNV, 0, nullptr, {}, 0xffffffffu}, + {"SecondaryViewportRelativeNV", 5256, 1, pygen_variable_caps_ShaderStereoViewNV, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, 0xffffffffu}, + {"NonUniformEXT", 5300, 1, pygen_variable_caps_ShaderNonUniformEXT, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"HlslCounterBufferGOOGLE", 5634, 0, nullptr, 1, pygen_variable_exts_SPV_GOOGLE_hlsl_functionality1, {SPV_OPERAND_TYPE_ID}, 0xffffffffu}, + {"HlslSemanticGOOGLE", 5635, 0, nullptr, 1, pygen_variable_exts_SPV_GOOGLE_hlsl_functionality1, {SPV_OPERAND_TYPE_LITERAL_STRING}, 0xffffffffu} +}; + +static const spv_operand_desc_t pygen_variable_BuiltInEntries[] = { + {"Position", 0, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"PointSize", 1, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ClipDistance", 3, 1, pygen_variable_caps_ClipDistance, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"CullDistance", 4, 1, pygen_variable_caps_CullDistance, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"VertexId", 5, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"InstanceId", 6, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"PrimitiveId", 7, 2, pygen_variable_caps_GeometryTessellation, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"InvocationId", 8, 2, pygen_variable_caps_GeometryTessellation, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Layer", 9, 1, pygen_variable_caps_Geometry, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ViewportIndex", 10, 1, pygen_variable_caps_MultiViewport, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"TessLevelOuter", 11, 1, pygen_variable_caps_Tessellation, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"TessLevelInner", 12, 1, pygen_variable_caps_Tessellation, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"TessCoord", 13, 1, pygen_variable_caps_Tessellation, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"PatchVertices", 14, 1, pygen_variable_caps_Tessellation, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FragCoord", 15, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"PointCoord", 16, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FrontFacing", 17, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SampleId", 18, 1, pygen_variable_caps_SampleRateShading, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SamplePosition", 19, 1, pygen_variable_caps_SampleRateShading, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SampleMask", 20, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FragDepth", 22, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"HelperInvocation", 23, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"NumWorkgroups", 24, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"WorkgroupSize", 25, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"WorkgroupId", 26, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"LocalInvocationId", 27, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GlobalInvocationId", 28, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"LocalInvocationIndex", 29, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"WorkDim", 30, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GlobalSize", 31, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"EnqueuedWorkgroupSize", 32, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GlobalOffset", 33, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GlobalLinearId", 34, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SubgroupSize", 36, 3, pygen_variable_caps_KernelGroupNonUniformSubgroupBallotKHR, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SubgroupMaxSize", 37, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"NumSubgroups", 38, 2, pygen_variable_caps_KernelGroupNonUniform, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"NumEnqueuedSubgroups", 39, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SubgroupId", 40, 2, pygen_variable_caps_KernelGroupNonUniform, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SubgroupLocalInvocationId", 41, 3, pygen_variable_caps_KernelGroupNonUniformSubgroupBallotKHR, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"VertexIndex", 42, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"InstanceIndex", 43, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SubgroupEqMask", 4416, 2, pygen_variable_caps_SubgroupBallotKHRGroupNonUniformBallot, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"SubgroupEqMaskKHR", 4416, 2, pygen_variable_caps_SubgroupBallotKHRGroupNonUniformBallot, 1, pygen_variable_exts_SPV_KHR_shader_ballot, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"SubgroupGeMask", 4417, 2, pygen_variable_caps_SubgroupBallotKHRGroupNonUniformBallot, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"SubgroupGeMaskKHR", 4417, 2, pygen_variable_caps_SubgroupBallotKHRGroupNonUniformBallot, 1, pygen_variable_exts_SPV_KHR_shader_ballot, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"SubgroupGtMask", 4418, 2, pygen_variable_caps_SubgroupBallotKHRGroupNonUniformBallot, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"SubgroupGtMaskKHR", 4418, 2, pygen_variable_caps_SubgroupBallotKHRGroupNonUniformBallot, 1, pygen_variable_exts_SPV_KHR_shader_ballot, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"SubgroupLeMask", 4419, 2, pygen_variable_caps_SubgroupBallotKHRGroupNonUniformBallot, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"SubgroupLeMaskKHR", 4419, 2, pygen_variable_caps_SubgroupBallotKHRGroupNonUniformBallot, 1, pygen_variable_exts_SPV_KHR_shader_ballot, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"SubgroupLtMask", 4420, 2, pygen_variable_caps_SubgroupBallotKHRGroupNonUniformBallot, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"SubgroupLtMaskKHR", 4420, 2, pygen_variable_caps_SubgroupBallotKHRGroupNonUniformBallot, 1, pygen_variable_exts_SPV_KHR_shader_ballot, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"BaseVertex", 4424, 1, pygen_variable_caps_DrawParameters, 1, pygen_variable_exts_SPV_KHR_shader_draw_parameters, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"BaseInstance", 4425, 1, pygen_variable_caps_DrawParameters, 1, pygen_variable_exts_SPV_KHR_shader_draw_parameters, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"DrawIndex", 4426, 1, pygen_variable_caps_DrawParameters, 1, pygen_variable_exts_SPV_KHR_shader_draw_parameters, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"DeviceIndex", 4438, 1, pygen_variable_caps_DeviceGroup, 1, pygen_variable_exts_SPV_KHR_device_group, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"ViewIndex", 4440, 1, pygen_variable_caps_MultiView, 1, pygen_variable_exts_SPV_KHR_multiview, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"BaryCoordNoPerspAMD", 4992, 0, nullptr, 1, pygen_variable_exts_SPV_AMD_shader_explicit_vertex_parameter, {}, 0xffffffffu}, + {"BaryCoordNoPerspCentroidAMD", 4993, 0, nullptr, 1, pygen_variable_exts_SPV_AMD_shader_explicit_vertex_parameter, {}, 0xffffffffu}, + {"BaryCoordNoPerspSampleAMD", 4994, 0, nullptr, 1, pygen_variable_exts_SPV_AMD_shader_explicit_vertex_parameter, {}, 0xffffffffu}, + {"BaryCoordSmoothAMD", 4995, 0, nullptr, 1, pygen_variable_exts_SPV_AMD_shader_explicit_vertex_parameter, {}, 0xffffffffu}, + {"BaryCoordSmoothCentroidAMD", 4996, 0, nullptr, 1, pygen_variable_exts_SPV_AMD_shader_explicit_vertex_parameter, {}, 0xffffffffu}, + {"BaryCoordSmoothSampleAMD", 4997, 0, nullptr, 1, pygen_variable_exts_SPV_AMD_shader_explicit_vertex_parameter, {}, 0xffffffffu}, + {"BaryCoordPullModelAMD", 4998, 0, nullptr, 1, pygen_variable_exts_SPV_AMD_shader_explicit_vertex_parameter, {}, 0xffffffffu}, + {"FragStencilRefEXT", 5014, 1, pygen_variable_caps_StencilExportEXT, 1, pygen_variable_exts_SPV_EXT_shader_stencil_export, {}, 0xffffffffu}, + {"ViewportMaskNV", 5253, 1, pygen_variable_caps_ShaderViewportMaskNV, 0, nullptr, {}, 0xffffffffu}, + {"SecondaryPositionNV", 5257, 1, pygen_variable_caps_ShaderStereoViewNV, 0, nullptr, {}, 0xffffffffu}, + {"SecondaryViewportMaskNV", 5258, 1, pygen_variable_caps_ShaderStereoViewNV, 0, nullptr, {}, 0xffffffffu}, + {"PositionPerViewNV", 5261, 1, pygen_variable_caps_PerViewAttributesNV, 0, nullptr, {}, 0xffffffffu}, + {"ViewportMaskPerViewNV", 5262, 1, pygen_variable_caps_PerViewAttributesNV, 0, nullptr, {}, 0xffffffffu}, + {"FullyCoveredEXT", 5264, 1, pygen_variable_caps_FragmentFullyCoveredEXT, 1, pygen_variable_exts_SPV_EXT_fragment_fully_covered, {}, 0xffffffffu} +}; + +static const spv_operand_desc_t pygen_variable_ScopeEntries[] = { + {"CrossDevice", 0, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Device", 1, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Workgroup", 2, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Subgroup", 3, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Invocation", 4, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)} +}; + +static const spv_operand_desc_t pygen_variable_GroupOperationEntries[] = { + {"Reduce", 0, 3, pygen_variable_caps_KernelGroupNonUniformArithmeticGroupNonUniformBallot, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"InclusiveScan", 1, 3, pygen_variable_caps_KernelGroupNonUniformArithmeticGroupNonUniformBallot, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ExclusiveScan", 2, 3, pygen_variable_caps_KernelGroupNonUniformArithmeticGroupNonUniformBallot, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ClusteredReduce", 3, 1, pygen_variable_caps_GroupNonUniformClustered, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"PartitionedReduceNV", 6, 1, pygen_variable_caps_GroupNonUniformPartitionedNV, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"PartitionedInclusiveScanNV", 7, 1, pygen_variable_caps_GroupNonUniformPartitionedNV, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"PartitionedExclusiveScanNV", 8, 1, pygen_variable_caps_GroupNonUniformPartitionedNV, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)} +}; + +static const spv_operand_desc_t pygen_variable_KernelEnqueueFlagsEntries[] = { + {"NoWait", 0, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"WaitKernel", 1, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"WaitWorkGroup", 2, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)} +}; + +static const spv_operand_desc_t pygen_variable_CapabilityEntries[] = { + {"Matrix", 0, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Shader", 1, 1, pygen_variable_caps_Matrix, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Geometry", 2, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Tessellation", 3, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Addresses", 4, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Linkage", 5, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Kernel", 6, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Vector16", 7, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Float16Buffer", 8, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Float16", 9, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Float64", 10, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Int64", 11, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Int64Atomics", 12, 1, pygen_variable_caps_Int64, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageBasic", 13, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageReadWrite", 14, 1, pygen_variable_caps_ImageBasic, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageMipmap", 15, 1, pygen_variable_caps_ImageBasic, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Pipes", 17, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Groups", 18, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"DeviceEnqueue", 19, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"LiteralSampler", 20, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"AtomicStorage", 21, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Int16", 22, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"TessellationPointSize", 23, 1, pygen_variable_caps_Tessellation, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GeometryPointSize", 24, 1, pygen_variable_caps_Geometry, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageGatherExtended", 25, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"StorageImageMultisample", 27, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"UniformBufferArrayDynamicIndexing", 28, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SampledImageArrayDynamicIndexing", 29, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"StorageBufferArrayDynamicIndexing", 30, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"StorageImageArrayDynamicIndexing", 31, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ClipDistance", 32, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"CullDistance", 33, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageCubeArray", 34, 1, pygen_variable_caps_SampledCubeArray, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SampleRateShading", 35, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageRect", 36, 1, pygen_variable_caps_SampledRect, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SampledRect", 37, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GenericPointer", 38, 1, pygen_variable_caps_Addresses, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Int8", 39, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"InputAttachment", 40, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SparseResidency", 41, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"MinLod", 42, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Sampled1D", 43, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Image1D", 44, 1, pygen_variable_caps_Sampled1D, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SampledCubeArray", 45, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SampledBuffer", 46, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageBuffer", 47, 1, pygen_variable_caps_SampledBuffer, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageMSArray", 48, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"StorageImageExtendedFormats", 49, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"ImageQuery", 50, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"DerivativeControl", 51, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"InterpolationFunction", 52, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"TransformFeedback", 53, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"GeometryStreams", 54, 1, pygen_variable_caps_Geometry, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"StorageImageReadWithoutFormat", 55, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"StorageImageWriteWithoutFormat", 56, 1, pygen_variable_caps_Shader, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"MultiViewport", 57, 1, pygen_variable_caps_Geometry, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SubgroupDispatch", 58, 1, pygen_variable_caps_DeviceEnqueue, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1,1)}, + {"NamedBarrier", 59, 1, pygen_variable_caps_Kernel, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1,1)}, + {"PipeStorage", 60, 1, pygen_variable_caps_Pipes, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1,1)}, + {"GroupNonUniform", 61, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformVote", 62, 1, pygen_variable_caps_GroupNonUniform, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformArithmetic", 63, 1, pygen_variable_caps_GroupNonUniform, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformBallot", 64, 1, pygen_variable_caps_GroupNonUniform, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformShuffle", 65, 1, pygen_variable_caps_GroupNonUniform, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformShuffleRelative", 66, 1, pygen_variable_caps_GroupNonUniform, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformClustered", 67, 1, pygen_variable_caps_GroupNonUniform, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"GroupNonUniformQuad", 68, 1, pygen_variable_caps_GroupNonUniform, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"SubgroupBallotKHR", 4423, 0, nullptr, 1, pygen_variable_exts_SPV_KHR_shader_ballot, {}, 0xffffffffu}, + {"DrawParameters", 4427, 1, pygen_variable_caps_Shader, 1, pygen_variable_exts_SPV_KHR_shader_draw_parameters, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"SubgroupVoteKHR", 4431, 0, nullptr, 1, pygen_variable_exts_SPV_KHR_subgroup_vote, {}, 0xffffffffu}, + {"StorageBuffer16BitAccess", 4433, 0, nullptr, 1, pygen_variable_exts_SPV_KHR_16bit_storage, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"StorageUniformBufferBlock16", 4433, 0, nullptr, 1, pygen_variable_exts_SPV_KHR_16bit_storage, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"StorageUniform16", 4434, 2, pygen_variable_caps_StorageBuffer16BitAccessStorageUniformBufferBlock16, 1, pygen_variable_exts_SPV_KHR_16bit_storage, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"UniformAndStorageBuffer16BitAccess", 4434, 2, pygen_variable_caps_StorageBuffer16BitAccessStorageUniformBufferBlock16, 1, pygen_variable_exts_SPV_KHR_16bit_storage, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"StoragePushConstant16", 4435, 0, nullptr, 1, pygen_variable_exts_SPV_KHR_16bit_storage, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"StorageInputOutput16", 4436, 0, nullptr, 1, pygen_variable_exts_SPV_KHR_16bit_storage, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"DeviceGroup", 4437, 0, nullptr, 1, pygen_variable_exts_SPV_KHR_device_group, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"MultiView", 4439, 1, pygen_variable_caps_Shader, 1, pygen_variable_exts_SPV_KHR_multiview, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"VariablePointersStorageBuffer", 4441, 1, pygen_variable_caps_Shader, 1, pygen_variable_exts_SPV_KHR_variable_pointers, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"VariablePointers", 4442, 1, pygen_variable_caps_VariablePointersStorageBuffer, 1, pygen_variable_exts_SPV_KHR_variable_pointers, {}, SPV_SPIRV_VERSION_WORD(1,3)}, + {"AtomicStorageOps", 4445, 0, nullptr, 1, pygen_variable_exts_SPV_KHR_shader_atomic_counter_ops, {}, 0xffffffffu}, + {"SampleMaskPostDepthCoverage", 4447, 0, nullptr, 1, pygen_variable_exts_SPV_KHR_post_depth_coverage, {}, 0xffffffffu}, + {"Float16ImageAMD", 5008, 1, pygen_variable_caps_Shader, 1, pygen_variable_exts_SPV_AMD_gpu_shader_half_float_fetch, {}, 0xffffffffu}, + {"ImageGatherBiasLodAMD", 5009, 1, pygen_variable_caps_Shader, 1, pygen_variable_exts_SPV_AMD_texture_gather_bias_lod, {}, 0xffffffffu}, + {"FragmentMaskAMD", 5010, 1, pygen_variable_caps_Shader, 1, pygen_variable_exts_SPV_AMD_shader_fragment_mask, {}, 0xffffffffu}, + {"StencilExportEXT", 5013, 1, pygen_variable_caps_Shader, 1, pygen_variable_exts_SPV_EXT_shader_stencil_export, {}, 0xffffffffu}, + {"ImageReadWriteLodAMD", 5015, 1, pygen_variable_caps_Shader, 1, pygen_variable_exts_SPV_AMD_shader_image_load_store_lod, {}, 0xffffffffu}, + {"SampleMaskOverrideCoverageNV", 5249, 1, pygen_variable_caps_SampleRateShading, 1, pygen_variable_exts_SPV_NV_sample_mask_override_coverage, {}, 0xffffffffu}, + {"GeometryShaderPassthroughNV", 5251, 1, pygen_variable_caps_Geometry, 1, pygen_variable_exts_SPV_NV_geometry_shader_passthrough, {}, 0xffffffffu}, + {"ShaderViewportIndexLayerEXT", 5254, 1, pygen_variable_caps_MultiViewport, 1, pygen_variable_exts_SPV_EXT_shader_viewport_index_layer, {}, 0xffffffffu}, + {"ShaderViewportIndexLayerNV", 5254, 1, pygen_variable_caps_MultiViewport, 1, pygen_variable_exts_SPV_NV_viewport_array2, {}, 0xffffffffu}, + {"ShaderViewportMaskNV", 5255, 1, pygen_variable_caps_ShaderViewportIndexLayerNV, 1, pygen_variable_exts_SPV_NV_viewport_array2, {}, 0xffffffffu}, + {"ShaderStereoViewNV", 5259, 1, pygen_variable_caps_ShaderViewportMaskNV, 1, pygen_variable_exts_SPV_NV_stereo_view_rendering, {}, 0xffffffffu}, + {"PerViewAttributesNV", 5260, 1, pygen_variable_caps_MultiView, 1, pygen_variable_exts_SPV_NVX_multiview_per_view_attributes, {}, 0xffffffffu}, + {"FragmentFullyCoveredEXT", 5265, 1, pygen_variable_caps_Shader, 1, pygen_variable_exts_SPV_EXT_fragment_fully_covered, {}, 0xffffffffu}, + {"GroupNonUniformPartitionedNV", 5297, 0, nullptr, 1, pygen_variable_exts_SPV_NV_shader_subgroup_partitioned, {}, 0xffffffffu}, + {"ShaderNonUniformEXT", 5301, 1, pygen_variable_caps_Shader, 1, pygen_variable_exts_SPV_EXT_descriptor_indexing, {}, 0xffffffffu}, + {"RuntimeDescriptorArrayEXT", 5302, 1, pygen_variable_caps_Shader, 1, pygen_variable_exts_SPV_EXT_descriptor_indexing, {}, 0xffffffffu}, + {"InputAttachmentArrayDynamicIndexingEXT", 5303, 1, pygen_variable_caps_InputAttachment, 1, pygen_variable_exts_SPV_EXT_descriptor_indexing, {}, 0xffffffffu}, + {"UniformTexelBufferArrayDynamicIndexingEXT", 5304, 1, pygen_variable_caps_SampledBuffer, 1, pygen_variable_exts_SPV_EXT_descriptor_indexing, {}, 0xffffffffu}, + {"StorageTexelBufferArrayDynamicIndexingEXT", 5305, 1, pygen_variable_caps_ImageBuffer, 1, pygen_variable_exts_SPV_EXT_descriptor_indexing, {}, 0xffffffffu}, + {"UniformBufferArrayNonUniformIndexingEXT", 5306, 1, pygen_variable_caps_ShaderNonUniformEXT, 1, pygen_variable_exts_SPV_EXT_descriptor_indexing, {}, 0xffffffffu}, + {"SampledImageArrayNonUniformIndexingEXT", 5307, 1, pygen_variable_caps_ShaderNonUniformEXT, 1, pygen_variable_exts_SPV_EXT_descriptor_indexing, {}, 0xffffffffu}, + {"StorageBufferArrayNonUniformIndexingEXT", 5308, 1, pygen_variable_caps_ShaderNonUniformEXT, 1, pygen_variable_exts_SPV_EXT_descriptor_indexing, {}, 0xffffffffu}, + {"StorageImageArrayNonUniformIndexingEXT", 5309, 1, pygen_variable_caps_ShaderNonUniformEXT, 1, pygen_variable_exts_SPV_EXT_descriptor_indexing, {}, 0xffffffffu}, + {"InputAttachmentArrayNonUniformIndexingEXT", 5310, 2, pygen_variable_caps_InputAttachmentShaderNonUniformEXT, 1, pygen_variable_exts_SPV_EXT_descriptor_indexing, {}, 0xffffffffu}, + {"UniformTexelBufferArrayNonUniformIndexingEXT", 5311, 2, pygen_variable_caps_SampledBufferShaderNonUniformEXT, 1, pygen_variable_exts_SPV_EXT_descriptor_indexing, {}, 0xffffffffu}, + {"StorageTexelBufferArrayNonUniformIndexingEXT", 5312, 2, pygen_variable_caps_ImageBufferShaderNonUniformEXT, 1, pygen_variable_exts_SPV_EXT_descriptor_indexing, {}, 0xffffffffu}, + {"SubgroupShuffleINTEL", 5568, 0, nullptr, 1, pygen_variable_exts_SPV_INTEL_subgroups, {}, 0xffffffffu}, + {"SubgroupBufferBlockIOINTEL", 5569, 0, nullptr, 1, pygen_variable_exts_SPV_INTEL_subgroups, {}, 0xffffffffu}, + {"SubgroupImageBlockIOINTEL", 5570, 0, nullptr, 1, pygen_variable_exts_SPV_INTEL_subgroups, {}, 0xffffffffu} +}; + +static const spv_operand_desc_t pygen_variable_DebugInfoFlagsEntries[] = { + {"FlagIsProtected", 0x01, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FlagIsPrivate", 0x02, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FlagIsPublic", 0x03, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FlagIsLocal", 0x04, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FlagIsDefinition", 0x08, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FlagFwdDecl", 0x10, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FlagArtificial", 0x20, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FlagExplicit", 0x40, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FlagPrototyped", 0x80, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FlagObjectPointer", 0x100, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FlagStaticMember", 0x200, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FlagIndirectVariable", 0x400, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FlagLValueReference", 0x800, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FlagRValueReference", 0x1000, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"FlagIsOptimized", 0x2000, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)} +}; + +static const spv_operand_desc_t pygen_variable_DebugBaseTypeAttributeEncodingEntries[] = { + {"Unspecified", 0, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Address", 1, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Boolean", 2, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Float", 4, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Signed", 5, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"SignedChar", 6, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Unsigned", 7, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"UnsignedChar", 8, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)} +}; + +static const spv_operand_desc_t pygen_variable_DebugCompositeTypeEntries[] = { + {"Class", 0, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Structure", 1, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Union", 2, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)} +}; + +static const spv_operand_desc_t pygen_variable_DebugTypeQualifierEntries[] = { + {"ConstType", 0, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"VolatileType", 1, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"RestrictType", 2, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)} +}; + +static const spv_operand_desc_t pygen_variable_DebugOperationEntries[] = { + {"Deref", 0, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Plus", 1, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Minus", 2, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"PlusUconst", 3, 0, nullptr, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"BitPiece", 4, 0, nullptr, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Swap", 5, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Xderef", 6, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"StackValue", 7, 0, nullptr, 0, nullptr, {}, SPV_SPIRV_VERSION_WORD(1, 0)}, + {"Constu", 8, 0, nullptr, 0, nullptr, {SPV_OPERAND_TYPE_LITERAL_INTEGER}, SPV_SPIRV_VERSION_WORD(1, 0)} +}; + +static const spv_operand_desc_group_t pygen_variable_OperandInfoTable[] = { + {SPV_OPERAND_TYPE_IMAGE, ARRAY_SIZE(pygen_variable_ImageOperandsEntries), pygen_variable_ImageOperandsEntries}, + {SPV_OPERAND_TYPE_FP_FAST_MATH_MODE, ARRAY_SIZE(pygen_variable_FPFastMathModeEntries), pygen_variable_FPFastMathModeEntries}, + {SPV_OPERAND_TYPE_SELECTION_CONTROL, ARRAY_SIZE(pygen_variable_SelectionControlEntries), pygen_variable_SelectionControlEntries}, + {SPV_OPERAND_TYPE_LOOP_CONTROL, ARRAY_SIZE(pygen_variable_LoopControlEntries), pygen_variable_LoopControlEntries}, + {SPV_OPERAND_TYPE_FUNCTION_CONTROL, ARRAY_SIZE(pygen_variable_FunctionControlEntries), pygen_variable_FunctionControlEntries}, + {SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID, ARRAY_SIZE(pygen_variable_MemorySemanticsEntries), pygen_variable_MemorySemanticsEntries}, + {SPV_OPERAND_TYPE_MEMORY_ACCESS, ARRAY_SIZE(pygen_variable_MemoryAccessEntries), pygen_variable_MemoryAccessEntries}, + {SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO, ARRAY_SIZE(pygen_variable_KernelProfilingInfoEntries), pygen_variable_KernelProfilingInfoEntries}, + {SPV_OPERAND_TYPE_SOURCE_LANGUAGE, ARRAY_SIZE(pygen_variable_SourceLanguageEntries), pygen_variable_SourceLanguageEntries}, + {SPV_OPERAND_TYPE_EXECUTION_MODEL, ARRAY_SIZE(pygen_variable_ExecutionModelEntries), pygen_variable_ExecutionModelEntries}, + {SPV_OPERAND_TYPE_ADDRESSING_MODEL, ARRAY_SIZE(pygen_variable_AddressingModelEntries), pygen_variable_AddressingModelEntries}, + {SPV_OPERAND_TYPE_MEMORY_MODEL, ARRAY_SIZE(pygen_variable_MemoryModelEntries), pygen_variable_MemoryModelEntries}, + {SPV_OPERAND_TYPE_EXECUTION_MODE, ARRAY_SIZE(pygen_variable_ExecutionModeEntries), pygen_variable_ExecutionModeEntries}, + {SPV_OPERAND_TYPE_STORAGE_CLASS, ARRAY_SIZE(pygen_variable_StorageClassEntries), pygen_variable_StorageClassEntries}, + {SPV_OPERAND_TYPE_DIMENSIONALITY, ARRAY_SIZE(pygen_variable_DimEntries), pygen_variable_DimEntries}, + {SPV_OPERAND_TYPE_SAMPLER_ADDRESSING_MODE, ARRAY_SIZE(pygen_variable_SamplerAddressingModeEntries), pygen_variable_SamplerAddressingModeEntries}, + {SPV_OPERAND_TYPE_SAMPLER_FILTER_MODE, ARRAY_SIZE(pygen_variable_SamplerFilterModeEntries), pygen_variable_SamplerFilterModeEntries}, + {SPV_OPERAND_TYPE_SAMPLER_IMAGE_FORMAT, ARRAY_SIZE(pygen_variable_ImageFormatEntries), pygen_variable_ImageFormatEntries}, + {SPV_OPERAND_TYPE_IMAGE_CHANNEL_ORDER, ARRAY_SIZE(pygen_variable_ImageChannelOrderEntries), pygen_variable_ImageChannelOrderEntries}, + {SPV_OPERAND_TYPE_IMAGE_CHANNEL_DATA_TYPE, ARRAY_SIZE(pygen_variable_ImageChannelDataTypeEntries), pygen_variable_ImageChannelDataTypeEntries}, + {SPV_OPERAND_TYPE_FP_ROUNDING_MODE, ARRAY_SIZE(pygen_variable_FPRoundingModeEntries), pygen_variable_FPRoundingModeEntries}, + {SPV_OPERAND_TYPE_LINKAGE_TYPE, ARRAY_SIZE(pygen_variable_LinkageTypeEntries), pygen_variable_LinkageTypeEntries}, + {SPV_OPERAND_TYPE_ACCESS_QUALIFIER, ARRAY_SIZE(pygen_variable_AccessQualifierEntries), pygen_variable_AccessQualifierEntries}, + {SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE, ARRAY_SIZE(pygen_variable_FunctionParameterAttributeEntries), pygen_variable_FunctionParameterAttributeEntries}, + {SPV_OPERAND_TYPE_DECORATION, ARRAY_SIZE(pygen_variable_DecorationEntries), pygen_variable_DecorationEntries}, + {SPV_OPERAND_TYPE_BUILT_IN, ARRAY_SIZE(pygen_variable_BuiltInEntries), pygen_variable_BuiltInEntries}, + {SPV_OPERAND_TYPE_SCOPE_ID, ARRAY_SIZE(pygen_variable_ScopeEntries), pygen_variable_ScopeEntries}, + {SPV_OPERAND_TYPE_GROUP_OPERATION, ARRAY_SIZE(pygen_variable_GroupOperationEntries), pygen_variable_GroupOperationEntries}, + {SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS, ARRAY_SIZE(pygen_variable_KernelEnqueueFlagsEntries), pygen_variable_KernelEnqueueFlagsEntries}, + {SPV_OPERAND_TYPE_CAPABILITY, ARRAY_SIZE(pygen_variable_CapabilityEntries), pygen_variable_CapabilityEntries}, + {SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS, ARRAY_SIZE(pygen_variable_DebugInfoFlagsEntries), pygen_variable_DebugInfoFlagsEntries}, + {SPV_OPERAND_TYPE_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING, ARRAY_SIZE(pygen_variable_DebugBaseTypeAttributeEncodingEntries), pygen_variable_DebugBaseTypeAttributeEncodingEntries}, + {SPV_OPERAND_TYPE_DEBUG_COMPOSITE_TYPE, ARRAY_SIZE(pygen_variable_DebugCompositeTypeEntries), pygen_variable_DebugCompositeTypeEntries}, + {SPV_OPERAND_TYPE_DEBUG_TYPE_QUALIFIER, ARRAY_SIZE(pygen_variable_DebugTypeQualifierEntries), pygen_variable_DebugTypeQualifierEntries}, + {SPV_OPERAND_TYPE_DEBUG_OPERATION, ARRAY_SIZE(pygen_variable_DebugOperationEntries), pygen_variable_DebugOperationEntries}, + {SPV_OPERAND_TYPE_OPTIONAL_IMAGE, ARRAY_SIZE(pygen_variable_ImageOperandsEntries), pygen_variable_ImageOperandsEntries}, + {SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS, ARRAY_SIZE(pygen_variable_MemoryAccessEntries), pygen_variable_MemoryAccessEntries}, + {SPV_OPERAND_TYPE_OPTIONAL_ACCESS_QUALIFIER, ARRAY_SIZE(pygen_variable_AccessQualifierEntries), pygen_variable_AccessQualifierEntries} +}; diff --git a/3rdparty/spirv-tools/include/generated/spv-amd-gcn-shader.insts.inc b/3rdparty/spirv-tools/include/generated/spv-amd-gcn-shader.insts.inc new file mode 100644 index 000000000..48f7f4768 --- /dev/null +++ b/3rdparty/spirv-tools/include/generated/spv-amd-gcn-shader.insts.inc @@ -0,0 +1,7 @@ + + +static const spv_ext_inst_desc_t spv_amd_gcn_shader_entries[] = { + {"CubeFaceIndexAMD", 1, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"CubeFaceCoordAMD", 2, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"TimeAMD", 3, 0, nullptr, {SPV_OPERAND_TYPE_NONE}} +}; diff --git a/3rdparty/spirv-tools/include/generated/spv-amd-shader-ballot.insts.inc b/3rdparty/spirv-tools/include/generated/spv-amd-shader-ballot.insts.inc new file mode 100644 index 000000000..6216051b9 --- /dev/null +++ b/3rdparty/spirv-tools/include/generated/spv-amd-shader-ballot.insts.inc @@ -0,0 +1,8 @@ + + +static const spv_ext_inst_desc_t spv_amd_shader_ballot_entries[] = { + {"SwizzleInvocationsAMD", 1, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"SwizzleInvocationsMaskedAMD", 2, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"WriteInvocationAMD", 3, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"MbcntAMD", 4, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}} +}; diff --git a/3rdparty/spirv-tools/include/generated/spv-amd-shader-explicit-vertex-parameter.insts.inc b/3rdparty/spirv-tools/include/generated/spv-amd-shader-explicit-vertex-parameter.insts.inc new file mode 100644 index 000000000..6b612854f --- /dev/null +++ b/3rdparty/spirv-tools/include/generated/spv-amd-shader-explicit-vertex-parameter.insts.inc @@ -0,0 +1,5 @@ + + +static const spv_ext_inst_desc_t spv_amd_shader_explicit_vertex_parameter_entries[] = { + {"InterpolateAtVertexAMD", 1, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}} +}; diff --git a/3rdparty/spirv-tools/include/generated/spv-amd-shader-trinary-minmax.insts.inc b/3rdparty/spirv-tools/include/generated/spv-amd-shader-trinary-minmax.insts.inc new file mode 100644 index 000000000..061e862a4 --- /dev/null +++ b/3rdparty/spirv-tools/include/generated/spv-amd-shader-trinary-minmax.insts.inc @@ -0,0 +1,13 @@ + + +static const spv_ext_inst_desc_t spv_amd_shader_trinary_minmax_entries[] = { + {"FMin3AMD", 1, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"UMin3AMD", 2, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"SMin3AMD", 3, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"FMax3AMD", 4, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"UMax3AMD", 5, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"SMax3AMD", 6, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"FMid3AMD", 7, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"UMid3AMD", 8, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}}, + {"SMid3AMD", 9, 0, nullptr, {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE}} +}; diff --git a/3rdparty/spirv-tools/include/spirv-tools/libspirv.h b/3rdparty/spirv-tools/include/spirv-tools/libspirv.h new file mode 100644 index 000000000..d6cb60c03 --- /dev/null +++ b/3rdparty/spirv-tools/include/spirv-tools/libspirv.h @@ -0,0 +1,586 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SPIRV_TOOLS_LIBSPIRV_H_ +#define SPIRV_TOOLS_LIBSPIRV_H_ + +#ifdef __cplusplus +extern "C" { +#else +#include +#endif + +#include +#include + +#if defined(SPIRV_TOOLS_SHAREDLIB) +#if defined(_WIN32) +#if defined(SPIRV_TOOLS_IMPLEMENTATION) +#define SPIRV_TOOLS_EXPORT __declspec(dllexport) +#else +#define SPIRV_TOOLS_EXPORT __declspec(dllimport) +#endif +#else +#if defined(SPIRV_TOOLS_IMPLEMENTATION) +#define SPIRV_TOOLS_EXPORT __attribute__((visibility("default"))) +#else +#define SPIRV_TOOLS_EXPORT +#endif +#endif +#else +#define SPIRV_TOOLS_EXPORT +#endif + +// Helpers + +#define SPV_BIT(shift) (1 << (shift)) + +#define SPV_FORCE_16_BIT_ENUM(name) _##name = 0x7fff +#define SPV_FORCE_32_BIT_ENUM(name) _##name = 0x7fffffff + +// Enumerations + +typedef enum spv_result_t { + SPV_SUCCESS = 0, + SPV_UNSUPPORTED = 1, + SPV_END_OF_STREAM = 2, + SPV_WARNING = 3, + SPV_FAILED_MATCH = 4, + SPV_REQUESTED_TERMINATION = 5, // Success, but signals early termination. + SPV_ERROR_INTERNAL = -1, + SPV_ERROR_OUT_OF_MEMORY = -2, + SPV_ERROR_INVALID_POINTER = -3, + SPV_ERROR_INVALID_BINARY = -4, + SPV_ERROR_INVALID_TEXT = -5, + SPV_ERROR_INVALID_TABLE = -6, + SPV_ERROR_INVALID_VALUE = -7, + SPV_ERROR_INVALID_DIAGNOSTIC = -8, + SPV_ERROR_INVALID_LOOKUP = -9, + SPV_ERROR_INVALID_ID = -10, + SPV_ERROR_INVALID_CFG = -11, + SPV_ERROR_INVALID_LAYOUT = -12, + SPV_ERROR_INVALID_CAPABILITY = -13, + SPV_ERROR_INVALID_DATA = -14, // Indicates data rules validation failure. + SPV_ERROR_MISSING_EXTENSION = -15, + SPV_ERROR_WRONG_VERSION = -16, // Indicates wrong SPIR-V version + SPV_FORCE_32_BIT_ENUM(spv_result_t) +} spv_result_t; + +// Severity levels of messages communicated to the consumer. +typedef enum spv_message_level_t { + SPV_MSG_FATAL, // Unrecoverable error due to environment. + // Will exit the program immediately. E.g., + // out of memory. + SPV_MSG_INTERNAL_ERROR, // Unrecoverable error due to SPIRV-Tools + // internals. + // Will exit the program immediately. E.g., + // unimplemented feature. + SPV_MSG_ERROR, // Normal error due to user input. + SPV_MSG_WARNING, // Warning information. + SPV_MSG_INFO, // General information. + SPV_MSG_DEBUG, // Debug information. +} spv_message_level_t; + +typedef enum spv_endianness_t { + SPV_ENDIANNESS_LITTLE, + SPV_ENDIANNESS_BIG, + SPV_FORCE_32_BIT_ENUM(spv_endianness_t) +} spv_endianness_t; + +// The kinds of operands that an instruction may have. +// +// Some operand types are "concrete". The binary parser uses a concrete +// operand type to describe an operand of a parsed instruction. +// +// The assembler uses all operand types. In addition to determining what +// kind of value an operand may be, non-concrete operand types capture the +// fact that an operand might be optional (may be absent, or present exactly +// once), or might occur zero or more times. +// +// Sometimes we also need to be able to express the fact that an operand +// is a member of an optional tuple of values. In that case the first member +// would be optional, and the subsequent members would be required. +typedef enum spv_operand_type_t { + // A sentinel value. + SPV_OPERAND_TYPE_NONE = 0, + + // Set 1: Operands that are IDs. + SPV_OPERAND_TYPE_ID, + SPV_OPERAND_TYPE_TYPE_ID, + SPV_OPERAND_TYPE_RESULT_ID, + SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID, // SPIR-V Sec 3.25 + SPV_OPERAND_TYPE_SCOPE_ID, // SPIR-V Sec 3.27 + + // Set 2: Operands that are literal numbers. + SPV_OPERAND_TYPE_LITERAL_INTEGER, // Always unsigned 32-bits. + // The Instruction argument to OpExtInst. It's an unsigned 32-bit literal + // number indicating which instruction to use from an extended instruction + // set. + SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER, + // The Opcode argument to OpSpecConstantOp. It determines the operation + // to be performed on constant operands to compute a specialization constant + // result. + SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER, + // A literal number whose format and size are determined by a previous operand + // in the same instruction. It's a signed integer, an unsigned integer, or a + // floating point number. It also has a specified bit width. The width + // may be larger than 32, which would require such a typed literal value to + // occupy multiple SPIR-V words. + SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER, + + // Set 3: The literal string operand type. + SPV_OPERAND_TYPE_LITERAL_STRING, + + // Set 4: Operands that are a single word enumerated value. + SPV_OPERAND_TYPE_SOURCE_LANGUAGE, // SPIR-V Sec 3.2 + SPV_OPERAND_TYPE_EXECUTION_MODEL, // SPIR-V Sec 3.3 + SPV_OPERAND_TYPE_ADDRESSING_MODEL, // SPIR-V Sec 3.4 + SPV_OPERAND_TYPE_MEMORY_MODEL, // SPIR-V Sec 3.5 + SPV_OPERAND_TYPE_EXECUTION_MODE, // SPIR-V Sec 3.6 + SPV_OPERAND_TYPE_STORAGE_CLASS, // SPIR-V Sec 3.7 + SPV_OPERAND_TYPE_DIMENSIONALITY, // SPIR-V Sec 3.8 + SPV_OPERAND_TYPE_SAMPLER_ADDRESSING_MODE, // SPIR-V Sec 3.9 + SPV_OPERAND_TYPE_SAMPLER_FILTER_MODE, // SPIR-V Sec 3.10 + SPV_OPERAND_TYPE_SAMPLER_IMAGE_FORMAT, // SPIR-V Sec 3.11 + SPV_OPERAND_TYPE_IMAGE_CHANNEL_ORDER, // SPIR-V Sec 3.12 + SPV_OPERAND_TYPE_IMAGE_CHANNEL_DATA_TYPE, // SPIR-V Sec 3.13 + SPV_OPERAND_TYPE_FP_ROUNDING_MODE, // SPIR-V Sec 3.16 + SPV_OPERAND_TYPE_LINKAGE_TYPE, // SPIR-V Sec 3.17 + SPV_OPERAND_TYPE_ACCESS_QUALIFIER, // SPIR-V Sec 3.18 + SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE, // SPIR-V Sec 3.19 + SPV_OPERAND_TYPE_DECORATION, // SPIR-V Sec 3.20 + SPV_OPERAND_TYPE_BUILT_IN, // SPIR-V Sec 3.21 + SPV_OPERAND_TYPE_GROUP_OPERATION, // SPIR-V Sec 3.28 + SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS, // SPIR-V Sec 3.29 + SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO, // SPIR-V Sec 3.30 + SPV_OPERAND_TYPE_CAPABILITY, // SPIR-V Sec 3.31 + + // Set 5: Operands that are a single word bitmask. + // Sometimes a set bit indicates the instruction requires still more operands. + SPV_OPERAND_TYPE_IMAGE, // SPIR-V Sec 3.14 + SPV_OPERAND_TYPE_FP_FAST_MATH_MODE, // SPIR-V Sec 3.15 + SPV_OPERAND_TYPE_SELECTION_CONTROL, // SPIR-V Sec 3.22 + SPV_OPERAND_TYPE_LOOP_CONTROL, // SPIR-V Sec 3.23 + SPV_OPERAND_TYPE_FUNCTION_CONTROL, // SPIR-V Sec 3.24 + SPV_OPERAND_TYPE_MEMORY_ACCESS, // SPIR-V Sec 3.26 + +// The remaining operand types are only used internally by the assembler. +// There are two categories: +// Optional : expands to 0 or 1 operand, like ? in regular expressions. +// Variable : expands to 0, 1 or many operands or pairs of operands. +// This is similar to * in regular expressions. + +// Macros for defining bounds on optional and variable operand types. +// Any variable operand type is also optional. +#define FIRST_OPTIONAL(ENUM) ENUM, SPV_OPERAND_TYPE_FIRST_OPTIONAL_TYPE = ENUM +#define FIRST_VARIABLE(ENUM) ENUM, SPV_OPERAND_TYPE_FIRST_VARIABLE_TYPE = ENUM +#define LAST_VARIABLE(ENUM) \ + ENUM, SPV_OPERAND_TYPE_LAST_VARIABLE_TYPE = ENUM, \ + SPV_OPERAND_TYPE_LAST_OPTIONAL_TYPE = ENUM + + // An optional operand represents zero or one logical operands. + // In an instruction definition, this may only appear at the end of the + // operand types. + FIRST_OPTIONAL(SPV_OPERAND_TYPE_OPTIONAL_ID), + // An optional image operand type. + SPV_OPERAND_TYPE_OPTIONAL_IMAGE, + // An optional memory access type. + SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS, + // An optional literal integer. + SPV_OPERAND_TYPE_OPTIONAL_LITERAL_INTEGER, + // An optional literal number, which may be either integer or floating point. + SPV_OPERAND_TYPE_OPTIONAL_LITERAL_NUMBER, + // Like SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER, but optional, and integral. + SPV_OPERAND_TYPE_OPTIONAL_TYPED_LITERAL_INTEGER, + // An optional literal string. + SPV_OPERAND_TYPE_OPTIONAL_LITERAL_STRING, + // An optional access qualifier + SPV_OPERAND_TYPE_OPTIONAL_ACCESS_QUALIFIER, + // An optional context-independent value, or CIV. CIVs are tokens that we can + // assemble regardless of where they occur -- literals, IDs, immediate + // integers, etc. + SPV_OPERAND_TYPE_OPTIONAL_CIV, + + // A variable operand represents zero or more logical operands. + // In an instruction definition, this may only appear at the end of the + // operand types. + FIRST_VARIABLE(SPV_OPERAND_TYPE_VARIABLE_ID), + SPV_OPERAND_TYPE_VARIABLE_LITERAL_INTEGER, + // A sequence of zero or more pairs of (typed literal integer, Id). + // Expands to zero or more: + // (SPV_OPERAND_TYPE_TYPED_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID) + // where the literal number must always be an integer of some sort. + SPV_OPERAND_TYPE_VARIABLE_LITERAL_INTEGER_ID, + // A sequence of zero or more pairs of (Id, Literal integer) + LAST_VARIABLE(SPV_OPERAND_TYPE_VARIABLE_ID_LITERAL_INTEGER), + + // The following are concrete enum types. + SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS, // DebugInfo Sec 3.2. A mask. + SPV_OPERAND_TYPE_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING, // DebugInfo Sec 3.3 + SPV_OPERAND_TYPE_DEBUG_COMPOSITE_TYPE, // DebugInfo Sec 3.4 + SPV_OPERAND_TYPE_DEBUG_TYPE_QUALIFIER, // DebugInfo Sec 3.5 + SPV_OPERAND_TYPE_DEBUG_OPERATION, // DebugInfo Sec 3.6 + + // This is a sentinel value, and does not represent an operand type. + // It should come last. + SPV_OPERAND_TYPE_NUM_OPERAND_TYPES, + + SPV_FORCE_32_BIT_ENUM(spv_operand_type_t) +} spv_operand_type_t; + +typedef enum spv_ext_inst_type_t { + SPV_EXT_INST_TYPE_NONE = 0, + SPV_EXT_INST_TYPE_GLSL_STD_450, + SPV_EXT_INST_TYPE_OPENCL_STD, + SPV_EXT_INST_TYPE_SPV_AMD_SHADER_EXPLICIT_VERTEX_PARAMETER, + SPV_EXT_INST_TYPE_SPV_AMD_SHADER_TRINARY_MINMAX, + SPV_EXT_INST_TYPE_SPV_AMD_GCN_SHADER, + SPV_EXT_INST_TYPE_SPV_AMD_SHADER_BALLOT, + SPV_EXT_INST_TYPE_DEBUGINFO, + + SPV_FORCE_32_BIT_ENUM(spv_ext_inst_type_t) +} spv_ext_inst_type_t; + +// This determines at a high level the kind of a binary-encoded literal +// number, but not the bit width. +// In principle, these could probably be folded into new entries in +// spv_operand_type_t. But then we'd have some special case differences +// between the assembler and disassembler. +typedef enum spv_number_kind_t { + SPV_NUMBER_NONE = 0, // The default for value initialization. + SPV_NUMBER_UNSIGNED_INT, + SPV_NUMBER_SIGNED_INT, + SPV_NUMBER_FLOATING, +} spv_number_kind_t; + +typedef enum spv_text_to_binary_options_t { + SPV_TEXT_TO_BINARY_OPTION_NONE = SPV_BIT(0), + // Numeric IDs in the binary will have the same values as in the source. + // Non-numeric IDs are allocated by filling in the gaps, starting with 1 + // and going up. + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS = SPV_BIT(1), + SPV_FORCE_32_BIT_ENUM(spv_text_to_binary_options_t) +} spv_text_to_binary_options_t; + +typedef enum spv_binary_to_text_options_t { + SPV_BINARY_TO_TEXT_OPTION_NONE = SPV_BIT(0), + SPV_BINARY_TO_TEXT_OPTION_PRINT = SPV_BIT(1), + SPV_BINARY_TO_TEXT_OPTION_COLOR = SPV_BIT(2), + SPV_BINARY_TO_TEXT_OPTION_INDENT = SPV_BIT(3), + SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET = SPV_BIT(4), + // Do not output the module header as leading comments in the assembly. + SPV_BINARY_TO_TEXT_OPTION_NO_HEADER = SPV_BIT(5), + // Use friendly names where possible. The heuristic may expand over + // time, but will use common names for scalar types, and debug names from + // OpName instructions. + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES = SPV_BIT(6), + SPV_FORCE_32_BIT_ENUM(spv_binary_to_text_options_t) +} spv_binary_to_text_options_t; + +// Structures + +// Information about an operand parsed from a binary SPIR-V module. +// Note that the values are not included. You still need access to the binary +// to extract the values. +typedef struct spv_parsed_operand_t { + // Location of the operand, in words from the start of the instruction. + uint16_t offset; + // Number of words occupied by this operand. + uint16_t num_words; + // The "concrete" operand type. See the definition of spv_operand_type_t + // for details. + spv_operand_type_t type; + // If type is a literal number type, then number_kind says whether it's + // a signed integer, an unsigned integer, or a floating point number. + spv_number_kind_t number_kind; + // The number of bits for a literal number type. + uint32_t number_bit_width; +} spv_parsed_operand_t; + +// An instruction parsed from a binary SPIR-V module. +typedef struct spv_parsed_instruction_t { + // An array of words for this instruction, in native endianness. + const uint32_t* words; + // The number of words in this instruction. + uint16_t num_words; + uint16_t opcode; + // The extended instruction type, if opcode is OpExtInst. Otherwise + // this is the "none" value. + spv_ext_inst_type_t ext_inst_type; + // The type id, or 0 if this instruction doesn't have one. + uint32_t type_id; + // The result id, or 0 if this instruction doesn't have one. + uint32_t result_id; + // The array of parsed operands. + const spv_parsed_operand_t* operands; + uint16_t num_operands; +} spv_parsed_instruction_t; + +typedef struct spv_const_binary_t { + const uint32_t* code; + const size_t wordCount; +} spv_const_binary_t; + +typedef struct spv_binary_t { + uint32_t* code; + size_t wordCount; +} spv_binary_t; + +typedef struct spv_text_t { + const char* str; + size_t length; +} spv_text_t; + +typedef struct spv_position_t { + size_t line; + size_t column; + size_t index; +} spv_position_t; + +typedef struct spv_diagnostic_t { + spv_position_t position; + char* error; + bool isTextSource; +} spv_diagnostic_t; + +// Opaque struct containing the context used to operate on a SPIR-V module. +// Its object is used by various translation API functions. +typedef struct spv_context_t spv_context_t; + +typedef struct spv_validator_options_t spv_validator_options_t; + +// Type Definitions + +typedef spv_const_binary_t* spv_const_binary; +typedef spv_binary_t* spv_binary; +typedef spv_text_t* spv_text; +typedef spv_position_t* spv_position; +typedef spv_diagnostic_t* spv_diagnostic; +typedef const spv_context_t* spv_const_context; +typedef spv_context_t* spv_context; +typedef spv_validator_options_t* spv_validator_options; +typedef const spv_validator_options_t* spv_const_validator_options; + +// Platform API + +// Returns the SPIRV-Tools software version as a null-terminated string. +// The contents of the underlying storage is valid for the remainder of +// the process. +SPIRV_TOOLS_EXPORT const char* spvSoftwareVersionString(void); +// Returns a null-terminated string containing the name of the project, +// the software version string, and commit details. +// The contents of the underlying storage is valid for the remainder of +// the process. +SPIRV_TOOLS_EXPORT const char* spvSoftwareVersionDetailsString(void); + +// Certain target environments impose additional restrictions on SPIR-V, so it's +// often necessary to specify which one applies. SPV_ENV_UNIVERSAL means +// environment-agnostic SPIR-V. +typedef enum { + SPV_ENV_UNIVERSAL_1_0, // SPIR-V 1.0 latest revision, no other restrictions. + SPV_ENV_VULKAN_1_0, // Vulkan 1.0 latest revision. + SPV_ENV_UNIVERSAL_1_1, // SPIR-V 1.1 latest revision, no other restrictions. + SPV_ENV_OPENCL_2_1, // OpenCL Full Profile 2.1 latest revision. + SPV_ENV_OPENCL_2_2, // OpenCL Full Profile 2.2 latest revision. + SPV_ENV_OPENGL_4_0, // OpenGL 4.0 plus GL_ARB_gl_spirv, latest revisions. + SPV_ENV_OPENGL_4_1, // OpenGL 4.1 plus GL_ARB_gl_spirv, latest revisions. + SPV_ENV_OPENGL_4_2, // OpenGL 4.2 plus GL_ARB_gl_spirv, latest revisions. + SPV_ENV_OPENGL_4_3, // OpenGL 4.3 plus GL_ARB_gl_spirv, latest revisions. + // There is no variant for OpenGL 4.4. + SPV_ENV_OPENGL_4_5, // OpenGL 4.5 plus GL_ARB_gl_spirv, latest revisions. + SPV_ENV_UNIVERSAL_1_2, // SPIR-V 1.2, latest revision, no other restrictions. + SPV_ENV_OPENCL_1_2, // OpenCL Full Profile 1.2 plus cl_khr_il_program, + // latest revision. + SPV_ENV_OPENCL_EMBEDDED_1_2, // OpenCL Embedded Profile 1.2 plus + // cl_khr_il_program, latest revision. + SPV_ENV_OPENCL_2_0, // OpenCL Full Profile 2.0 plus cl_khr_il_program, + // latest revision. + SPV_ENV_OPENCL_EMBEDDED_2_0, // OpenCL Embedded Profile 2.0 plus + // cl_khr_il_program, latest revision. + SPV_ENV_OPENCL_EMBEDDED_2_1, // OpenCL Embedded Profile 2.1 latest revision. + SPV_ENV_OPENCL_EMBEDDED_2_2, // OpenCL Embedded Profile 2.2 latest revision. + SPV_ENV_UNIVERSAL_1_3, // SPIR-V 1.3 latest revision, no other restrictions. + SPV_ENV_VULKAN_1_1, // Vulkan 1.1 latest revision. +} spv_target_env; + +// SPIR-V Validator can be parameterized with the following Universal Limits. +typedef enum { + spv_validator_limit_max_struct_members, + spv_validator_limit_max_struct_depth, + spv_validator_limit_max_local_variables, + spv_validator_limit_max_global_variables, + spv_validator_limit_max_switch_branches, + spv_validator_limit_max_function_args, + spv_validator_limit_max_control_flow_nesting_depth, + spv_validator_limit_max_access_chain_indexes, +} spv_validator_limit; + +// Returns a string describing the given SPIR-V target environment. +SPIRV_TOOLS_EXPORT const char* spvTargetEnvDescription(spv_target_env env); + +// Creates a context object. Returns null if env is invalid. +SPIRV_TOOLS_EXPORT spv_context spvContextCreate(spv_target_env env); + +// Destroys the given context object. +SPIRV_TOOLS_EXPORT void spvContextDestroy(spv_context context); + +// Creates a Validator options object with default options. Returns a valid +// options object. The object remains valid until it is passed into +// spvValidatorOptionsDestroy. +SPIRV_TOOLS_EXPORT spv_validator_options spvValidatorOptionsCreate(void); + +// Destroys the given Validator options object. +SPIRV_TOOLS_EXPORT void spvValidatorOptionsDestroy( + spv_validator_options options); + +// Records the maximum Universal Limit that is considered valid in the given +// Validator options object. argument must be a valid options object. +SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetUniversalLimit( + spv_validator_options options, spv_validator_limit limit_type, + uint32_t limit); + +// Record whether or not the validator should relax the rules on types for +// stores to structs. When relaxed, it will allow a type mismatch as long as +// the types are structs with the same layout. Two structs have the same layout +// if +// +// 1) the members of the structs are either the same type or are structs with +// same layout, and +// +// 2) the decorations that affect the memory layout are identical for both +// types. Other decorations are not relevant. +SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetRelaxStoreStruct( + spv_validator_options options, bool val); + +// Records whether or not the validator should relax the rules on pointer usage +// in logical addressing mode. +// +// When relaxed, it will allow the following usage cases of pointers: +// 1) OpVariable allocating an object whose type is a pointer type +// 2) OpReturnValue returning a pointer value +SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetRelaxLogicalPointer( + spv_validator_options options, bool val); + +// 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 +// diagnostic is non-null. The generated binary is independent of the context +// and may outlive it. +SPIRV_TOOLS_EXPORT spv_result_t spvTextToBinary(const spv_const_context context, + const char* text, + const size_t length, + spv_binary* binary, + spv_diagnostic* diagnostic); + +// Encodes the given SPIR-V assembly text to its binary representation. Same as +// spvTextToBinary but with options. The options parameter is a bit field of +// spv_text_to_binary_options_t. +SPIRV_TOOLS_EXPORT spv_result_t spvTextToBinaryWithOptions( + const spv_const_context context, const char* text, const size_t length, + const uint32_t options, spv_binary* binary, spv_diagnostic* diagnostic); + +// Frees an allocated text stream. This is a no-op if the text parameter +// is a null pointer. +SPIRV_TOOLS_EXPORT void spvTextDestroy(spv_text text); + +// Decodes the given SPIR-V binary representation to its assembly text. The +// word_count parameter specifies the number of words for binary. The options +// parameter is a bit field of spv_binary_to_text_options_t. Decoded text will +// be stored into *text. Any error will be written into *diagnostic if +// diagnostic is non-null. +SPIRV_TOOLS_EXPORT spv_result_t spvBinaryToText(const spv_const_context context, + const uint32_t* binary, + const size_t word_count, + const uint32_t options, + spv_text* text, + spv_diagnostic* diagnostic); + +// Frees a binary stream from memory. This is a no-op if binary is a null +// pointer. +SPIRV_TOOLS_EXPORT void spvBinaryDestroy(spv_binary binary); + +// Validates a SPIR-V binary for correctness. Any errors will be written into +// *diagnostic if diagnostic is non-null. +SPIRV_TOOLS_EXPORT spv_result_t spvValidate(const spv_const_context context, + const spv_const_binary binary, + spv_diagnostic* diagnostic); + +// Validates a SPIR-V binary for correctness. Uses the provided Validator +// options. Any errors will be written into *diagnostic if diagnostic is +// non-null. +SPIRV_TOOLS_EXPORT spv_result_t spvValidateWithOptions( + const spv_const_context context, const spv_const_validator_options options, + const spv_const_binary binary, spv_diagnostic* diagnostic); + +// Validates a raw SPIR-V binary for correctness. Any errors will be written +// into *diagnostic if diagnostic is non-null. +SPIRV_TOOLS_EXPORT spv_result_t +spvValidateBinary(const spv_const_context context, const uint32_t* words, + const size_t num_words, spv_diagnostic* diagnostic); + +// Creates a diagnostic object. The position parameter specifies the location in +// the text/binary stream. The message parameter, copied into the diagnostic +// object, contains the error message to display. +SPIRV_TOOLS_EXPORT spv_diagnostic +spvDiagnosticCreate(const spv_position position, const char* message); + +// Destroys a diagnostic object. This is a no-op if diagnostic is a null +// pointer. +SPIRV_TOOLS_EXPORT void spvDiagnosticDestroy(spv_diagnostic diagnostic); + +// Prints the diagnostic to stderr. +SPIRV_TOOLS_EXPORT spv_result_t +spvDiagnosticPrint(const spv_diagnostic diagnostic); + +// The binary parser interface. + +// A pointer to a function that accepts a parsed SPIR-V header. +// The integer arguments are the 32-bit words from the header, as specified +// in SPIR-V 1.0 Section 2.3 Table 1. +// The function should return SPV_SUCCESS if parsing should continue. +typedef spv_result_t (*spv_parsed_header_fn_t)( + void* user_data, spv_endianness_t endian, uint32_t magic, uint32_t version, + uint32_t generator, uint32_t id_bound, uint32_t reserved); + +// A pointer to a function that accepts a parsed SPIR-V instruction. +// The parsed_instruction value is transient: it may be overwritten +// or released immediately after the function has returned. That also +// applies to the words array member of the parsed instruction. The +// function should return SPV_SUCCESS if and only if parsing should +// continue. +typedef spv_result_t (*spv_parsed_instruction_fn_t)( + void* user_data, const spv_parsed_instruction_t* parsed_instruction); + +// Parses a SPIR-V binary, specified as counted sequence of 32-bit words. +// Parsing feedback is provided via two callbacks provided as function +// pointers. Each callback function pointer can be a null pointer, in +// which case it is never called. Otherwise, in a valid parse the +// parsed-header callback is called once, and then the parsed-instruction +// callback once for each instruction in the stream. The user_data parameter +// is supplied as context to the callbacks. Returns SPV_SUCCESS on successful +// parse where the callbacks always return SPV_SUCCESS. For an invalid parse, +// returns a status code other than SPV_SUCCESS, and if diagnostic is non-null +// also emits a diagnostic. If a callback returns anything other than +// SPV_SUCCESS, then that status code is returned, no further callbacks are +// issued, and no additional diagnostics are emitted. +SPIRV_TOOLS_EXPORT spv_result_t spvBinaryParse( + const spv_const_context context, void* user_data, const uint32_t* words, + const size_t num_words, spv_parsed_header_fn_t parse_header, + spv_parsed_instruction_fn_t parse_instruction, spv_diagnostic* diagnostic); + +#ifdef __cplusplus +} +#endif + +#endif // SPIRV_TOOLS_LIBSPIRV_H_ diff --git a/3rdparty/spirv-tools/include/spirv-tools/libspirv.hpp b/3rdparty/spirv-tools/include/spirv-tools/libspirv.hpp new file mode 100644 index 000000000..2e4aa628d --- /dev/null +++ b/3rdparty/spirv-tools/include/spirv-tools/libspirv.hpp @@ -0,0 +1,174 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SPIRV_TOOLS_LIBSPIRV_HPP_ +#define SPIRV_TOOLS_LIBSPIRV_HPP_ + +#include +#include +#include +#include + +#include "spirv-tools/libspirv.h" + +namespace spvtools { + +// Message consumer. The C strings for source and message are only alive for the +// specific invocation. +using MessageConsumer = std::function; + +// C++ RAII wrapper around the C context object spv_context. +class Context { + public: + // Constructs a context targeting the given environment |env|. + // + // The constructed instance will have an empty message consumer, which just + // ignores all messages from the library. Use SetMessageConsumer() to supply + // one if messages are of concern. + explicit Context(spv_target_env env); + + // Enables move constructor/assignment operations. + Context(Context&& other); + Context& operator=(Context&& other); + + // Disables copy constructor/assignment operations. + Context(const Context&) = delete; + Context& operator=(const Context&) = delete; + + // Destructs this instance. + ~Context(); + + // 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); + + // Returns the underlying spv_context. + spv_context& CContext(); + const spv_context& CContext() const; + + private: + spv_context context_; +}; + +// A RAII wrapper around a validator options object. +class ValidatorOptions { + public: + ValidatorOptions() : options_(spvValidatorOptionsCreate()) {} + ~ValidatorOptions() { spvValidatorOptionsDestroy(options_); } + // Allow implicit conversion to the underlying object. + operator spv_validator_options() const { return options_; } + + // Sets a limit. + void SetUniversalLimit(spv_validator_limit limit_type, uint32_t limit) { + spvValidatorOptionsSetUniversalLimit(options_, limit_type, limit); + } + + void SetRelaxStructStore(bool val) { + spvValidatorOptionsSetRelaxStoreStruct(options_, val); + } + + // Records whether or not the validator should relax the rules on pointer + // usage in logical addressing mode. + // + // When relaxed, it will allow the following usage cases of pointers: + // 1) OpVariable allocating an object whose type is a pointer type + // 2) OpReturnValue returning a pointer value + void SetRelaxLogicalPointer(bool val) { + spvValidatorOptionsSetRelaxLogicalPointer(options_, val); + } + + private: + spv_validator_options options_; +}; + +// C++ interface for SPIRV-Tools functionalities. It wraps the context +// (including target environment and the corresponding SPIR-V grammar) and +// provides methods for assembling, disassembling, and validating. +// +// Instances of this class provide basic thread-safety guarantee. +class SpirvTools { + public: + enum { + // Default assembling option used by assemble(): + kDefaultAssembleOption = SPV_TEXT_TO_BINARY_OPTION_NONE, + + // Default disassembling option used by Disassemble(): + // * Avoid prefix comments from decoding the SPIR-V module header, and + // * Use friendly names for variables. + kDefaultDisassembleOption = SPV_BINARY_TO_TEXT_OPTION_NO_HEADER | + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES + }; + + // Constructs an instance targeting the given environment |env|. + // + // The constructed instance will have an empty message consumer, which just + // ignores all messages from the library. Use SetMessageConsumer() to supply + // one if messages are of concern. + explicit SpirvTools(spv_target_env env); + + // Disables copy/move constructor/assignment operations. + SpirvTools(const SpirvTools&) = delete; + SpirvTools(SpirvTools&&) = delete; + SpirvTools& operator=(const SpirvTools&) = delete; + SpirvTools& operator=(SpirvTools&&) = delete; + + // Destructs this instance. + ~SpirvTools(); + + // 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); + + // Assembles the given assembly |text| and writes the result to |binary|. + // Returns true on successful assembling. |binary| will be kept untouched if + // assembling is unsuccessful. + bool Assemble(const std::string& text, std::vector* binary, + uint32_t options = kDefaultAssembleOption) const; + // |text_size| specifies the number of bytes in |text|. A terminating null + // character is not required to present in |text| as long as |text| is valid. + bool Assemble(const char* text, size_t text_size, + std::vector* binary, + uint32_t options = kDefaultAssembleOption) const; + + // Disassembles the given SPIR-V |binary| with the given |options| and writes + // the assembly to |text|. Returns ture on successful disassembling. |text| + // will be kept untouched if diassembling is unsuccessful. + bool Disassemble(const std::vector& binary, std::string* text, + uint32_t options = kDefaultDisassembleOption) const; + // |binary_size| specifies the number of words in |binary|. + bool Disassemble(const uint32_t* binary, size_t binary_size, + std::string* text, + uint32_t options = kDefaultDisassembleOption) const; + + // Validates the given SPIR-V |binary|. Returns true if no issues are found. + // Otherwise, returns false and communicates issues via the message consumer + // registered. + bool Validate(const std::vector& binary) const; + // |binary_size| specifies the number of words in |binary|. + bool Validate(const uint32_t* binary, size_t binary_size) const; + // Like the previous overload, but takes an options object. + bool Validate(const uint32_t* binary, size_t binary_size, + const ValidatorOptions& options) const; + + private: + struct Impl; // Opaque struct for holding the data fields used by this class. + std::unique_ptr impl_; // Unique pointer to implementation data. +}; + +} // namespace spvtools + +#endif // SPIRV_TOOLS_LIBSPIRV_HPP_ diff --git a/3rdparty/spirv-tools/include/spirv-tools/linker.hpp b/3rdparty/spirv-tools/include/spirv-tools/linker.hpp new file mode 100644 index 000000000..cce78a445 --- /dev/null +++ b/3rdparty/spirv-tools/include/spirv-tools/linker.hpp @@ -0,0 +1,97 @@ +// Copyright (c) 2017 Pierre Moreau +// +// 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 SPIRV_TOOLS_LINKER_HPP_ +#define SPIRV_TOOLS_LINKER_HPP_ + +#include + +#include +#include + +#include "libspirv.hpp" + +namespace spvtools { + +class LinkerOptions { + public: + LinkerOptions() + : create_library_(false), + verify_ids_(false), + allow_partial_linkage_(false) {} + + // Returns whether a library or an executable should be produced by the + // linking phase. + // + // All exported symbols are kept when creating a library, whereas they will + // be removed when creating an executable. + // The returned value will be true if creating a library, and false if + // creating an executable. + bool GetCreateLibrary() const { return create_library_; } + + // Sets whether a library or an executable should be produced. + void SetCreateLibrary(bool create_library) { + create_library_ = create_library; + } + + // Returns whether to verify the uniqueness of the unique ids in the merged + // context. + bool GetVerifyIds() const { return verify_ids_; } + + // Sets whether to verify the uniqueness of the unique ids in the merged + // context. + void SetVerifyIds(bool verify_ids) { verify_ids_ = verify_ids; } + + // Returns whether to allow for imported symbols to have no corresponding + // exported symbols + bool GetAllowPartialLinkage() const { return allow_partial_linkage_; } + + // Sets whether to allow for imported symbols to have no corresponding + // exported symbols + void SetAllowPartialLinkage(bool allow_partial_linkage) { + allow_partial_linkage_ = allow_partial_linkage; + } + + private: + bool create_library_; + bool verify_ids_; + bool allow_partial_linkage_; +}; + +// Links one or more SPIR-V modules into a new SPIR-V module. That is, combine +// several SPIR-V modules into one, resolving link dependencies between them. +// +// At least one binary has to be provided in |binaries|. Those binaries do not +// have to be valid, but they should be at least parseable. +// The functions can fail due to the following: +// * The given context was not initialised using `spvContextCreate()`; +// * No input modules were given; +// * One or more of those modules were not parseable; +// * The input modules used different addressing or memory models; +// * The ID or global variable number limit were exceeded; +// * Some entry points were defined multiple times; +// * Some imported symbols did not have an exported counterpart; +// * Possibly other reasons. +spv_result_t Link(const Context& context, + const std::vector>& binaries, + std::vector* linked_binary, + const LinkerOptions& options = LinkerOptions()); +spv_result_t Link(const Context& context, const uint32_t* const* binaries, + const size_t* binary_sizes, size_t num_binaries, + std::vector* linked_binary, + const LinkerOptions& options = LinkerOptions()); + +} // namespace spvtools + +#endif // SPIRV_TOOLS_LINKER_HPP_ diff --git a/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp b/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp new file mode 100644 index 000000000..3a03988e2 --- /dev/null +++ b/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp @@ -0,0 +1,557 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SPIRV_TOOLS_OPTIMIZER_HPP_ +#define SPIRV_TOOLS_OPTIMIZER_HPP_ + +#include +#include +#include +#include +#include + +#include "libspirv.hpp" + +namespace spvtools { + +// C++ interface for SPIR-V optimization functionalities. It wraps the context +// (including target environment and the corresponding SPIR-V grammar) and +// provides methods for registering optimization passes and optimizing. +// +// Instances of this class provides basic thread-safety guarantee. +class Optimizer { + public: + // The token for an optimization pass. It is returned via one of the + // Create*Pass() standalone functions at the end of this header file and + // consumed by the RegisterPass() method. Tokens are one-time objects that + // only support move; copying is not allowed. + struct PassToken { + struct Impl; // Opaque struct for holding inernal data. + + PassToken(std::unique_ptr); + + // Tokens can only be moved. Copying is disabled. + PassToken(const PassToken&) = delete; + PassToken(PassToken&&); + PassToken& operator=(const PassToken&) = delete; + PassToken& operator=(PassToken&&); + + ~PassToken(); + + std::unique_ptr impl_; // Unique pointer to internal data. + }; + + // Constructs an instance with the given target |env|, which is used to decode + // the binaries to be optimized later. + // + // The constructed instance will have an empty message consumer, which just + // ignores all messages from the library. Use SetMessageConsumer() to supply + // one if messages are of concern. + explicit Optimizer(spv_target_env env); + + // Disables copy/move constructor/assignment operations. + Optimizer(const Optimizer&) = delete; + Optimizer(Optimizer&&) = delete; + Optimizer& operator=(const Optimizer&) = delete; + Optimizer& operator=(Optimizer&&) = delete; + + // Destructs this instance. + ~Optimizer(); + + // 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); + + // Registers the given |pass| to this optimizer. Passes will be run in the + // exact order of registration. The token passed in will be consumed by this + // method. + Optimizer& RegisterPass(PassToken&& pass); + + // Registers passes that attempt to improve performance of generated code. + // This sequence of passes is subject to constant review and will change + // from time to time. + Optimizer& RegisterPerformancePasses(); + + // Registers passes that attempt to improve the size of generated code. + // This sequence of passes is subject to constant review and will change + // from time to time. + Optimizer& RegisterSizePasses(); + + // Registers passes that attempt to legalize the generated code. + // + // Note: this recipe is specially for legalizing SPIR-V. It should be used + // by compilers after translating HLSL source code literally. It should + // *not* be used by general workloads for performance or size improvement. + // + // This sequence of passes is subject to constant review and will change + // from time to time. + Optimizer& RegisterLegalizationPasses(); + + // Optimizes the given SPIR-V module |original_binary| and writes the + // optimized binary into |optimized_binary|. + // Returns true on successful optimization, whether or not the module is + // modified. Returns false if errors occur when processing |original_binary| + // using any of the registered passes. In that case, no further passes are + // executed and the contents in |optimized_binary| may be invalid. + // + // It's allowed to alias |original_binary| to the start of |optimized_binary|. + bool Run(const uint32_t* original_binary, size_t original_binary_size, + std::vector* optimized_binary) const; + + // Returns a vector of strings with all the pass names added to this + // optimizer's pass manager. These strings are valid until the associated + // pass manager is destroyed. + std::vector GetPassNames() const; + + // Sets the option to print the disassembly before each pass and after the + // last pass. If |out| is null, then no output is generated. Otherwise, + // output is sent to the |out| output stream. + Optimizer& SetPrintAll(std::ostream* out); + + // Sets the option to print the resource utilization of each pass. If |out| + // is null, then no output is generated. Otherwise, output is sent to the + // |out| output stream. + Optimizer& SetTimeReport(std::ostream* out); + + private: + struct Impl; // Opaque struct for holding internal data. + std::unique_ptr impl_; // Unique pointer to internal data. +}; + +// Creates a null pass. +// A null pass does nothing to the SPIR-V module to be optimized. +Optimizer::PassToken CreateNullPass(); + +// Creates a strip-debug-info pass. +// A strip-debug-info pass removes all debug instructions (as documented in +// Section 3.32.2 of the SPIR-V spec) of the SPIR-V module to be optimized. +Optimizer::PassToken CreateStripDebugInfoPass(); + +// Creates a strip-reflect-info pass. +// A strip-reflect-info pass removes all reflections instructions. +// For now, this is limited to removing decorations defined in +// SPV_GOOGLE_hlsl_functionality1. The coverage may expand in +// the future. +Optimizer::PassToken CreateStripReflectInfoPass(); + +// Creates an eliminate-dead-functions pass. +// An eliminate-dead-functions pass will remove all functions that are not in +// the call trees rooted at entry points and exported functions. These +// functions are not needed because they will never be called. +Optimizer::PassToken CreateEliminateDeadFunctionsPass(); + +// Creates a set-spec-constant-default-value pass from a mapping from spec-ids +// to the default values in the form of string. +// A set-spec-constant-default-value pass sets the default values for the +// spec constants that have SpecId decorations (i.e., those defined by +// OpSpecConstant{|True|False} instructions). +Optimizer::PassToken CreateSetSpecConstantDefaultValuePass( + const std::unordered_map& id_value_map); + +// Creates a set-spec-constant-default-value pass from a mapping from spec-ids +// to the default values in the form of bit pattern. +// A set-spec-constant-default-value pass sets the default values for the +// spec constants that have SpecId decorations (i.e., those defined by +// OpSpecConstant{|True|False} instructions). +Optimizer::PassToken CreateSetSpecConstantDefaultValuePass( + const std::unordered_map>& id_value_map); + +// Creates a flatten-decoration pass. +// A flatten-decoration pass replaces grouped decorations with equivalent +// ungrouped decorations. That is, it replaces each OpDecorationGroup +// instruction and associated OpGroupDecorate and OpGroupMemberDecorate +// instructions with equivalent OpDecorate and OpMemberDecorate instructions. +// The pass does not attempt to preserve debug information for instructions +// it removes. +Optimizer::PassToken CreateFlattenDecorationPass(); + +// Creates a freeze-spec-constant-value pass. +// A freeze-spec-constant pass specializes the value of spec constants to +// their default values. This pass only processes the spec constants that have +// SpecId decorations (defined by OpSpecConstant, OpSpecConstantTrue, or +// OpSpecConstantFalse instructions) and replaces them with their normal +// counterparts (OpConstant, OpConstantTrue, or OpConstantFalse). The +// corresponding SpecId annotation instructions will also be removed. This +// pass does not fold the newly added normal constants and does not process +// other spec constants defined by OpSpecConstantComposite or +// OpSpecConstantOp. +Optimizer::PassToken CreateFreezeSpecConstantValuePass(); + +// Creates a fold-spec-constant-op-and-composite pass. +// A fold-spec-constant-op-and-composite pass folds spec constants defined by +// OpSpecConstantOp or OpSpecConstantComposite instruction, to normal Constants +// defined by OpConstantTrue, OpConstantFalse, OpConstant, OpConstantNull, or +// OpConstantComposite instructions. Note that spec constants defined with +// OpSpecConstant, OpSpecConstantTrue, or OpSpecConstantFalse instructions are +// not handled, as these instructions indicate their value are not determined +// and can be changed in future. A spec constant is foldable if all of its +// value(s) can be determined from the module. E.g., an integer spec constant +// defined with OpSpecConstantOp instruction can be folded if its value won't +// change later. This pass will replace the original OpSpecContantOp instruction +// with an OpConstant instruction. When folding composite spec constants, +// new instructions may be inserted to define the components of the composite +// constant first, then the original spec constants will be replaced by +// OpConstantComposite instructions. +// +// There are some operations not supported yet: +// OpSConvert, OpFConvert, OpQuantizeToF16 and +// all the operations under Kernel capability. +// TODO(qining): Add support for the operations listed above. +Optimizer::PassToken CreateFoldSpecConstantOpAndCompositePass(); + +// Creates a unify-constant pass. +// A unify-constant pass de-duplicates the constants. Constants with the exact +// same value and identical form will be unified and only one constant will +// be kept for each unique pair of type and value. +// There are several cases not handled by this pass: +// 1) Constants defined by OpConstantNull instructions (null constants) and +// constants defined by OpConstantFalse, OpConstant or OpConstantComposite +// with value 0 (zero-valued normal constants) are not considered equivalent. +// So null constants won't be used to replace zero-valued normal constants, +// vice versa. +// 2) Whenever there are decorations to the constant's result id id, the +// constant won't be handled, which means, it won't be used to replace any +// other constants, neither can other constants replace it. +// 3) NaN in float point format with different bit patterns are not unified. +Optimizer::PassToken CreateUnifyConstantPass(); + +// Creates a eliminate-dead-constant pass. +// A eliminate-dead-constant pass removes dead constants, including normal +// contants defined by OpConstant, OpConstantComposite, OpConstantTrue, or +// OpConstantFalse and spec constants defined by OpSpecConstant, +// OpSpecConstantComposite, OpSpecConstantTrue, OpSpecConstantFalse or +// OpSpecConstantOp. +Optimizer::PassToken CreateEliminateDeadConstantPass(); + +// Creates a strength-reduction pass. +// A strength-reduction pass will look for opportunities to replace an +// instruction with an equivalent and less expensive one. For example, +// multiplying by a power of 2 can be replaced by a bit shift. +Optimizer::PassToken CreateStrengthReductionPass(); + +// Creates a block merge pass. +// This pass searches for blocks with a single Branch to a block with no +// other predecessors and merges the blocks into a single block. Continue +// blocks and Merge blocks are not candidates for the second block. +// +// The pass is most useful after Dead Branch Elimination, which can leave +// such sequences of blocks. Merging them makes subsequent passes more +// effective, such as single block local store-load elimination. +// +// While this pass reduces the number of occurrences of this sequence, at +// this time it does not guarantee all such sequences are eliminated. +// +// Presence of phi instructions can inhibit this optimization. Handling +// these is left for future improvements. +Optimizer::PassToken CreateBlockMergePass(); + +// Creates an exhaustive inline pass. +// An exhaustive inline pass attempts to exhaustively inline all function +// calls in all functions in an entry point call tree. The intent is to enable, +// albeit through brute force, analysis and optimization across function +// calls by subsequent optimization passes. As the inlining is exhaustive, +// there is no attempt to optimize for size or runtime performance. Functions +// that are not in the call tree of an entry point are not changed. +Optimizer::PassToken CreateInlineExhaustivePass(); + +// Creates an opaque inline pass. +// An opaque inline pass inlines all function calls in all functions in all +// entry point call trees where the called function contains an opaque type +// in either its parameter types or return type. An opaque type is currently +// defined as Image, Sampler or SampledImage. The intent is to enable, albeit +// through brute force, analysis and optimization across these function calls +// by subsequent passes in order to remove the storing of opaque types which is +// not legal in Vulkan. Functions that are not in the call tree of an entry +// point are not changed. +Optimizer::PassToken CreateInlineOpaquePass(); + +// Creates a single-block local variable load/store elimination pass. +// For every entry point function, do single block memory optimization of +// function variables referenced only with non-access-chain loads and stores. +// For each targeted variable load, if previous store to that variable in the +// block, replace the load's result id with the value id of the store. +// If previous load within the block, replace the current load's result id +// with the previous load's result id. In either case, delete the current +// load. Finally, check if any remaining stores are useless, and delete store +// and variable if possible. +// +// The presence of access chain references and function calls can inhibit +// the above optimization. +// +// Only modules with relaxed logical addressing (see opt/instruction.h) are +// currently processed. +// +// This pass is most effective if preceeded by Inlining and +// LocalAccessChainConvert. This pass will reduce the work needed to be done +// by LocalSingleStoreElim and LocalMultiStoreElim. +// +// Only functions in the call tree of an entry point are processed. +Optimizer::PassToken CreateLocalSingleBlockLoadStoreElimPass(); + +// Create dead branch elimination pass. +// For each entry point function, this pass will look for SelectionMerge +// BranchConditionals with constant condition and convert to a Branch to +// the indicated label. It will delete resulting dead blocks. +// +// For all phi functions in merge block, replace all uses with the id +// corresponding to the living predecessor. +// +// Note that some branches and blocks may be left to avoid creating invalid +// control flow. Improving this is left to future work. +// +// This pass is most effective when preceeded by passes which eliminate +// local loads and stores, effectively propagating constant values where +// possible. +Optimizer::PassToken CreateDeadBranchElimPass(); + +// Creates an SSA local variable load/store elimination pass. +// For every entry point function, eliminate all loads and stores of function +// scope variables only referenced with non-access-chain loads and stores. +// Eliminate the variables as well. +// +// The presence of access chain references and function calls can inhibit +// the above optimization. +// +// Only shader modules with relaxed logical addressing (see opt/instruction.h) +// are currently processed. Currently modules with any extensions enabled are +// not processed. This is left for future work. +// +// This pass is most effective if preceeded by Inlining and +// LocalAccessChainConvert. LocalSingleStoreElim and LocalSingleBlockElim +// will reduce the work that this pass has to do. +Optimizer::PassToken CreateLocalMultiStoreElimPass(); + +// Creates a local access chain conversion pass. +// A local access chain conversion pass identifies all function scope +// variables which are accessed only with loads, stores and access chains +// with constant indices. It then converts all loads and stores of such +// variables into equivalent sequences of loads, stores, extracts and inserts. +// +// This pass only processes entry point functions. It currently only converts +// non-nested, non-ptr access chains. It does not process modules with +// non-32-bit integer types present. Optional memory access options on loads +// and stores are ignored as we are only processing function scope variables. +// +// This pass unifies access to these variables to a single mode and simplifies +// subsequent analysis and elimination of these variables along with their +// loads and stores allowing values to propagate to their points of use where +// possible. +Optimizer::PassToken CreateLocalAccessChainConvertPass(); + +// Creates a local single store elimination pass. +// For each entry point function, this pass eliminates loads and stores for +// function scope variable that are stored to only once, where possible. Only +// whole variable loads and stores are eliminated; access-chain references are +// not optimized. Replace all loads of such variables with the value that is +// stored and eliminate any resulting dead code. +// +// Currently, the presence of access chains and function calls can inhibit this +// pass, however the Inlining and LocalAccessChainConvert passes can make it +// more effective. In additional, many non-load/store memory operations are +// not supported and will prohibit optimization of a function. Support of +// these operations are future work. +// +// Only shader modules with relaxed logical addressing (see opt/instruction.h) +// are currently processed. +// +// This pass will reduce the work needed to be done by LocalSingleBlockElim +// and LocalMultiStoreElim and can improve the effectiveness of other passes +// such as DeadBranchElimination which depend on values for their analysis. +Optimizer::PassToken CreateLocalSingleStoreElimPass(); + +// Creates an insert/extract elimination pass. +// This pass processes each entry point function in the module, searching for +// extracts on a sequence of inserts. It further searches the sequence for an +// insert with indices identical to the extract. If such an insert can be +// found before hitting a conflicting insert, the extract's result id is +// replaced with the id of the values from the insert. +// +// Besides removing extracts this pass enables subsequent dead code elimination +// passes to delete the inserts. This pass performs best after access chains are +// converted to inserts and extracts and local loads and stores are eliminated. +Optimizer::PassToken CreateInsertExtractElimPass(); + +// Creates a dead insert elimination pass. +// This pass processes each entry point function in the module, searching for +// unreferenced inserts into composite types. These are most often unused +// stores to vector components. They are unused because they are never +// referenced, or because there is another insert to the same component between +// the insert and the reference. After removing the inserts, dead code +// elimination is attempted on the inserted values. +// +// This pass performs best after access chains are converted to inserts and +// extracts and local loads and stores are eliminated. While executing this +// pass can be advantageous on its own, it is also advantageous to execute +// this pass after CreateInsertExtractPass() as it will remove any unused +// inserts created by that pass. +Optimizer::PassToken CreateDeadInsertElimPass(); + +// Creates a pass to consolidate uniform references. +// For each entry point function in the module, first change all constant index +// access chain loads into equivalent composite extracts. Then consolidate +// identical uniform loads into one uniform load. Finally, consolidate +// identical uniform extracts into one uniform extract. This may require +// moving a load or extract to a point which dominates all uses. +// +// This pass requires a module to have structured control flow ie shader +// capability. It also requires logical addressing ie Addresses capability +// is not enabled. It also currently does not support any extensions. +// +// This pass currently only optimizes loads with a single index. +Optimizer::PassToken CreateCommonUniformElimPass(); + +// Create aggressive dead code elimination pass +// This pass eliminates unused code from the module. In addition, +// it detects and eliminates code which may have spurious uses but which do +// not contribute to the output of the function. The most common cause of +// such code sequences is summations in loops whose result is no longer used +// due to dead code elimination. This optimization has additional compile +// time cost over standard dead code elimination. +// +// This pass only processes entry point functions. It also only processes +// shaders with relaxed logical addressing (see opt/instruction.h). It +// currently will not process functions with function calls. Unreachable +// functions are deleted. +// +// This pass will be made more effective by first running passes that remove +// dead control flow and inlines function calls. +// +// This pass can be especially useful after running Local Access Chain +// Conversion, which tends to cause cycles of dead code to be left after +// Store/Load elimination passes are completed. These cycles cannot be +// eliminated with standard dead code elimination. +Optimizer::PassToken CreateAggressiveDCEPass(); + +// Creates a compact ids pass. +// The pass remaps result ids to a compact and gapless range starting from %1. +Optimizer::PassToken CreateCompactIdsPass(); + +// Creates a remove duplicate pass. +// This pass removes various duplicates: +// * duplicate capabilities; +// * duplicate extended instruction imports; +// * duplicate types; +// * duplicate decorations. +Optimizer::PassToken CreateRemoveDuplicatesPass(); + +// Creates a CFG cleanup pass. +// This pass removes cruft from the control flow graph of functions that are +// reachable from entry points and exported functions. It currently includes the +// following functionality: +// +// - Removal of unreachable basic blocks. +Optimizer::PassToken CreateCFGCleanupPass(); + +// Create dead variable elimination pass. +// This pass will delete module scope variables, along with their decorations, +// that are not referenced. +Optimizer::PassToken CreateDeadVariableEliminationPass(); + +// create merge return pass. +// changes functions that have multiple return statements so they have a single +// return statement. +// +// for structured control flow it is assumed that the only unreachable blocks in +// the function are trivial merge and continue blocks. +// +// a trivial merge block contains the label and an opunreachable instructions, +// nothing else. a trivial continue block contain a label and an opbranch to +// the header, nothing else. +// +// these conditions are guaranteed to be met after running dead-branch +// elimination. +Optimizer::PassToken CreateMergeReturnPass(); + +// Create value numbering pass. +// This pass will look for instructions in the same basic block that compute the +// same value, and remove the redundant ones. +Optimizer::PassToken CreateLocalRedundancyEliminationPass(); + +// Create LICM pass. +// This pass will look for invariant instructions inside loops and hoist them to +// the loops preheader. +Optimizer::PassToken CreateLoopInvariantCodeMotionPass(); + +// Creates a loop unswitch pass. +// This pass will look for loop independent branch conditions and move the +// condition out of the loop and version the loop based on the taken branch. +// Works best after LICM and local multi store elimination pass. +Optimizer::PassToken CreateLoopUnswitchPass(); + +// Create global value numbering pass. +// This pass will look for instructions where the same value is computed on all +// paths leading to the instruction. Those instructions are deleted. +Optimizer::PassToken CreateRedundancyEliminationPass(); + +// Create scalar replacement pass. +// This pass replaces composite function scope variables with variables for each +// element if those elements are accessed individually. +Optimizer::PassToken CreateScalarReplacementPass(); + +// Create a private to local pass. +// This pass looks for variables delcared in the private storage class that are +// used in only one function. Those variables are moved to the function storage +// class in the function that they are used. +Optimizer::PassToken CreatePrivateToLocalPass(); + +// Creates a conditional constant propagation (CCP) pass. +// This pass implements the SSA-CCP algorithm in +// +// Constant propagation with conditional branches, +// Wegman and Zadeck, ACM TOPLAS 13(2):181-210. +// +// Constant values in expressions and conditional jumps are folded and +// simplified. This may reduce code size by removing never executed jump targets +// and computations with constant operands. +Optimizer::PassToken CreateCCPPass(); + +// Creates a workaround driver bugs pass. This pass attempts to work around +// a known driver bug (issue #1209) by identifying the bad code sequences and +// rewriting them. +// +// Current workaround: Avoid OpUnreachable instructions in loops. +Optimizer::PassToken CreateWorkaround1209Pass(); + +// Creates a pass that converts if-then-else like assignments into OpSelect. +Optimizer::PassToken CreateIfConversionPass(); + +// Creates a pass that will replace instructions that are not valid for the +// current shader stage by constants. Has no effect on non-shader modules. +Optimizer::PassToken CreateReplaceInvalidOpcodePass(); + +// Creates a pass that simplifies instructions using the instruction folder. +Optimizer::PassToken CreateSimplificationPass(); + +// Create loop unroller pass. +// Creates a pass to unroll loops which have the "Unroll" loop control +// mask set. The loops must meet a specific criteria in order to be unrolled +// safely this criteria is checked before doing the unroll by the +// LoopUtils::CanPerformUnroll method. Any loop that does not meet the criteria +// won't be unrolled. See CanPerformUnroll LoopUtils.h for more information. +Optimizer::PassToken CreateLoopUnrollPass(bool fully_unroll, int factor = 0); + +// Create the SSA rewrite pass. +// This pass converts load/store operations on function local variables into +// operations on SSA IDs. This allows SSA optimizers to act on these variables. +// Only variables that are local to the function and of supported types are +// processed (see IsSSATargetVar for details). +Optimizer::PassToken CreateSSARewritePass(); + +// Create copy propagate arrays pass. +// This pass looks to copy propagate memory references for arrays. It looks +// for specific code patterns to recognize array copies. +Optimizer::PassToken CreateCopyPropagateArraysPass(); +} // namespace spvtools + +#endif // SPIRV_TOOLS_OPTIMIZER_HPP_ diff --git a/3rdparty/spirv-tools/projects.md b/3rdparty/spirv-tools/projects.md new file mode 100644 index 000000000..8f7f0bcd9 --- /dev/null +++ b/3rdparty/spirv-tools/projects.md @@ -0,0 +1,82 @@ +# Tracking SPIRV-Tools work with GitHub projects + +We are experimenting with using the [GitHub Project +feature](https://help.github.com/articles/tracking-the-progress-of-your-work-with-projects/) +to track progress toward large goals. + +For more on GitHub Projects in general, see: +* [Introductory blog post](https://github.com/blog/2256-a-whole-new-github-universe-announcing-new-tools-forums-and-features) +* [Introductory video](https://www.youtube.com/watch?v=C6MGKHkNtxU) + +The current SPIRV-Tools project list can be found at +[https://github.com/KhronosGroup/SPIRV-Tools/projects](https://github.com/KhronosGroup/SPIRV-Tools/projects) + +## How we use a Project + +A GitHub Project is a set of work with an overall purpose, and +consists of a collection of *Cards*. +Each card is either a *Note* or a regular GitHub *Issue.* +A Note can be converted to an Issue. + +In our projects, a card represents work, i.e. a change that can +be applied to the repository. +The work could be a feature, a bug to be fixed, documentation to be +updated, etc. + +A project and its cards are used as a [Kanban +board](https://en.wikipedia.org/wiki/Kanban_board), where cards progress +through a workflow starting with ideas through to implementation and completion. + +In our usage, a *project manager* is someone who organizes the work. +They manage the creation and movement of cards +through the project workflow: +* They create cards to capture ideas, or to decompose large ideas into smaller + ones. +* They determine if the work for a card has been completed. +* Normally they are the person (or persons) who can approve and merge a pull + request into the `master` branch. + +Our projects organize cards into the following columns: +* `Ideas`: Work which could be done, captured either as Cards or Notes. + * A card in this column could be marked as a [PLACEHOLDER](#placeholders). +* `Ready to start`: Issues which represent work we'd like to do, and which + are not blocked by other work. + * The issue should be narrow enough that it can usually be addressed by a + single pull request. + * We want these to be Issues (not Notes) so that someone can claim the work + by updating the Issue with their intent to do the work. + Once an Issue is claimed, the project manager moves the corresponding card + from `Ready to start` to `In progress`. +* `In progress`: Issues which were in `Ready to start` but which have been + claimed by someone. +* `Done`: Issues which have been resolved, by completing their work. + * The changes have been applied to the repository, typically by being pushed + into the `master` branch. + * Other kinds of work could update repository settings, for example. +* `Rejected ideas`: Work which has been considered, but which we don't want + implemented. + * We keep rejected ideas so they are not proposed again. This serves + as a form of institutional memory. + * We should record why an idea is rejected. For this reason, a rejected + idea is likely to be an Issue which has been closed. + +## Prioritization + +We are considering prioritizing cards in the `Ideas` and `Ready to start` +columns so that things that should be considered first float up to the top. + +Experience will tell us if we stick to that rule, and if it proves helpful. + +## Placeholders + +A *placeholder* is a Note or Issue that represents a possibly large amount +of work that can be broadly defined but which may not have been broken down +into small implementable pieces of work. + +Use a placeholder to capture a big idea, but without doing the upfront work +to consider all the details of how it should be implemented. +Over time, break off pieces of the placeholder into implementable Issues. +Move those Issues into the `Ready to start` column when they become unblocked. + +We delete the placeholder when all its work has been decomposed into +implementable cards. diff --git a/3rdparty/spirv-tools/source/CMakeLists.txt b/3rdparty/spirv-tools/source/CMakeLists.txt new file mode 100644 index 000000000..e9444750a --- /dev/null +++ b/3rdparty/spirv-tools/source/CMakeLists.txt @@ -0,0 +1,362 @@ +# Copyright (c) 2015-2016 The Khronos Group Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(GRAMMAR_PROCESSING_SCRIPT "${spirv-tools_SOURCE_DIR}/utils/generate_grammar_tables.py") +set(VIMSYNTAX_PROCESSING_SCRIPT "${spirv-tools_SOURCE_DIR}/utils/generate_vim_syntax.py") +set(XML_REGISTRY_PROCESSING_SCRIPT "${spirv-tools_SOURCE_DIR}/utils/generate_registry_tables.py") +set(LANG_HEADER_PROCESSING_SCRIPT "${spirv-tools_SOURCE_DIR}/utils/generate_language_headers.py") + +# For now, assume the DebugInfo grammar file is in the current directory. +# It might migrate to SPIRV-Headers. +set(DEBUGINFO_GRAMMAR_JSON_FILE "${CMAKE_CURRENT_SOURCE_DIR}/extinst.debuginfo.grammar.json") + +# macro() definitions are used in the following because we need to append .inc +# file paths into some global lists (*_CPP_DEPENDS). And those global lists are +# later used by set_source_files_properties() calls. +# function() definitions are not suitable because they create new scopes. +macro(spvtools_core_tables VERSION) + set(GRAMMAR_JSON_FILE "${SPIRV_HEADER_INCLUDE_DIR}/spirv/${VERSION}/spirv.core.grammar.json") + set(GRAMMAR_INSTS_INC_FILE "${spirv-tools_BINARY_DIR}/core.insts-${VERSION}.inc") + set(GRAMMAR_KINDS_INC_FILE "${spirv-tools_BINARY_DIR}/operand.kinds-${VERSION}.inc") + add_custom_command(OUTPUT ${GRAMMAR_INSTS_INC_FILE} ${GRAMMAR_KINDS_INC_FILE} + COMMAND ${PYTHON_EXECUTABLE} ${GRAMMAR_PROCESSING_SCRIPT} + --spirv-core-grammar=${GRAMMAR_JSON_FILE} + --extinst-debuginfo-grammar=${DEBUGINFO_GRAMMAR_JSON_FILE} + --core-insts-output=${GRAMMAR_INSTS_INC_FILE} + --operand-kinds-output=${GRAMMAR_KINDS_INC_FILE} + DEPENDS ${GRAMMAR_PROCESSING_SCRIPT} ${GRAMMAR_JSON_FILE} ${DEBUGINFO_GRAMMAR_JSON_FILE} + COMMENT "Generate info tables for SPIR-V v${VERSION} core instructions and operands.") + list(APPEND OPCODE_CPP_DEPENDS ${GRAMMAR_INSTS_INC_FILE}) + list(APPEND OPERAND_CPP_DEPENDS ${GRAMMAR_KINDS_INC_FILE}) +endmacro(spvtools_core_tables) + +macro(spvtools_enum_string_mapping VERSION) + set(GRAMMAR_JSON_FILE "${SPIRV_HEADER_INCLUDE_DIR}/spirv/${VERSION}/spirv.core.grammar.json") + set(GRAMMAR_EXTENSION_ENUM_INC_FILE "${spirv-tools_BINARY_DIR}/extension_enum.inc") + set(GRAMMAR_ENUM_STRING_MAPPING_INC_FILE "${spirv-tools_BINARY_DIR}/enum_string_mapping.inc") + add_custom_command(OUTPUT ${GRAMMAR_EXTENSION_ENUM_INC_FILE} + ${GRAMMAR_ENUM_STRING_MAPPING_INC_FILE} + COMMAND ${PYTHON_EXECUTABLE} ${GRAMMAR_PROCESSING_SCRIPT} + --spirv-core-grammar=${GRAMMAR_JSON_FILE} + --extinst-debuginfo-grammar=${DEBUGINFO_GRAMMAR_JSON_FILE} + --extension-enum-output=${GRAMMAR_EXTENSION_ENUM_INC_FILE} + --enum-string-mapping-output=${GRAMMAR_ENUM_STRING_MAPPING_INC_FILE} + DEPENDS ${GRAMMAR_PROCESSING_SCRIPT} ${GRAMMAR_JSON_FILE} ${DEBUGINFO_GRAMMAR_JSON_FILE} + COMMENT "Generate enum-string mapping for SPIR-V v${VERSION}.") + list(APPEND EXTENSION_H_DEPENDS ${GRAMMAR_EXTENSION_ENUM_INC_FILE}) + list(APPEND ENUM_STRING_MAPPING_CPP_DEPENDS ${GRAMMAR_ENUM_STRING_MAPPING_INC_FILE}) +endmacro(spvtools_enum_string_mapping) + +macro(spvtools_vimsyntax VERSION CLVERSION) + set(GRAMMAR_JSON_FILE "${SPIRV_HEADER_INCLUDE_DIR}/spirv/${VERSION}/spirv.core.grammar.json") + set(GLSL_GRAMMAR_JSON_FILE "${SPIRV_HEADER_INCLUDE_DIR}/spirv/${VERSION}/extinst.glsl.std.450.grammar.json") + set(OPENCL_GRAMMAR_JSON_FILE "${SPIRV_HEADER_INCLUDE_DIR}/spirv/${VERSION}/extinst.opencl.std.100.grammar.json") + set(VIMSYNTAX_FILE "${spirv-tools_BINARY_DIR}/spvasm.vim") + add_custom_command(OUTPUT ${VIMSYNTAX_FILE} + COMMAND ${PYTHON_EXECUTABLE} ${VIMSYNTAX_PROCESSING_SCRIPT} + --spirv-core-grammar=${GRAMMAR_JSON_FILE} + --extinst-debuginfo-grammar=${DEBUGINFO_GRAMMAR_JSON_FILE} + --extinst-glsl-grammar=${GLSL_GRAMMAR_JSON_FILE} + --extinst-opencl-grammar=${OPENCL_GRAMMAR_JSON_FILE} + >${VIMSYNTAX_FILE} + DEPENDS ${VIMSYNTAX_PROCESSING_SCRIPT} ${GRAMMAR_JSON_FILE} + ${GLSL_GRAMMAR_JSON_FILE} ${OPENCL_GRAMMAR_JSON_FILE} ${DEBUGINFO_GRAMMAR_JSON_FILE} + COMMENT "Generate spvasm.vim: Vim syntax file for SPIR-V assembly.") +endmacro(spvtools_vimsyntax) + +macro(spvtools_glsl_tables VERSION) + set(CORE_GRAMMAR_JSON_FILE "${SPIRV_HEADER_INCLUDE_DIR}/spirv/${VERSION}/spirv.core.grammar.json") + set(GLSL_GRAMMAR_JSON_FILE "${SPIRV_HEADER_INCLUDE_DIR}/spirv/${VERSION}/extinst.glsl.std.450.grammar.json") + set(GRAMMAR_INC_FILE "${spirv-tools_BINARY_DIR}/glsl.std.450.insts.inc") + add_custom_command(OUTPUT ${GRAMMAR_INC_FILE} + COMMAND ${PYTHON_EXECUTABLE} ${GRAMMAR_PROCESSING_SCRIPT} + --extinst-glsl-grammar=${GLSL_GRAMMAR_JSON_FILE} + --glsl-insts-output=${GRAMMAR_INC_FILE} + DEPENDS ${GRAMMAR_PROCESSING_SCRIPT} ${CORE_GRAMMAR_JSON_FILE} ${GLSL_GRAMMAR_JSON_FILE} + COMMENT "Generate info tables for GLSL extended instructions and operands v${VERSION}.") + list(APPEND EXTINST_CPP_DEPENDS ${GRAMMAR_INC_FILE}) +endmacro(spvtools_glsl_tables) + +macro(spvtools_opencl_tables VERSION) + set(CORE_GRAMMAR_JSON_FILE "${SPIRV_HEADER_INCLUDE_DIR}/spirv/${VERSION}/spirv.core.grammar.json") + set(OPENCL_GRAMMAR_JSON_FILE "${SPIRV_HEADER_INCLUDE_DIR}/spirv/${VERSION}/extinst.opencl.std.100.grammar.json") + set(GRAMMAR_INC_FILE "${spirv-tools_BINARY_DIR}/opencl.std.insts.inc") + add_custom_command(OUTPUT ${GRAMMAR_INC_FILE} + COMMAND ${PYTHON_EXECUTABLE} ${GRAMMAR_PROCESSING_SCRIPT} + --extinst-opencl-grammar=${OPENCL_GRAMMAR_JSON_FILE} + --opencl-insts-output=${GRAMMAR_INC_FILE} + DEPENDS ${GRAMMAR_PROCESSING_SCRIPT} ${CORE_GRAMMAR_JSON_FILE} ${OPENCL_GRAMMAR_JSON_FILE} + COMMENT "Generate info tables for OpenCL extended instructions and operands v${VERSION}.") + list(APPEND EXTINST_CPP_DEPENDS ${GRAMMAR_INC_FILE}) +endmacro(spvtools_opencl_tables) + +macro(spvtools_vendor_tables VENDOR_TABLE) + set(INSTS_FILE "${spirv-tools_BINARY_DIR}/${VENDOR_TABLE}.insts.inc") + set(GRAMMAR_FILE "${spirv-tools_SOURCE_DIR}/source/extinst.${VENDOR_TABLE}.grammar.json") + add_custom_command(OUTPUT ${INSTS_FILE} + COMMAND ${PYTHON_EXECUTABLE} ${GRAMMAR_PROCESSING_SCRIPT} + --extinst-vendor-grammar=${GRAMMAR_FILE} + --vendor-insts-output=${INSTS_FILE} + DEPENDS ${GRAMMAR_PROCESSING_SCRIPT} ${GRAMMAR_FILE} + COMMENT "Generate extended instruction tables for ${VENDOR_TABLE}.") + list(APPEND EXTINST_CPP_DEPENDS ${INSTS_FILE}) + add_custom_target(spirv-tools-${VENDOR_TABLE} DEPENDS ${INSTS_FILE}) + set_property(TARGET spirv-tools-${VENDOR_TABLE} PROPERTY FOLDER "SPIRV-Tools build") +endmacro(spvtools_vendor_tables) + +macro(spvtools_extinst_lang_headers NAME GRAMMAR_FILE) + set(OUTBASE ${spirv-tools_BINARY_DIR}/${NAME}) + set(OUT_H ${OUTBASE}.h) + add_custom_command(OUTPUT ${OUT_H} + COMMAND ${PYTHON_EXECUTABLE} ${LANG_HEADER_PROCESSING_SCRIPT} + --extinst-name=${NAME} + --extinst-grammar=${GRAMMAR_FILE} + --extinst-output-base=${OUTBASE} + DEPENDS ${LANG_HEADER_PROCESSING_SCRIPT} ${GRAMMAR_FILE} + COMMENT "Generate language specific header for ${NAME}.") + list(APPEND EXTINST_CPP_DEPENDS ${OUT_H}) + add_custom_target(spirv-tools-header-${NAME} DEPENDS ${OUT_H}) + set_property(TARGET spirv-tools-header-${NAME} PROPERTY FOLDER "SPIRV-Tools build") +endmacro(spvtools_extinst_lang_headers) + +spvtools_core_tables("unified1") +spvtools_enum_string_mapping("unified1") +spvtools_opencl_tables("unified1") +spvtools_glsl_tables("unified1") +spvtools_vendor_tables("spv-amd-shader-explicit-vertex-parameter") +spvtools_vendor_tables("spv-amd-shader-trinary-minmax") +spvtools_vendor_tables("spv-amd-gcn-shader") +spvtools_vendor_tables("spv-amd-shader-ballot") +spvtools_vendor_tables("debuginfo") +spvtools_extinst_lang_headers("DebugInfo" ${DEBUGINFO_GRAMMAR_JSON_FILE}) + +spvtools_vimsyntax("unified1" "1.0") +add_custom_target(spirv-tools-vimsyntax DEPENDS ${VIMSYNTAX_FILE}) +set_property(TARGET spirv-tools-vimsyntax PROPERTY FOLDER "SPIRV-Tools utilities") + +# Extract the list of known generators from the SPIR-V XML registry file. +set(GENERATOR_INC_FILE ${spirv-tools_BINARY_DIR}/generators.inc) +set(SPIRV_XML_REGISTRY_FILE ${SPIRV_HEADER_INCLUDE_DIR}/spirv/spir-v.xml) +add_custom_command(OUTPUT ${GENERATOR_INC_FILE} + COMMAND ${PYTHON_EXECUTABLE} ${XML_REGISTRY_PROCESSING_SCRIPT} + --xml=${SPIRV_XML_REGISTRY_FILE} + --generator-output=${GENERATOR_INC_FILE} + DEPENDS ${XML_REGISTRY_PROCESSING_SCRIPT} ${SPIRV_XML_REGISTRY_FILE} + COMMENT "Generate tables based on the SPIR-V XML registry.") +list(APPEND OPCODE_CPP_DEPENDS ${GENERATOR_INC_FILE}) + +# The following .cpp files include the above generated .inc files. +# Add those .inc files as their dependencies. +# +# Why using such an awkward way? +# * If we use add_custom_target() to define a target to generate all .inc files +# and let ${SPIRV_TOOLS} depend on it, then we need to run ninja twice every +# time the grammar is updated: the first time is for generating those .inc +# files, and the second time is for rebuilding .cpp files, when ninja finds +# out that .inc files are updated. +# * If we use add_custom_command() with PRE_BUILD, then the grammar processing +# script will always run no matter whether the grammar is updated. +# * add_dependencies() is used to add *target* dependencies to a target. +# * The following solution only generates .inc files when the script or the +# grammar files is updated, and in a single ninja run. +set_source_files_properties( + ${CMAKE_CURRENT_SOURCE_DIR}/opcode.cpp + PROPERTIES OBJECT_DEPENDS "${OPCODE_CPP_DEPENDS}") +set_source_files_properties( + ${CMAKE_CURRENT_SOURCE_DIR}/operand.cpp + PROPERTIES OBJECT_DEPENDS "${OPERAND_CPP_DEPENDS}") +set_source_files_properties( + ${CMAKE_CURRENT_SOURCE_DIR}/ext_inst.cpp + PROPERTIES OBJECT_DEPENDS "${EXTINST_CPP_DEPENDS}") +set_source_files_properties( + ${CMAKE_CURRENT_SOURCE_DIR}/enum_string_mapping.cpp + PROPERTIES OBJECT_DEPENDS "${ENUM_STRING_MAPPING_CPP_DEPENDS}") + +set_source_files_properties( + ${CMAKE_CURRENT_SOURCE_DIR}/extension.h + PROPERTIES HEADER_FILE_ONLY TRUE) +set_source_files_properties( + ${CMAKE_CURRENT_SOURCE_DIR}/extension.h + PROPERTIES OBJECT_DEPENDS "${EXTENSION_H_DEPENDS}") + +set(SPIRV_TOOLS_BUILD_VERSION_INC + ${spirv-tools_BINARY_DIR}/build-version.inc) +set(SPIRV_TOOLS_BUILD_VERSION_INC_GENERATOR + ${spirv-tools_SOURCE_DIR}/utils/update_build_version.py) +set(SPIRV_TOOLS_CHANGES_FILE + ${spirv-tools_SOURCE_DIR}/CHANGES) +add_custom_command(OUTPUT ${SPIRV_TOOLS_BUILD_VERSION_INC} + COMMAND ${PYTHON_EXECUTABLE} + ${SPIRV_TOOLS_BUILD_VERSION_INC_GENERATOR} + ${spirv-tools_SOURCE_DIR} ${SPIRV_TOOLS_BUILD_VERSION_INC} + DEPENDS ${SPIRV_TOOLS_BUILD_VERSION_INC_GENERATOR} + ${SPIRV_TOOLS_CHANGES_FILE} + COMMENT "Update build-version.inc in the SPIRV-Tools build directory (if necessary).") +# Convenience target for standalone generation of the build-version.inc file. +# This is not required for any dependence chain. +add_custom_target(spirv-tools-build-version + DEPENDS ${SPIRV_TOOLS_BUILD_VERSION_INC}) +set_property(TARGET spirv-tools-build-version PROPERTY FOLDER "SPIRV-Tools build") + +add_subdirectory(comp) +add_subdirectory(opt) +add_subdirectory(link) + +set(SPIRV_SOURCES + ${spirv-tools_SOURCE_DIR}/include/spirv-tools/libspirv.h + + ${CMAKE_CURRENT_SOURCE_DIR}/util/bitutils.h + ${CMAKE_CURRENT_SOURCE_DIR}/util/bit_stream.h + ${CMAKE_CURRENT_SOURCE_DIR}/util/hex_float.h + ${CMAKE_CURRENT_SOURCE_DIR}/util/parse_number.h + ${CMAKE_CURRENT_SOURCE_DIR}/util/string_utils.h + ${CMAKE_CURRENT_SOURCE_DIR}/util/timer.h + ${CMAKE_CURRENT_SOURCE_DIR}/assembly_grammar.h + ${CMAKE_CURRENT_SOURCE_DIR}/binary.h + ${CMAKE_CURRENT_SOURCE_DIR}/cfa.h + ${CMAKE_CURRENT_SOURCE_DIR}/diagnostic.h + ${CMAKE_CURRENT_SOURCE_DIR}/disassemble.h + ${CMAKE_CURRENT_SOURCE_DIR}/enum_set.h + ${CMAKE_CURRENT_SOURCE_DIR}/enum_string_mapping.h + ${CMAKE_CURRENT_SOURCE_DIR}/ext_inst.h + ${CMAKE_CURRENT_SOURCE_DIR}/extensions.h + ${CMAKE_CURRENT_SOURCE_DIR}/id_descriptor.h + ${CMAKE_CURRENT_SOURCE_DIR}/instruction.h + ${CMAKE_CURRENT_SOURCE_DIR}/latest_version_glsl_std_450_header.h + ${CMAKE_CURRENT_SOURCE_DIR}/latest_version_opencl_std_header.h + ${CMAKE_CURRENT_SOURCE_DIR}/latest_version_spirv_header.h + ${CMAKE_CURRENT_SOURCE_DIR}/macro.h + ${CMAKE_CURRENT_SOURCE_DIR}/name_mapper.h + ${CMAKE_CURRENT_SOURCE_DIR}/opcode.h + ${CMAKE_CURRENT_SOURCE_DIR}/operand.h + ${CMAKE_CURRENT_SOURCE_DIR}/parsed_operand.h + ${CMAKE_CURRENT_SOURCE_DIR}/print.h + ${CMAKE_CURRENT_SOURCE_DIR}/spirv_constant.h + ${CMAKE_CURRENT_SOURCE_DIR}/spirv_definition.h + ${CMAKE_CURRENT_SOURCE_DIR}/spirv_endian.h + ${CMAKE_CURRENT_SOURCE_DIR}/spirv_target_env.h + ${CMAKE_CURRENT_SOURCE_DIR}/spirv_validator_options.h + ${CMAKE_CURRENT_SOURCE_DIR}/table.h + ${CMAKE_CURRENT_SOURCE_DIR}/text.h + ${CMAKE_CURRENT_SOURCE_DIR}/text_handler.h + ${CMAKE_CURRENT_SOURCE_DIR}/validate.h + + ${CMAKE_CURRENT_SOURCE_DIR}/util/bit_stream.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/util/parse_number.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/util/string_utils.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/assembly_grammar.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/binary.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/diagnostic.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/disassemble.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/enum_string_mapping.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ext_inst.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/extensions.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/id_descriptor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/libspirv.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/message.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/name_mapper.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/opcode.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/operand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/parsed_operand.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/print.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/software_version.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/spirv_endian.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/spirv_stats.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/spirv_target_env.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/spirv_validator_options.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/table.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/text.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/text_handler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/validate.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/validate_adjacency.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/validate_arithmetics.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/validate_atomics.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/validate_barriers.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/validate_bitwise.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/validate_builtins.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/validate_capability.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/validate_cfg.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/validate_composites.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/validate_conversion.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/validate_datarules.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/validate_decorations.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/validate_derivatives.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/validate_ext_inst.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/validate_id.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/validate_image.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/validate_instruction.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/validate_layout.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/validate_literals.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/validate_logicals.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/validate_primitives.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/validate_type_unique.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/val/decoration.h + ${CMAKE_CURRENT_SOURCE_DIR}/val/basic_block.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/val/construct.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/val/function.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/val/instruction.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/val/validation_state.cpp) + +if (${SPIRV_TIMER_ENABLED}) + set(SPIRV_SOURCES + ${SPIRV_SOURCES} + ${CMAKE_CURRENT_SOURCE_DIR}/util/timer.cpp) +endif() + +# The software_version.cpp file includes build-version.inc. +# Rebuild the software_version.cpp object file if it is older than +# build-version.inc or whenever build-version.inc itself is out of +# date. In the latter case, rebuild build-version.inc first. +# CMake is not smart enough to detect this dependency automatically. +# Without this, the dependency detection system for #included files +# does not kick in on a clean build for the following reason: The +# build will fail early because it doesn't know how to build the +# missing source file build-version.inc. That occurs before the +# preprocessor is run on software_version.cpp to detect the +# #include dependency. +set_source_files_properties( + ${CMAKE_CURRENT_SOURCE_DIR}/software_version.cpp + PROPERTIES OBJECT_DEPENDS "${SPIRV_TOOLS_BUILD_VERSION_INC}") + +add_library(${SPIRV_TOOLS} ${SPIRV_SOURCES}) +spvtools_default_compile_options(${SPIRV_TOOLS}) +target_include_directories(${SPIRV_TOOLS} + PUBLIC ${spirv-tools_SOURCE_DIR}/include + PRIVATE ${spirv-tools_BINARY_DIR} + PRIVATE ${SPIRV_HEADER_INCLUDE_DIR} + ) +set_property(TARGET ${SPIRV_TOOLS} PROPERTY FOLDER "SPIRV-Tools libraries") +spvtools_check_symbol_exports(${SPIRV_TOOLS}) + +add_library(${SPIRV_TOOLS}-shared SHARED ${SPIRV_SOURCES}) +spvtools_default_compile_options(${SPIRV_TOOLS}-shared) +target_include_directories(${SPIRV_TOOLS}-shared + PUBLIC ${spirv-tools_SOURCE_DIR}/include + PRIVATE ${spirv-tools_BINARY_DIR} + PRIVATE ${SPIRV_HEADER_INCLUDE_DIR} + ) +set_target_properties(${SPIRV_TOOLS}-shared PROPERTIES CXX_VISIBILITY_PRESET hidden) +set_property(TARGET ${SPIRV_TOOLS}-shared PROPERTY FOLDER "SPIRV-Tools libraries") +spvtools_check_symbol_exports(${SPIRV_TOOLS}-shared) +target_compile_definitions(${SPIRV_TOOLS}-shared + PRIVATE SPIRV_TOOLS_IMPLEMENTATION + PUBLIC SPIRV_TOOLS_SHAREDLIB +) + +if(ENABLE_SPIRV_TOOLS_INSTALL) + install(TARGETS ${SPIRV_TOOLS} ${SPIRV_TOOLS}-shared + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) +endif(ENABLE_SPIRV_TOOLS_INSTALL) diff --git a/3rdparty/spirv-tools/source/assembly_grammar.cpp b/3rdparty/spirv-tools/source/assembly_grammar.cpp new file mode 100644 index 000000000..2fde64053 --- /dev/null +++ b/3rdparty/spirv-tools/source/assembly_grammar.cpp @@ -0,0 +1,263 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "assembly_grammar.h" + +#include +#include +#include + +#include "ext_inst.h" +#include "opcode.h" +#include "operand.h" +#include "table.h" + +namespace { + +/// @brief Parses a mask expression string for the given operand type. +/// +/// A mask expression is a sequence of one or more terms separated by '|', +/// where each term a named enum value for the given type. No whitespace +/// is permitted. +/// +/// On success, the value is written to pValue. +/// +/// @param[in] operandTable operand lookup table +/// @param[in] type of the operand +/// @param[in] textValue word of text to be parsed +/// @param[out] pValue where the resulting value is written +/// +/// @return result code +spv_result_t spvTextParseMaskOperand(spv_target_env env, + const spv_operand_table operandTable, + const spv_operand_type_t type, + const char* textValue, uint32_t* pValue) { + if (textValue == nullptr) return SPV_ERROR_INVALID_TEXT; + size_t text_length = strlen(textValue); + if (text_length == 0) return SPV_ERROR_INVALID_TEXT; + const char* text_end = textValue + text_length; + + // We only support mask expressions in ASCII, so the separator value is a + // char. + const char separator = '|'; + + // Accumulate the result by interpreting one word at a time, scanning + // from left to right. + uint32_t value = 0; + const char* begin = textValue; // The left end of the current word. + const char* end = nullptr; // One character past the end of the current word. + do { + end = std::find(begin, text_end, separator); + + spv_operand_desc entry = nullptr; + if (spvOperandTableNameLookup(env, operandTable, type, begin, end - begin, + &entry)) { + return SPV_ERROR_INVALID_TEXT; + } + value |= entry->value; + + // Advance to the next word by skipping over the separator. + begin = end + 1; + } while (end != text_end); + + *pValue = value; + return SPV_SUCCESS; +} + +// Associates an opcode with its name. +struct SpecConstantOpcodeEntry { + SpvOp opcode; + const char* name; +}; + +// All the opcodes allowed as the operation for OpSpecConstantOp. +// The name does not have the usual "Op" prefix. For example opcode SpvOpIAdd +// is associated with the name "IAdd". +// +// clang-format off +#define CASE(NAME) { SpvOp##NAME, #NAME } +const SpecConstantOpcodeEntry kOpSpecConstantOpcodes[] = { + // Conversion + CASE(SConvert), + CASE(FConvert), + CASE(ConvertFToS), + CASE(ConvertSToF), + CASE(ConvertFToU), + CASE(ConvertUToF), + CASE(UConvert), + CASE(ConvertPtrToU), + CASE(ConvertUToPtr), + CASE(GenericCastToPtr), + CASE(PtrCastToGeneric), + CASE(Bitcast), + CASE(QuantizeToF16), + // Arithmetic + CASE(SNegate), + CASE(Not), + CASE(IAdd), + CASE(ISub), + CASE(IMul), + CASE(UDiv), + CASE(SDiv), + CASE(UMod), + CASE(SRem), + CASE(SMod), + CASE(ShiftRightLogical), + CASE(ShiftRightArithmetic), + CASE(ShiftLeftLogical), + CASE(BitwiseOr), + CASE(BitwiseAnd), + CASE(BitwiseXor), + CASE(FNegate), + CASE(FAdd), + CASE(FSub), + CASE(FMul), + CASE(FDiv), + CASE(FRem), + CASE(FMod), + // Composite + CASE(VectorShuffle), + CASE(CompositeExtract), + CASE(CompositeInsert), + // Logical + CASE(LogicalOr), + CASE(LogicalAnd), + CASE(LogicalNot), + CASE(LogicalEqual), + CASE(LogicalNotEqual), + CASE(Select), + // Comparison + CASE(IEqual), + CASE(INotEqual), + CASE(ULessThan), + CASE(SLessThan), + CASE(UGreaterThan), + CASE(SGreaterThan), + CASE(ULessThanEqual), + CASE(SLessThanEqual), + CASE(UGreaterThanEqual), + CASE(SGreaterThanEqual), + // Memory + CASE(AccessChain), + CASE(InBoundsAccessChain), + CASE(PtrAccessChain), + CASE(InBoundsPtrAccessChain), +}; + +// The 59 is determined by counting the opcodes listed in the spec. +static_assert(59 == sizeof(kOpSpecConstantOpcodes)/sizeof(kOpSpecConstantOpcodes[0]), + "OpSpecConstantOp opcode table is incomplete"); +#undef CASE +// clang-format on + +const size_t kNumOpSpecConstantOpcodes = + sizeof(kOpSpecConstantOpcodes) / sizeof(kOpSpecConstantOpcodes[0]); + +} // anonymous namespace + +namespace libspirv { + +bool AssemblyGrammar::isValid() const { + return operandTable_ && opcodeTable_ && extInstTable_; +} + +CapabilitySet AssemblyGrammar::filterCapsAgainstTargetEnv( + const SpvCapability* cap_array, uint32_t count) const { + CapabilitySet cap_set; + for (uint32_t i = 0; i < count; ++i) { + spv_operand_desc cap_desc = {}; + if (SPV_SUCCESS == lookupOperand(SPV_OPERAND_TYPE_CAPABILITY, + static_cast(cap_array[i]), + &cap_desc)) { + // spvOperandTableValueLookup() filters capabilities internally + // according to the current target environment by itself. So we + // should be safe to add this capability if the lookup succeeds. + cap_set.Add(cap_array[i]); + } + } + return cap_set; +} + +spv_result_t AssemblyGrammar::lookupOpcode(const char* name, + spv_opcode_desc* desc) const { + return spvOpcodeTableNameLookup(target_env_, opcodeTable_, name, desc); +} + +spv_result_t AssemblyGrammar::lookupOpcode(SpvOp opcode, + spv_opcode_desc* desc) const { + return spvOpcodeTableValueLookup(target_env_, opcodeTable_, opcode, desc); +} + +spv_result_t AssemblyGrammar::lookupOperand(spv_operand_type_t type, + const char* name, size_t name_len, + spv_operand_desc* desc) const { + return spvOperandTableNameLookup(target_env_, operandTable_, type, name, + name_len, desc); +} + +spv_result_t AssemblyGrammar::lookupOperand(spv_operand_type_t type, + uint32_t operand, + spv_operand_desc* desc) const { + return spvOperandTableValueLookup(target_env_, operandTable_, type, operand, + desc); +} + +spv_result_t AssemblyGrammar::lookupSpecConstantOpcode(const char* name, + SpvOp* opcode) const { + const auto* last = kOpSpecConstantOpcodes + kNumOpSpecConstantOpcodes; + const auto* found = + std::find_if(kOpSpecConstantOpcodes, last, + [name](const SpecConstantOpcodeEntry& entry) { + return 0 == strcmp(name, entry.name); + }); + if (found == last) return SPV_ERROR_INVALID_LOOKUP; + *opcode = found->opcode; + return SPV_SUCCESS; +} + +spv_result_t AssemblyGrammar::lookupSpecConstantOpcode(SpvOp opcode) const { + const auto* last = kOpSpecConstantOpcodes + kNumOpSpecConstantOpcodes; + const auto* found = + std::find_if(kOpSpecConstantOpcodes, last, + [opcode](const SpecConstantOpcodeEntry& entry) { + return opcode == entry.opcode; + }); + if (found == last) return SPV_ERROR_INVALID_LOOKUP; + return SPV_SUCCESS; +} + +spv_result_t AssemblyGrammar::parseMaskOperand(const spv_operand_type_t type, + const char* textValue, + uint32_t* pValue) const { + return spvTextParseMaskOperand(target_env_, operandTable_, type, textValue, + pValue); +} +spv_result_t AssemblyGrammar::lookupExtInst(spv_ext_inst_type_t type, + const char* textValue, + spv_ext_inst_desc* extInst) const { + return spvExtInstTableNameLookup(extInstTable_, type, textValue, extInst); +} + +spv_result_t AssemblyGrammar::lookupExtInst(spv_ext_inst_type_t type, + uint32_t firstWord, + spv_ext_inst_desc* extInst) const { + return spvExtInstTableValueLookup(extInstTable_, type, firstWord, extInst); +} + +void AssemblyGrammar::pushOperandTypesForMask( + const spv_operand_type_t type, const uint32_t mask, + spv_operand_pattern_t* pattern) const { + spvPushOperandTypesForMask(target_env_, operandTable_, type, mask, pattern); +} +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/assembly_grammar.h b/3rdparty/spirv-tools/source/assembly_grammar.h new file mode 100644 index 000000000..6837a0b6e --- /dev/null +++ b/3rdparty/spirv-tools/source/assembly_grammar.h @@ -0,0 +1,137 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_ASSEMBLY_GRAMMAR_H_ +#define LIBSPIRV_ASSEMBLY_GRAMMAR_H_ + +#include "enum_set.h" +#include "latest_version_spirv_header.h" +#include "operand.h" +#include "spirv-tools/libspirv.h" +#include "table.h" + +namespace libspirv { + +// Encapsulates the grammar to use for SPIR-V assembly. +// Contains methods to query for valid instructions and operands. +class AssemblyGrammar { + public: + explicit AssemblyGrammar(const spv_const_context context) + : target_env_(context->target_env), + operandTable_(context->operand_table), + opcodeTable_(context->opcode_table), + extInstTable_(context->ext_inst_table) {} + + // Returns true if the internal tables have been initialized with valid data. + bool isValid() const; + + // Returns the SPIR-V target environment. + spv_target_env target_env() const { return target_env_; } + + // Removes capabilities not available in the current target environment and + // returns the rest. + CapabilitySet filterCapsAgainstTargetEnv(const SpvCapability* cap_array, + uint32_t count) const; + + // Fills in the desc parameter with the information about the opcode + // of the given name. Returns SPV_SUCCESS if the opcode was found, and + // SPV_ERROR_INVALID_LOOKUP if the opcode does not exist. + spv_result_t lookupOpcode(const char* name, spv_opcode_desc* desc) const; + + // Fills in the desc parameter with the information about the opcode + // of the valid. Returns SPV_SUCCESS if the opcode was found, and + // SPV_ERROR_INVALID_LOOKUP if the opcode does not exist. + spv_result_t lookupOpcode(SpvOp opcode, spv_opcode_desc* desc) const; + + // Fills in the desc parameter with the information about the given + // operand. Returns SPV_SUCCESS if the operand was found, and + // SPV_ERROR_INVALID_LOOKUP otherwise. + spv_result_t lookupOperand(spv_operand_type_t type, const char* name, + size_t name_len, spv_operand_desc* desc) const; + + // Fills in the desc parameter with the information about the given + // operand. Returns SPV_SUCCESS if the operand was found, and + // SPV_ERROR_INVALID_LOOKUP otherwise. + spv_result_t lookupOperand(spv_operand_type_t type, uint32_t operand, + spv_operand_desc* desc) const; + + // Finds operand entry in the grammar table and returns its name. + // Returns "Unknown" if not found. + const char* lookupOperandName(spv_operand_type_t type, + uint32_t operand) const { + spv_operand_desc desc = nullptr; + if (lookupOperand(type, operand, &desc) != SPV_SUCCESS || !desc) { + return "Unknown"; + } + return desc->name; + } + + // Finds the opcode for the given OpSpecConstantOp opcode name. The name + // should not have the "Op" prefix. For example, "IAdd" corresponds to + // the integer add opcode for OpSpecConstantOp. On success, returns + // SPV_SUCCESS and sends the discovered operation code through the opcode + // parameter. On failure, returns SPV_ERROR_INVALID_LOOKUP. + spv_result_t lookupSpecConstantOpcode(const char* name, SpvOp* opcode) const; + + // Returns SPV_SUCCESS if the given opcode is valid as the opcode operand + // to OpSpecConstantOp. + spv_result_t lookupSpecConstantOpcode(SpvOp opcode) const; + + // Parses a mask expression string for the given operand type. + // + // A mask expression is a sequence of one or more terms separated by '|', + // where each term is a named enum value for a given type. No whitespace + // is permitted. + // + // On success, the value is written to pValue, and SPV_SUCCESS is returned. + // The operand type is defined by the type parameter, and the text to be + // parsed is defined by the textValue parameter. + spv_result_t parseMaskOperand(const spv_operand_type_t type, + const char* textValue, uint32_t* pValue) const; + + // Writes the extended operand with the given type and text to the *extInst + // parameter. + // Returns SPV_SUCCESS if the value could be found. + spv_result_t lookupExtInst(spv_ext_inst_type_t type, const char* textValue, + spv_ext_inst_desc* extInst) const; + + // Writes the extended operand with the given type and first encoded word + // to the *extInst parameter. + // Returns SPV_SUCCESS if the value could be found. + spv_result_t lookupExtInst(spv_ext_inst_type_t type, uint32_t firstWord, + spv_ext_inst_desc* extInst) const; + + // Inserts the operands expected after the given typed mask onto the end + // of the given pattern. + // + // Each set bit in the mask represents zero or more operand types that + // should be appended onto the pattern. Operands for a less significant + // bit must always match before operands for a more significant bit, so + // the operands for a less significant bit must appear closer to the end + // of the pattern stack. + // + // If a set bit is unknown, then we assume it has no operands. + void pushOperandTypesForMask(const spv_operand_type_t type, + const uint32_t mask, + spv_operand_pattern_t* pattern) const; + + private: + const spv_target_env target_env_; + const spv_operand_table operandTable_; + const spv_opcode_table opcodeTable_; + const spv_ext_inst_table extInstTable_; +}; +} // namespace libspirv + +#endif // LIBSPIRV_ASSEMBLY_GRAMMAR_H_ diff --git a/3rdparty/spirv-tools/source/binary.cpp b/3rdparty/spirv-tools/source/binary.cpp new file mode 100644 index 000000000..7ac5765ee --- /dev/null +++ b/3rdparty/spirv-tools/source/binary.cpp @@ -0,0 +1,789 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "binary.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "assembly_grammar.h" +#include "diagnostic.h" +#include "ext_inst.h" +#include "latest_version_spirv_header.h" +#include "opcode.h" +#include "operand.h" +#include "spirv_constant.h" +#include "spirv_endian.h" + +spv_result_t spvBinaryHeaderGet(const spv_const_binary binary, + const spv_endianness_t endian, + spv_header_t* pHeader) { + if (!binary->code) return SPV_ERROR_INVALID_BINARY; + if (binary->wordCount < SPV_INDEX_INSTRUCTION) + return SPV_ERROR_INVALID_BINARY; + if (!pHeader) return SPV_ERROR_INVALID_POINTER; + + // TODO: Validation checking? + pHeader->magic = spvFixWord(binary->code[SPV_INDEX_MAGIC_NUMBER], endian); + pHeader->version = spvFixWord(binary->code[SPV_INDEX_VERSION_NUMBER], endian); + pHeader->generator = + spvFixWord(binary->code[SPV_INDEX_GENERATOR_NUMBER], endian); + pHeader->bound = spvFixWord(binary->code[SPV_INDEX_BOUND], endian); + pHeader->schema = spvFixWord(binary->code[SPV_INDEX_SCHEMA], endian); + pHeader->instructions = &binary->code[SPV_INDEX_INSTRUCTION]; + + return SPV_SUCCESS; +} + +namespace { + +// A SPIR-V binary parser. A parser instance communicates detailed parse +// results via callbacks. +class Parser { + public: + // The user_data value is provided to the callbacks as context. + Parser(const spv_const_context context, void* user_data, + spv_parsed_header_fn_t parsed_header_fn, + spv_parsed_instruction_fn_t parsed_instruction_fn) + : grammar_(context), + consumer_(context->consumer), + user_data_(user_data), + parsed_header_fn_(parsed_header_fn), + parsed_instruction_fn_(parsed_instruction_fn) {} + + // Parses the specified binary SPIR-V module, issuing callbacks on a parsed + // header and for each parsed instruction. Returns SPV_SUCCESS on success. + // Otherwise returns an error code and issues a diagnostic. + spv_result_t parse(const uint32_t* words, size_t num_words, + spv_diagnostic* diagnostic); + + private: + // All remaining methods work on the current module parse state. + + // Like the parse method, but works on the current module parse state. + spv_result_t parseModule(); + + // Parses an instruction at the current position of the binary. Assumes + // the header has been parsed, the endian has been set, and the word index is + // still in range. Advances the parsing position past the instruction, and + // updates other parsing state for the current module. + // On success, returns SPV_SUCCESS and issues the parsed-instruction callback. + // On failure, returns an error code and issues a diagnostic. + spv_result_t parseInstruction(); + + // Parses an instruction operand with the given type, for an instruction + // starting at inst_offset words into the SPIR-V binary. + // If the SPIR-V binary is the same endianness as the host, then the + // endian_converted_inst_words parameter is ignored. Otherwise, this method + // appends the words for this operand, converted to host native endianness, + // to the end of endian_converted_inst_words. This method also updates the + // expected_operands parameter, and the scalar members of the inst parameter. + // On success, returns SPV_SUCCESS, advances past the operand, and pushes a + // new entry on to the operands vector. Otherwise returns an error code and + // issues a diagnostic. + spv_result_t parseOperand(size_t inst_offset, spv_parsed_instruction_t* inst, + const spv_operand_type_t type, + std::vector* endian_converted_inst_words, + std::vector* operands, + spv_operand_pattern_t* expected_operands); + + // Records the numeric type for an operand according to the type information + // associated with the given non-zero type Id. This can fail if the type Id + // is not a type Id, or if the type Id does not reference a scalar numeric + // type. On success, return SPV_SUCCESS and populates the num_words, + // number_kind, and number_bit_width fields of parsed_operand. + spv_result_t setNumericTypeInfoForType(spv_parsed_operand_t* parsed_operand, + uint32_t type_id); + + // Records the number type for an instruction at the given offset, if that + // instruction generates a type. For types that aren't scalar numbers, + // record something with number kind SPV_NUMBER_NONE. + void recordNumberType(size_t inst_offset, + const spv_parsed_instruction_t* inst); + + // Returns a diagnostic stream object initialized with current position in + // the input stream, and for the given error code. Any data written to the + // returned object will be propagated to the current parse's diagnostic + // object. + libspirv::DiagnosticStream diagnostic(spv_result_t error) { + return libspirv::DiagnosticStream({0, 0, _.word_index}, consumer_, error); + } + + // Returns a diagnostic stream object with the default parse error code. + libspirv::DiagnosticStream diagnostic() { + // The default failure for parsing is invalid binary. + return diagnostic(SPV_ERROR_INVALID_BINARY); + } + + // Issues a diagnostic describing an exhaustion of input condition when + // trying to decode an instruction operand, and returns + // SPV_ERROR_INVALID_BINARY. + spv_result_t exhaustedInputDiagnostic(size_t inst_offset, SpvOp opcode, + spv_operand_type_t type) { + return diagnostic() << "End of input reached while decoding Op" + << spvOpcodeString(opcode) << " starting at word " + << inst_offset + << ((_.word_index < _.num_words) ? ": truncated " + : ": missing ") + << spvOperandTypeStr(type) << " operand at word offset " + << _.word_index - inst_offset << "."; + } + + // Returns the endian-corrected word at the current position. + uint32_t peek() const { return peekAt(_.word_index); } + + // Returns the endian-corrected word at the given position. + uint32_t peekAt(size_t index) const { + assert(index < _.num_words); + return spvFixWord(_.words[index], _.endian); + } + + // Data members + + const libspirv::AssemblyGrammar grammar_; // SPIR-V syntax utility. + const spvtools::MessageConsumer& consumer_; // Message consumer callback. + void* const user_data_; // Context for the callbacks + const spv_parsed_header_fn_t parsed_header_fn_; // Parsed header callback + const spv_parsed_instruction_fn_t + parsed_instruction_fn_; // Parsed instruction callback + + // Describes the format of a typed literal number. + struct NumberType { + spv_number_kind_t type; + uint32_t bit_width; + }; + + // The state used to parse a single SPIR-V binary module. + struct State { + State(const uint32_t* words_arg, size_t num_words_arg, + spv_diagnostic* diagnostic_arg) + : words(words_arg), + num_words(num_words_arg), + diagnostic(diagnostic_arg), + word_index(0), + endian(), + requires_endian_conversion(false) { + // Temporary storage for parser state within a single instruction. + // Most instructions require fewer than 25 words or operands. + operands.reserve(25); + endian_converted_words.reserve(25); + expected_operands.reserve(25); + } + State() : State(0, 0, nullptr) {} + const uint32_t* words; // Words in the binary SPIR-V module. + size_t num_words; // Number of words in the module. + spv_diagnostic* diagnostic; // Where diagnostics go. + size_t word_index; // The current position in words. + spv_endianness_t endian; // The endianness of the binary. + // Is the SPIR-V binary in a different endiannes from the host native + // endianness? + bool requires_endian_conversion; + + // Maps a result ID to its type ID. By convention: + // - a result ID that is a type definition maps to itself. + // - a result ID without a type maps to 0. (E.g. for OpLabel) + std::unordered_map id_to_type_id; + // Maps a type ID to its number type description. + std::unordered_map type_id_to_number_type_info; + // Maps an ExtInstImport id to the extended instruction type. + std::unordered_map + import_id_to_ext_inst_type; + + // Used by parseOperand + std::vector operands; + std::vector endian_converted_words; + spv_operand_pattern_t expected_operands; + } _; +}; + +spv_result_t Parser::parse(const uint32_t* words, size_t num_words, + spv_diagnostic* diagnostic_arg) { + _ = State(words, num_words, diagnostic_arg); + + const spv_result_t result = parseModule(); + + // Clear the module state. The tables might be big. + _ = State(); + + return result; +} + +spv_result_t Parser::parseModule() { + if (!_.words) return diagnostic() << "Missing module."; + + if (_.num_words < SPV_INDEX_INSTRUCTION) + return diagnostic() << "Module has incomplete header: only " << _.num_words + << " words instead of " << SPV_INDEX_INSTRUCTION; + + // Check the magic number and detect the module's endianness. + spv_const_binary_t binary{_.words, _.num_words}; + if (spvBinaryEndianness(&binary, &_.endian)) { + return diagnostic() << "Invalid SPIR-V magic number '" << std::hex + << _.words[0] << "'."; + } + _.requires_endian_conversion = !spvIsHostEndian(_.endian); + + // Process the header. + spv_header_t header; + if (spvBinaryHeaderGet(&binary, _.endian, &header)) { + // It turns out there is no way to trigger this error since the only + // failure cases are already handled above, with better messages. + return diagnostic(SPV_ERROR_INTERNAL) + << "Internal error: unhandled header parse failure"; + } + if (parsed_header_fn_) { + if (auto error = parsed_header_fn_(user_data_, _.endian, header.magic, + header.version, header.generator, + header.bound, header.schema)) { + return error; + } + } + + // Process the instructions. + _.word_index = SPV_INDEX_INSTRUCTION; + while (_.word_index < _.num_words) + if (auto error = parseInstruction()) return error; + + // Running off the end should already have been reported earlier. + assert(_.word_index == _.num_words); + + return SPV_SUCCESS; +} + +spv_result_t Parser::parseInstruction() { + // The zero values for all members except for opcode are the + // correct initial values. + spv_parsed_instruction_t inst = {}; + + const uint32_t first_word = peek(); + + // If the module's endianness is different from the host native endianness, + // then converted_words contains the the endian-translated words in the + // instruction. + _.endian_converted_words.clear(); + _.endian_converted_words.push_back(first_word); + + // After a successful parse of the instruction, the inst.operands member + // will point to this vector's storage. + _.operands.clear(); + + assert(_.word_index < _.num_words); + // Decompose and check the first word. + uint16_t inst_word_count = 0; + spvOpcodeSplit(first_word, &inst_word_count, &inst.opcode); + if (inst_word_count < 1) { + return diagnostic() << "Invalid instruction word count: " + << inst_word_count; + } + spv_opcode_desc opcode_desc; + if (grammar_.lookupOpcode(static_cast(inst.opcode), &opcode_desc)) + return diagnostic() << "Invalid opcode: " << inst.opcode; + + // Advance past the opcode word. But remember the of the start + // of the instruction. + const size_t inst_offset = _.word_index; + _.word_index++; + + // Maintains the ordered list of expected operand types. + // For many instructions we only need the {numTypes, operandTypes} + // entries in opcode_desc. However, sometimes we need to modify + // the list as we parse the operands. This occurs when an operand + // has its own logical operands (such as the LocalSize operand for + // ExecutionMode), or for extended instructions that may have their + // own operands depending on the selected extended instruction. + _.expected_operands.clear(); + for (auto i = 0; i < opcode_desc->numTypes; i++) + _.expected_operands.push_back( + opcode_desc->operandTypes[opcode_desc->numTypes - i - 1]); + + while (_.word_index < inst_offset + inst_word_count) { + const uint16_t inst_word_index = uint16_t(_.word_index - inst_offset); + if (_.expected_operands.empty()) { + return diagnostic() << "Invalid instruction Op" << opcode_desc->name + << " starting at word " << inst_offset + << ": expected no more operands after " + << inst_word_index + << " words, but stated word count is " + << inst_word_count << "."; + } + + spv_operand_type_t type = + spvTakeFirstMatchableOperand(&_.expected_operands); + + if (auto error = + parseOperand(inst_offset, &inst, type, &_.endian_converted_words, + &_.operands, &_.expected_operands)) { + return error; + } + } + + if (!_.expected_operands.empty() && + !spvOperandIsOptional(_.expected_operands.back())) { + return diagnostic() << "End of input reached while decoding Op" + << opcode_desc->name << " starting at word " + << inst_offset << ": expected more operands after " + << inst_word_count << " words."; + } + + if ((inst_offset + inst_word_count) != _.word_index) { + return diagnostic() << "Invalid word count: Op" << opcode_desc->name + << " starting at word " << inst_offset + << " says it has " << inst_word_count + << " words, but found " << _.word_index - inst_offset + << " words instead."; + } + + // Check the computed length of the endian-converted words vector against + // the declared number of words in the instruction. If endian conversion + // is required, then they should match. If no endian conversion was + // performed, then the vector only contains the initial opcode/word-count + // word. + assert(!_.requires_endian_conversion || + (inst_word_count == _.endian_converted_words.size())); + assert(_.requires_endian_conversion || + (_.endian_converted_words.size() == 1)); + + recordNumberType(inst_offset, &inst); + + if (_.requires_endian_conversion) { + // We must wait until here to set this pointer, because the vector might + // have been be resized while we accumulated its elements. + inst.words = _.endian_converted_words.data(); + } else { + // If no conversion is required, then just point to the underlying binary. + // This saves time and space. + inst.words = _.words + inst_offset; + } + inst.num_words = inst_word_count; + + // We must wait until here to set this pointer, because the vector might + // have been be resized while we accumulated its elements. + inst.operands = _.operands.data(); + inst.num_operands = uint16_t(_.operands.size()); + + // Issue the callback. The callee should know that all the storage in inst + // is transient, and will disappear immediately afterward. + if (parsed_instruction_fn_) { + if (auto error = parsed_instruction_fn_(user_data_, &inst)) return error; + } + + return SPV_SUCCESS; +} + +spv_result_t Parser::parseOperand(size_t inst_offset, + spv_parsed_instruction_t* inst, + const spv_operand_type_t type, + std::vector* words, + std::vector* operands, + spv_operand_pattern_t* expected_operands) { + const SpvOp opcode = static_cast(inst->opcode); + // We'll fill in this result as we go along. + spv_parsed_operand_t parsed_operand; + parsed_operand.offset = uint16_t(_.word_index - inst_offset); + // Most operands occupy one word. This might be be adjusted later. + parsed_operand.num_words = 1; + // The type argument is the one used by the grammar to parse the instruction. + // But it can exposes internal parser details such as whether an operand is + // optional or actually represents a variable-length sequence of operands. + // The resulting type should be adjusted to avoid those internal details. + // In most cases, the resulting operand type is the same as the grammar type. + parsed_operand.type = type; + + // Assume non-numeric values. This will be updated for literal numbers. + parsed_operand.number_kind = SPV_NUMBER_NONE; + parsed_operand.number_bit_width = 0; + + if (_.word_index >= _.num_words) + return exhaustedInputDiagnostic(inst_offset, opcode, type); + + const uint32_t word = peek(); + + // Do the words in this operand have to be converted to native endianness? + // True for all but literal strings. + bool convert_operand_endianness = true; + + switch (type) { + case SPV_OPERAND_TYPE_TYPE_ID: + if (!word) + return diagnostic(SPV_ERROR_INVALID_ID) << "Error: Type Id is 0"; + inst->type_id = word; + break; + + case SPV_OPERAND_TYPE_RESULT_ID: + if (!word) + return diagnostic(SPV_ERROR_INVALID_ID) << "Error: Result Id is 0"; + inst->result_id = word; + // Save the result ID to type ID mapping. + // In the grammar, type ID always appears before result ID. + if (_.id_to_type_id.find(inst->result_id) != _.id_to_type_id.end()) + return diagnostic(SPV_ERROR_INVALID_ID) + << "Id " << inst->result_id << " is defined more than once"; + // Record it. + // A regular value maps to its type. Some instructions (e.g. OpLabel) + // have no type Id, and will map to 0. The result Id for a + // type-generating instruction (e.g. OpTypeInt) maps to itself. + _.id_to_type_id[inst->result_id] = + spvOpcodeGeneratesType(opcode) ? inst->result_id : inst->type_id; + break; + + case SPV_OPERAND_TYPE_ID: + case SPV_OPERAND_TYPE_OPTIONAL_ID: + if (!word) return diagnostic(SPV_ERROR_INVALID_ID) << "Id is 0"; + parsed_operand.type = SPV_OPERAND_TYPE_ID; + + if (opcode == SpvOpExtInst && parsed_operand.offset == 3) { + // The current word is the extended instruction set Id. + // Set the extended instruction set type for the current instruction. + auto ext_inst_type_iter = _.import_id_to_ext_inst_type.find(word); + if (ext_inst_type_iter == _.import_id_to_ext_inst_type.end()) { + return diagnostic(SPV_ERROR_INVALID_ID) + << "OpExtInst set Id " << word + << " does not reference an OpExtInstImport result Id"; + } + inst->ext_inst_type = ext_inst_type_iter->second; + } + break; + + case SPV_OPERAND_TYPE_SCOPE_ID: + case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID: + // Check for trivially invalid values. The operand descriptions already + // have the word "ID" in them. + if (!word) return diagnostic() << spvOperandTypeStr(type) << " is 0"; + break; + + case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER: { + assert(SpvOpExtInst == opcode); + assert(inst->ext_inst_type != SPV_EXT_INST_TYPE_NONE); + spv_ext_inst_desc ext_inst; + if (grammar_.lookupExtInst(inst->ext_inst_type, word, &ext_inst)) + return diagnostic() << "Invalid extended instruction number: " << word; + spvPushOperandTypes(ext_inst->operandTypes, expected_operands); + } break; + + case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: { + assert(SpvOpSpecConstantOp == opcode); + if (grammar_.lookupSpecConstantOpcode(SpvOp(word))) { + return diagnostic() + << "Invalid " << spvOperandTypeStr(type) << ": " << word; + } + spv_opcode_desc opcode_entry = nullptr; + if (grammar_.lookupOpcode(SpvOp(word), &opcode_entry)) { + return diagnostic(SPV_ERROR_INTERNAL) + << "OpSpecConstant opcode table out of sync"; + } + // OpSpecConstant opcodes must have a type and result. We've already + // processed them, so skip them when preparing to parse the other + // operants for the opcode. + assert(opcode_entry->hasType); + assert(opcode_entry->hasResult); + assert(opcode_entry->numTypes >= 2); + spvPushOperandTypes(opcode_entry->operandTypes + 2, expected_operands); + } break; + + case SPV_OPERAND_TYPE_LITERAL_INTEGER: + case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_INTEGER: + // These are regular single-word literal integer operands. + // Post-parsing validation should check the range of the parsed value. + parsed_operand.type = SPV_OPERAND_TYPE_LITERAL_INTEGER; + // It turns out they are always unsigned integers! + parsed_operand.number_kind = SPV_NUMBER_UNSIGNED_INT; + parsed_operand.number_bit_width = 32; + break; + + case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER: + case SPV_OPERAND_TYPE_OPTIONAL_TYPED_LITERAL_INTEGER: + parsed_operand.type = SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER; + if (opcode == SpvOpSwitch) { + // The literal operands have the same type as the value + // referenced by the selector Id. + const uint32_t selector_id = peekAt(inst_offset + 1); + const auto type_id_iter = _.id_to_type_id.find(selector_id); + if (type_id_iter == _.id_to_type_id.end() || + type_id_iter->second == 0) { + return diagnostic() << "Invalid OpSwitch: selector id " << selector_id + << " has no type"; + } + uint32_t type_id = type_id_iter->second; + + if (selector_id == type_id) { + // Recall that by convention, a result ID that is a type definition + // maps to itself. + return diagnostic() << "Invalid OpSwitch: selector id " << selector_id + << " is a type, not a value"; + } + if (auto error = setNumericTypeInfoForType(&parsed_operand, type_id)) + return error; + if (parsed_operand.number_kind != SPV_NUMBER_UNSIGNED_INT && + parsed_operand.number_kind != SPV_NUMBER_SIGNED_INT) { + return diagnostic() << "Invalid OpSwitch: selector id " << selector_id + << " is not a scalar integer"; + } + } else { + assert(opcode == SpvOpConstant || opcode == SpvOpSpecConstant); + // The literal number type is determined by the type Id for the + // constant. + assert(inst->type_id); + if (auto error = + setNumericTypeInfoForType(&parsed_operand, inst->type_id)) + return error; + } + break; + + case SPV_OPERAND_TYPE_LITERAL_STRING: + case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_STRING: { + convert_operand_endianness = false; + const char* string = + reinterpret_cast(_.words + _.word_index); + // Compute the length of the string, but make sure we don't run off the + // end of the input. + const size_t remaining_input_bytes = + sizeof(uint32_t) * (_.num_words - _.word_index); + const size_t string_num_content_bytes = + spv_strnlen_s(string, remaining_input_bytes); + // If there was no terminating null byte, then that's an end-of-input + // error. + if (string_num_content_bytes == remaining_input_bytes) + return exhaustedInputDiagnostic(inst_offset, opcode, type); + // Account for null in the word length, so add 1 for null, then add 3 to + // make sure we round up. The following is equivalent to: + // (string_num_content_bytes + 1 + 3) / 4 + const size_t string_num_words = string_num_content_bytes / 4 + 1; + // Make sure we can record the word count without overflow. + // + // This error can't currently be triggered because of validity + // checks elsewhere. + if (string_num_words > std::numeric_limits::max()) { + return diagnostic() << "Literal string is longer than " + << std::numeric_limits::max() + << " words: " << string_num_words << " words long"; + } + parsed_operand.num_words = uint16_t(string_num_words); + parsed_operand.type = SPV_OPERAND_TYPE_LITERAL_STRING; + + if (SpvOpExtInstImport == opcode) { + // Record the extended instruction type for the ID for this import. + // There is only one string literal argument to OpExtInstImport, + // so it's sufficient to guard this just on the opcode. + const spv_ext_inst_type_t ext_inst_type = + spvExtInstImportTypeGet(string); + if (SPV_EXT_INST_TYPE_NONE == ext_inst_type) { + return diagnostic() + << "Invalid extended instruction import '" << string << "'"; + } + // We must have parsed a valid result ID. It's a condition + // of the grammar, and we only accept non-zero result Ids. + assert(inst->result_id); + _.import_id_to_ext_inst_type[inst->result_id] = ext_inst_type; + } + } break; + + case SPV_OPERAND_TYPE_CAPABILITY: + case SPV_OPERAND_TYPE_SOURCE_LANGUAGE: + case SPV_OPERAND_TYPE_EXECUTION_MODEL: + case SPV_OPERAND_TYPE_ADDRESSING_MODEL: + case SPV_OPERAND_TYPE_MEMORY_MODEL: + case SPV_OPERAND_TYPE_EXECUTION_MODE: + case SPV_OPERAND_TYPE_STORAGE_CLASS: + case SPV_OPERAND_TYPE_DIMENSIONALITY: + case SPV_OPERAND_TYPE_SAMPLER_ADDRESSING_MODE: + case SPV_OPERAND_TYPE_SAMPLER_FILTER_MODE: + case SPV_OPERAND_TYPE_SAMPLER_IMAGE_FORMAT: + case SPV_OPERAND_TYPE_FP_ROUNDING_MODE: + case SPV_OPERAND_TYPE_LINKAGE_TYPE: + case SPV_OPERAND_TYPE_ACCESS_QUALIFIER: + case SPV_OPERAND_TYPE_OPTIONAL_ACCESS_QUALIFIER: + case SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE: + case SPV_OPERAND_TYPE_DECORATION: + case SPV_OPERAND_TYPE_BUILT_IN: + case SPV_OPERAND_TYPE_GROUP_OPERATION: + case SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS: + case SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO: + case SPV_OPERAND_TYPE_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING: + case SPV_OPERAND_TYPE_DEBUG_COMPOSITE_TYPE: + case SPV_OPERAND_TYPE_DEBUG_TYPE_QUALIFIER: + case SPV_OPERAND_TYPE_DEBUG_OPERATION: { + // A single word that is a plain enum value. + + // Map an optional operand type to its corresponding concrete type. + if (type == SPV_OPERAND_TYPE_OPTIONAL_ACCESS_QUALIFIER) + parsed_operand.type = SPV_OPERAND_TYPE_ACCESS_QUALIFIER; + + spv_operand_desc entry; + if (grammar_.lookupOperand(type, word, &entry)) { + return diagnostic() + << "Invalid " << spvOperandTypeStr(parsed_operand.type) + << " operand: " << word; + } + // Prepare to accept operands to this operand, if needed. + spvPushOperandTypes(entry->operandTypes, expected_operands); + } break; + + case SPV_OPERAND_TYPE_FP_FAST_MATH_MODE: + case SPV_OPERAND_TYPE_FUNCTION_CONTROL: + case SPV_OPERAND_TYPE_LOOP_CONTROL: + case SPV_OPERAND_TYPE_IMAGE: + case SPV_OPERAND_TYPE_OPTIONAL_IMAGE: + case SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS: + case SPV_OPERAND_TYPE_SELECTION_CONTROL: + case SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS: { + // This operand is a mask. + + // Map an optional operand type to its corresponding concrete type. + if (type == SPV_OPERAND_TYPE_OPTIONAL_IMAGE) + parsed_operand.type = SPV_OPERAND_TYPE_IMAGE; + else if (type == SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS) + parsed_operand.type = SPV_OPERAND_TYPE_MEMORY_ACCESS; + + // Check validity of set mask bits. Also prepare for operands for those + // masks if they have any. To get operand order correct, scan from + // MSB to LSB since we can only prepend operands to a pattern. + // The only case in the grammar where you have more than one mask bit + // having an operand is for image operands. See SPIR-V 3.14 Image + // Operands. + uint32_t remaining_word = word; + for (uint32_t mask = (1u << 31); remaining_word; mask >>= 1) { + if (remaining_word & mask) { + spv_operand_desc entry; + if (grammar_.lookupOperand(type, mask, &entry)) { + return diagnostic() + << "Invalid " << spvOperandTypeStr(parsed_operand.type) + << " operand: " << word << " has invalid mask component " + << mask; + } + remaining_word ^= mask; + spvPushOperandTypes(entry->operandTypes, expected_operands); + } + } + if (word == 0) { + // An all-zeroes mask *might* also be valid. + spv_operand_desc entry; + if (SPV_SUCCESS == grammar_.lookupOperand(type, 0, &entry)) { + // Prepare for its operands, if any. + spvPushOperandTypes(entry->operandTypes, expected_operands); + } + } + } break; + default: + return diagnostic() << "Internal error: Unhandled operand type: " << type; + } + + assert(spvOperandIsConcrete(parsed_operand.type)); + + operands->push_back(parsed_operand); + + const size_t index_after_operand = _.word_index + parsed_operand.num_words; + + // Avoid buffer overrun for the cases where the operand has more than one + // word, and where it isn't a string. (Those other cases have already been + // handled earlier.) For example, this error can occur for a multi-word + // argument to OpConstant, or a multi-word case literal operand for OpSwitch. + if (_.num_words < index_after_operand) + return exhaustedInputDiagnostic(inst_offset, opcode, type); + + if (_.requires_endian_conversion) { + // Copy instruction words. Translate to native endianness as needed. + if (convert_operand_endianness) { + const spv_endianness_t endianness = _.endian; + std::transform(_.words + _.word_index, _.words + index_after_operand, + std::back_inserter(*words), + [endianness](const uint32_t raw_word) { + return spvFixWord(raw_word, endianness); + }); + } else { + words->insert(words->end(), _.words + _.word_index, + _.words + index_after_operand); + } + } + + // Advance past the operand. + _.word_index = index_after_operand; + + return SPV_SUCCESS; +} + +spv_result_t Parser::setNumericTypeInfoForType( + spv_parsed_operand_t* parsed_operand, uint32_t type_id) { + assert(type_id != 0); + auto type_info_iter = _.type_id_to_number_type_info.find(type_id); + if (type_info_iter == _.type_id_to_number_type_info.end()) { + return diagnostic() << "Type Id " << type_id << " is not a type"; + } + const NumberType& info = type_info_iter->second; + if (info.type == SPV_NUMBER_NONE) { + // This is a valid type, but for something other than a scalar number. + return diagnostic() << "Type Id " << type_id + << " is not a scalar numeric type"; + } + + parsed_operand->number_kind = info.type; + parsed_operand->number_bit_width = info.bit_width; + // Round up the word count. + parsed_operand->num_words = static_cast((info.bit_width + 31) / 32); + return SPV_SUCCESS; +} + +void Parser::recordNumberType(size_t inst_offset, + const spv_parsed_instruction_t* inst) { + const SpvOp opcode = static_cast(inst->opcode); + if (spvOpcodeGeneratesType(opcode)) { + NumberType info = {SPV_NUMBER_NONE, 0}; + if (SpvOpTypeInt == opcode) { + const bool is_signed = peekAt(inst_offset + 3) != 0; + info.type = is_signed ? SPV_NUMBER_SIGNED_INT : SPV_NUMBER_UNSIGNED_INT; + info.bit_width = peekAt(inst_offset + 2); + } else if (SpvOpTypeFloat == opcode) { + info.type = SPV_NUMBER_FLOATING; + info.bit_width = peekAt(inst_offset + 2); + } + // The *result* Id of a type generating instruction is the type Id. + _.type_id_to_number_type_info[inst->result_id] = info; + } +} + +} // anonymous namespace + +spv_result_t spvBinaryParse(const spv_const_context context, void* user_data, + const uint32_t* code, const size_t num_words, + spv_parsed_header_fn_t parsed_header, + spv_parsed_instruction_fn_t parsed_instruction, + spv_diagnostic* diagnostic) { + spv_context_t hijack_context = *context; + if (diagnostic) { + *diagnostic = nullptr; + libspirv::UseDiagnosticAsMessageConsumer(&hijack_context, diagnostic); + } + Parser parser(&hijack_context, user_data, parsed_header, parsed_instruction); + return parser.parse(code, num_words, diagnostic); +} + +// TODO(dneto): This probably belongs in text.cpp since that's the only place +// that a spv_binary_t value is created. +void spvBinaryDestroy(spv_binary binary) { + if (!binary) return; + delete[] binary->code; + delete binary; +} + +size_t spv_strnlen_s(const char* str, size_t strsz) { + if (!str) return 0; + for (size_t i = 0; i < strsz; i++) { + if (!str[i]) return i; + } + return strsz; +} diff --git a/3rdparty/spirv-tools/source/binary.h b/3rdparty/spirv-tools/source/binary.h new file mode 100644 index 000000000..f6237e32b --- /dev/null +++ b/3rdparty/spirv-tools/source/binary.h @@ -0,0 +1,36 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_BINARY_H_ +#define LIBSPIRV_BINARY_H_ + +#include "spirv-tools/libspirv.h" +#include "spirv_definition.h" + +// Functions + +// Grabs the header from the SPIR-V module given in the binary parameter. The +// endian parameter specifies the endianness of the binary module. On success, +// returns SPV_SUCCESS and writes the parsed header into *header. +spv_result_t spvBinaryHeaderGet(const spv_const_binary binary, + const spv_endianness_t endian, + spv_header_t* header); + +// Returns the number of non-null characters in str before the first null +// character, or strsz if there is no null character. Examines at most the +// first strsz characters in str. Returns 0 if str is nullptr. This is a +// replacement for C11's strnlen_s which might not exist in all environments. +size_t spv_strnlen_s(const char* str, size_t strsz); + +#endif // LIBSPIRV_BINARY_H_ diff --git a/3rdparty/spirv-tools/source/cfa.h b/3rdparty/spirv-tools/source/cfa.h new file mode 100644 index 000000000..1022e3f2d --- /dev/null +++ b/3rdparty/spirv-tools/source/cfa.h @@ -0,0 +1,338 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SPVTOOLS_CFA_H_ +#define SPVTOOLS_CFA_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using std::find; +using std::function; +using std::get; +using std::pair; +using std::unordered_map; +using std::unordered_set; +using std::vector; + +namespace spvtools { + +// Control Flow Analysis of control flow graphs of basic block nodes |BB|. +template +class CFA { + using bb_ptr = BB*; + using cbb_ptr = const BB*; + using bb_iter = typename std::vector::const_iterator; + using get_blocks_func = std::function*(const BB*)>; + + struct block_info { + cbb_ptr block; ///< pointer to the block + bb_iter iter; ///< Iterator to the current child node being processed + }; + + /// Returns true if a block with @p id is found in the @p work_list vector + /// + /// @param[in] work_list Set of blocks visited in the the depth first + /// traversal + /// of the CFG + /// @param[in] id The ID of the block being checked + /// + /// @return true if the edge work_list.back().block->id() => id is a back-edge + static bool FindInWorkList(const std::vector& work_list, + uint32_t id); + + public: + /// @brief Depth first traversal starting from the \p entry BasicBlock + /// + /// This function performs a depth first traversal from the \p entry + /// BasicBlock and calls the pre/postorder functions when it needs to process + /// the node in pre order, post order. It also calls the backedge function + /// when a back edge is encountered. + /// + /// @param[in] entry The root BasicBlock of a CFG + /// @param[in] successor_func A function which will return a pointer to the + /// successor nodes + /// @param[in] preorder A function that will be called for every block in a + /// CFG following preorder traversal semantics + /// @param[in] postorder A function that will be called for every block in a + /// CFG following postorder traversal semantics + /// @param[in] backedge A function that will be called when a backedge is + /// encountered during a traversal + /// NOTE: The @p successor_func and predecessor_func each return a pointer to + /// a + /// collection such that iterators to that collection remain valid for the + /// lifetime of the algorithm. + static void DepthFirstTraversal( + const BB* entry, get_blocks_func successor_func, + std::function preorder, + std::function postorder, + std::function backedge); + + /// @brief Calculates dominator edges for a set of blocks + /// + /// Computes dominators using the algorithm of Cooper, Harvey, and Kennedy + /// "A Simple, Fast Dominance Algorithm", 2001. + /// + /// The algorithm assumes there is a unique root node (a node without + /// predecessors), and it is therefore at the end of the postorder vector. + /// + /// This function calculates the dominator edges for a set of blocks in the + /// CFG. + /// Uses the dominator algorithm by Cooper et al. + /// + /// @param[in] postorder A vector of blocks in post order traversal + /// order + /// in a CFG + /// @param[in] predecessor_func Function used to get the predecessor nodes of + /// a + /// block + /// + /// @return the dominator tree of the graph, as a vector of pairs of nodes. + /// The first node in the pair is a node in the graph. The second node in the + /// pair is its immediate dominator in the sense of Cooper et.al., where a + /// block + /// without predecessors (such as the root node) is its own immediate + /// dominator. + static vector> CalculateDominators( + const vector& postorder, get_blocks_func predecessor_func); + + // Computes a minimal set of root nodes required to traverse, in the forward + // direction, the CFG represented by the given vector of blocks, and successor + // and predecessor functions. When considering adding two nodes, each having + // predecessors, favour using the one that appears earlier on the input blocks + // list. + static std::vector TraversalRoots(const std::vector& blocks, + get_blocks_func succ_func, + get_blocks_func pred_func); + + static void ComputeAugmentedCFG( + std::vector& ordered_blocks, BB* pseudo_entry_block, + BB* pseudo_exit_block, + std::unordered_map>* augmented_successors_map, + std::unordered_map>* + augmented_predecessors_map, + get_blocks_func succ_func, get_blocks_func pred_func); +}; + +template +bool CFA::FindInWorkList(const vector& work_list, uint32_t id) { + for (const auto b : work_list) { + if (b.block->id() == id) return true; + } + return false; +} + +template +void CFA::DepthFirstTraversal(const BB* entry, + get_blocks_func successor_func, + function preorder, + function postorder, + function backedge) { + unordered_set processed; + + /// NOTE: work_list is the sequence of nodes from the root node to the node + /// being processed in the traversal + vector work_list; + work_list.reserve(10); + + work_list.push_back({entry, begin(*successor_func(entry))}); + preorder(entry); + processed.insert(entry->id()); + + while (!work_list.empty()) { + block_info& top = work_list.back(); + if (top.iter == end(*successor_func(top.block))) { + postorder(top.block); + work_list.pop_back(); + } else { + BB* child = *top.iter; + top.iter++; + if (FindInWorkList(work_list, child->id())) { + backedge(top.block, child); + } + if (processed.count(child->id()) == 0) { + preorder(child); + work_list.emplace_back( + block_info{child, begin(*successor_func(child))}); + processed.insert(child->id()); + } + } + } +} + +template +vector> CFA::CalculateDominators( + const vector& postorder, get_blocks_func predecessor_func) { + struct block_detail { + size_t dominator; ///< The index of blocks's dominator in post order array + size_t postorder_index; ///< The index of the block in the post order array + }; + const size_t undefined_dom = postorder.size(); + + unordered_map idoms; + for (size_t i = 0; i < postorder.size(); i++) { + idoms[postorder[i]] = {undefined_dom, i}; + } + idoms[postorder.back()].dominator = idoms[postorder.back()].postorder_index; + + bool changed = true; + while (changed) { + changed = false; + for (auto b = postorder.rbegin() + 1; b != postorder.rend(); ++b) { + const vector& predecessors = *predecessor_func(*b); + // Find the first processed/reachable predecessor that is reachable + // in the forward traversal. + auto res = find_if(begin(predecessors), end(predecessors), + [&idoms, undefined_dom](BB* pred) { + return idoms.count(pred) && + idoms[pred].dominator != undefined_dom; + }); + if (res == end(predecessors)) continue; + const BB* idom = *res; + size_t idom_idx = idoms[idom].postorder_index; + + // all other predecessors + for (const auto* p : predecessors) { + if (idom == p) continue; + // Only consider nodes reachable in the forward traversal. + // Otherwise the intersection doesn't make sense and will never + // terminate. + if (!idoms.count(p)) continue; + if (idoms[p].dominator != undefined_dom) { + size_t finger1 = idoms[p].postorder_index; + size_t finger2 = idom_idx; + while (finger1 != finger2) { + while (finger1 < finger2) { + finger1 = idoms[postorder[finger1]].dominator; + } + while (finger2 < finger1) { + finger2 = idoms[postorder[finger2]].dominator; + } + } + idom_idx = finger1; + } + } + if (idoms[*b].dominator != idom_idx) { + idoms[*b].dominator = idom_idx; + changed = true; + } + } + } + + vector> out; + for (auto idom : idoms) { + // NOTE: performing a const cast for convenient usage with + // UpdateImmediateDominators + out.push_back({const_cast(get<0>(idom)), + const_cast(postorder[get<1>(idom).dominator])}); + } + return out; +} + +template +std::vector CFA::TraversalRoots(const std::vector& blocks, + get_blocks_func succ_func, + get_blocks_func pred_func) { + // The set of nodes which have been visited from any of the roots so far. + std::unordered_set visited; + + auto mark_visited = [&visited](const BB* b) { visited.insert(b); }; + auto ignore_block = [](const BB*) {}; + auto ignore_blocks = [](const BB*, const BB*) {}; + + auto traverse_from_root = [&mark_visited, &succ_func, &ignore_block, + &ignore_blocks](const BB* entry) { + DepthFirstTraversal(entry, succ_func, mark_visited, ignore_block, + ignore_blocks); + }; + + std::vector result; + + // First collect nodes without predecessors. + for (auto block : blocks) { + if (pred_func(block)->empty()) { + assert(visited.count(block) == 0 && "Malformed graph!"); + result.push_back(block); + traverse_from_root(block); + } + } + + // Now collect other stranded nodes. These must be in unreachable cycles. + for (auto block : blocks) { + if (visited.count(block) == 0) { + result.push_back(block); + traverse_from_root(block); + } + } + + return result; +} + +template +void CFA::ComputeAugmentedCFG( + std::vector& ordered_blocks, BB* pseudo_entry_block, + BB* pseudo_exit_block, + std::unordered_map>* augmented_successors_map, + std::unordered_map>* augmented_predecessors_map, + get_blocks_func succ_func, get_blocks_func pred_func) { + // Compute the successors of the pseudo-entry block, and + // the predecessors of the pseudo exit block. + auto sources = TraversalRoots(ordered_blocks, succ_func, pred_func); + + // For the predecessor traversals, reverse the order of blocks. This + // will affect the post-dominance calculation as follows: + // - Suppose you have blocks A and B, with A appearing before B in + // the list of blocks. + // - Also, A branches only to B, and B branches only to A. + // - We want to compute A as dominating B, and B as post-dominating B. + // By using reversed blocks for predecessor traversal roots discovery, + // we'll add an edge from B to the pseudo-exit node, rather than from A. + // All this is needed to correctly process the dominance/post-dominance + // constraint when A is a loop header that points to itself as its + // own continue target, and B is the latch block for the loop. + std::vector reversed_blocks(ordered_blocks.rbegin(), + ordered_blocks.rend()); + auto sinks = TraversalRoots(reversed_blocks, pred_func, succ_func); + + // Wire up the pseudo entry block. + (*augmented_successors_map)[pseudo_entry_block] = sources; + for (auto block : sources) { + auto& augmented_preds = (*augmented_predecessors_map)[block]; + const auto preds = pred_func(block); + augmented_preds.reserve(1 + preds->size()); + augmented_preds.push_back(pseudo_entry_block); + augmented_preds.insert(augmented_preds.end(), preds->begin(), preds->end()); + } + + // Wire up the pseudo exit block. + (*augmented_predecessors_map)[pseudo_exit_block] = sinks; + for (auto block : sinks) { + auto& augmented_succ = (*augmented_successors_map)[block]; + const auto succ = succ_func(block); + augmented_succ.reserve(1 + succ->size()); + augmented_succ.push_back(pseudo_exit_block); + augmented_succ.insert(augmented_succ.end(), succ->begin(), succ->end()); + } +} + +} // namespace spvtools + +#endif // SPVTOOLS_CFA_H_ diff --git a/3rdparty/spirv-tools/source/comp/CMakeLists.txt b/3rdparty/spirv-tools/source/comp/CMakeLists.txt new file mode 100644 index 000000000..ff52d5e1e --- /dev/null +++ b/3rdparty/spirv-tools/source/comp/CMakeLists.txt @@ -0,0 +1,38 @@ +# Copyright (c) 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +if(SPIRV_BUILD_COMPRESSION) + add_library(SPIRV-Tools-comp markv_codec.cpp) + + spvtools_default_compile_options(SPIRV-Tools-comp) + target_include_directories(SPIRV-Tools-comp + PUBLIC ${spirv-tools_SOURCE_DIR}/include + PUBLIC ${SPIRV_HEADER_INCLUDE_DIR} + PRIVATE ${spirv-tools_BINARY_DIR} + ) + + target_link_libraries(SPIRV-Tools-comp + PUBLIC ${SPIRV_TOOLS}) + + set_property(TARGET SPIRV-Tools-comp PROPERTY FOLDER "SPIRV-Tools libraries") + spvtools_check_symbol_exports(SPIRV-Tools-comp) + + if(ENABLE_SPIRV_TOOLS_INSTALL) + install(TARGETS SPIRV-Tools-comp + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + endif(ENABLE_SPIRV_TOOLS_INSTALL) + +endif(SPIRV_BUILD_COMPRESSION) diff --git a/3rdparty/spirv-tools/source/comp/markv.h b/3rdparty/spirv-tools/source/comp/markv.h new file mode 100644 index 000000000..288e68085 --- /dev/null +++ b/3rdparty/spirv-tools/source/comp/markv.h @@ -0,0 +1,74 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// MARK-V is a compression format for SPIR-V binaries. It strips away +// non-essential information (such as result ids which can be regenerated) and +// uses various bit reduction techiniques to reduce the size of the binary and +// make it more similar to other compressed SPIR-V files to further improve +// compression of the dataset. + +#ifndef SPIRV_TOOLS_MARKV_HPP_ +#define SPIRV_TOOLS_MARKV_HPP_ + +#include +#include + +#include "markv_model.h" +#include "spirv-tools/libspirv.hpp" + +namespace spvtools { + +struct MarkvCodecOptions { + bool validate_spirv_binary = false; +}; + +// Debug callback. Called once per instruction. +// |words| is instruction SPIR-V words. +// |bits| is a textual representation of the MARK-V bit sequence used to encode +// the instruction (char '0' for 0, char '1' for 1). +// |comment| contains all logs generated while processing the instruction. +using MarkvDebugConsumer = + std::function& words, + const std::string& bits, const std::string& comment)>; + +// Logging callback. Called often (if decoder reads a single bit, the log +// consumer will receive 1 character string with that bit). +// This callback is more suitable for continous output than MarkvDebugConsumer, +// for example if the codec crashes it would allow to pinpoint on which operand +// or bit the crash happened. +// |snippet| could be any atomic fragment of text logged by the codec. It can +// contain a paragraph of text with newlines, or can be just one character. +using MarkvLogConsumer = std::function; + +// Encodes the given SPIR-V binary to MARK-V binary. +// |log_consumer| is optional (pass MarkvLogConsumer() to disable). +// |debug_consumer| is optional (pass MarkvDebugConsumer() to disable). +spv_result_t SpirvToMarkv( + spv_const_context context, const std::vector& spirv, + const MarkvCodecOptions& options, const MarkvModel& markv_model, + MessageConsumer message_consumer, MarkvLogConsumer log_consumer, + MarkvDebugConsumer debug_consumer, std::vector* markv); + +// Decodes a SPIR-V binary from the given MARK-V binary. +// |log_consumer| is optional (pass MarkvLogConsumer() to disable). +// |debug_consumer| is optional (pass MarkvDebugConsumer() to disable). +spv_result_t MarkvToSpirv( + spv_const_context context, const std::vector& markv, + const MarkvCodecOptions& options, const MarkvModel& markv_model, + MessageConsumer message_consumer, MarkvLogConsumer log_consumer, + MarkvDebugConsumer debug_consumer, std::vector* spirv); + +} // namespace spvtools + +#endif // SPIRV_TOOLS_MARKV_HPP_ diff --git a/3rdparty/spirv-tools/source/comp/markv_codec.cpp b/3rdparty/spirv-tools/source/comp/markv_codec.cpp new file mode 100644 index 000000000..cbc25ab02 --- /dev/null +++ b/3rdparty/spirv-tools/source/comp/markv_codec.cpp @@ -0,0 +1,2920 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Contains +// - SPIR-V to MARK-V encoder +// - MARK-V to SPIR-V decoder +// +// MARK-V is a compression format for SPIR-V binaries. It strips away +// non-essential information (such as result ids which can be regenerated) and +// uses various bit reduction techiniques to reduce the size of the binary. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "latest_version_glsl_std_450_header.h" +#include "latest_version_opencl_std_header.h" +#include "latest_version_spirv_header.h" + +#include "binary.h" +#include "diagnostic.h" +#include "enum_string_mapping.h" +#include "ext_inst.h" +#include "extensions.h" +#include "id_descriptor.h" +#include "instruction.h" +#include "markv.h" +#include "markv_model.h" +#include "opcode.h" +#include "operand.h" +#include "spirv-tools/libspirv.h" +#include "spirv_endian.h" +#include "spirv_validator_options.h" +#include "util/bit_stream.h" +#include "util/huffman_codec.h" +#include "util/move_to_front.h" +#include "util/parse_number.h" +#include "val/instruction.h" +#include "val/validation_state.h" +#include "validate.h" + +using libspirv::DiagnosticStream; +using libspirv::IdDescriptorCollection; +using libspirv::Instruction; +using libspirv::ValidationState_t; +using spvutils::BitReaderWord64; +using spvutils::BitWriterWord64; +using spvutils::HuffmanCodec; +using MoveToFront = spvutils::MoveToFront; +using MultiMoveToFront = spvutils::MultiMoveToFront; + +namespace spvtools { + +namespace { + +const uint32_t kSpirvMagicNumber = SpvMagicNumber; +const uint32_t kMarkvMagicNumber = 0x07230303; + +// Handles for move-to-front sequences. Enums which end with "Begin" define +// handle spaces which start at that value and span 16 or 32 bit wide. +enum : uint64_t { + kMtfNone = 0, + // All ids. + kMtfAll, + // All forward declared ids. + kMtfForwardDeclared, + // All type ids except for generated by OpTypeFunction. + kMtfTypeNonFunction, + // All labels. + kMtfLabel, + // All ids created by instructions which had type_id. + kMtfObject, + // All types generated by OpTypeFloat, OpTypeInt, OpTypeBool. + kMtfTypeScalar, + // All composite types. + kMtfTypeComposite, + // Boolean type or any vector type of it. + kMtfTypeBoolScalarOrVector, + // All float types or any vector floats type. + kMtfTypeFloatScalarOrVector, + // All int types or any vector int type. + kMtfTypeIntScalarOrVector, + // All types declared as return types in OpTypeFunction. + kMtfTypeReturnedByFunction, + // All composite objects. + kMtfComposite, + // All bool objects or vectors of bools. + kMtfBoolScalarOrVector, + // All float objects or vectors of float. + kMtfFloatScalarOrVector, + // All int objects or vectors of int. + kMtfIntScalarOrVector, + // All pointer types which point to composited. + kMtfTypePointerToComposite, + // Used by EncodeMtfRankHuffman. + kMtfGenericNonZeroRank, + // Handle space for ids of specific type. + kMtfIdOfTypeBegin = 0x10000, + // Handle space for ids generated by specific opcode. + kMtfIdGeneratedByOpcode = 0x20000, + // Handle space for ids of objects with type generated by specific opcode. + kMtfIdWithTypeGeneratedByOpcodeBegin = 0x30000, + // All vectors of specific component type. + kMtfVectorOfComponentTypeBegin = 0x40000, + // All vector types of specific size. + kMtfTypeVectorOfSizeBegin = 0x50000, + // All pointer types to specific type. + kMtfPointerToTypeBegin = 0x60000, + // All function types which return specific type. + kMtfFunctionTypeWithReturnTypeBegin = 0x70000, + // All function objects which return specific type. + kMtfFunctionWithReturnTypeBegin = 0x80000, + // Short id descriptor space (max 16-bit). + kMtfShortIdDescriptorSpaceBegin = 0x90000, + // Long id descriptor space (32-bit). + kMtfLongIdDescriptorSpaceBegin = 0x100000000, +}; + +// Signals that the value is not in the coding scheme and a fallback method +// needs to be used. +const uint64_t kMarkvNoneOfTheAbove = MarkvModel::GetMarkvNoneOfTheAbove(); + +// Mtf ranks smaller than this are encoded with Huffman coding. +const uint32_t kMtfSmallestRankEncodedByValue = 10; + +// Signals that the mtf rank is too large to be encoded with Huffman. +const uint32_t kMtfRankEncodedByValueSignal = + std::numeric_limits::max(); + +const size_t kCommentNumWhitespaces = 2; + +const size_t kByteBreakAfterInstIfLessThanUntilNextByte = 8; + +const uint32_t kShortDescriptorNumBits = 8; + +// Custom hash function used to produce short descriptors. +uint32_t ShortHashU32Array(const std::vector& words) { + // The hash function is a sum of hashes of each word seeded by word index. + // Knuth's multiplicative hash is used to hash the words. + const uint32_t kKnuthMulHash = 2654435761; + uint32_t val = 0; + for (uint32_t i = 0; i < words.size(); ++i) { + val += (words[i] + i + 123) * kKnuthMulHash; + } + return 1 + val % ((1 << kShortDescriptorNumBits) - 1); +} + +// Returns a set of mtf rank codecs based on a plausible hand-coded +// distribution. +std::map>> +GetMtfHuffmanCodecs() { + std::map>> codecs; + + std::unique_ptr> codec; + + codec.reset(new HuffmanCodec(std::map({ + {0, 5}, + {1, 40}, + {2, 10}, + {3, 5}, + {4, 5}, + {5, 5}, + {6, 3}, + {7, 3}, + {8, 3}, + {9, 3}, + {kMtfRankEncodedByValueSignal, 10}, + }))); + codecs.emplace(kMtfAll, std::move(codec)); + + codec.reset(new HuffmanCodec(std::map({ + {1, 50}, + {2, 20}, + {3, 5}, + {4, 5}, + {5, 2}, + {6, 1}, + {7, 1}, + {8, 1}, + {9, 1}, + {kMtfRankEncodedByValueSignal, 10}, + }))); + codecs.emplace(kMtfGenericNonZeroRank, std::move(codec)); + + return codecs; +} + +// Returns true if the opcode has a fixed number of operands. May return a +// false negative. +bool OpcodeHasFixedNumberOfOperands(SpvOp opcode) { + switch (opcode) { + // TODO(atgoo@github.com) This is not a complete list. + case SpvOpNop: + case SpvOpName: + case SpvOpUndef: + case SpvOpSizeOf: + case SpvOpLine: + case SpvOpNoLine: + case SpvOpDecorationGroup: + case SpvOpExtension: + case SpvOpExtInstImport: + case SpvOpMemoryModel: + case SpvOpCapability: + case SpvOpTypeVoid: + case SpvOpTypeBool: + case SpvOpTypeInt: + case SpvOpTypeFloat: + case SpvOpTypeVector: + case SpvOpTypeMatrix: + case SpvOpTypeSampler: + case SpvOpTypeSampledImage: + case SpvOpTypeArray: + case SpvOpTypePointer: + case SpvOpConstantTrue: + case SpvOpConstantFalse: + case SpvOpLabel: + case SpvOpBranch: + case SpvOpFunction: + case SpvOpFunctionParameter: + case SpvOpFunctionEnd: + case SpvOpBitcast: + case SpvOpCopyObject: + case SpvOpTranspose: + case SpvOpSNegate: + case SpvOpFNegate: + case SpvOpIAdd: + case SpvOpFAdd: + case SpvOpISub: + case SpvOpFSub: + case SpvOpIMul: + case SpvOpFMul: + case SpvOpUDiv: + case SpvOpSDiv: + case SpvOpFDiv: + case SpvOpUMod: + case SpvOpSRem: + case SpvOpSMod: + case SpvOpFRem: + case SpvOpFMod: + case SpvOpVectorTimesScalar: + case SpvOpMatrixTimesScalar: + case SpvOpVectorTimesMatrix: + case SpvOpMatrixTimesVector: + case SpvOpMatrixTimesMatrix: + case SpvOpOuterProduct: + case SpvOpDot: + return true; + default: + break; + } + return false; +} + +size_t GetNumBitsToNextByte(size_t bit_pos) { return (8 - (bit_pos % 8)) % 8; } + +// Defines and returns current MARK-V version. +uint32_t GetMarkvVersion() { + const uint32_t kVersionMajor = 1; + const uint32_t kVersionMinor = 4; + return kVersionMinor | (kVersionMajor << 16); +} + +class MarkvLogger { + public: + MarkvLogger(MarkvLogConsumer log_consumer, MarkvDebugConsumer debug_consumer) + : log_consumer_(log_consumer), debug_consumer_(debug_consumer) {} + + void AppendText(const std::string& str) { + Append(str); + use_delimiter_ = false; + } + + void AppendTextNewLine(const std::string& str) { + Append(str); + Append("\n"); + use_delimiter_ = false; + } + + void AppendBitSequence(const std::string& str) { + if (debug_consumer_) instruction_bits_ << str; + if (use_delimiter_) Append("-"); + Append(str); + use_delimiter_ = true; + } + + void AppendWhitespaces(size_t num) { + Append(std::string(num, ' ')); + use_delimiter_ = false; + } + + void NewLine() { + Append("\n"); + use_delimiter_ = false; + } + + bool DebugInstruction(const spv_parsed_instruction_t& inst) { + bool result = true; + if (debug_consumer_) { + result = debug_consumer_( + std::vector(inst.words, inst.words + inst.num_words), + instruction_bits_.str(), instruction_comment_.str()); + instruction_bits_.str(std::string()); + instruction_comment_.str(std::string()); + } + return result; + } + + private: + MarkvLogger(const MarkvLogger&) = delete; + MarkvLogger(MarkvLogger&&) = delete; + MarkvLogger& operator=(const MarkvLogger&) = delete; + MarkvLogger& operator=(MarkvLogger&&) = delete; + + void Append(const std::string& str) { + if (log_consumer_) log_consumer_(str); + if (debug_consumer_) instruction_comment_ << str; + } + + MarkvLogConsumer log_consumer_; + MarkvDebugConsumer debug_consumer_; + + std::stringstream instruction_bits_; + std::stringstream instruction_comment_; + + // If true a delimiter will be appended before the next bit sequence. + // Used to generate outputs like: 1100-0 1110-1-1100-1-1111-0 110-0. + bool use_delimiter_ = false; +}; + +// Base class for MARK-V encoder and decoder. Contains common functionality +// such as: +// - Validator connection and validation state. +// - SPIR-V grammar and helper functions. +class MarkvCodecBase { + public: + virtual ~MarkvCodecBase() { spvValidatorOptionsDestroy(validator_options_); } + + MarkvCodecBase() = delete; + + protected: + struct MarkvHeader { + MarkvHeader() { + magic_number = kMarkvMagicNumber; + markv_version = GetMarkvVersion(); + markv_model = 0; + markv_length_in_bits = 0; + spirv_version = 0; + spirv_generator = 0; + } + + uint32_t magic_number; + uint32_t markv_version; + // Magic number to identify or verify MarkvModel used for encoding. + uint32_t markv_model; + uint32_t markv_length_in_bits; + uint32_t spirv_version; + uint32_t spirv_generator; + }; + + // |model| is owned by the caller, must be not null and valid during the + // lifetime of the codec. + explicit MarkvCodecBase(spv_const_context context, + spv_validator_options validator_options, + const MarkvModel* model) + : validator_options_(validator_options), + grammar_(context), + model_(model), + short_id_descriptors_(ShortHashU32Array), + mtf_huffman_codecs_(GetMtfHuffmanCodecs()), + context_(context), + vstate_(validator_options + ? new ValidationState_t(context, validator_options_) + : nullptr) {} + + // Validates a single instruction and updates validation state of the module. + // Does nothing and returns SPV_SUCCESS if validator was not created. + spv_result_t UpdateValidationState(const spv_parsed_instruction_t& inst) { + if (!vstate_) return SPV_SUCCESS; + + return ValidateInstructionAndUpdateValidationState(vstate_.get(), &inst); + } + + // Returns instruction which created |id| or nullptr if such instruction was + // not registered. + const Instruction* FindDef(uint32_t id) const { + const auto it = id_to_def_instruction_.find(id); + if (it == id_to_def_instruction_.end()) return nullptr; + return it->second; + } + + // Returns type id of vector type component. + uint32_t GetVectorComponentType(uint32_t vector_type_id) const { + const Instruction* type_inst = FindDef(vector_type_id); + assert(type_inst); + assert(type_inst->opcode() == SpvOpTypeVector); + + const uint32_t component_type = + type_inst->word(type_inst->operands()[1].offset); + return component_type; + } + + // Returns mtf handle for ids of given type. + uint64_t GetMtfIdOfType(uint32_t type_id) const { + return kMtfIdOfTypeBegin + type_id; + } + + // Returns mtf handle for ids generated by given opcode. + uint64_t GetMtfIdGeneratedByOpcode(SpvOp opcode) const { + return kMtfIdGeneratedByOpcode + opcode; + } + + // Returns mtf handle for ids of type generated by given opcode. + uint64_t GetMtfIdWithTypeGeneratedByOpcode(SpvOp opcode) const { + return kMtfIdWithTypeGeneratedByOpcodeBegin + opcode; + } + + // Returns mtf handle for vectors of specific component type. + uint64_t GetMtfVectorOfComponentType(uint32_t type_id) const { + return kMtfVectorOfComponentTypeBegin + type_id; + } + + // Returns mtf handle for vector type of specific size. + uint64_t GetMtfTypeVectorOfSize(uint32_t size) const { + return kMtfTypeVectorOfSizeBegin + size; + } + + // Returns mtf handle for pointers to specific size. + uint64_t GetMtfPointerToType(uint32_t type_id) const { + return kMtfPointerToTypeBegin + type_id; + } + + // Returns mtf handle for function types with given return type. + uint64_t GetMtfFunctionTypeWithReturnType(uint32_t type_id) const { + return kMtfFunctionTypeWithReturnTypeBegin + type_id; + } + + // Returns mtf handle for functions with given return type. + uint64_t GetMtfFunctionWithReturnType(uint32_t type_id) const { + return kMtfFunctionWithReturnTypeBegin + type_id; + } + + // Returns mtf handle for the given long id descriptor. + uint64_t GetMtfLongIdDescriptor(uint32_t descriptor) const { + return kMtfLongIdDescriptorSpaceBegin + descriptor; + } + + // Returns mtf handle for the given short id descriptor. + uint64_t GetMtfShortIdDescriptor(uint32_t descriptor) const { + return kMtfShortIdDescriptorSpaceBegin + descriptor; + } + + // Process data from the current instruction. This would update MTFs and + // other data containers. + void ProcessCurInstruction(); + + // Returns move-to-front handle to be used for the current operand slot. + // Mtf handle is chosen based on a set of rules defined by SPIR-V grammar. + uint64_t GetRuleBasedMtf(); + + // Returns words of the current instruction. Decoder has a different + // implementation and the array is valid only until the previously decoded + // word. + virtual const uint32_t* GetInstWords() const { return inst_.words; } + + // Returns the opcode of the previous instruction. + SpvOp GetPrevOpcode() const { + if (instructions_.empty()) return SpvOpNop; + + return instructions_.back()->opcode(); + } + + // Returns diagnostic stream, position index is set to instruction number. + DiagnosticStream Diag(spv_result_t error_code) const { + return DiagnosticStream({0, 0, instructions_.size()}, context_->consumer, + error_code); + } + + // Returns current id bound. + uint32_t GetIdBound() const { return id_bound_; } + + // Sets current id bound, expected to be no lower than the previous one. + void SetIdBound(uint32_t id_bound) { + assert(id_bound >= id_bound_); + id_bound_ = id_bound; + if (vstate_) vstate_->setIdBound(id_bound); + } + + // Returns Huffman codec for ranks of the mtf with given |handle|. + // Different mtfs can use different rank distributions. + // May return nullptr if the codec doesn't exist. + const spvutils::HuffmanCodec* GetMtfHuffmanCodec( + uint64_t handle) const { + const auto it = mtf_huffman_codecs_.find(handle); + if (it == mtf_huffman_codecs_.end()) return nullptr; + return it->second.get(); + } + + // Promotes id in all move-to-front sequences if ids can be shared by multiple + // sequences. + void PromoteIfNeeded(uint32_t id) { + if (!model_->AnyDescriptorHasCodingScheme() && + model_->id_fallback_strategy() == + MarkvModel::IdFallbackStrategy::kShortDescriptor) { + // Move-to-front sequences do not share ids. Nothing to do. + return; + } + multi_mtf_.Promote(id); + } + + spv_validator_options validator_options_ = nullptr; + const libspirv::AssemblyGrammar grammar_; + MarkvHeader header_; + + // MARK-V model, not owned. + const MarkvModel* model_ = nullptr; + + // Current instruction, current operand and current operand index. + spv_parsed_instruction_t inst_; + spv_parsed_operand_t operand_; + uint32_t operand_index_; + + // Maps a result ID to its type ID. By convention: + // - a result ID that is a type definition maps to itself. + // - a result ID without a type maps to 0. (E.g. for OpLabel) + std::unordered_map id_to_type_id_; + + // Container for all move-to-front sequences. + MultiMoveToFront multi_mtf_; + + // Id of the current function or zero if outside of function. + uint32_t cur_function_id_ = 0; + + // Return type of the current function. + uint32_t cur_function_return_type_ = 0; + + // Remaining function parameter types. This container is filled on OpFunction, + // and drained on OpFunctionParameter. + std::list remaining_function_parameter_types_; + + // List of ids local to the current function. + std::vector ids_local_to_cur_function_; + + // List of instructions in the order they are given in the module. + std::vector> instructions_; + + // Container/computer for long (32-bit) id descriptors. + IdDescriptorCollection long_id_descriptors_; + + // Container/computer for short id descriptors. + // Short descriptors are stored in uint32_t, but their actual bit width is + // defined with kShortDescriptorNumBits. + // It doesn't seem logical to have a different computer for short id + // descriptors, since one could actually map/truncate long descriptors. + // But as short descriptors have collisions, the efficiency of + // compression depends on the collision pattern, and short descriptors + // produced by function ShortHashU32Array have been empirically proven to + // produce better results. + IdDescriptorCollection short_id_descriptors_; + + // Huffman codecs for move-to-front ranks. The map key is mtf handle. Doesn't + // need to contain a different codec for every handle as most use one and the + // same. + std::map>> + mtf_huffman_codecs_; + + // If not nullptr, codec will log comments on the compression process. + std::unique_ptr logger_; + + private: + spv_const_context context_ = nullptr; + + std::unique_ptr vstate_; + + // Maps result id to the instruction which defined it. + std::unordered_map id_to_def_instruction_; + + uint32_t id_bound_ = 1; +}; + +// SPIR-V to MARK-V encoder. Exposes functions EncodeHeader and +// EncodeInstruction which can be used as callback by spvBinaryParse. +// Encoded binary is written to an internally maintained bitstream. +// After the last instruction is encoded, the resulting MARK-V binary can be +// acquired by calling GetMarkvBinary(). +// The encoder uses SPIR-V validator to keep internal state, therefore +// SPIR-V binary needs to be able to pass validator checks. +// CreateCommentsLogger() can be used to enable the encoder to write comments +// on how encoding was done, which can later be accessed with GetComments(). +class MarkvEncoder : public MarkvCodecBase { + public: + // |model| is owned by the caller, must be not null and valid during the + // lifetime of MarkvEncoder. + MarkvEncoder(spv_const_context context, const MarkvCodecOptions& options, + const MarkvModel* model) + : MarkvCodecBase(context, GetValidatorOptions(options), model), + options_(options) { + (void)options_; + } + + // Writes data from SPIR-V header to MARK-V header. + spv_result_t EncodeHeader(spv_endianness_t /* endian */, uint32_t /* magic */, + uint32_t version, uint32_t generator, + uint32_t id_bound, uint32_t /* schema */) { + SetIdBound(id_bound); + header_.spirv_version = version; + header_.spirv_generator = generator; + return SPV_SUCCESS; + } + + // Creates an internal logger which writes comments on the encoding process. + void CreateLogger(MarkvLogConsumer log_consumer, + MarkvDebugConsumer debug_consumer) { + logger_.reset(new MarkvLogger(log_consumer, debug_consumer)); + writer_.SetCallback( + [this](const std::string& str) { logger_->AppendBitSequence(str); }); + } + + // Encodes SPIR-V instruction to MARK-V and writes to bit stream. + // Operation can fail if the instruction fails to pass the validator or if + // the encoder stubmles on something unexpected. + spv_result_t EncodeInstruction(const spv_parsed_instruction_t& inst); + + // Concatenates MARK-V header and the bit stream with encoded instructions + // into a single buffer and returns it as spv_markv_binary. The returned + // value is owned by the caller and needs to be destroyed with + // spvMarkvBinaryDestroy(). + std::vector GetMarkvBinary() { + header_.markv_length_in_bits = + static_cast(sizeof(header_) * 8 + writer_.GetNumBits()); + header_.markv_model = + (model_->model_type() << 16) | model_->model_version(); + + const size_t num_bytes = sizeof(header_) + writer_.GetDataSizeBytes(); + std::vector markv(num_bytes); + + assert(writer_.GetData()); + std::memcpy(markv.data(), &header_, sizeof(header_)); + std::memcpy(markv.data() + sizeof(header_), writer_.GetData(), + writer_.GetDataSizeBytes()); + return markv; + } + + // Optionally adds disassembly to the comments. + // Disassembly should contain all instructions in the module separated by + // \n, and no header. + void SetDisassembly(std::string&& disassembly) { + disassembly_.reset(new std::stringstream(std::move(disassembly))); + } + + // Extracts the next instruction line from the disassembly and logs it. + void LogDisassemblyInstruction() { + if (logger_ && disassembly_) { + std::string line; + std::getline(*disassembly_, line, '\n'); + logger_->AppendTextNewLine(line); + } + } + + private: + // Creates and returns validator options. Returned value owned by the caller. + static spv_validator_options GetValidatorOptions( + const MarkvCodecOptions& options) { + return options.validate_spirv_binary ? spvValidatorOptionsCreate() + : nullptr; + } + + // Writes a single word to bit stream. operand_.type determines if the word is + // encoded and how. + spv_result_t EncodeNonIdWord(uint32_t word); + + // Writes both opcode and num_operands as a single code. + // Returns SPV_UNSUPPORTED iff no suitable codec was found. + spv_result_t EncodeOpcodeAndNumOperands(uint32_t opcode, + uint32_t num_operands); + + // Writes mtf rank to bit stream. |mtf| is used to determine the codec + // scheme. |fallback_method| is used if no codec defined for |mtf|. + spv_result_t EncodeMtfRankHuffman(uint32_t rank, uint64_t mtf, + uint64_t fallback_method); + + // Writes id using coding based on mtf associated with the id descriptor. + // Returns SPV_UNSUPPORTED iff fallback method needs to be used. + spv_result_t EncodeIdWithDescriptor(uint32_t id); + + // Writes id using coding based on the given |mtf|, which is expected to + // contain the given |id|. + spv_result_t EncodeExistingId(uint64_t mtf, uint32_t id); + + // Writes type id of the current instruction if can't be inferred. + spv_result_t EncodeTypeId(); + + // Writes result id of the current instruction if can't be inferred. + spv_result_t EncodeResultId(); + + // Writes ids which are neither type nor result ids. + spv_result_t EncodeRefId(uint32_t id); + + // Writes bits to the stream until the beginning of the next byte if the + // number of bits until the next byte is less than |byte_break_if_less_than|. + void AddByteBreak(size_t byte_break_if_less_than); + + // Encodes a literal number operand and writes it to the bit stream. + spv_result_t EncodeLiteralNumber(const spv_parsed_operand_t& operand); + + MarkvCodecOptions options_; + + // Bit stream where encoded instructions are written. + BitWriterWord64 writer_; + + // If not nullptr, disassembled instruction lines will be written to comments. + // Format: \n separated instruction lines, no header. + std::unique_ptr disassembly_; +}; + +// Decodes MARK-V buffers written by MarkvEncoder. +class MarkvDecoder : public MarkvCodecBase { + public: + // |model| is owned by the caller, must be not null and valid during the + // lifetime of MarkvEncoder. + MarkvDecoder(spv_const_context context, const std::vector& markv, + const MarkvCodecOptions& options, const MarkvModel* model) + : MarkvCodecBase(context, GetValidatorOptions(options), model), + options_(options), + reader_(markv) { + (void)options_; + SetIdBound(1); + parsed_operands_.reserve(25); + inst_words_.reserve(25); + } + + // Creates an internal logger which writes comments on the decoding process. + void CreateLogger(MarkvLogConsumer log_consumer, + MarkvDebugConsumer debug_consumer) { + logger_.reset(new MarkvLogger(log_consumer, debug_consumer)); + } + + // Decodes SPIR-V from MARK-V and stores the words in |spirv_binary|. + // Can be called only once. Fails if data of wrong format or ends prematurely, + // of if validation fails. + spv_result_t DecodeModule(std::vector* spirv_binary); + + private: + // Describes the format of a typed literal number. + struct NumberType { + spv_number_kind_t type; + uint32_t bit_width; + }; + + // Creates and returns validator options. Returned value owned by the caller. + static spv_validator_options GetValidatorOptions( + const MarkvCodecOptions& options) { + return options.validate_spirv_binary ? spvValidatorOptionsCreate() + : nullptr; + } + + // Reads a single bit from reader_. The read bit is stored in |bit|. + // Returns false iff reader_ fails. + bool ReadBit(bool* bit) { + uint64_t bits = 0; + const bool result = reader_.ReadBits(&bits, 1); + if (result) *bit = bits ? true : false; + return result; + }; + + // Returns ReadBit bound to the class object. + std::function GetReadBitCallback() { + return std::bind(&MarkvDecoder::ReadBit, this, std::placeholders::_1); + } + + // Reads a single non-id word from bit stream. operand_.type determines if + // the word needs to be decoded and how. + spv_result_t DecodeNonIdWord(uint32_t* word); + + // Reads and decodes both opcode and num_operands as a single code. + // Returns SPV_UNSUPPORTED iff no suitable codec was found. + spv_result_t DecodeOpcodeAndNumberOfOperands(uint32_t* opcode, + uint32_t* num_operands); + + // Reads mtf rank from bit stream. |mtf| is used to determine the codec + // scheme. |fallback_method| is used if no codec defined for |mtf|. + spv_result_t DecodeMtfRankHuffman(uint64_t mtf, uint32_t fallback_method, + uint32_t* rank); + + // Reads id using coding based on mtf associated with the id descriptor. + // Returns SPV_UNSUPPORTED iff fallback method needs to be used. + spv_result_t DecodeIdWithDescriptor(uint32_t* id); + + // Reads id using coding based on the given |mtf|, which is expected to + // contain the needed |id|. + spv_result_t DecodeExistingId(uint64_t mtf, uint32_t* id); + + // Reads type id of the current instruction if can't be inferred. + spv_result_t DecodeTypeId(); + + // Reads result id of the current instruction if can't be inferred. + spv_result_t DecodeResultId(); + + // Reads id which is neither type nor result id. + spv_result_t DecodeRefId(uint32_t* id); + + // Reads and discards bits until the beginning of the next byte if the + // number of bits until the next byte is less than |byte_break_if_less_than|. + bool ReadToByteBreak(size_t byte_break_if_less_than); + + // Returns instruction words decoded up to this point. + const uint32_t* GetInstWords() const override { return inst_words_.data(); } + + // Reads a literal number as it is described in |operand| from the bit stream, + // decodes and writes it to spirv_. + spv_result_t DecodeLiteralNumber(const spv_parsed_operand_t& operand); + + // Reads instruction from bit stream, decodes and validates it. + // Decoded instruction is valid until the next call of DecodeInstruction(). + spv_result_t DecodeInstruction(); + + // Read operand from the stream decodes and validates it. + spv_result_t DecodeOperand(size_t operand_offset, + const spv_operand_type_t type, + spv_operand_pattern_t* expected_operands); + + // Records the numeric type for an operand according to the type information + // associated with the given non-zero type Id. This can fail if the type Id + // is not a type Id, or if the type Id does not reference a scalar numeric + // type. On success, return SPV_SUCCESS and populates the num_words, + // number_kind, and number_bit_width fields of parsed_operand. + spv_result_t SetNumericTypeInfoForType(spv_parsed_operand_t* parsed_operand, + uint32_t type_id); + + // Records the number type for the current instruction, if it generates a + // type. For types that aren't scalar numbers, record something with number + // kind SPV_NUMBER_NONE. + void RecordNumberType(); + + MarkvCodecOptions options_; + + // Temporary sink where decoded SPIR-V words are written. Once it contains the + // entire module, the container is moved and returned. + std::vector spirv_; + + // Bit stream containing encoded data. + BitReaderWord64 reader_; + + // Temporary storage for operands of the currently parsed instruction. + // Valid until next DecodeInstruction call. + std::vector parsed_operands_; + + // Temporary storage for current instruction words. + // Valid until next DecodeInstruction call. + std::vector inst_words_; + + // Maps a type ID to its number type description. + std::unordered_map type_id_to_number_type_info_; + + // Maps an ExtInstImport id to the extended instruction type. + std::unordered_map import_id_to_ext_inst_type_; +}; + +void MarkvCodecBase::ProcessCurInstruction() { + instructions_.emplace_back(new Instruction(&inst_)); + + const SpvOp opcode = SpvOp(inst_.opcode); + + if (inst_.result_id) { + id_to_def_instruction_.emplace(inst_.result_id, instructions_.back().get()); + + // Collect ids local to the current function. + if (cur_function_id_) { + ids_local_to_cur_function_.push_back(inst_.result_id); + } + + // Starting new function. + if (opcode == SpvOpFunction) { + cur_function_id_ = inst_.result_id; + cur_function_return_type_ = inst_.type_id; + if (model_->id_fallback_strategy() == + MarkvModel::IdFallbackStrategy::kRuleBased) { + multi_mtf_.Insert(GetMtfFunctionWithReturnType(inst_.type_id), + inst_.result_id); + } + + // Store function parameter types in a queue, so that we know which types + // to expect in the following OpFunctionParameter instructions. + const Instruction* def_inst = FindDef(inst_.words[4]); + assert(def_inst); + assert(def_inst->opcode() == SpvOpTypeFunction); + for (uint32_t i = 3; i < def_inst->words().size(); ++i) { + remaining_function_parameter_types_.push_back(def_inst->word(i)); + } + } + } + + // Remove local ids from MTFs if function end. + if (opcode == SpvOpFunctionEnd) { + cur_function_id_ = 0; + for (uint32_t id : ids_local_to_cur_function_) multi_mtf_.RemoveFromAll(id); + ids_local_to_cur_function_.clear(); + assert(remaining_function_parameter_types_.empty()); + } + + if (!inst_.result_id) return; + + { + // Save the result ID to type ID mapping. + // In the grammar, type ID always appears before result ID. + // A regular value maps to its type. Some instructions (e.g. OpLabel) + // have no type Id, and will map to 0. The result Id for a + // type-generating instruction (e.g. OpTypeInt) maps to itself. + auto insertion_result = id_to_type_id_.emplace( + inst_.result_id, spvOpcodeGeneratesType(SpvOp(inst_.opcode)) + ? inst_.result_id + : inst_.type_id); + (void)insertion_result; + assert(insertion_result.second); + } + + // Add result_id to MTFs. + if (model_->id_fallback_strategy() == + MarkvModel::IdFallbackStrategy::kRuleBased) { + switch (opcode) { + case SpvOpTypeFloat: + case SpvOpTypeInt: + case SpvOpTypeBool: + case SpvOpTypeVector: + case SpvOpTypePointer: + case SpvOpExtInstImport: + case SpvOpTypeSampledImage: + case SpvOpTypeImage: + case SpvOpTypeSampler: + multi_mtf_.Insert(GetMtfIdGeneratedByOpcode(opcode), inst_.result_id); + break; + default: + break; + } + + if (spvOpcodeIsComposite(opcode)) { + multi_mtf_.Insert(kMtfTypeComposite, inst_.result_id); + } + + if (opcode == SpvOpLabel) { + multi_mtf_.InsertOrPromote(kMtfLabel, inst_.result_id); + } + + if (opcode == SpvOpTypeInt) { + multi_mtf_.Insert(kMtfTypeScalar, inst_.result_id); + multi_mtf_.Insert(kMtfTypeIntScalarOrVector, inst_.result_id); + } + + if (opcode == SpvOpTypeFloat) { + multi_mtf_.Insert(kMtfTypeScalar, inst_.result_id); + multi_mtf_.Insert(kMtfTypeFloatScalarOrVector, inst_.result_id); + } + + if (opcode == SpvOpTypeBool) { + multi_mtf_.Insert(kMtfTypeScalar, inst_.result_id); + multi_mtf_.Insert(kMtfTypeBoolScalarOrVector, inst_.result_id); + } + + if (opcode == SpvOpTypeVector) { + const uint32_t component_type_id = inst_.words[2]; + const uint32_t size = inst_.words[3]; + if (multi_mtf_.HasValue(GetMtfIdGeneratedByOpcode(SpvOpTypeFloat), + component_type_id)) { + multi_mtf_.Insert(kMtfTypeFloatScalarOrVector, inst_.result_id); + } else if (multi_mtf_.HasValue(GetMtfIdGeneratedByOpcode(SpvOpTypeInt), + component_type_id)) { + multi_mtf_.Insert(kMtfTypeIntScalarOrVector, inst_.result_id); + } else if (multi_mtf_.HasValue(GetMtfIdGeneratedByOpcode(SpvOpTypeBool), + component_type_id)) { + multi_mtf_.Insert(kMtfTypeBoolScalarOrVector, inst_.result_id); + } + multi_mtf_.Insert(GetMtfTypeVectorOfSize(size), inst_.result_id); + } + + if (inst_.opcode == SpvOpTypeFunction) { + const uint32_t return_type = inst_.words[2]; + multi_mtf_.Insert(kMtfTypeReturnedByFunction, return_type); + multi_mtf_.Insert(GetMtfFunctionTypeWithReturnType(return_type), + inst_.result_id); + } + + if (inst_.type_id) { + const Instruction* type_inst = FindDef(inst_.type_id); + assert(type_inst); + + multi_mtf_.Insert(kMtfObject, inst_.result_id); + + multi_mtf_.Insert(GetMtfIdOfType(inst_.type_id), inst_.result_id); + + if (multi_mtf_.HasValue(kMtfTypeFloatScalarOrVector, inst_.type_id)) { + multi_mtf_.Insert(kMtfFloatScalarOrVector, inst_.result_id); + } + + if (multi_mtf_.HasValue(kMtfTypeIntScalarOrVector, inst_.type_id)) + multi_mtf_.Insert(kMtfIntScalarOrVector, inst_.result_id); + + if (multi_mtf_.HasValue(kMtfTypeBoolScalarOrVector, inst_.type_id)) + multi_mtf_.Insert(kMtfBoolScalarOrVector, inst_.result_id); + + if (multi_mtf_.HasValue(kMtfTypeComposite, inst_.type_id)) + multi_mtf_.Insert(kMtfComposite, inst_.result_id); + + switch (type_inst->opcode()) { + case SpvOpTypeInt: + case SpvOpTypeBool: + case SpvOpTypePointer: + case SpvOpTypeVector: + case SpvOpTypeImage: + case SpvOpTypeSampledImage: + case SpvOpTypeSampler: + multi_mtf_.Insert( + GetMtfIdWithTypeGeneratedByOpcode(type_inst->opcode()), + inst_.result_id); + break; + default: + break; + } + + if (type_inst->opcode() == SpvOpTypeVector) { + const uint32_t component_type = type_inst->word(2); + multi_mtf_.Insert(GetMtfVectorOfComponentType(component_type), + inst_.result_id); + } + + if (type_inst->opcode() == SpvOpTypePointer) { + assert(type_inst->operands().size() > 2); + assert(type_inst->words().size() > type_inst->operands()[2].offset); + const uint32_t data_type = + type_inst->word(type_inst->operands()[2].offset); + multi_mtf_.Insert(GetMtfPointerToType(data_type), inst_.result_id); + + if (multi_mtf_.HasValue(kMtfTypeComposite, data_type)) + multi_mtf_.Insert(kMtfTypePointerToComposite, inst_.result_id); + } + } + + if (spvOpcodeGeneratesType(opcode)) { + if (opcode != SpvOpTypeFunction) { + multi_mtf_.Insert(kMtfTypeNonFunction, inst_.result_id); + } + } + } + + if (model_->AnyDescriptorHasCodingScheme()) { + const uint32_t long_descriptor = + long_id_descriptors_.ProcessInstruction(inst_); + if (model_->DescriptorHasCodingScheme(long_descriptor)) + multi_mtf_.Insert(GetMtfLongIdDescriptor(long_descriptor), + inst_.result_id); + } + + if (model_->id_fallback_strategy() == + MarkvModel::IdFallbackStrategy::kShortDescriptor) { + const uint32_t short_descriptor = + short_id_descriptors_.ProcessInstruction(inst_); + multi_mtf_.Insert(GetMtfShortIdDescriptor(short_descriptor), + inst_.result_id); + } +} + +uint64_t MarkvCodecBase::GetRuleBasedMtf() { + // This function is only called for id operands (but not result ids). + assert(spvIsIdType(operand_.type) || + operand_.type == SPV_OPERAND_TYPE_OPTIONAL_ID); + assert(operand_.type != SPV_OPERAND_TYPE_RESULT_ID); + + const SpvOp opcode = static_cast(inst_.opcode); + + // All operand slots which expect label id. + if ((inst_.opcode == SpvOpLoopMerge && operand_index_ <= 1) || + (inst_.opcode == SpvOpSelectionMerge && operand_index_ == 0) || + (inst_.opcode == SpvOpBranch && operand_index_ == 0) || + (inst_.opcode == SpvOpBranchConditional && + (operand_index_ == 1 || operand_index_ == 2)) || + (inst_.opcode == SpvOpPhi && operand_index_ >= 3 && + operand_index_ % 2 == 1) || + (inst_.opcode == SpvOpSwitch && operand_index_ > 0)) { + return kMtfLabel; + } + + switch (opcode) { + case SpvOpFAdd: + case SpvOpFSub: + case SpvOpFMul: + case SpvOpFDiv: + case SpvOpFRem: + case SpvOpFMod: + case SpvOpFNegate: { + if (operand_index_ == 0) return kMtfTypeFloatScalarOrVector; + return GetMtfIdOfType(inst_.type_id); + } + + case SpvOpISub: + case SpvOpIAdd: + case SpvOpIMul: + case SpvOpSDiv: + case SpvOpUDiv: + case SpvOpSMod: + case SpvOpUMod: + case SpvOpSRem: + case SpvOpSNegate: { + if (operand_index_ == 0) return kMtfTypeIntScalarOrVector; + + return kMtfIntScalarOrVector; + } + + // TODO(atgoo@github.com) Add OpConvertFToU and other opcodes. + + case SpvOpFOrdEqual: + case SpvOpFUnordEqual: + case SpvOpFOrdNotEqual: + case SpvOpFUnordNotEqual: + case SpvOpFOrdLessThan: + case SpvOpFUnordLessThan: + case SpvOpFOrdGreaterThan: + case SpvOpFUnordGreaterThan: + case SpvOpFOrdLessThanEqual: + case SpvOpFUnordLessThanEqual: + case SpvOpFOrdGreaterThanEqual: + case SpvOpFUnordGreaterThanEqual: { + if (operand_index_ == 0) return kMtfTypeBoolScalarOrVector; + if (operand_index_ == 2) return kMtfFloatScalarOrVector; + if (operand_index_ == 3) { + const uint32_t first_operand_id = GetInstWords()[3]; + const uint32_t first_operand_type = id_to_type_id_.at(first_operand_id); + return GetMtfIdOfType(first_operand_type); + } + break; + } + + case SpvOpVectorShuffle: { + if (operand_index_ == 0) { + assert(inst_.num_operands > 4); + return GetMtfTypeVectorOfSize(inst_.num_operands - 4); + } + + assert(inst_.type_id); + if (operand_index_ == 2 || operand_index_ == 3) + return GetMtfVectorOfComponentType( + GetVectorComponentType(inst_.type_id)); + break; + } + + case SpvOpVectorTimesScalar: { + if (operand_index_ == 0) { + // TODO(atgoo@github.com) Could be narrowed to vector of floats. + return GetMtfIdGeneratedByOpcode(SpvOpTypeVector); + } + + assert(inst_.type_id); + if (operand_index_ == 2) return GetMtfIdOfType(inst_.type_id); + if (operand_index_ == 3) + return GetMtfIdOfType(GetVectorComponentType(inst_.type_id)); + break; + } + + case SpvOpDot: { + if (operand_index_ == 0) return GetMtfIdGeneratedByOpcode(SpvOpTypeFloat); + + assert(inst_.type_id); + if (operand_index_ == 2) + return GetMtfVectorOfComponentType(inst_.type_id); + if (operand_index_ == 3) { + const uint32_t vector_id = GetInstWords()[3]; + const uint32_t vector_type = id_to_type_id_.at(vector_id); + return GetMtfIdOfType(vector_type); + } + break; + } + + case SpvOpTypeVector: { + if (operand_index_ == 1) { + return kMtfTypeScalar; + } + break; + } + + case SpvOpTypeMatrix: { + if (operand_index_ == 1) { + return GetMtfIdGeneratedByOpcode(SpvOpTypeVector); + } + break; + } + + case SpvOpTypePointer: { + if (operand_index_ == 2) { + return kMtfTypeNonFunction; + } + break; + } + + case SpvOpTypeStruct: { + if (operand_index_ >= 1) { + return kMtfTypeNonFunction; + } + break; + } + + case SpvOpTypeFunction: { + if (operand_index_ == 1) { + return kMtfTypeNonFunction; + } + + if (operand_index_ >= 2) { + return kMtfTypeNonFunction; + } + break; + } + + case SpvOpLoad: { + if (operand_index_ == 0) return kMtfTypeNonFunction; + + if (operand_index_ == 2) { + assert(inst_.type_id); + return GetMtfPointerToType(inst_.type_id); + } + break; + } + + case SpvOpStore: { + if (operand_index_ == 0) + return GetMtfIdWithTypeGeneratedByOpcode(SpvOpTypePointer); + if (operand_index_ == 1) { + const uint32_t pointer_id = GetInstWords()[1]; + const uint32_t pointer_type = id_to_type_id_.at(pointer_id); + const Instruction* pointer_inst = FindDef(pointer_type); + assert(pointer_inst); + assert(pointer_inst->opcode() == SpvOpTypePointer); + const uint32_t data_type = + pointer_inst->word(pointer_inst->operands()[2].offset); + return GetMtfIdOfType(data_type); + } + break; + } + + case SpvOpVariable: { + if (operand_index_ == 0) + return GetMtfIdGeneratedByOpcode(SpvOpTypePointer); + break; + } + + case SpvOpAccessChain: { + if (operand_index_ == 0) + return GetMtfIdGeneratedByOpcode(SpvOpTypePointer); + if (operand_index_ == 2) return kMtfTypePointerToComposite; + if (operand_index_ >= 3) + return GetMtfIdWithTypeGeneratedByOpcode(SpvOpTypeInt); + break; + } + + case SpvOpCompositeConstruct: { + if (operand_index_ == 0) return kMtfTypeComposite; + if (operand_index_ >= 2) { + const uint32_t composite_type = GetInstWords()[1]; + if (multi_mtf_.HasValue(kMtfTypeFloatScalarOrVector, composite_type)) + return kMtfFloatScalarOrVector; + if (multi_mtf_.HasValue(kMtfTypeIntScalarOrVector, composite_type)) + return kMtfIntScalarOrVector; + if (multi_mtf_.HasValue(kMtfTypeBoolScalarOrVector, composite_type)) + return kMtfBoolScalarOrVector; + } + break; + } + + case SpvOpCompositeExtract: { + if (operand_index_ == 2) return kMtfComposite; + break; + } + + case SpvOpConstantComposite: { + if (operand_index_ == 0) return kMtfTypeComposite; + if (operand_index_ >= 2) { + const Instruction* composite_type_inst = FindDef(inst_.type_id); + assert(composite_type_inst); + if (composite_type_inst->opcode() == SpvOpTypeVector) { + return GetMtfIdOfType(composite_type_inst->word(2)); + } + } + break; + } + + case SpvOpExtInst: { + if (operand_index_ == 2) + return GetMtfIdGeneratedByOpcode(SpvOpExtInstImport); + if (operand_index_ >= 4) { + const uint32_t return_type = GetInstWords()[1]; + const uint32_t ext_inst_type = inst_.ext_inst_type; + const uint32_t ext_inst_index = GetInstWords()[4]; + // TODO(atgoo@github.com) The list of extended instructions is + // incomplete. Only common instructions and low-hanging fruits listed. + if (ext_inst_type == SPV_EXT_INST_TYPE_GLSL_STD_450) { + switch (ext_inst_index) { + case GLSLstd450FAbs: + case GLSLstd450FClamp: + case GLSLstd450FMax: + case GLSLstd450FMin: + case GLSLstd450FMix: + case GLSLstd450Step: + case GLSLstd450SmoothStep: + case GLSLstd450Fma: + case GLSLstd450Pow: + case GLSLstd450Exp: + case GLSLstd450Exp2: + case GLSLstd450Log: + case GLSLstd450Log2: + case GLSLstd450Sqrt: + case GLSLstd450InverseSqrt: + case GLSLstd450Fract: + case GLSLstd450Floor: + case GLSLstd450Ceil: + case GLSLstd450Radians: + case GLSLstd450Degrees: + case GLSLstd450Sin: + case GLSLstd450Cos: + case GLSLstd450Tan: + case GLSLstd450Sinh: + case GLSLstd450Cosh: + case GLSLstd450Tanh: + case GLSLstd450Asin: + case GLSLstd450Acos: + case GLSLstd450Atan: + case GLSLstd450Atan2: + case GLSLstd450Asinh: + case GLSLstd450Acosh: + case GLSLstd450Atanh: + case GLSLstd450MatrixInverse: + case GLSLstd450Cross: + case GLSLstd450Normalize: + case GLSLstd450Reflect: + case GLSLstd450FaceForward: + return GetMtfIdOfType(return_type); + case GLSLstd450Length: + case GLSLstd450Distance: + case GLSLstd450Refract: + return kMtfFloatScalarOrVector; + default: + break; + } + } else if (ext_inst_type == SPV_EXT_INST_TYPE_OPENCL_STD) { + switch (ext_inst_index) { + case OpenCLLIB::Fabs: + case OpenCLLIB::FClamp: + case OpenCLLIB::Fmax: + case OpenCLLIB::Fmin: + case OpenCLLIB::Step: + case OpenCLLIB::Smoothstep: + case OpenCLLIB::Fma: + case OpenCLLIB::Pow: + case OpenCLLIB::Exp: + case OpenCLLIB::Exp2: + case OpenCLLIB::Log: + case OpenCLLIB::Log2: + case OpenCLLIB::Sqrt: + case OpenCLLIB::Rsqrt: + case OpenCLLIB::Fract: + case OpenCLLIB::Floor: + case OpenCLLIB::Ceil: + case OpenCLLIB::Radians: + case OpenCLLIB::Degrees: + case OpenCLLIB::Sin: + case OpenCLLIB::Cos: + case OpenCLLIB::Tan: + case OpenCLLIB::Sinh: + case OpenCLLIB::Cosh: + case OpenCLLIB::Tanh: + case OpenCLLIB::Asin: + case OpenCLLIB::Acos: + case OpenCLLIB::Atan: + case OpenCLLIB::Atan2: + case OpenCLLIB::Asinh: + case OpenCLLIB::Acosh: + case OpenCLLIB::Atanh: + case OpenCLLIB::Cross: + case OpenCLLIB::Normalize: + return GetMtfIdOfType(return_type); + case OpenCLLIB::Length: + case OpenCLLIB::Distance: + return kMtfFloatScalarOrVector; + default: + break; + } + } + } + break; + } + + case SpvOpFunction: { + if (operand_index_ == 0) return kMtfTypeReturnedByFunction; + + if (operand_index_ == 3) { + const uint32_t return_type = GetInstWords()[1]; + return GetMtfFunctionTypeWithReturnType(return_type); + } + break; + } + + case SpvOpFunctionCall: { + if (operand_index_ == 0) return kMtfTypeReturnedByFunction; + + if (operand_index_ == 2) { + const uint32_t return_type = GetInstWords()[1]; + return GetMtfFunctionWithReturnType(return_type); + } + + if (operand_index_ >= 3) { + const uint32_t function_id = GetInstWords()[3]; + const Instruction* function_inst = FindDef(function_id); + if (!function_inst) return kMtfObject; + + assert(function_inst->opcode() == SpvOpFunction); + + const uint32_t function_type_id = function_inst->word(4); + const Instruction* function_type_inst = FindDef(function_type_id); + assert(function_type_inst); + assert(function_type_inst->opcode() == SpvOpTypeFunction); + + const uint32_t argument_type = function_type_inst->word(operand_index_); + return GetMtfIdOfType(argument_type); + } + break; + } + + case SpvOpReturnValue: { + if (operand_index_ == 0) return GetMtfIdOfType(cur_function_return_type_); + break; + } + + case SpvOpBranchConditional: { + if (operand_index_ == 0) + return GetMtfIdWithTypeGeneratedByOpcode(SpvOpTypeBool); + break; + } + + case SpvOpSampledImage: { + if (operand_index_ == 0) + return GetMtfIdGeneratedByOpcode(SpvOpTypeSampledImage); + if (operand_index_ == 2) + return GetMtfIdWithTypeGeneratedByOpcode(SpvOpTypeImage); + if (operand_index_ == 3) + return GetMtfIdWithTypeGeneratedByOpcode(SpvOpTypeSampler); + break; + } + + case SpvOpImageSampleImplicitLod: { + if (operand_index_ == 0) + return GetMtfIdGeneratedByOpcode(SpvOpTypeVector); + if (operand_index_ == 2) + return GetMtfIdWithTypeGeneratedByOpcode(SpvOpTypeSampledImage); + if (operand_index_ == 3) + return GetMtfIdWithTypeGeneratedByOpcode(SpvOpTypeVector); + break; + } + + default: + break; + } + + return kMtfNone; +} + +spv_result_t MarkvEncoder::EncodeNonIdWord(uint32_t word) { + auto* codec = model_->GetNonIdWordHuffmanCodec(inst_.opcode, operand_index_); + + if (codec) { + uint64_t bits = 0; + size_t num_bits = 0; + if (codec->Encode(word, &bits, &num_bits)) { + // Encoding successful. + writer_.WriteBits(bits, num_bits); + return SPV_SUCCESS; + } else { + // Encoding failed, write kMarkvNoneOfTheAbove flag. + if (!codec->Encode(kMarkvNoneOfTheAbove, &bits, &num_bits)) + return Diag(SPV_ERROR_INTERNAL) + << "Non-id word Huffman table for " + << spvOpcodeString(SpvOp(inst_.opcode)) << " operand index " + << operand_index_ << " is missing kMarkvNoneOfTheAbove"; + writer_.WriteBits(bits, num_bits); + } + } + + // Fallback encoding. + const size_t chunk_length = + model_->GetOperandVariableWidthChunkLength(operand_.type); + if (chunk_length) { + writer_.WriteVariableWidthU32(word, chunk_length); + } else { + writer_.WriteUnencoded(word); + } + return SPV_SUCCESS; +} + +spv_result_t MarkvDecoder::DecodeNonIdWord(uint32_t* word) { + auto* codec = model_->GetNonIdWordHuffmanCodec(inst_.opcode, operand_index_); + + if (codec) { + uint64_t decoded_value = 0; + if (!codec->DecodeFromStream(GetReadBitCallback(), &decoded_value)) + return Diag(SPV_ERROR_INVALID_BINARY) + << "Failed to decode non-id word with Huffman"; + + if (decoded_value != kMarkvNoneOfTheAbove) { + // The word decoded successfully. + *word = uint32_t(decoded_value); + assert(*word == decoded_value); + return SPV_SUCCESS; + } + + // Received kMarkvNoneOfTheAbove signal, use fallback decoding. + } + + const size_t chunk_length = + model_->GetOperandVariableWidthChunkLength(operand_.type); + if (chunk_length) { + if (!reader_.ReadVariableWidthU32(word, chunk_length)) + return Diag(SPV_ERROR_INVALID_BINARY) + << "Failed to decode non-id word with varint"; + } else { + if (!reader_.ReadUnencoded(word)) + return Diag(SPV_ERROR_INVALID_BINARY) + << "Failed to read unencoded non-id word"; + } + return SPV_SUCCESS; +} + +spv_result_t MarkvEncoder::EncodeOpcodeAndNumOperands(uint32_t opcode, + uint32_t num_operands) { + uint64_t bits = 0; + size_t num_bits = 0; + + const uint32_t word = opcode | (num_operands << 16); + + // First try to use the Markov chain codec. + auto* codec = + model_->GetOpcodeAndNumOperandsMarkovHuffmanCodec(GetPrevOpcode()); + if (codec) { + if (codec->Encode(word, &bits, &num_bits)) { + // The word was successfully encoded into bits/num_bits. + writer_.WriteBits(bits, num_bits); + return SPV_SUCCESS; + } else { + // The word is not in the Huffman table. Write kMarkvNoneOfTheAbove + // and use fallback encoding. + if (!codec->Encode(kMarkvNoneOfTheAbove, &bits, &num_bits)) + return Diag(SPV_ERROR_INTERNAL) + << "opcode_and_num_operands Huffman table for " + << spvOpcodeString(GetPrevOpcode()) + << "is missing kMarkvNoneOfTheAbove"; + writer_.WriteBits(bits, num_bits); + } + } + + // Fallback to base-rate codec. + codec = model_->GetOpcodeAndNumOperandsMarkovHuffmanCodec(SpvOpNop); + assert(codec); + if (codec->Encode(word, &bits, &num_bits)) { + // The word was successfully encoded into bits/num_bits. + writer_.WriteBits(bits, num_bits); + return SPV_SUCCESS; + } else { + // The word is not in the Huffman table. Write kMarkvNoneOfTheAbove + // and return false. + if (!codec->Encode(kMarkvNoneOfTheAbove, &bits, &num_bits)) + return Diag(SPV_ERROR_INTERNAL) + << "Global opcode_and_num_operands Huffman table is missing " + << "kMarkvNoneOfTheAbove"; + writer_.WriteBits(bits, num_bits); + return SPV_UNSUPPORTED; + } +} + +spv_result_t MarkvDecoder::DecodeOpcodeAndNumberOfOperands( + uint32_t* opcode, uint32_t* num_operands) { + // First try to use the Markov chain codec. + auto* codec = + model_->GetOpcodeAndNumOperandsMarkovHuffmanCodec(GetPrevOpcode()); + if (codec) { + uint64_t decoded_value = 0; + if (!codec->DecodeFromStream(GetReadBitCallback(), &decoded_value)) + return Diag(SPV_ERROR_INTERNAL) + << "Failed to decode opcode_and_num_operands, previous opcode is " + << spvOpcodeString(GetPrevOpcode()); + + if (decoded_value != kMarkvNoneOfTheAbove) { + // The word was successfully decoded. + *opcode = uint32_t(decoded_value & 0xFFFF); + *num_operands = uint32_t(decoded_value >> 16); + return SPV_SUCCESS; + } + + // Received kMarkvNoneOfTheAbove signal, use fallback decoding. + } + + // Fallback to base-rate codec. + codec = model_->GetOpcodeAndNumOperandsMarkovHuffmanCodec(SpvOpNop); + assert(codec); + uint64_t decoded_value = 0; + if (!codec->DecodeFromStream(GetReadBitCallback(), &decoded_value)) + return Diag(SPV_ERROR_INTERNAL) + << "Failed to decode opcode_and_num_operands with global codec"; + + if (decoded_value == kMarkvNoneOfTheAbove) { + // Received kMarkvNoneOfTheAbove signal, fallback further. + return SPV_UNSUPPORTED; + } + + *opcode = uint32_t(decoded_value & 0xFFFF); + *num_operands = uint32_t(decoded_value >> 16); + return SPV_SUCCESS; +} + +spv_result_t MarkvEncoder::EncodeMtfRankHuffman(uint32_t rank, uint64_t mtf, + uint64_t fallback_method) { + const auto* codec = GetMtfHuffmanCodec(mtf); + if (!codec) { + assert(fallback_method != kMtfNone); + codec = GetMtfHuffmanCodec(fallback_method); + } + + if (!codec) return Diag(SPV_ERROR_INTERNAL) << "No codec to encode MTF rank"; + + uint64_t bits = 0; + size_t num_bits = 0; + if (rank < kMtfSmallestRankEncodedByValue) { + // Encode using Huffman coding. + if (!codec->Encode(rank, &bits, &num_bits)) + return Diag(SPV_ERROR_INTERNAL) + << "Failed to encode MTF rank with Huffman"; + + writer_.WriteBits(bits, num_bits); + } else { + // Encode by value. + if (!codec->Encode(kMtfRankEncodedByValueSignal, &bits, &num_bits)) + return Diag(SPV_ERROR_INTERNAL) + << "Failed to encode kMtfRankEncodedByValueSignal"; + + writer_.WriteBits(bits, num_bits); + writer_.WriteVariableWidthU32(rank - kMtfSmallestRankEncodedByValue, + model_->mtf_rank_chunk_length()); + } + return SPV_SUCCESS; +} + +spv_result_t MarkvDecoder::DecodeMtfRankHuffman(uint64_t mtf, + uint32_t fallback_method, + uint32_t* rank) { + const auto* codec = GetMtfHuffmanCodec(mtf); + if (!codec) { + assert(fallback_method != kMtfNone); + codec = GetMtfHuffmanCodec(fallback_method); + } + + if (!codec) return Diag(SPV_ERROR_INTERNAL) << "No codec to decode MTF rank"; + + uint32_t decoded_value = 0; + if (!codec->DecodeFromStream(GetReadBitCallback(), &decoded_value)) + return Diag(SPV_ERROR_INTERNAL) << "Failed to decode MTF rank with Huffman"; + + if (decoded_value == kMtfRankEncodedByValueSignal) { + // Decode by value. + if (!reader_.ReadVariableWidthU32(rank, model_->mtf_rank_chunk_length())) + return Diag(SPV_ERROR_INTERNAL) + << "Failed to decode MTF rank with varint"; + *rank += kMtfSmallestRankEncodedByValue; + } else { + // Decode using Huffman coding. + assert(decoded_value < kMtfSmallestRankEncodedByValue); + *rank = decoded_value; + } + return SPV_SUCCESS; +} + +spv_result_t MarkvEncoder::EncodeIdWithDescriptor(uint32_t id) { + // Get the descriptor for id. + const uint32_t long_descriptor = long_id_descriptors_.GetDescriptor(id); + auto* codec = + model_->GetIdDescriptorHuffmanCodec(inst_.opcode, operand_index_); + uint64_t bits = 0; + size_t num_bits = 0; + uint64_t mtf = kMtfNone; + if (long_descriptor && codec && + codec->Encode(long_descriptor, &bits, &num_bits)) { + // If the descriptor exists and is in the table, write the descriptor and + // proceed to encoding the rank. + writer_.WriteBits(bits, num_bits); + mtf = GetMtfLongIdDescriptor(long_descriptor); + } else { + if (codec) { + // The descriptor doesn't exist or we have no coding for it. Write + // kMarkvNoneOfTheAbove and go to fallback method. + if (!codec->Encode(kMarkvNoneOfTheAbove, &bits, &num_bits)) + return Diag(SPV_ERROR_INTERNAL) + << "Descriptor Huffman table for " + << spvOpcodeString(SpvOp(inst_.opcode)) << " operand index " + << operand_index_ << " is missing kMarkvNoneOfTheAbove"; + + writer_.WriteBits(bits, num_bits); + } + + if (model_->id_fallback_strategy() != + MarkvModel::IdFallbackStrategy::kShortDescriptor) { + return SPV_UNSUPPORTED; + } + + const uint32_t short_descriptor = short_id_descriptors_.GetDescriptor(id); + writer_.WriteBits(short_descriptor, kShortDescriptorNumBits); + + if (short_descriptor == 0) { + // Forward declared id. + return SPV_UNSUPPORTED; + } + + mtf = GetMtfShortIdDescriptor(short_descriptor); + } + + // Descriptor has been encoded. Now encode the rank of the id in the + // associated mtf sequence. + return EncodeExistingId(mtf, id); +} + +spv_result_t MarkvDecoder::DecodeIdWithDescriptor(uint32_t* id) { + auto* codec = + model_->GetIdDescriptorHuffmanCodec(inst_.opcode, operand_index_); + + uint64_t mtf = kMtfNone; + if (codec) { + uint64_t decoded_value = 0; + if (!codec->DecodeFromStream(GetReadBitCallback(), &decoded_value)) + return Diag(SPV_ERROR_INTERNAL) + << "Failed to decode descriptor with Huffman"; + + if (decoded_value != kMarkvNoneOfTheAbove) { + const uint32_t long_descriptor = uint32_t(decoded_value); + mtf = GetMtfLongIdDescriptor(long_descriptor); + } + } + + if (mtf == kMtfNone) { + if (model_->id_fallback_strategy() != + MarkvModel::IdFallbackStrategy::kShortDescriptor) { + return SPV_UNSUPPORTED; + } + + uint64_t decoded_value = 0; + if (!reader_.ReadBits(&decoded_value, kShortDescriptorNumBits)) + return Diag(SPV_ERROR_INTERNAL) << "Failed to read short descriptor"; + const uint32_t short_descriptor = uint32_t(decoded_value); + if (short_descriptor == 0) { + // Forward declared id. + return SPV_UNSUPPORTED; + } + mtf = GetMtfShortIdDescriptor(short_descriptor); + } + + return DecodeExistingId(mtf, id); +} + +spv_result_t MarkvEncoder::EncodeExistingId(uint64_t mtf, uint32_t id) { + assert(multi_mtf_.GetSize(mtf) > 0); + if (multi_mtf_.GetSize(mtf) == 1) { + // If the sequence has only one element no need to write rank, the decoder + // would make the same decision. + return SPV_SUCCESS; + } + + uint32_t rank = 0; + if (!multi_mtf_.RankFromValue(mtf, id, &rank)) + return Diag(SPV_ERROR_INTERNAL) << "Id is not in the MTF sequence"; + + return EncodeMtfRankHuffman(rank, mtf, kMtfGenericNonZeroRank); +} + +spv_result_t MarkvDecoder::DecodeExistingId(uint64_t mtf, uint32_t* id) { + assert(multi_mtf_.GetSize(mtf) > 0); + *id = 0; + + uint32_t rank = 0; + + if (multi_mtf_.GetSize(mtf) == 1) { + rank = 1; + } else { + const spv_result_t result = + DecodeMtfRankHuffman(mtf, kMtfGenericNonZeroRank, &rank); + if (result != SPV_SUCCESS) return result; + } + + assert(rank); + if (!multi_mtf_.ValueFromRank(mtf, rank, id)) + return Diag(SPV_ERROR_INTERNAL) << "MTF rank is out of bounds"; + + return SPV_SUCCESS; +} + +spv_result_t MarkvEncoder::EncodeRefId(uint32_t id) { + { + // Try to encode using id descriptor mtfs. + const spv_result_t result = EncodeIdWithDescriptor(id); + if (result != SPV_UNSUPPORTED) return result; + // If can't be done continue with other methods. + } + + const bool can_forward_declare = spvOperandCanBeForwardDeclaredFunction( + SpvOp(inst_.opcode))(operand_index_); + uint32_t rank = 0; + + if (model_->id_fallback_strategy() == + MarkvModel::IdFallbackStrategy::kRuleBased) { + // Encode using rule-based mtf. + uint64_t mtf = GetRuleBasedMtf(); + + if (mtf != kMtfNone && !can_forward_declare) { + assert(multi_mtf_.HasValue(kMtfAll, id)); + return EncodeExistingId(mtf, id); + } + + if (mtf == kMtfNone) mtf = kMtfAll; + + if (!multi_mtf_.RankFromValue(mtf, id, &rank)) { + // This is the first occurrence of a forward declared id. + multi_mtf_.Insert(kMtfAll, id); + multi_mtf_.Insert(kMtfForwardDeclared, id); + if (mtf != kMtfAll) multi_mtf_.Insert(mtf, id); + rank = 0; + } + + return EncodeMtfRankHuffman(rank, mtf, kMtfAll); + } else { + assert(can_forward_declare); + + if (!multi_mtf_.RankFromValue(kMtfForwardDeclared, id, &rank)) { + // This is the first occurrence of a forward declared id. + multi_mtf_.Insert(kMtfForwardDeclared, id); + rank = 0; + } + + writer_.WriteVariableWidthU32(rank, model_->mtf_rank_chunk_length()); + return SPV_SUCCESS; + } +} + +spv_result_t MarkvDecoder::DecodeRefId(uint32_t* id) { + { + const spv_result_t result = DecodeIdWithDescriptor(id); + if (result != SPV_UNSUPPORTED) return result; + } + + const bool can_forward_declare = spvOperandCanBeForwardDeclaredFunction( + SpvOp(inst_.opcode))(operand_index_); + uint32_t rank = 0; + *id = 0; + + if (model_->id_fallback_strategy() == + MarkvModel::IdFallbackStrategy::kRuleBased) { + uint64_t mtf = GetRuleBasedMtf(); + if (mtf != kMtfNone && !can_forward_declare) { + return DecodeExistingId(mtf, id); + } + + if (mtf == kMtfNone) mtf = kMtfAll; + { + const spv_result_t result = DecodeMtfRankHuffman(mtf, kMtfAll, &rank); + if (result != SPV_SUCCESS) return result; + } + + if (rank == 0) { + // This is the first occurrence of a forward declared id. + *id = GetIdBound(); + SetIdBound(*id + 1); + multi_mtf_.Insert(kMtfAll, *id); + multi_mtf_.Insert(kMtfForwardDeclared, *id); + if (mtf != kMtfAll) multi_mtf_.Insert(mtf, *id); + } else { + if (!multi_mtf_.ValueFromRank(mtf, rank, id)) + return Diag(SPV_ERROR_INTERNAL) << "MTF rank out of bounds"; + } + } else { + assert(can_forward_declare); + + if (!reader_.ReadVariableWidthU32(&rank, model_->mtf_rank_chunk_length())) + return Diag(SPV_ERROR_INTERNAL) + << "Failed to decode MTF rank with varint"; + + if (rank == 0) { + // This is the first occurrence of a forward declared id. + *id = GetIdBound(); + SetIdBound(*id + 1); + multi_mtf_.Insert(kMtfForwardDeclared, *id); + } else { + if (!multi_mtf_.ValueFromRank(kMtfForwardDeclared, rank, id)) + return Diag(SPV_ERROR_INTERNAL) << "MTF rank out of bounds"; + } + } + assert(*id); + return SPV_SUCCESS; +} + +spv_result_t MarkvEncoder::EncodeTypeId() { + if (inst_.opcode == SpvOpFunctionParameter) { + assert(!remaining_function_parameter_types_.empty()); + assert(inst_.type_id == remaining_function_parameter_types_.front()); + remaining_function_parameter_types_.pop_front(); + return SPV_SUCCESS; + } + + { + // Try to encode using id descriptor mtfs. + const spv_result_t result = EncodeIdWithDescriptor(inst_.type_id); + if (result != SPV_UNSUPPORTED) return result; + // If can't be done continue with other methods. + } + + assert(model_->id_fallback_strategy() == + MarkvModel::IdFallbackStrategy::kRuleBased); + + uint64_t mtf = GetRuleBasedMtf(); + assert(!spvOperandCanBeForwardDeclaredFunction(SpvOp(inst_.opcode))( + operand_index_)); + + if (mtf == kMtfNone) { + mtf = kMtfTypeNonFunction; + // Function types should have been handled by GetRuleBasedMtf. + assert(inst_.opcode != SpvOpFunction); + } + + return EncodeExistingId(mtf, inst_.type_id); +} + +spv_result_t MarkvDecoder::DecodeTypeId() { + if (inst_.opcode == SpvOpFunctionParameter) { + assert(!remaining_function_parameter_types_.empty()); + inst_.type_id = remaining_function_parameter_types_.front(); + remaining_function_parameter_types_.pop_front(); + return SPV_SUCCESS; + } + + { + const spv_result_t result = DecodeIdWithDescriptor(&inst_.type_id); + if (result != SPV_UNSUPPORTED) return result; + } + + assert(model_->id_fallback_strategy() == + MarkvModel::IdFallbackStrategy::kRuleBased); + + uint64_t mtf = GetRuleBasedMtf(); + assert(!spvOperandCanBeForwardDeclaredFunction(SpvOp(inst_.opcode))( + operand_index_)); + + if (mtf == kMtfNone) { + mtf = kMtfTypeNonFunction; + // Function types should have been handled by GetRuleBasedMtf. + assert(inst_.opcode != SpvOpFunction); + } + + return DecodeExistingId(mtf, &inst_.type_id); +} + +spv_result_t MarkvEncoder::EncodeResultId() { + uint32_t rank = 0; + + const uint64_t num_still_forward_declared = + multi_mtf_.GetSize(kMtfForwardDeclared); + + if (num_still_forward_declared) { + // We write the rank only if kMtfForwardDeclared is not empty. If it is + // empty the decoder knows that there are no forward declared ids to expect. + if (multi_mtf_.RankFromValue(kMtfForwardDeclared, inst_.result_id, &rank)) { + // This is a definition of a forward declared id. We can remove the id + // from kMtfForwardDeclared. + if (!multi_mtf_.Remove(kMtfForwardDeclared, inst_.result_id)) + return Diag(SPV_ERROR_INTERNAL) + << "Failed to remove id from kMtfForwardDeclared"; + writer_.WriteBits(1, 1); + writer_.WriteVariableWidthU32(rank, model_->mtf_rank_chunk_length()); + } else { + rank = 0; + writer_.WriteBits(0, 1); + } + } + + if (model_->id_fallback_strategy() == + MarkvModel::IdFallbackStrategy::kRuleBased) { + if (!rank) { + multi_mtf_.Insert(kMtfAll, inst_.result_id); + } + } + + return SPV_SUCCESS; +} + +spv_result_t MarkvDecoder::DecodeResultId() { + uint32_t rank = 0; + + const uint64_t num_still_forward_declared = + multi_mtf_.GetSize(kMtfForwardDeclared); + + if (num_still_forward_declared) { + // Some ids were forward declared. Check if this id is one of them. + uint64_t id_was_forward_declared; + if (!reader_.ReadBits(&id_was_forward_declared, 1)) + return Diag(SPV_ERROR_INVALID_BINARY) + << "Failed to read id_was_forward_declared flag"; + + if (id_was_forward_declared) { + if (!reader_.ReadVariableWidthU32(&rank, model_->mtf_rank_chunk_length())) + return Diag(SPV_ERROR_INVALID_BINARY) + << "Failed to read MTF rank of forward declared id"; + + if (rank) { + // The id was forward declared, recover it from kMtfForwardDeclared. + if (!multi_mtf_.ValueFromRank(kMtfForwardDeclared, rank, + &inst_.result_id)) + return Diag(SPV_ERROR_INTERNAL) + << "Forward declared MTF rank is out of bounds"; + + // We can now remove the id from kMtfForwardDeclared. + if (!multi_mtf_.Remove(kMtfForwardDeclared, inst_.result_id)) + return Diag(SPV_ERROR_INTERNAL) + << "Failed to remove id from kMtfForwardDeclared"; + } + } + } + + if (inst_.result_id == 0) { + // The id was not forward declared, issue a new id. + inst_.result_id = GetIdBound(); + SetIdBound(inst_.result_id + 1); + } + + if (model_->id_fallback_strategy() == + MarkvModel::IdFallbackStrategy::kRuleBased) { + if (!rank) { + multi_mtf_.Insert(kMtfAll, inst_.result_id); + } + } + + return SPV_SUCCESS; +} + +spv_result_t MarkvEncoder::EncodeLiteralNumber( + const spv_parsed_operand_t& operand) { + if (operand.number_bit_width <= 32) { + const uint32_t word = inst_.words[operand.offset]; + return EncodeNonIdWord(word); + } else { + assert(operand.number_bit_width <= 64); + const uint64_t word = uint64_t(inst_.words[operand.offset]) | + (uint64_t(inst_.words[operand.offset + 1]) << 32); + if (operand.number_kind == SPV_NUMBER_UNSIGNED_INT) { + writer_.WriteVariableWidthU64(word, model_->u64_chunk_length()); + } else if (operand.number_kind == SPV_NUMBER_SIGNED_INT) { + int64_t val = 0; + std::memcpy(&val, &word, 8); + writer_.WriteVariableWidthS64(val, model_->s64_chunk_length(), + model_->s64_block_exponent()); + } else if (operand.number_kind == SPV_NUMBER_FLOATING) { + writer_.WriteUnencoded(word); + } else { + return Diag(SPV_ERROR_INTERNAL) << "Unsupported bit length"; + } + } + return SPV_SUCCESS; +} + +spv_result_t MarkvDecoder::DecodeLiteralNumber( + const spv_parsed_operand_t& operand) { + if (operand.number_bit_width <= 32) { + uint32_t word = 0; + const spv_result_t result = DecodeNonIdWord(&word); + if (result != SPV_SUCCESS) return result; + inst_words_.push_back(word); + } else { + assert(operand.number_bit_width <= 64); + uint64_t word = 0; + if (operand.number_kind == SPV_NUMBER_UNSIGNED_INT) { + if (!reader_.ReadVariableWidthU64(&word, model_->u64_chunk_length())) + return Diag(SPV_ERROR_INVALID_BINARY) << "Failed to read literal U64"; + } else if (operand.number_kind == SPV_NUMBER_SIGNED_INT) { + int64_t val = 0; + if (!reader_.ReadVariableWidthS64(&val, model_->s64_chunk_length(), + model_->s64_block_exponent())) + return Diag(SPV_ERROR_INVALID_BINARY) << "Failed to read literal S64"; + std::memcpy(&word, &val, 8); + } else if (operand.number_kind == SPV_NUMBER_FLOATING) { + if (!reader_.ReadUnencoded(&word)) + return Diag(SPV_ERROR_INVALID_BINARY) << "Failed to read literal F64"; + } else { + return Diag(SPV_ERROR_INTERNAL) << "Unsupported bit length"; + } + inst_words_.push_back(static_cast(word)); + inst_words_.push_back(static_cast(word >> 32)); + } + return SPV_SUCCESS; +} + +void MarkvEncoder::AddByteBreak(size_t byte_break_if_less_than) { + const size_t num_bits_to_next_byte = + GetNumBitsToNextByte(writer_.GetNumBits()); + if (num_bits_to_next_byte == 0 || + num_bits_to_next_byte > byte_break_if_less_than) + return; + + if (logger_) { + logger_->AppendWhitespaces(kCommentNumWhitespaces); + logger_->AppendText(""); + } + + writer_.WriteBits(0, num_bits_to_next_byte); +} + +bool MarkvDecoder::ReadToByteBreak(size_t byte_break_if_less_than) { + const size_t num_bits_to_next_byte = + GetNumBitsToNextByte(reader_.GetNumReadBits()); + if (num_bits_to_next_byte == 0 || + num_bits_to_next_byte > byte_break_if_less_than) + return true; + + uint64_t bits = 0; + if (!reader_.ReadBits(&bits, num_bits_to_next_byte)) return false; + + assert(bits == 0); + if (bits != 0) return false; + + return true; +} + +spv_result_t MarkvEncoder::EncodeInstruction( + const spv_parsed_instruction_t& inst) { + SpvOp opcode = SpvOp(inst.opcode); + inst_ = inst; + + const spv_result_t validation_result = UpdateValidationState(inst); + if (validation_result != SPV_SUCCESS) return validation_result; + + LogDisassemblyInstruction(); + + const spv_result_t opcode_encodig_result = + EncodeOpcodeAndNumOperands(opcode, inst.num_operands); + if (opcode_encodig_result < 0) return opcode_encodig_result; + + if (opcode_encodig_result != SPV_SUCCESS) { + // Fallback encoding for opcode and num_operands. + writer_.WriteVariableWidthU32(opcode, model_->opcode_chunk_length()); + + if (!OpcodeHasFixedNumberOfOperands(opcode)) { + // If the opcode has a variable number of operands, encode the number of + // operands with the instruction. + + if (logger_) logger_->AppendWhitespaces(kCommentNumWhitespaces); + + writer_.WriteVariableWidthU16(inst.num_operands, + model_->num_operands_chunk_length()); + } + } + + // Write operands. + const uint32_t num_operands = inst_.num_operands; + for (operand_index_ = 0; operand_index_ < num_operands; ++operand_index_) { + operand_ = inst_.operands[operand_index_]; + + if (logger_) { + logger_->AppendWhitespaces(kCommentNumWhitespaces); + logger_->AppendText("<"); + logger_->AppendText(spvOperandTypeStr(operand_.type)); + logger_->AppendText(">"); + } + + switch (operand_.type) { + case SPV_OPERAND_TYPE_RESULT_ID: + case SPV_OPERAND_TYPE_TYPE_ID: + case SPV_OPERAND_TYPE_ID: + case SPV_OPERAND_TYPE_OPTIONAL_ID: + case SPV_OPERAND_TYPE_SCOPE_ID: + case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID: { + const uint32_t id = inst_.words[operand_.offset]; + if (operand_.type == SPV_OPERAND_TYPE_TYPE_ID) { + const spv_result_t result = EncodeTypeId(); + if (result != SPV_SUCCESS) return result; + } else if (operand_.type == SPV_OPERAND_TYPE_RESULT_ID) { + const spv_result_t result = EncodeResultId(); + if (result != SPV_SUCCESS) return result; + } else { + const spv_result_t result = EncodeRefId(id); + if (result != SPV_SUCCESS) return result; + } + + PromoteIfNeeded(id); + break; + } + + case SPV_OPERAND_TYPE_LITERAL_INTEGER: { + const spv_result_t result = + EncodeNonIdWord(inst_.words[operand_.offset]); + if (result != SPV_SUCCESS) return result; + break; + } + + case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER: { + const spv_result_t result = EncodeLiteralNumber(operand_); + if (result != SPV_SUCCESS) return result; + break; + } + + case SPV_OPERAND_TYPE_LITERAL_STRING: { + const char* src = + reinterpret_cast(&inst_.words[operand_.offset]); + + auto* codec = model_->GetLiteralStringHuffmanCodec(opcode); + if (codec) { + uint64_t bits = 0; + size_t num_bits = 0; + const std::string str = src; + if (codec->Encode(str, &bits, &num_bits)) { + writer_.WriteBits(bits, num_bits); + break; + } else { + bool result = + codec->Encode("kMarkvNoneOfTheAbove", &bits, &num_bits); + (void)result; + assert(result); + writer_.WriteBits(bits, num_bits); + } + } + + const size_t length = spv_strnlen_s(src, operand_.num_words * 4); + if (length == operand_.num_words * 4) + return Diag(SPV_ERROR_INVALID_BINARY) + << "Failed to find terminal character of literal string"; + for (size_t i = 0; i < length + 1; ++i) writer_.WriteUnencoded(src[i]); + break; + } + + default: { + for (int i = 0; i < operand_.num_words; ++i) { + const uint32_t word = inst_.words[operand_.offset + i]; + const spv_result_t result = EncodeNonIdWord(word); + if (result != SPV_SUCCESS) return result; + } + break; + } + } + } + + AddByteBreak(kByteBreakAfterInstIfLessThanUntilNextByte); + + if (logger_) { + logger_->NewLine(); + logger_->NewLine(); + if (!logger_->DebugInstruction(inst_)) return SPV_REQUESTED_TERMINATION; + } + + ProcessCurInstruction(); + + return SPV_SUCCESS; +} + +spv_result_t MarkvDecoder::DecodeModule(std::vector* spirv_binary) { + const bool header_read_success = + reader_.ReadUnencoded(&header_.magic_number) && + reader_.ReadUnencoded(&header_.markv_version) && + reader_.ReadUnencoded(&header_.markv_model) && + reader_.ReadUnencoded(&header_.markv_length_in_bits) && + reader_.ReadUnencoded(&header_.spirv_version) && + reader_.ReadUnencoded(&header_.spirv_generator); + + if (!header_read_success) + return Diag(SPV_ERROR_INVALID_BINARY) << "Unable to read MARK-V header"; + + if (header_.markv_length_in_bits == 0) + return Diag(SPV_ERROR_INVALID_BINARY) + << "Header markv_length_in_bits field is zero"; + + if (header_.magic_number != kMarkvMagicNumber) + return Diag(SPV_ERROR_INVALID_BINARY) + << "MARK-V binary has incorrect magic number"; + + // TODO(atgoo@github.com): Print version strings. + if (header_.markv_version != GetMarkvVersion()) + return Diag(SPV_ERROR_INVALID_BINARY) + << "MARK-V binary and the codec have different versions"; + + const uint32_t model_type = header_.markv_model >> 16; + const uint32_t model_version = header_.markv_model & 0xFFFF; + if (model_type != model_->model_type()) + return Diag(SPV_ERROR_INVALID_BINARY) + << "MARK-V binary and the codec use different MARK-V models"; + + if (model_version != model_->model_version()) + return Diag(SPV_ERROR_INVALID_BINARY) + << "MARK-V binary and the codec use different versions if the same " + << "MARK-V model"; + + spirv_.reserve(header_.markv_length_in_bits / 2); // Heuristic. + spirv_.resize(5, 0); + spirv_[0] = kSpirvMagicNumber; + spirv_[1] = header_.spirv_version; + spirv_[2] = header_.spirv_generator; + + if (logger_) { + reader_.SetCallback( + [this](const std::string& str) { logger_->AppendBitSequence(str); }); + } + + while (reader_.GetNumReadBits() < header_.markv_length_in_bits) { + inst_ = {}; + const spv_result_t decode_result = DecodeInstruction(); + if (decode_result != SPV_SUCCESS) return decode_result; + + const spv_result_t validation_result = UpdateValidationState(inst_); + if (validation_result != SPV_SUCCESS) return validation_result; + } + + if (reader_.GetNumReadBits() != header_.markv_length_in_bits || + !reader_.OnlyZeroesLeft()) { + return Diag(SPV_ERROR_INVALID_BINARY) + << "MARK-V binary has wrong stated bit length " + << reader_.GetNumReadBits() << " " << header_.markv_length_in_bits; + } + + // Decoding of the module is finished, validation state should have correct + // id bound. + spirv_[3] = GetIdBound(); + + *spirv_binary = std::move(spirv_); + return SPV_SUCCESS; +} + +// TODO(atgoo@github.com): The implementation borrows heavily from +// Parser::parseOperand. +// Consider coupling them together in some way once MARK-V codec is more mature. +// For now it's better to keep the code independent for experimentation +// purposes. +spv_result_t MarkvDecoder::DecodeOperand( + size_t operand_offset, const spv_operand_type_t type, + spv_operand_pattern_t* expected_operands) { + const SpvOp opcode = static_cast(inst_.opcode); + + memset(&operand_, 0, sizeof(operand_)); + + assert((operand_offset >> 16) == 0); + operand_.offset = static_cast(operand_offset); + operand_.type = type; + + // Set default values, may be updated later. + operand_.number_kind = SPV_NUMBER_NONE; + operand_.number_bit_width = 0; + + const size_t first_word_index = inst_words_.size(); + + switch (type) { + case SPV_OPERAND_TYPE_RESULT_ID: { + const spv_result_t result = DecodeResultId(); + if (result != SPV_SUCCESS) return result; + + inst_words_.push_back(inst_.result_id); + SetIdBound(std::max(GetIdBound(), inst_.result_id + 1)); + PromoteIfNeeded(inst_.result_id); + break; + } + + case SPV_OPERAND_TYPE_TYPE_ID: { + const spv_result_t result = DecodeTypeId(); + if (result != SPV_SUCCESS) return result; + + inst_words_.push_back(inst_.type_id); + SetIdBound(std::max(GetIdBound(), inst_.type_id + 1)); + PromoteIfNeeded(inst_.type_id); + break; + } + + case SPV_OPERAND_TYPE_ID: + case SPV_OPERAND_TYPE_OPTIONAL_ID: + case SPV_OPERAND_TYPE_SCOPE_ID: + case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID: { + uint32_t id = 0; + const spv_result_t result = DecodeRefId(&id); + if (result != SPV_SUCCESS) return result; + + if (id == 0) return Diag(SPV_ERROR_INVALID_BINARY) << "Decoded id is 0"; + + if (type == SPV_OPERAND_TYPE_ID || type == SPV_OPERAND_TYPE_OPTIONAL_ID) { + operand_.type = SPV_OPERAND_TYPE_ID; + + if (opcode == SpvOpExtInst && operand_.offset == 3) { + // The current word is the extended instruction set id. + // Set the extended instruction set type for the current + // instruction. + auto ext_inst_type_iter = import_id_to_ext_inst_type_.find(id); + if (ext_inst_type_iter == import_id_to_ext_inst_type_.end()) { + return Diag(SPV_ERROR_INVALID_ID) + << "OpExtInst set id " << id + << " does not reference an OpExtInstImport result Id"; + } + inst_.ext_inst_type = ext_inst_type_iter->second; + } + } + + inst_words_.push_back(id); + SetIdBound(std::max(GetIdBound(), id + 1)); + PromoteIfNeeded(id); + break; + } + + case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER: { + uint32_t word = 0; + const spv_result_t result = DecodeNonIdWord(&word); + if (result != SPV_SUCCESS) return result; + + inst_words_.push_back(word); + + assert(SpvOpExtInst == opcode); + assert(inst_.ext_inst_type != SPV_EXT_INST_TYPE_NONE); + spv_ext_inst_desc ext_inst; + if (grammar_.lookupExtInst(inst_.ext_inst_type, word, &ext_inst)) + return Diag(SPV_ERROR_INVALID_BINARY) + << "Invalid extended instruction number: " << word; + spvPushOperandTypes(ext_inst->operandTypes, expected_operands); + break; + } + + case SPV_OPERAND_TYPE_LITERAL_INTEGER: + case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_INTEGER: { + // These are regular single-word literal integer operands. + // Post-parsing validation should check the range of the parsed value. + operand_.type = SPV_OPERAND_TYPE_LITERAL_INTEGER; + // It turns out they are always unsigned integers! + operand_.number_kind = SPV_NUMBER_UNSIGNED_INT; + operand_.number_bit_width = 32; + + uint32_t word = 0; + const spv_result_t result = DecodeNonIdWord(&word); + if (result != SPV_SUCCESS) return result; + + inst_words_.push_back(word); + break; + } + + case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER: + case SPV_OPERAND_TYPE_OPTIONAL_TYPED_LITERAL_INTEGER: { + operand_.type = SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER; + if (opcode == SpvOpSwitch) { + // The literal operands have the same type as the value + // referenced by the selector Id. + const uint32_t selector_id = inst_words_.at(1); + const auto type_id_iter = id_to_type_id_.find(selector_id); + if (type_id_iter == id_to_type_id_.end() || type_id_iter->second == 0) { + return Diag(SPV_ERROR_INVALID_BINARY) + << "Invalid OpSwitch: selector id " << selector_id + << " has no type"; + } + uint32_t type_id = type_id_iter->second; + + if (selector_id == type_id) { + // Recall that by convention, a result ID that is a type definition + // maps to itself. + return Diag(SPV_ERROR_INVALID_BINARY) + << "Invalid OpSwitch: selector id " << selector_id + << " is a type, not a value"; + } + if (auto error = SetNumericTypeInfoForType(&operand_, type_id)) + return error; + if (operand_.number_kind != SPV_NUMBER_UNSIGNED_INT && + operand_.number_kind != SPV_NUMBER_SIGNED_INT) { + return Diag(SPV_ERROR_INVALID_BINARY) + << "Invalid OpSwitch: selector id " << selector_id + << " is not a scalar integer"; + } + } else { + assert(opcode == SpvOpConstant || opcode == SpvOpSpecConstant); + // The literal number type is determined by the type Id for the + // constant. + assert(inst_.type_id); + if (auto error = SetNumericTypeInfoForType(&operand_, inst_.type_id)) + return error; + } + + if (auto error = DecodeLiteralNumber(operand_)) return error; + + break; + } + + case SPV_OPERAND_TYPE_LITERAL_STRING: + case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_STRING: { + operand_.type = SPV_OPERAND_TYPE_LITERAL_STRING; + std::vector str; + auto* codec = model_->GetLiteralStringHuffmanCodec(inst_.opcode); + + if (codec) { + std::string decoded_string; + const bool huffman_result = + codec->DecodeFromStream(GetReadBitCallback(), &decoded_string); + assert(huffman_result); + if (!huffman_result) + return Diag(SPV_ERROR_INVALID_BINARY) + << "Failed to read literal string"; + + if (decoded_string != "kMarkvNoneOfTheAbove") { + std::copy(decoded_string.begin(), decoded_string.end(), + std::back_inserter(str)); + str.push_back('\0'); + } + } + + // The loop is expected to terminate once we encounter '\0' or exhaust + // the bit stream. + if (str.empty()) { + while (true) { + char ch = 0; + if (!reader_.ReadUnencoded(&ch)) + return Diag(SPV_ERROR_INVALID_BINARY) + << "Failed to read literal string"; + + str.push_back(ch); + + if (ch == '\0') break; + } + } + + while (str.size() % 4 != 0) str.push_back('\0'); + + inst_words_.resize(inst_words_.size() + str.size() / 4); + std::memcpy(&inst_words_[first_word_index], str.data(), str.size()); + + if (SpvOpExtInstImport == opcode) { + // Record the extended instruction type for the ID for this import. + // There is only one string literal argument to OpExtInstImport, + // so it's sufficient to guard this just on the opcode. + const spv_ext_inst_type_t ext_inst_type = + spvExtInstImportTypeGet(str.data()); + if (SPV_EXT_INST_TYPE_NONE == ext_inst_type) { + return Diag(SPV_ERROR_INVALID_BINARY) + << "Invalid extended instruction import '" << str.data() + << "'"; + } + // We must have parsed a valid result ID. It's a condition + // of the grammar, and we only accept non-zero result Ids. + assert(inst_.result_id); + const bool inserted = + import_id_to_ext_inst_type_.emplace(inst_.result_id, ext_inst_type) + .second; + (void)inserted; + assert(inserted); + } + break; + } + + case SPV_OPERAND_TYPE_CAPABILITY: + case SPV_OPERAND_TYPE_SOURCE_LANGUAGE: + case SPV_OPERAND_TYPE_EXECUTION_MODEL: + case SPV_OPERAND_TYPE_ADDRESSING_MODEL: + case SPV_OPERAND_TYPE_MEMORY_MODEL: + case SPV_OPERAND_TYPE_EXECUTION_MODE: + case SPV_OPERAND_TYPE_STORAGE_CLASS: + case SPV_OPERAND_TYPE_DIMENSIONALITY: + case SPV_OPERAND_TYPE_SAMPLER_ADDRESSING_MODE: + case SPV_OPERAND_TYPE_SAMPLER_FILTER_MODE: + case SPV_OPERAND_TYPE_SAMPLER_IMAGE_FORMAT: + case SPV_OPERAND_TYPE_FP_ROUNDING_MODE: + case SPV_OPERAND_TYPE_LINKAGE_TYPE: + case SPV_OPERAND_TYPE_ACCESS_QUALIFIER: + case SPV_OPERAND_TYPE_OPTIONAL_ACCESS_QUALIFIER: + case SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE: + case SPV_OPERAND_TYPE_DECORATION: + case SPV_OPERAND_TYPE_BUILT_IN: + case SPV_OPERAND_TYPE_GROUP_OPERATION: + case SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS: + case SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO: { + // A single word that is a plain enum value. + uint32_t word = 0; + const spv_result_t result = DecodeNonIdWord(&word); + if (result != SPV_SUCCESS) return result; + + inst_words_.push_back(word); + + // Map an optional operand type to its corresponding concrete type. + if (type == SPV_OPERAND_TYPE_OPTIONAL_ACCESS_QUALIFIER) + operand_.type = SPV_OPERAND_TYPE_ACCESS_QUALIFIER; + + spv_operand_desc entry; + if (grammar_.lookupOperand(type, word, &entry)) { + return Diag(SPV_ERROR_INVALID_BINARY) + << "Invalid " << spvOperandTypeStr(operand_.type) + << " operand: " << word; + } + + // Prepare to accept operands to this operand, if needed. + spvPushOperandTypes(entry->operandTypes, expected_operands); + break; + } + + case SPV_OPERAND_TYPE_FP_FAST_MATH_MODE: + case SPV_OPERAND_TYPE_FUNCTION_CONTROL: + case SPV_OPERAND_TYPE_LOOP_CONTROL: + case SPV_OPERAND_TYPE_IMAGE: + case SPV_OPERAND_TYPE_OPTIONAL_IMAGE: + case SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS: + case SPV_OPERAND_TYPE_SELECTION_CONTROL: { + // This operand is a mask. + uint32_t word = 0; + const spv_result_t result = DecodeNonIdWord(&word); + if (result != SPV_SUCCESS) return result; + + inst_words_.push_back(word); + + // Map an optional operand type to its corresponding concrete type. + if (type == SPV_OPERAND_TYPE_OPTIONAL_IMAGE) + operand_.type = SPV_OPERAND_TYPE_IMAGE; + else if (type == SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS) + operand_.type = SPV_OPERAND_TYPE_MEMORY_ACCESS; + + // Check validity of set mask bits. Also prepare for operands for those + // masks if they have any. To get operand order correct, scan from + // MSB to LSB since we can only prepend operands to a pattern. + // The only case in the grammar where you have more than one mask bit + // having an operand is for image operands. See SPIR-V 3.14 Image + // Operands. + uint32_t remaining_word = word; + for (uint32_t mask = (1u << 31); remaining_word; mask >>= 1) { + if (remaining_word & mask) { + spv_operand_desc entry; + if (grammar_.lookupOperand(type, mask, &entry)) { + return Diag(SPV_ERROR_INVALID_BINARY) + << "Invalid " << spvOperandTypeStr(operand_.type) + << " operand: " << word << " has invalid mask component " + << mask; + } + remaining_word ^= mask; + spvPushOperandTypes(entry->operandTypes, expected_operands); + } + } + if (word == 0) { + // An all-zeroes mask *might* also be valid. + spv_operand_desc entry; + if (SPV_SUCCESS == grammar_.lookupOperand(type, 0, &entry)) { + // Prepare for its operands, if any. + spvPushOperandTypes(entry->operandTypes, expected_operands); + } + } + break; + } + default: + return Diag(SPV_ERROR_INVALID_BINARY) + << "Internal error: Unhandled operand type: " << type; + } + + operand_.num_words = uint16_t(inst_words_.size() - first_word_index); + + assert(spvOperandIsConcrete(operand_.type)); + + parsed_operands_.push_back(operand_); + + return SPV_SUCCESS; +} + +spv_result_t MarkvDecoder::DecodeInstruction() { + parsed_operands_.clear(); + inst_words_.clear(); + + // Opcode/num_words placeholder, the word will be filled in later. + inst_words_.push_back(0); + + bool num_operands_still_unknown = true; + { + uint32_t opcode = 0; + uint32_t num_operands = 0; + + const spv_result_t opcode_decoding_result = + DecodeOpcodeAndNumberOfOperands(&opcode, &num_operands); + if (opcode_decoding_result < 0) return opcode_decoding_result; + + if (opcode_decoding_result == SPV_SUCCESS) { + inst_.num_operands = static_cast(num_operands); + num_operands_still_unknown = false; + } else { + if (!reader_.ReadVariableWidthU32(&opcode, + model_->opcode_chunk_length())) { + return Diag(SPV_ERROR_INVALID_BINARY) + << "Failed to read opcode of instruction"; + } + } + + inst_.opcode = static_cast(opcode); + } + + const SpvOp opcode = static_cast(inst_.opcode); + + spv_opcode_desc opcode_desc; + if (grammar_.lookupOpcode(opcode, &opcode_desc) != SPV_SUCCESS) { + return Diag(SPV_ERROR_INVALID_BINARY) << "Invalid opcode"; + } + + spv_operand_pattern_t expected_operands; + expected_operands.reserve(opcode_desc->numTypes); + for (auto i = 0; i < opcode_desc->numTypes; i++) { + expected_operands.push_back( + opcode_desc->operandTypes[opcode_desc->numTypes - i - 1]); + } + + if (num_operands_still_unknown) { + if (!OpcodeHasFixedNumberOfOperands(opcode)) { + if (!reader_.ReadVariableWidthU16(&inst_.num_operands, + model_->num_operands_chunk_length())) + return Diag(SPV_ERROR_INVALID_BINARY) + << "Failed to read num_operands of instruction"; + } else { + inst_.num_operands = static_cast(expected_operands.size()); + } + } + + for (operand_index_ = 0; + operand_index_ < static_cast(inst_.num_operands); + ++operand_index_) { + assert(!expected_operands.empty()); + const spv_operand_type_t type = + spvTakeFirstMatchableOperand(&expected_operands); + + const size_t operand_offset = inst_words_.size(); + + const spv_result_t decode_result = + DecodeOperand(operand_offset, type, &expected_operands); + + if (decode_result != SPV_SUCCESS) return decode_result; + } + + assert(inst_.num_operands == parsed_operands_.size()); + + // Only valid while inst_words_ and parsed_operands_ remain unchanged (until + // next DecodeInstruction call). + inst_.words = inst_words_.data(); + inst_.operands = parsed_operands_.empty() ? nullptr : parsed_operands_.data(); + inst_.num_words = static_cast(inst_words_.size()); + inst_words_[0] = spvOpcodeMake(inst_.num_words, SpvOp(inst_.opcode)); + + std::copy(inst_words_.begin(), inst_words_.end(), std::back_inserter(spirv_)); + + assert(inst_.num_words == + std::accumulate( + parsed_operands_.begin(), parsed_operands_.end(), 1, + [](int num_words, const spv_parsed_operand_t& operand) { + return num_words += operand.num_words; + }) && + "num_words in instruction doesn't correspond to the sum of num_words" + "in the operands"); + + RecordNumberType(); + ProcessCurInstruction(); + + if (!ReadToByteBreak(kByteBreakAfterInstIfLessThanUntilNextByte)) + return Diag(SPV_ERROR_INVALID_BINARY) << "Failed to read to byte break"; + + if (logger_) { + logger_->NewLine(); + std::stringstream ss; + ss << spvOpcodeString(opcode) << " "; + for (size_t index = 1; index < inst_words_.size(); ++index) + ss << inst_words_[index] << " "; + logger_->AppendText(ss.str()); + logger_->NewLine(); + logger_->NewLine(); + if (!logger_->DebugInstruction(inst_)) return SPV_REQUESTED_TERMINATION; + } + + return SPV_SUCCESS; +} + +spv_result_t MarkvDecoder::SetNumericTypeInfoForType( + spv_parsed_operand_t* parsed_operand, uint32_t type_id) { + assert(type_id != 0); + auto type_info_iter = type_id_to_number_type_info_.find(type_id); + if (type_info_iter == type_id_to_number_type_info_.end()) { + return Diag(SPV_ERROR_INVALID_BINARY) + << "Type Id " << type_id << " is not a type"; + } + + const NumberType& info = type_info_iter->second; + if (info.type == SPV_NUMBER_NONE) { + // This is a valid type, but for something other than a scalar number. + return Diag(SPV_ERROR_INVALID_BINARY) + << "Type Id " << type_id << " is not a scalar numeric type"; + } + + parsed_operand->number_kind = info.type; + parsed_operand->number_bit_width = info.bit_width; + // Round up the word count. + parsed_operand->num_words = static_cast((info.bit_width + 31) / 32); + return SPV_SUCCESS; +} + +void MarkvDecoder::RecordNumberType() { + const SpvOp opcode = static_cast(inst_.opcode); + if (spvOpcodeGeneratesType(opcode)) { + NumberType info = {SPV_NUMBER_NONE, 0}; + if (SpvOpTypeInt == opcode) { + info.bit_width = inst_.words[inst_.operands[1].offset]; + info.type = inst_.words[inst_.operands[2].offset] + ? SPV_NUMBER_SIGNED_INT + : SPV_NUMBER_UNSIGNED_INT; + } else if (SpvOpTypeFloat == opcode) { + info.bit_width = inst_.words[inst_.operands[1].offset]; + info.type = SPV_NUMBER_FLOATING; + } + // The *result* Id of a type generating instruction is the type Id. + type_id_to_number_type_info_[inst_.result_id] = info; + } +} + +spv_result_t EncodeHeader(void* user_data, spv_endianness_t endian, + uint32_t magic, uint32_t version, uint32_t generator, + uint32_t id_bound, uint32_t schema) { + MarkvEncoder* encoder = reinterpret_cast(user_data); + return encoder->EncodeHeader(endian, magic, version, generator, id_bound, + schema); +} + +spv_result_t EncodeInstruction(void* user_data, + const spv_parsed_instruction_t* inst) { + MarkvEncoder* encoder = reinterpret_cast(user_data); + return encoder->EncodeInstruction(*inst); +} + +} // namespace + +spv_result_t SpirvToMarkv( + spv_const_context context, const std::vector& spirv, + const MarkvCodecOptions& options, const MarkvModel& markv_model, + MessageConsumer message_consumer, MarkvLogConsumer log_consumer, + MarkvDebugConsumer debug_consumer, std::vector* markv) { + spv_context_t hijack_context = *context; + libspirv::SetContextMessageConsumer(&hijack_context, message_consumer); + + spv_const_binary_t spirv_binary = {spirv.data(), spirv.size()}; + + spv_endianness_t endian; + spv_position_t position = {}; + if (spvBinaryEndianness(&spirv_binary, &endian)) { + return DiagnosticStream(position, hijack_context.consumer, + SPV_ERROR_INVALID_BINARY) + << "Invalid SPIR-V magic number."; + } + + spv_header_t header; + if (spvBinaryHeaderGet(&spirv_binary, endian, &header)) { + return DiagnosticStream(position, hijack_context.consumer, + SPV_ERROR_INVALID_BINARY) + << "Invalid SPIR-V header."; + } + + MarkvEncoder encoder(&hijack_context, options, &markv_model); + + if (log_consumer || debug_consumer) { + encoder.CreateLogger(log_consumer, debug_consumer); + + spv_text text = nullptr; + if (spvBinaryToText(&hijack_context, spirv.data(), spirv.size(), + SPV_BINARY_TO_TEXT_OPTION_NO_HEADER, &text, + nullptr) != SPV_SUCCESS) { + return DiagnosticStream(position, hijack_context.consumer, + SPV_ERROR_INVALID_BINARY) + << "Failed to disassemble SPIR-V binary."; + } + assert(text); + encoder.SetDisassembly(std::string(text->str, text->length)); + spvTextDestroy(text); + } + + if (spvBinaryParse(&hijack_context, &encoder, spirv.data(), spirv.size(), + EncodeHeader, EncodeInstruction, nullptr) != SPV_SUCCESS) { + return DiagnosticStream(position, hijack_context.consumer, + SPV_ERROR_INVALID_BINARY) + << "Unable to encode to MARK-V."; + } + + *markv = encoder.GetMarkvBinary(); + return SPV_SUCCESS; +} + +spv_result_t MarkvToSpirv( + spv_const_context context, const std::vector& markv, + const MarkvCodecOptions& options, const MarkvModel& markv_model, + MessageConsumer message_consumer, MarkvLogConsumer log_consumer, + MarkvDebugConsumer debug_consumer, std::vector* spirv) { + spv_position_t position = {}; + spv_context_t hijack_context = *context; + libspirv::SetContextMessageConsumer(&hijack_context, message_consumer); + + MarkvDecoder decoder(&hijack_context, markv, options, &markv_model); + + if (log_consumer || debug_consumer) + decoder.CreateLogger(log_consumer, debug_consumer); + + if (decoder.DecodeModule(spirv) != SPV_SUCCESS) { + return DiagnosticStream(position, hijack_context.consumer, + SPV_ERROR_INVALID_BINARY) + << "Unable to decode MARK-V."; + } + + assert(!spirv->empty()); + return SPV_SUCCESS; +} + +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/comp/markv_model.h b/3rdparty/spirv-tools/source/comp/markv_model.h new file mode 100644 index 000000000..606396e6d --- /dev/null +++ b/3rdparty/spirv-tools/source/comp/markv_model.h @@ -0,0 +1,232 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_COMP_MARKV_MODEL_H_ +#define LIBSPIRV_COMP_MARKV_MODEL_H_ + +#include +#include +#include + +#include "latest_version_spirv_header.h" +#include "spirv-tools/libspirv.h" +#include "util/huffman_codec.h" + +namespace spvtools { + +// Base class for MARK-V models. +// The class contains encoding/decoding model with various constants and +// codecs used by the compression algorithm. +class MarkvModel { + public: + MarkvModel() + : operand_chunk_lengths_( + static_cast(SPV_OPERAND_TYPE_NUM_OPERAND_TYPES), 0) { + // Set default values. + operand_chunk_lengths_[SPV_OPERAND_TYPE_TYPE_ID] = 4; + operand_chunk_lengths_[SPV_OPERAND_TYPE_RESULT_ID] = 8; + operand_chunk_lengths_[SPV_OPERAND_TYPE_ID] = 8; + operand_chunk_lengths_[SPV_OPERAND_TYPE_SCOPE_ID] = 8; + operand_chunk_lengths_[SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID] = 8; + operand_chunk_lengths_[SPV_OPERAND_TYPE_LITERAL_INTEGER] = 6; + operand_chunk_lengths_[SPV_OPERAND_TYPE_OPTIONAL_LITERAL_INTEGER] = 6; + operand_chunk_lengths_[SPV_OPERAND_TYPE_CAPABILITY] = 6; + operand_chunk_lengths_[SPV_OPERAND_TYPE_SOURCE_LANGUAGE] = 3; + operand_chunk_lengths_[SPV_OPERAND_TYPE_EXECUTION_MODEL] = 3; + operand_chunk_lengths_[SPV_OPERAND_TYPE_ADDRESSING_MODEL] = 2; + operand_chunk_lengths_[SPV_OPERAND_TYPE_MEMORY_MODEL] = 2; + operand_chunk_lengths_[SPV_OPERAND_TYPE_EXECUTION_MODE] = 6; + operand_chunk_lengths_[SPV_OPERAND_TYPE_STORAGE_CLASS] = 4; + operand_chunk_lengths_[SPV_OPERAND_TYPE_DIMENSIONALITY] = 3; + operand_chunk_lengths_[SPV_OPERAND_TYPE_SAMPLER_ADDRESSING_MODE] = 3; + operand_chunk_lengths_[SPV_OPERAND_TYPE_SAMPLER_FILTER_MODE] = 2; + operand_chunk_lengths_[SPV_OPERAND_TYPE_SAMPLER_IMAGE_FORMAT] = 6; + operand_chunk_lengths_[SPV_OPERAND_TYPE_FP_ROUNDING_MODE] = 2; + operand_chunk_lengths_[SPV_OPERAND_TYPE_LINKAGE_TYPE] = 2; + operand_chunk_lengths_[SPV_OPERAND_TYPE_ACCESS_QUALIFIER] = 2; + operand_chunk_lengths_[SPV_OPERAND_TYPE_OPTIONAL_ACCESS_QUALIFIER] = 2; + operand_chunk_lengths_[SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE] = 3; + operand_chunk_lengths_[SPV_OPERAND_TYPE_DECORATION] = 6; + operand_chunk_lengths_[SPV_OPERAND_TYPE_BUILT_IN] = 6; + operand_chunk_lengths_[SPV_OPERAND_TYPE_GROUP_OPERATION] = 2; + operand_chunk_lengths_[SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS] = 2; + operand_chunk_lengths_[SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO] = 2; + operand_chunk_lengths_[SPV_OPERAND_TYPE_FP_FAST_MATH_MODE] = 4; + operand_chunk_lengths_[SPV_OPERAND_TYPE_FUNCTION_CONTROL] = 4; + operand_chunk_lengths_[SPV_OPERAND_TYPE_LOOP_CONTROL] = 4; + operand_chunk_lengths_[SPV_OPERAND_TYPE_IMAGE] = 4; + operand_chunk_lengths_[SPV_OPERAND_TYPE_OPTIONAL_IMAGE] = 4; + operand_chunk_lengths_[SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS] = 4; + operand_chunk_lengths_[SPV_OPERAND_TYPE_SELECTION_CONTROL] = 4; + operand_chunk_lengths_[SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER] = 6; + operand_chunk_lengths_[SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER] = 6; + } + + uint32_t model_type() const { return model_type_; } + uint32_t model_version() const { return model_version_; } + + uint32_t opcode_chunk_length() const { return opcode_chunk_length_; } + uint32_t num_operands_chunk_length() const { + return num_operands_chunk_length_; + } + uint32_t mtf_rank_chunk_length() const { return mtf_rank_chunk_length_; } + + uint32_t u64_chunk_length() const { return u64_chunk_length_; } + uint32_t s64_chunk_length() const { return s64_chunk_length_; } + uint32_t s64_block_exponent() const { return s64_block_exponent_; } + + enum class IdFallbackStrategy { + kRuleBased = 0, + kShortDescriptor, + }; + + IdFallbackStrategy id_fallback_strategy() const { + return id_fallback_strategy_; + } + + // Returns a codec for common opcode_and_num_operands words for the given + // previous opcode. May return nullptr if the codec doesn't exist. + const spvutils::HuffmanCodec* + GetOpcodeAndNumOperandsMarkovHuffmanCodec(uint32_t prev_opcode) const { + if (prev_opcode == SpvOpNop) + return opcode_and_num_operands_huffman_codec_.get(); + + const auto it = + opcode_and_num_operands_markov_huffman_codecs_.find(prev_opcode); + if (it == opcode_and_num_operands_markov_huffman_codecs_.end()) + return nullptr; + return it->second.get(); + } + + // Returns a codec for common non-id words used for given operand slot. + // Operand slot is defined by the opcode and the operand index. + // May return nullptr if the codec doesn't exist. + const spvutils::HuffmanCodec* GetNonIdWordHuffmanCodec( + uint32_t opcode, uint32_t operand_index) const { + const auto it = non_id_word_huffman_codecs_.find( + std::pair(opcode, operand_index)); + if (it == non_id_word_huffman_codecs_.end()) return nullptr; + return it->second.get(); + } + + // Returns a codec for common id descriptos used for given operand slot. + // Operand slot is defined by the opcode and the operand index. + // May return nullptr if the codec doesn't exist. + const spvutils::HuffmanCodec* GetIdDescriptorHuffmanCodec( + uint32_t opcode, uint32_t operand_index) const { + const auto it = id_descriptor_huffman_codecs_.find( + std::pair(opcode, operand_index)); + if (it == id_descriptor_huffman_codecs_.end()) return nullptr; + return it->second.get(); + } + + // Returns a codec for common strings used by the given opcode. + // Operand slot is defined by the opcode and the operand index. + // May return nullptr if the codec doesn't exist. + const spvutils::HuffmanCodec* GetLiteralStringHuffmanCodec( + uint32_t opcode) const { + const auto it = literal_string_huffman_codecs_.find(opcode); + if (it == literal_string_huffman_codecs_.end()) return nullptr; + return it->second.get(); + } + + // Checks if |descriptor| has a coding scheme in any of + // id_descriptor_huffman_codecs_. + bool DescriptorHasCodingScheme(uint32_t descriptor) const { + return descriptors_with_coding_scheme_.count(descriptor); + } + + // Checks if any descriptor has a coding scheme. + bool AnyDescriptorHasCodingScheme() const { + return !descriptors_with_coding_scheme_.empty(); + } + + // Returns chunk length used for variable length encoding of spirv operand + // words. + uint32_t GetOperandVariableWidthChunkLength(spv_operand_type_t type) const { + return operand_chunk_lengths_.at(static_cast(type)); + } + + // Sets model type. + void SetModelType(uint32_t in_model_type) { model_type_ = in_model_type; } + + // Sets model version. + void SetModelVersion(uint32_t in_model_version) { + model_version_ = in_model_version; + } + + // Returns value used by Huffman codecs as a signal that a value is not in the + // coding table. + static uint64_t GetMarkvNoneOfTheAbove() { + // Magic number. + return 1111111111111111111; + } + + MarkvModel(const MarkvModel&) = delete; + const MarkvModel& operator=(const MarkvModel&) = delete; + + protected: + // Huffman codec for base-rate of opcode_and_num_operands. + std::unique_ptr> + opcode_and_num_operands_huffman_codec_; + + // Huffman codecs for opcode_and_num_operands. The map key is previous opcode. + std::map>> + opcode_and_num_operands_markov_huffman_codecs_; + + // Huffman codecs for non-id single-word operand values. + // The map key is pair . + std::map, + std::unique_ptr>> + non_id_word_huffman_codecs_; + + // Huffman codecs for id descriptors. The map key is pair + // . + std::map, + std::unique_ptr>> + id_descriptor_huffman_codecs_; + + // Set of all descriptors which have a coding scheme in any of + // id_descriptor_huffman_codecs_. + std::unordered_set descriptors_with_coding_scheme_; + + // Huffman codecs for literal strings. The map key is the opcode of the + // current instruction. This assumes, that there is no more than one literal + // string operand per instruction, but would still work even if this is not + // the case. Names and debug information strings are not collected. + std::map>> + literal_string_huffman_codecs_; + + // Chunk lengths used for variable width encoding of operands (index is + // spv_operand_type of the operand). + std::vector operand_chunk_lengths_; + + uint32_t opcode_chunk_length_ = 7; + uint32_t num_operands_chunk_length_ = 3; + uint32_t mtf_rank_chunk_length_ = 5; + + uint32_t u64_chunk_length_ = 8; + uint32_t s64_chunk_length_ = 8; + uint32_t s64_block_exponent_ = 10; + + IdFallbackStrategy id_fallback_strategy_ = + IdFallbackStrategy::kShortDescriptor; + + uint32_t model_type_ = 0; + uint32_t model_version_ = 0; +}; + +} // namespace spvtools + +#endif // LIBSPIRV_COMP_MARKV_MODEL_H_ diff --git a/3rdparty/spirv-tools/source/diagnostic.cpp b/3rdparty/spirv-tools/source/diagnostic.cpp new file mode 100644 index 000000000..578712069 --- /dev/null +++ b/3rdparty/spirv-tools/source/diagnostic.cpp @@ -0,0 +1,186 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "diagnostic.h" + +#include +#include +#include +#include + +#include "table.h" + +// Diagnostic API + +spv_diagnostic spvDiagnosticCreate(const spv_position position, + const char* message) { + spv_diagnostic diagnostic = new spv_diagnostic_t; + if (!diagnostic) return nullptr; + size_t length = strlen(message) + 1; + diagnostic->error = new char[length]; + if (!diagnostic->error) { + delete diagnostic; + return nullptr; + } + diagnostic->position = *position; + diagnostic->isTextSource = false; + memset(diagnostic->error, 0, length); + strncpy(diagnostic->error, message, length); + return diagnostic; +} + +void spvDiagnosticDestroy(spv_diagnostic diagnostic) { + if (!diagnostic) return; + delete[] diagnostic->error; + delete diagnostic; +} + +spv_result_t spvDiagnosticPrint(const spv_diagnostic diagnostic) { + if (!diagnostic) return SPV_ERROR_INVALID_DIAGNOSTIC; + + if (diagnostic->isTextSource) { + // NOTE: This is a text position + // NOTE: add 1 to the line as editors start at line 1, we are counting new + // line characters to start at line 0 + std::cerr << "error: " << diagnostic->position.line + 1 << ": " + << diagnostic->position.column + 1 << ": " << diagnostic->error + << "\n"; + return SPV_SUCCESS; + } else { + // NOTE: Assume this is a binary position + std::cerr << "error: " << diagnostic->position.index << ": " + << diagnostic->error << "\n"; + return SPV_SUCCESS; + } +} + +namespace libspirv { + +DiagnosticStream::DiagnosticStream(DiagnosticStream&& other) + : stream_(), + position_(other.position_), + consumer_(other.consumer_), + error_(other.error_) { + // Prevent the other object from emitting output during destruction. + other.error_ = SPV_FAILED_MATCH; + // Some platforms are missing support for std::ostringstream functionality, + // including: move constructor, swap method. Either would have been a + // better choice than copying the string. + stream_ << other.stream_.str(); +} + +DiagnosticStream::~DiagnosticStream() { + if (error_ != SPV_FAILED_MATCH && consumer_ != nullptr) { + auto level = SPV_MSG_ERROR; + switch (error_) { + case SPV_SUCCESS: + case SPV_REQUESTED_TERMINATION: // Essentially success. + level = SPV_MSG_INFO; + break; + case SPV_WARNING: + level = SPV_MSG_WARNING; + break; + case SPV_UNSUPPORTED: + case SPV_ERROR_INTERNAL: + case SPV_ERROR_INVALID_TABLE: + level = SPV_MSG_INTERNAL_ERROR; + break; + case SPV_ERROR_OUT_OF_MEMORY: + level = SPV_MSG_FATAL; + break; + default: + break; + } + consumer_(level, "input", position_, stream_.str().c_str()); + } +} + +void UseDiagnosticAsMessageConsumer(spv_context context, + spv_diagnostic* diagnostic) { + assert(diagnostic && *diagnostic == nullptr); + + auto create_diagnostic = [diagnostic](spv_message_level_t, const char*, + const spv_position_t& position, + const char* message) { + auto p = position; + spvDiagnosticDestroy(*diagnostic); // Avoid memory leak. + *diagnostic = spvDiagnosticCreate(&p, message); + }; + libspirv::SetContextMessageConsumer(context, std::move(create_diagnostic)); +} + +std::string spvResultToString(spv_result_t res) { + std::string out; + switch (res) { + case SPV_SUCCESS: + out = "SPV_SUCCESS"; + break; + case SPV_UNSUPPORTED: + out = "SPV_UNSUPPORTED"; + break; + case SPV_END_OF_STREAM: + out = "SPV_END_OF_STREAM"; + break; + case SPV_WARNING: + out = "SPV_WARNING"; + break; + case SPV_FAILED_MATCH: + out = "SPV_FAILED_MATCH"; + break; + case SPV_REQUESTED_TERMINATION: + out = "SPV_REQUESTED_TERMINATION"; + break; + case SPV_ERROR_INTERNAL: + out = "SPV_ERROR_INTERNAL"; + break; + case SPV_ERROR_OUT_OF_MEMORY: + out = "SPV_ERROR_OUT_OF_MEMORY"; + break; + case SPV_ERROR_INVALID_POINTER: + out = "SPV_ERROR_INVALID_POINTER"; + break; + case SPV_ERROR_INVALID_BINARY: + out = "SPV_ERROR_INVALID_BINARY"; + break; + case SPV_ERROR_INVALID_TEXT: + out = "SPV_ERROR_INVALID_TEXT"; + break; + case SPV_ERROR_INVALID_TABLE: + out = "SPV_ERROR_INVALID_TABLE"; + break; + case SPV_ERROR_INVALID_VALUE: + out = "SPV_ERROR_INVALID_VALUE"; + break; + case SPV_ERROR_INVALID_DIAGNOSTIC: + out = "SPV_ERROR_INVALID_DIAGNOSTIC"; + break; + case SPV_ERROR_INVALID_LOOKUP: + out = "SPV_ERROR_INVALID_LOOKUP"; + break; + case SPV_ERROR_INVALID_ID: + out = "SPV_ERROR_INVALID_ID"; + break; + case SPV_ERROR_INVALID_CFG: + out = "SPV_ERROR_INVALID_CFG"; + break; + case SPV_ERROR_INVALID_LAYOUT: + out = "SPV_ERROR_INVALID_LAYOUT"; + break; + default: + out = "Unknown Error"; + } + return out; +} + +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/diagnostic.h b/3rdparty/spirv-tools/source/diagnostic.h new file mode 100644 index 000000000..3d06f8df0 --- /dev/null +++ b/3rdparty/spirv-tools/source/diagnostic.h @@ -0,0 +1,75 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_DIAGNOSTIC_H_ +#define LIBSPIRV_DIAGNOSTIC_H_ + +#include +#include + +#include "spirv-tools/libspirv.hpp" + +namespace libspirv { + +// A DiagnosticStream remembers the current position of the input and an error +// code, and captures diagnostic messages via the left-shift operator. +// If the error code is not SPV_FAILED_MATCH, then captured messages are +// emitted during the destructor. +class DiagnosticStream { + public: + DiagnosticStream(spv_position_t position, + const spvtools::MessageConsumer& consumer, + spv_result_t error) + : position_(position), consumer_(consumer), error_(error) {} + + // Creates a DiagnosticStream from an expiring DiagnosticStream. + // The new object takes the contents of the other, and prevents the + // other from emitting anything during destruction. + DiagnosticStream(DiagnosticStream&& other); + + // Destroys a DiagnosticStream. + // If its status code is something other than SPV_FAILED_MATCH + // then emit the accumulated message to the consumer. + ~DiagnosticStream(); + + // Adds the given value to the diagnostic message to be written. + template + DiagnosticStream& operator<<(const T& val) { + stream_ << val; + return *this; + } + + // Conversion operator to spv_result, returning the error code. + operator spv_result_t() { return error_; } + + private: + std::ostringstream stream_; + spv_position_t position_; + spvtools::MessageConsumer consumer_; // Message consumer callback. + spv_result_t error_; +}; + +// Changes the MessageConsumer in |context| to one that updates |diagnostic| +// with the last message received. +// +// This function expects that |diagnostic| is not nullptr and its content is a +// nullptr. +void UseDiagnosticAsMessageConsumer(spv_context context, + spv_diagnostic* diagnostic); + +std::string spvResultToString(spv_result_t res); + +} // namespace libspirv + +#endif // LIBSPIRV_DIAGNOSTIC_H_ diff --git a/3rdparty/spirv-tools/source/disassemble.cpp b/3rdparty/spirv-tools/source/disassemble.cpp new file mode 100644 index 000000000..909886c09 --- /dev/null +++ b/3rdparty/spirv-tools/source/disassemble.cpp @@ -0,0 +1,476 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file contains a disassembler: It converts a SPIR-V binary +// to text. + +#include +#include +#include +#include +#include +#include + +#include "assembly_grammar.h" +#include "binary.h" +#include "diagnostic.h" +#include "disassemble.h" +#include "ext_inst.h" +#include "name_mapper.h" +#include "opcode.h" +#include "parsed_operand.h" +#include "print.h" +#include "spirv-tools/libspirv.h" +#include "spirv_constant.h" +#include "spirv_endian.h" +#include "util/hex_float.h" + +namespace { + +// A Disassembler instance converts a SPIR-V binary to its assembly +// representation. +class Disassembler { + public: + Disassembler(const libspirv::AssemblyGrammar& grammar, uint32_t options, + libspirv::NameMapper name_mapper) + : grammar_(grammar), + print_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_PRINT, options)), + color_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_COLOR, options)), + indent_(spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_INDENT, options) + ? kStandardIndent + : 0), + text_(), + out_(print_ ? out_stream() : out_stream(text_)), + stream_(out_.get()), + header_(!spvIsInBitfield(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER, options)), + show_byte_offset_(spvIsInBitfield( + SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET, options)), + byte_offset_(0), + name_mapper_(std::move(name_mapper)) {} + + // Emits the assembly header for the module, and sets up internal state + // so subsequent callbacks can handle the cases where the entire module + // is either big-endian or little-endian. + spv_result_t HandleHeader(spv_endianness_t endian, uint32_t version, + uint32_t generator, uint32_t id_bound, + uint32_t schema); + // Emits the assembly text for the given instruction. + spv_result_t HandleInstruction(const spv_parsed_instruction_t& inst); + + // If not printing, populates text_result with the accumulated text. + // Returns SPV_SUCCESS on success. + spv_result_t SaveTextResult(spv_text* text_result) const; + + private: + enum { kStandardIndent = 15 }; + + using out_stream = libspirv::out_stream; + + // Emits an operand for the given instruction, where the instruction + // is at offset words from the start of the binary. + void EmitOperand(const spv_parsed_instruction_t& inst, + const uint16_t operand_index); + + // Emits a mask expression for the given mask word of the specified type. + void EmitMaskOperand(const spv_operand_type_t type, const uint32_t word); + + // Resets the output color, if color is turned on. + void ResetColor() { + if (color_) out_.get() << libspirv::clr::reset{print_}; + } + // Sets the output to grey, if color is turned on. + void SetGrey() { + if (color_) out_.get() << libspirv::clr::grey{print_}; + } + // Sets the output to blue, if color is turned on. + void SetBlue() { + if (color_) out_.get() << libspirv::clr::blue{print_}; + } + // Sets the output to yellow, if color is turned on. + void SetYellow() { + if (color_) out_.get() << libspirv::clr::yellow{print_}; + } + // Sets the output to red, if color is turned on. + void SetRed() { + if (color_) out_.get() << libspirv::clr::red{print_}; + } + // Sets the output to green, if color is turned on. + void SetGreen() { + if (color_) out_.get() << libspirv::clr::green{print_}; + } + + const libspirv::AssemblyGrammar& grammar_; + const bool print_; // Should we also print to the standard output stream? + const bool color_; // Should we print in colour? + const int indent_; // How much to indent. 0 means don't indent + spv_endianness_t endian_; // The detected endianness of the binary. + std::stringstream text_; // Captures the text, if not printing. + out_stream out_; // The Output stream. Either to text_ or standard output. + std::ostream& stream_; // The output std::stream. + const bool header_; // Should we output header as the leading comment? + const bool show_byte_offset_; // Should we print byte offset, in hex? + size_t byte_offset_; // The number of bytes processed so far. + libspirv::NameMapper name_mapper_; +}; + +spv_result_t Disassembler::HandleHeader(spv_endianness_t endian, + uint32_t version, uint32_t generator, + uint32_t id_bound, uint32_t schema) { + endian_ = endian; + + if (header_) { + SetGrey(); + const char* generator_tool = + spvGeneratorStr(SPV_GENERATOR_TOOL_PART(generator)); + stream_ << "; SPIR-V\n" + << "; Version: " << SPV_SPIRV_VERSION_MAJOR_PART(version) << "." + << SPV_SPIRV_VERSION_MINOR_PART(version) << "\n" + << "; Generator: " << generator_tool; + // For unknown tools, print the numeric tool value. + if (0 == strcmp("Unknown", generator_tool)) { + stream_ << "(" << SPV_GENERATOR_TOOL_PART(generator) << ")"; + } + // Print the miscellaneous part of the generator word on the same + // line as the tool name. + stream_ << "; " << SPV_GENERATOR_MISC_PART(generator) << "\n" + << "; Bound: " << id_bound << "\n" + << "; Schema: " << schema << "\n"; + ResetColor(); + } + + byte_offset_ = SPV_INDEX_INSTRUCTION * sizeof(uint32_t); + + return SPV_SUCCESS; +} + +spv_result_t Disassembler::HandleInstruction( + const spv_parsed_instruction_t& inst) { + if (inst.result_id) { + SetBlue(); + const std::string id_name = name_mapper_(inst.result_id); + if (indent_) + stream_ << std::setw(std::max(0, indent_ - 3 - int(id_name.size()))); + stream_ << "%" << id_name; + ResetColor(); + stream_ << " = "; + } else { + stream_ << std::string(indent_, ' '); + } + + stream_ << "Op" << spvOpcodeString(static_cast(inst.opcode)); + + for (uint16_t i = 0; i < inst.num_operands; i++) { + const spv_operand_type_t type = inst.operands[i].type; + assert(type != SPV_OPERAND_TYPE_NONE); + if (type == SPV_OPERAND_TYPE_RESULT_ID) continue; + stream_ << " "; + EmitOperand(inst, i); + } + + if (show_byte_offset_) { + SetGrey(); + auto saved_flags = stream_.flags(); + auto saved_fill = stream_.fill(); + stream_ << " ; 0x" << std::setw(8) << std::hex << std::setfill('0') + << byte_offset_; + stream_.flags(saved_flags); + stream_.fill(saved_fill); + ResetColor(); + } + + byte_offset_ += inst.num_words * sizeof(uint32_t); + + stream_ << "\n"; + return SPV_SUCCESS; +} + +void Disassembler::EmitOperand(const spv_parsed_instruction_t& inst, + const uint16_t operand_index) { + assert(operand_index < inst.num_operands); + const spv_parsed_operand_t& operand = inst.operands[operand_index]; + const uint32_t word = inst.words[operand.offset]; + switch (operand.type) { + case SPV_OPERAND_TYPE_RESULT_ID: + assert(false && " is not supposed to be handled here"); + SetBlue(); + stream_ << "%" << name_mapper_(word); + break; + case SPV_OPERAND_TYPE_ID: + case SPV_OPERAND_TYPE_TYPE_ID: + case SPV_OPERAND_TYPE_SCOPE_ID: + case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID: + SetYellow(); + stream_ << "%" << name_mapper_(word); + break; + case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER: { + spv_ext_inst_desc ext_inst; + if (grammar_.lookupExtInst(inst.ext_inst_type, word, &ext_inst)) + assert(false && "should have caught this earlier"); + SetRed(); + stream_ << ext_inst->name; + } break; + case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: { + spv_opcode_desc opcode_desc; + if (grammar_.lookupOpcode(SpvOp(word), &opcode_desc)) + assert(false && "should have caught this earlier"); + SetRed(); + stream_ << opcode_desc->name; + } break; + case SPV_OPERAND_TYPE_LITERAL_INTEGER: + case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER: { + SetRed(); + libspirv::EmitNumericLiteral(&stream_, inst, operand); + ResetColor(); + } break; + case SPV_OPERAND_TYPE_LITERAL_STRING: { + stream_ << "\""; + SetGreen(); + // Strings are always little-endian, and null-terminated. + // Write out the characters, escaping as needed, and without copying + // the entire string. + auto c_str = reinterpret_cast(inst.words + operand.offset); + for (auto p = c_str; *p; ++p) { + if (*p == '"' || *p == '\\') stream_ << '\\'; + stream_ << *p; + } + ResetColor(); + stream_ << '"'; + } break; + case SPV_OPERAND_TYPE_CAPABILITY: + case SPV_OPERAND_TYPE_SOURCE_LANGUAGE: + case SPV_OPERAND_TYPE_EXECUTION_MODEL: + case SPV_OPERAND_TYPE_ADDRESSING_MODEL: + case SPV_OPERAND_TYPE_MEMORY_MODEL: + case SPV_OPERAND_TYPE_EXECUTION_MODE: + case SPV_OPERAND_TYPE_STORAGE_CLASS: + case SPV_OPERAND_TYPE_DIMENSIONALITY: + case SPV_OPERAND_TYPE_SAMPLER_ADDRESSING_MODE: + case SPV_OPERAND_TYPE_SAMPLER_FILTER_MODE: + case SPV_OPERAND_TYPE_SAMPLER_IMAGE_FORMAT: + case SPV_OPERAND_TYPE_FP_ROUNDING_MODE: + case SPV_OPERAND_TYPE_LINKAGE_TYPE: + case SPV_OPERAND_TYPE_ACCESS_QUALIFIER: + case SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE: + case SPV_OPERAND_TYPE_DECORATION: + case SPV_OPERAND_TYPE_BUILT_IN: + case SPV_OPERAND_TYPE_GROUP_OPERATION: + case SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS: + case SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO: + case SPV_OPERAND_TYPE_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING: + case SPV_OPERAND_TYPE_DEBUG_COMPOSITE_TYPE: + case SPV_OPERAND_TYPE_DEBUG_TYPE_QUALIFIER: + case SPV_OPERAND_TYPE_DEBUG_OPERATION: { + spv_operand_desc entry; + if (grammar_.lookupOperand(operand.type, word, &entry)) + assert(false && "should have caught this earlier"); + stream_ << entry->name; + } break; + case SPV_OPERAND_TYPE_FP_FAST_MATH_MODE: + case SPV_OPERAND_TYPE_FUNCTION_CONTROL: + case SPV_OPERAND_TYPE_LOOP_CONTROL: + case SPV_OPERAND_TYPE_IMAGE: + case SPV_OPERAND_TYPE_MEMORY_ACCESS: + case SPV_OPERAND_TYPE_SELECTION_CONTROL: + case SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS: + EmitMaskOperand(operand.type, word); + break; + default: + assert(false && "unhandled or invalid case"); + } + ResetColor(); +} + +void Disassembler::EmitMaskOperand(const spv_operand_type_t type, + const uint32_t word) { + // Scan the mask from least significant bit to most significant bit. For each + // set bit, emit the name of that bit. Separate multiple names with '|'. + uint32_t remaining_word = word; + uint32_t mask; + int num_emitted = 0; + for (mask = 1; remaining_word; mask <<= 1) { + if (remaining_word & mask) { + remaining_word ^= mask; + spv_operand_desc entry; + if (grammar_.lookupOperand(type, mask, &entry)) + assert(false && "should have caught this earlier"); + if (num_emitted) stream_ << "|"; + stream_ << entry->name; + num_emitted++; + } + } + if (!num_emitted) { + // An operand value of 0 was provided, so represent it by the name + // of the 0 value. In many cases, that's "None". + spv_operand_desc entry; + if (SPV_SUCCESS == grammar_.lookupOperand(type, 0, &entry)) + stream_ << entry->name; + } +} + +spv_result_t Disassembler::SaveTextResult(spv_text* text_result) const { + if (!print_) { + size_t length = text_.str().size(); + char* str = new char[length + 1]; + if (!str) return SPV_ERROR_OUT_OF_MEMORY; + strncpy(str, text_.str().c_str(), length + 1); + spv_text text = new spv_text_t(); + if (!text) { + delete[] str; + return SPV_ERROR_OUT_OF_MEMORY; + } + text->str = str; + text->length = length; + *text_result = text; + } + return SPV_SUCCESS; +} + +spv_result_t DisassembleHeader(void* user_data, spv_endianness_t endian, + uint32_t /* magic */, uint32_t version, + uint32_t generator, uint32_t id_bound, + uint32_t schema) { + assert(user_data); + auto disassembler = static_cast(user_data); + return disassembler->HandleHeader(endian, version, generator, id_bound, + schema); +} + +spv_result_t DisassembleInstruction( + void* user_data, const spv_parsed_instruction_t* parsed_instruction) { + assert(user_data); + auto disassembler = static_cast(user_data); + return disassembler->HandleInstruction(*parsed_instruction); +} + +// Simple wrapper class to provide extra data necessary for targeted +// instruction disassembly. +class WrappedDisassembler { + public: + WrappedDisassembler(Disassembler* dis, const uint32_t* binary, size_t wc) + : disassembler_(dis), inst_binary_(binary), word_count_(wc) {} + + Disassembler* disassembler() { return disassembler_; } + const uint32_t* inst_binary() const { return inst_binary_; } + size_t word_count() const { return word_count_; } + + private: + Disassembler* disassembler_; + const uint32_t* inst_binary_; + const size_t word_count_; +}; + +spv_result_t DisassembleTargetHeader(void* user_data, spv_endianness_t endian, + uint32_t /* magic */, uint32_t version, + uint32_t generator, uint32_t id_bound, + uint32_t schema) { + assert(user_data); + auto wrapped = static_cast(user_data); + return wrapped->disassembler()->HandleHeader(endian, version, generator, + id_bound, schema); +} + +spv_result_t DisassembleTargetInstruction( + void* user_data, const spv_parsed_instruction_t* parsed_instruction) { + assert(user_data); + auto wrapped = static_cast(user_data); + // Check if this is the instruction we want to disassemble. + if (wrapped->word_count() == parsed_instruction->num_words && + std::equal(wrapped->inst_binary(), + wrapped->inst_binary() + wrapped->word_count(), + parsed_instruction->words)) { + // Found the target instruction. Disassemble it and signal that we should + // stop searching so we don't output the same instruction again. + if (auto error = + wrapped->disassembler()->HandleInstruction(*parsed_instruction)) + return error; + return SPV_REQUESTED_TERMINATION; + } + return SPV_SUCCESS; +} + +} // anonymous namespace + +spv_result_t spvBinaryToText(const spv_const_context context, + const uint32_t* code, const size_t wordCount, + const uint32_t options, spv_text* pText, + spv_diagnostic* pDiagnostic) { + spv_context_t hijack_context = *context; + if (pDiagnostic) { + *pDiagnostic = nullptr; + libspirv::UseDiagnosticAsMessageConsumer(&hijack_context, pDiagnostic); + } + + const libspirv::AssemblyGrammar grammar(&hijack_context); + if (!grammar.isValid()) return SPV_ERROR_INVALID_TABLE; + + // Generate friendly names for Ids if requested. + std::unique_ptr friendly_mapper; + libspirv::NameMapper name_mapper = libspirv::GetTrivialNameMapper(); + if (options & SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) { + friendly_mapper.reset( + new libspirv::FriendlyNameMapper(&hijack_context, code, wordCount)); + name_mapper = friendly_mapper->GetNameMapper(); + } + + // Now disassemble! + Disassembler disassembler(grammar, options, name_mapper); + if (auto error = spvBinaryParse(&hijack_context, &disassembler, code, + wordCount, DisassembleHeader, + DisassembleInstruction, pDiagnostic)) { + return error; + } + + return disassembler.SaveTextResult(pText); +} + +std::string spvtools::spvInstructionBinaryToText(const spv_target_env env, + const uint32_t* instCode, + const size_t instWordCount, + const uint32_t* code, + const size_t wordCount, + const uint32_t options) { + spv_context context = spvContextCreate(env); + const libspirv::AssemblyGrammar grammar(context); + if (!grammar.isValid()) { + spvContextDestroy(context); + return ""; + } + + // Generate friendly names for Ids if requested. + std::unique_ptr friendly_mapper; + libspirv::NameMapper name_mapper = libspirv::GetTrivialNameMapper(); + if (options & SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) { + friendly_mapper.reset( + new libspirv::FriendlyNameMapper(context, code, wordCount)); + name_mapper = friendly_mapper->GetNameMapper(); + } + + // Now disassemble! + Disassembler disassembler(grammar, options, name_mapper); + WrappedDisassembler wrapped(&disassembler, instCode, instWordCount); + spvBinaryParse(context, &wrapped, code, wordCount, DisassembleTargetHeader, + DisassembleTargetInstruction, nullptr); + + spv_text text = nullptr; + std::string output; + if (disassembler.SaveTextResult(&text) == SPV_SUCCESS) { + output.assign(text->str, text->str + text->length); + // Drop trailing newline characters. + while (!output.empty() && output.back() == '\n') output.pop_back(); + } + spvTextDestroy(text); + spvContextDestroy(context); + + return output; +} diff --git a/3rdparty/spirv-tools/source/disassemble.h b/3rdparty/spirv-tools/source/disassemble.h new file mode 100644 index 000000000..b833dd07a --- /dev/null +++ b/3rdparty/spirv-tools/source/disassemble.h @@ -0,0 +1,38 @@ +// Copyright (c) 2018 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SPIRV_TOOLS_DISASSEMBLE_H_ +#define SPIRV_TOOLS_DISASSEMBLE_H_ + +#include + +#include "spirv-tools/libspirv.h" + +namespace spvtools { + +// Decodes the given SPIR-V instruction binary representation to its assembly +// text. The context is inferred from the provided module binary. The options +// parameter is a bit field of spv_binary_to_text_options_t. Decoded text will +// be stored into *text. Any error will be written into *diagnostic if +// diagnostic is non-null. +std::string spvInstructionBinaryToText(const spv_target_env env, + const uint32_t* inst_binary, + const size_t inst_word_count, + const uint32_t* binary, + const size_t word_count, + const uint32_t options); + +} // namespace spvtools + +#endif // SPIRV_TOOLS_DISASSEMBLE_H_ diff --git a/3rdparty/spirv-tools/source/enum_set.h b/3rdparty/spirv-tools/source/enum_set.h new file mode 100644 index 000000000..75a49f06b --- /dev/null +++ b/3rdparty/spirv-tools/source/enum_set.h @@ -0,0 +1,172 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_ENUM_SET_H +#define LIBSPIRV_ENUM_SET_H + +#include +#include +#include +#include +#include + +#include "latest_version_spirv_header.h" + +namespace libspirv { + +// A set of values of a 32-bit enum type. +// It is fast and compact for the common case, where enum values +// are at most 63. But it can represent enums with larger values, +// as may appear in extensions. +template +class EnumSet { + private: + // The ForEach method will call the functor on enum values in + // enum value order (lowest to highest). To make that easier, use + // an ordered set for the overflow values. + using OverflowSetType = std::set; + + public: + // Construct an empty set. + EnumSet() {} + // Construct an set with just the given enum value. + explicit EnumSet(EnumType c) { Add(c); } + // Construct an set from an initializer list of enum values. + EnumSet(std::initializer_list cs) { + for (auto c : cs) Add(c); + } + EnumSet(uint32_t count, const EnumType* ptr) { + for (uint32_t i = 0; i < count; ++i) Add(ptr[i]); + } + // Copy constructor. + EnumSet(const EnumSet& other) { *this = other; } + // Move constructor. The moved-from set is emptied. + EnumSet(EnumSet&& other) { + mask_ = other.mask_; + overflow_ = std::move(other.overflow_); + other.mask_ = 0; + other.overflow_.reset(nullptr); + } + // Assignment operator. + EnumSet& operator=(const EnumSet& other) { + if (&other != this) { + mask_ = other.mask_; + overflow_.reset(other.overflow_ ? new OverflowSetType(*other.overflow_) + : nullptr); + } + return *this; + } + + // Adds the given enum value to the set. This has no effect if the + // enum value is already in the set. + void Add(EnumType c) { AddWord(ToWord(c)); } + + // Returns true if this enum value is in the set. + bool Contains(EnumType c) const { return ContainsWord(ToWord(c)); } + + // Applies f to each enum in the set, in order from smallest enum + // value to largest. + void ForEach(std::function f) const { + for (uint32_t i = 0; i < 64; ++i) { + if (mask_ & AsMask(i)) f(static_cast(i)); + } + if (overflow_) { + for (uint32_t c : *overflow_) f(static_cast(c)); + } + } + + // Returns true if the set is empty. + bool IsEmpty() const { + if (mask_) return false; + if (overflow_ && !overflow_->empty()) return false; + return true; + } + + // Returns true if the set contains ANY of the elements of |in_set|, + // or if |in_set| is empty. + bool HasAnyOf(const EnumSet& in_set) const { + if (in_set.IsEmpty()) return true; + + if (mask_ & in_set.mask_) return true; + + if (!overflow_ || !in_set.overflow_) return false; + + for (uint32_t item : *in_set.overflow_) { + if (overflow_->find(item) != overflow_->end()) return true; + } + + return false; + } + + private: + // Adds the given enum value (as a 32-bit word) to the set. This has no + // effect if the enum value is already in the set. + void AddWord(uint32_t word) { + if (auto new_bits = AsMask(word)) { + mask_ |= new_bits; + } else { + Overflow().insert(word); + } + } + + // Returns true if the enum represented as a 32-bit word is in the set. + bool ContainsWord(uint32_t word) const { + // We shouldn't call Overflow() since this is a const method. + if (auto bits = AsMask(word)) { + return (mask_ & bits) != 0; + } else if (auto overflow = overflow_.get()) { + return overflow->find(word) != overflow->end(); + } + // The word is large, but the set doesn't have large members, so + // it doesn't have an overflow set. + return false; + } + + // Returns the enum value as a uint32_t. + uint32_t ToWord(EnumType value) const { + static_assert(sizeof(EnumType) <= sizeof(uint32_t), + "EnumType must statically castable to uint32_t"); + return static_cast(value); + } + + // Determines whether the given enum value can be represented + // as a bit in a uint64_t mask. If so, then returns that mask bit. + // Otherwise, returns 0. + uint64_t AsMask(uint32_t word) const { + if (word > 63) return 0; + return uint64_t(1) << word; + } + + // Ensures that overflow_set_ references a set. A new empty set is + // allocated if one doesn't exist yet. Returns overflow_set_. + OverflowSetType& Overflow() { + if (overflow_.get() == nullptr) { + overflow_.reset(new OverflowSetType); + } + return *overflow_; + } + + // Enums with values up to 63 are stored as bits in this mask. + uint64_t mask_ = 0; + // Enums with values larger than 63 are stored in this set. + // This set should normally be empty or very small. + std::unique_ptr overflow_ = {}; +}; + +// A set of SpvCapability, optimized for small capability values. +using CapabilitySet = EnumSet; + +} // namespace libspirv + +#endif // LIBSPIRV_ENUM_SET_H diff --git a/3rdparty/spirv-tools/source/enum_string_mapping.cpp b/3rdparty/spirv-tools/source/enum_string_mapping.cpp new file mode 100644 index 000000000..e993b5842 --- /dev/null +++ b/3rdparty/spirv-tools/source/enum_string_mapping.cpp @@ -0,0 +1,29 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "enum_string_mapping.h" + +#include +#include +#include +#include +#include + +#include "extensions.h" + +namespace libspirv { + +#include "enum_string_mapping.inc" + +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/enum_string_mapping.h b/3rdparty/spirv-tools/source/enum_string_mapping.h new file mode 100644 index 000000000..4b126810a --- /dev/null +++ b/3rdparty/spirv-tools/source/enum_string_mapping.h @@ -0,0 +1,36 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_ENUM_STRING_MAPPING_H_ +#define LIBSPIRV_ENUM_STRING_MAPPING_H_ + +#include + +#include "extensions.h" +#include "latest_version_spirv_header.h" + +namespace libspirv { + +// Finds Extension enum corresponding to |str|. Returns false if not found. +bool GetExtensionFromString(const char* str, Extension* extension); + +// Returns text string corresponding to |extension|. +const char* ExtensionToString(Extension extension); + +// Returns text string corresponding to |capability|. +const char* CapabilityToString(SpvCapability capability); + +} // namespace libspirv + +#endif // LIBSPIRV_ENUM_STRING_MAPPING_H_ diff --git a/3rdparty/spirv-tools/source/ext_inst.cpp b/3rdparty/spirv-tools/source/ext_inst.cpp new file mode 100644 index 000000000..6218eb115 --- /dev/null +++ b/3rdparty/spirv-tools/source/ext_inst.cpp @@ -0,0 +1,161 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ext_inst.h" + +#include +#include + +// DebugInfo extended instruction set. +// See https://www.khronos.org/registry/spir-v/specs/1.0/DebugInfo.html +// TODO(dneto): DebugInfo.h should probably move to SPIRV-Headers. +#include "DebugInfo.h" + +#include "latest_version_glsl_std_450_header.h" +#include "latest_version_opencl_std_header.h" +#include "macro.h" +#include "spirv_definition.h" + +#include "debuginfo.insts.inc" // defines opencl_entries +#include "glsl.std.450.insts.inc" // defines glsl_entries +#include "opencl.std.insts.inc" // defines opencl_entries + +#include "spv-amd-gcn-shader.insts.inc" +#include "spv-amd-shader-ballot.insts.inc" +#include "spv-amd-shader-explicit-vertex-parameter.insts.inc" +#include "spv-amd-shader-trinary-minmax.insts.inc" + +static const spv_ext_inst_group_t kGroups_1_0[] = { + {SPV_EXT_INST_TYPE_GLSL_STD_450, ARRAY_SIZE(glsl_entries), glsl_entries}, + {SPV_EXT_INST_TYPE_OPENCL_STD, ARRAY_SIZE(opencl_entries), opencl_entries}, + {SPV_EXT_INST_TYPE_SPV_AMD_SHADER_EXPLICIT_VERTEX_PARAMETER, + ARRAY_SIZE(spv_amd_shader_explicit_vertex_parameter_entries), + spv_amd_shader_explicit_vertex_parameter_entries}, + {SPV_EXT_INST_TYPE_SPV_AMD_SHADER_TRINARY_MINMAX, + ARRAY_SIZE(spv_amd_shader_trinary_minmax_entries), + spv_amd_shader_trinary_minmax_entries}, + {SPV_EXT_INST_TYPE_SPV_AMD_GCN_SHADER, + ARRAY_SIZE(spv_amd_gcn_shader_entries), spv_amd_gcn_shader_entries}, + {SPV_EXT_INST_TYPE_SPV_AMD_SHADER_BALLOT, + ARRAY_SIZE(spv_amd_shader_ballot_entries), spv_amd_shader_ballot_entries}, + {SPV_EXT_INST_TYPE_DEBUGINFO, ARRAY_SIZE(debuginfo_entries), + debuginfo_entries}, +}; + +static const spv_ext_inst_table_t kTable_1_0 = {ARRAY_SIZE(kGroups_1_0), + kGroups_1_0}; + +spv_result_t spvExtInstTableGet(spv_ext_inst_table* pExtInstTable, + spv_target_env env) { + if (!pExtInstTable) return SPV_ERROR_INVALID_POINTER; + + switch (env) { + // The extended instruction sets are all version 1.0 so far. + case SPV_ENV_UNIVERSAL_1_0: + case SPV_ENV_VULKAN_1_0: + case SPV_ENV_UNIVERSAL_1_1: + case SPV_ENV_UNIVERSAL_1_2: + case SPV_ENV_OPENCL_1_2: + case SPV_ENV_OPENCL_EMBEDDED_1_2: + case SPV_ENV_OPENCL_2_0: + case SPV_ENV_OPENCL_EMBEDDED_2_0: + case SPV_ENV_OPENCL_2_1: + case SPV_ENV_OPENCL_EMBEDDED_2_1: + case SPV_ENV_OPENCL_2_2: + case SPV_ENV_OPENCL_EMBEDDED_2_2: + case SPV_ENV_OPENGL_4_0: + case SPV_ENV_OPENGL_4_1: + case SPV_ENV_OPENGL_4_2: + case SPV_ENV_OPENGL_4_3: + case SPV_ENV_OPENGL_4_5: + case SPV_ENV_UNIVERSAL_1_3: + case SPV_ENV_VULKAN_1_1: + *pExtInstTable = &kTable_1_0; + return SPV_SUCCESS; + default: + assert(0 && "Unknown spv_target_env in spvExtInstTableGet()"); + return SPV_ERROR_INVALID_TABLE; + } +} + +spv_ext_inst_type_t spvExtInstImportTypeGet(const char* name) { + // The names are specified by the respective extension instruction + // specifications. + if (!strcmp("GLSL.std.450", name)) { + return SPV_EXT_INST_TYPE_GLSL_STD_450; + } + if (!strcmp("OpenCL.std", name)) { + return SPV_EXT_INST_TYPE_OPENCL_STD; + } + if (!strcmp("SPV_AMD_shader_explicit_vertex_parameter", name)) { + return SPV_EXT_INST_TYPE_SPV_AMD_SHADER_EXPLICIT_VERTEX_PARAMETER; + } + if (!strcmp("SPV_AMD_shader_trinary_minmax", name)) { + return SPV_EXT_INST_TYPE_SPV_AMD_SHADER_TRINARY_MINMAX; + } + if (!strcmp("SPV_AMD_gcn_shader", name)) { + return SPV_EXT_INST_TYPE_SPV_AMD_GCN_SHADER; + } + if (!strcmp("SPV_AMD_shader_ballot", name)) { + return SPV_EXT_INST_TYPE_SPV_AMD_SHADER_BALLOT; + } + if (!strcmp("DebugInfo", name)) { + return SPV_EXT_INST_TYPE_DEBUGINFO; + } + return SPV_EXT_INST_TYPE_NONE; +} + +spv_result_t spvExtInstTableNameLookup(const spv_ext_inst_table table, + const spv_ext_inst_type_t type, + const char* name, + spv_ext_inst_desc* pEntry) { + if (!table) return SPV_ERROR_INVALID_TABLE; + if (!pEntry) return SPV_ERROR_INVALID_POINTER; + + for (uint32_t groupIndex = 0; groupIndex < table->count; groupIndex++) { + const auto& group = table->groups[groupIndex]; + if (type != group.type) continue; + for (uint32_t index = 0; index < group.count; index++) { + const auto& entry = group.entries[index]; + if (!strcmp(name, entry.name)) { + *pEntry = &entry; + return SPV_SUCCESS; + } + } + } + + return SPV_ERROR_INVALID_LOOKUP; +} + +spv_result_t spvExtInstTableValueLookup(const spv_ext_inst_table table, + const spv_ext_inst_type_t type, + const uint32_t value, + spv_ext_inst_desc* pEntry) { + if (!table) return SPV_ERROR_INVALID_TABLE; + if (!pEntry) return SPV_ERROR_INVALID_POINTER; + + for (uint32_t groupIndex = 0; groupIndex < table->count; groupIndex++) { + const auto& group = table->groups[groupIndex]; + if (type != group.type) continue; + for (uint32_t index = 0; index < group.count; index++) { + const auto& entry = group.entries[index]; + if (value == entry.ext_inst) { + *pEntry = &entry; + return SPV_SUCCESS; + } + } + } + + return SPV_ERROR_INVALID_LOOKUP; +} diff --git a/3rdparty/spirv-tools/source/ext_inst.h b/3rdparty/spirv-tools/source/ext_inst.h new file mode 100644 index 000000000..1a16b4b2d --- /dev/null +++ b/3rdparty/spirv-tools/source/ext_inst.h @@ -0,0 +1,40 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_EXT_INST_H_ +#define LIBSPIRV_EXT_INST_H_ + +#include "spirv-tools/libspirv.h" +#include "table.h" + +// Gets the type of the extended instruction set with the specified name. +spv_ext_inst_type_t spvExtInstImportTypeGet(const char* name); + +// Finds the named extented instruction of the given type in the given extended +// instruction table. On success, returns SPV_SUCCESS and writes a handle of +// the instruction entry into *entry. +spv_result_t spvExtInstTableNameLookup(const spv_ext_inst_table table, + const spv_ext_inst_type_t type, + const char* name, + spv_ext_inst_desc* entry); + +// Finds the extented instruction of the given type in the given extended +// instruction table by value. On success, returns SPV_SUCCESS and writes a +// handle of the instruction entry into *entry. +spv_result_t spvExtInstTableValueLookup(const spv_ext_inst_table table, + const spv_ext_inst_type_t type, + const uint32_t value, + spv_ext_inst_desc* pEntry); + +#endif // LIBSPIRV_EXT_INST_H_ diff --git a/3rdparty/spirv-tools/source/extensions.cpp b/3rdparty/spirv-tools/source/extensions.cpp new file mode 100644 index 000000000..065543c11 --- /dev/null +++ b/3rdparty/spirv-tools/source/extensions.cpp @@ -0,0 +1,44 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "extensions.h" + +#include +#include +#include + +#include "enum_string_mapping.h" + +namespace libspirv { + +std::string GetExtensionString(const spv_parsed_instruction_t* inst) { + if (inst->opcode != SpvOpExtension) return "ERROR_not_op_extension"; + + assert(inst->num_operands == 1); + + const auto& operand = inst->operands[0]; + assert(operand.type == SPV_OPERAND_TYPE_LITERAL_STRING); + assert(inst->num_words > operand.offset); + + return reinterpret_cast(inst->words + operand.offset); +} + +std::string ExtensionSetToString(const ExtensionSet& extensions) { + std::stringstream ss; + extensions.ForEach( + [&ss](Extension ext) { ss << ExtensionToString(ext) << " "; }); + return ss.str(); +} + +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/extensions.h b/3rdparty/spirv-tools/source/extensions.h new file mode 100644 index 000000000..9947f1a9b --- /dev/null +++ b/3rdparty/spirv-tools/source/extensions.h @@ -0,0 +1,40 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_EXTENSIONS_H_ +#define LIBSPIRV_EXTENSIONS_H_ + +#include + +#include "enum_set.h" +#include "spirv-tools/libspirv.h" + +namespace libspirv { + +// The known SPIR-V extensions. +enum Extension { +#include "extension_enum.inc" +}; + +using ExtensionSet = EnumSet; + +// Returns literal string operand of OpExtension instruction. +std::string GetExtensionString(const spv_parsed_instruction_t* inst); + +// Returns text string listing |extensions| separated by whitespace. +std::string ExtensionSetToString(const ExtensionSet& extensions); + +} // namespace libspirv + +#endif // LIBSPIRV_EXTENSIONS_H_ diff --git a/3rdparty/spirv-tools/source/extinst.debuginfo.grammar.json b/3rdparty/spirv-tools/source/extinst.debuginfo.grammar.json new file mode 100644 index 000000000..9212f6f48 --- /dev/null +++ b/3rdparty/spirv-tools/source/extinst.debuginfo.grammar.json @@ -0,0 +1,568 @@ +{ + "copyright" : [ + "Copyright (c) 2017 The Khronos Group Inc.", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy", + "of this software and/or associated documentation files (the \"Materials\"),", + "to deal in the Materials without restriction, including without limitation", + "the rights to use, copy, modify, merge, publish, distribute, sublicense,", + "and/or sell copies of the Materials, and to permit persons to whom the", + "Materials are furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in", + "all copies or substantial portions of the Materials.", + "", + "MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS", + "STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND", + "HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ ", + "", + "THE MATERIALS ARE PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS", + "OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL", + "THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING", + "FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS", + "IN THE MATERIALS." + ], + "version" : 100, + "revision" : 1, + "instructions" : [ + { + "opname" : "DebugInfoNone", + "opcode" : 0 + }, + { + "opname" : "DebugCompilationUnit", + "opcode" : 1, + "operands" : [ + { "kind" : "IdRef", "name" : "'Source'" }, + { "kind" : "LiteralInteger", "name" : "'Version'" }, + { "kind" : "LiteralInteger", "name" : "'DWARF Version'" } + ] + }, + { + "opname" : "DebugTypeBasic", + "opcode" : 2, + "operands" : [ + { "kind" : "IdRef", "name" : "'Name'" }, + { "kind" : "IdRef", "name" : "'Size'" }, + { "kind" : "DebugBaseTypeAttributeEncoding", "name" : "'Encoding'" } + ] + }, + { + "opname" : "DebugTypePointer", + "opcode" : 3, + "operands" : [ + { "kind" : "IdRef", "name" : "'Base Type'" }, + { "kind" : "StorageClass", "name" : "'Storage Class'" }, + { "kind" : "DebugInfoFlags", "name" : "'Literal Flags'" } + ] + }, + { + "opname" : "DebugTypeQualifier", + "opcode" : 4, + "operands" : [ + { "kind" : "IdRef", "name" : "'Base Type'" }, + { "kind" : "DebugTypeQualifier", "name" : "'Type Qualifier'" } + ] + }, + { + "opname" : "DebugTypeArray", + "opcode" : 5, + "operands" : [ + { "kind" : "IdRef", "name" : "'Base Type'" }, + { "kind" : "IdRef", "name" : "'Component Counts'", "quantifier" : "*" } + ] + }, + { + "opname" : "DebugTypeVector", + "opcode" : 6, + "operands" : [ + { "kind" : "IdRef", "name" : "'Base Type'" }, + { "kind" : "LiteralInteger", "name" : "'Component Count'" } + ] + }, + { + "opname" : "DebugTypedef", + "opcode" : 7, + "operands" : [ + { "kind" : "IdRef", "name" : "'Name'" }, + { "kind" : "IdRef", "name" : "'Base Type'" }, + { "kind" : "IdRef", "name" : "'Source'" }, + { "kind" : "LiteralInteger", "name" : "'Line'" }, + { "kind" : "LiteralInteger", "name" : "'Column'" }, + { "kind" : "IdRef", "name" : "'Parent'" } + ] + }, + { + "opname" : "DebugTypeFunction", + "opcode" : 8, + "operands" : [ + { "kind" : "IdRef", "name" : "'Return Type'" }, + { "kind" : "IdRef", "name" : "'Paramter Types'", "quantifier" : "*" } + ] + }, + { + "opname" : "DebugTypeEnum", + "opcode" : 9, + "operands" : [ + { "kind" : "IdRef", "name" : "'Name'" }, + { "kind" : "IdRef", "name" : "'Underlying Type'" }, + { "kind" : "IdRef", "name" : "'Source'" }, + { "kind" : "LiteralInteger", "name" : "'Line'" }, + { "kind" : "LiteralInteger", "name" : "'Column'" }, + { "kind" : "IdRef", "name" : "'Parent'" }, + { "kind" : "IdRef", "name" : "'Size'" }, + { "kind" : "DebugInfoFlags", "name" : "'Flags'" }, + { "kind" : "PairIdRefIdRef", "name" : "'Value, Name, Value, Name, ...'", "quantifier" : "*" } + ] + }, + { + "opname" : "DebugTypeComposite", + "opcode" : 10, + "operands" : [ + { "kind" : "IdRef", "name" : "'Name'" }, + { "kind" : "DebugCompositeType", "name" : "'Tag'" }, + { "kind" : "IdRef", "name" : "'Source'" }, + { "kind" : "LiteralInteger", "name" : "'Line'" }, + { "kind" : "LiteralInteger", "name" : "'Column'" }, + { "kind" : "IdRef", "name" : "'Parent'" }, + { "kind" : "IdRef", "name" : "'Size'" }, + { "kind" : "DebugInfoFlags", "name" : "'Flags'" }, + { "kind" : "IdRef", "name" : "'Members'", "quantifier" : "*" } + ] + }, + { + "opname" : "DebugTypeMember", + "opcode" : 11, + "operands" : [ + { "kind" : "IdRef", "name" : "'Name'" }, + { "kind" : "IdRef", "name" : "'Type'" }, + { "kind" : "IdRef", "name" : "'Source'" }, + { "kind" : "LiteralInteger", "name" : "'Line'" }, + { "kind" : "LiteralInteger", "name" : "'Column'" }, + { "kind" : "IdRef", "name" : "'Parent'" }, + { "kind" : "IdRef", "name" : "'Offset'" }, + { "kind" : "IdRef", "name" : "'Size'" }, + { "kind" : "DebugInfoFlags", "name" : "'Flags'" }, + { "kind" : "IdRef", "name" : "'Value'", "quantifier" : "?" } + ] + }, + { + "opname" : "DebugTypeInheritance", + "opcode" : 12, + "operands" : [ + { "kind" : "IdRef", "name" : "'Child'" }, + { "kind" : "IdRef", "name" : "'Parent'" }, + { "kind" : "IdRef", "name" : "'Offset'" }, + { "kind" : "IdRef", "name" : "'Size'" }, + { "kind" : "DebugInfoFlags", "name" : "'Flags'" } + ] + }, + { + "opname" : "DebugTypePtrToMember", + "opcode" : 13, + "operands" : [ + { "kind" : "IdRef", "name" : "'Member Type'" }, + { "kind" : "IdRef", "name" : "'Parent'" } + ] + }, + { + "opname" : "DebugTypeTemplate", + "opcode" : 14, + "operands" : [ + { "kind" : "IdRef", "name" : "'Target'" }, + { "kind" : "IdRef", "name" : "'Parameters'", "quantifier" : "*" } + ] + }, + { + "opname" : "DebugTypeTemplateParameter", + "opcode" : 15, + "operands" : [ + { "kind" : "IdRef", "name" : "'Name'" }, + { "kind" : "IdRef", "name" : "'Actual Type'" }, + { "kind" : "IdRef", "name" : "'Value'" }, + { "kind" : "IdRef", "name" : "'Source'" }, + { "kind" : "LiteralInteger", "name" : "'Line'" }, + { "kind" : "LiteralInteger", "name" : "'Column'" } + ] + }, + { + "opname" : "DebugTypeTemplateTemplateParameter", + "opcode" : 16, + "operands" : [ + { "kind" : "IdRef", "name" : "'Name'" }, + { "kind" : "IdRef", "name" : "'Template Name'" }, + { "kind" : "IdRef", "name" : "'Source'" }, + { "kind" : "LiteralInteger", "name" : "'Line'" }, + { "kind" : "LiteralInteger", "name" : "'Column'" } + ] + }, + { + "opname" : "DebugTypeTemplateParameterPack", + "opcode" : 17, + "operands" : [ + { "kind" : "IdRef", "name" : "'Name'" }, + { "kind" : "IdRef", "name" : "'Source'" }, + { "kind" : "LiteralInteger", "name" : "'Line'" }, + { "kind" : "LiteralInteger", "name" : "'Column'" }, + { "kind" : "IdRef", "name" : "'Template Parameters'", "quantifier" : "*" } + ] + }, + { + "opname" : "DebugGlobalVariable", + "opcode" : 18, + "operands" : [ + { "kind" : "IdRef", "name" : "'Name'" }, + { "kind" : "IdRef", "name" : "'Type'" }, + { "kind" : "IdRef", "name" : "'Source'" }, + { "kind" : "LiteralInteger", "name" : "'Line'" }, + { "kind" : "LiteralInteger", "name" : "'Column'" }, + { "kind" : "IdRef", "name" : "'Parent'" }, + { "kind" : "IdRef", "name" : "'Linkage Name'" }, + { "kind" : "IdRef", "name" : "'Variable'" }, + { "kind" : "DebugInfoFlags", "name" : "'Flags'" }, + { "kind" : "IdRef", "name" : "'Static Member Declaration'", "quantifier" : "?" } + ] + }, + { + "opname" : "DebugFunctionDeclaration", + "opcode" : 19, + "operands" : [ + { "kind" : "IdRef", "name" : "'Name'" }, + { "kind" : "IdRef", "name" : "'Type'" }, + { "kind" : "IdRef", "name" : "'Source'" }, + { "kind" : "LiteralInteger", "name" : "'Line'" }, + { "kind" : "LiteralInteger", "name" : "'Column'" }, + { "kind" : "IdRef", "name" : "'Parent'" }, + { "kind" : "IdRef", "name" : "'Linkage Name'" }, + { "kind" : "DebugInfoFlags", "name" : "'Flags'" } + ] + }, + { + "opname" : "DebugFunction", + "opcode" : 20, + "operands" : [ + { "kind" : "IdRef", "name" : "'Name'" }, + { "kind" : "IdRef", "name" : "'Type'" }, + { "kind" : "IdRef", "name" : "'Source'" }, + { "kind" : "LiteralInteger", "name" : "'Line'" }, + { "kind" : "LiteralInteger", "name" : "'Column'" }, + { "kind" : "IdRef", "name" : "'Parent'" }, + { "kind" : "IdRef", "name" : "'Linkage Name'" }, + { "kind" : "DebugInfoFlags", "name" : "'Flags'" }, + { "kind" : "LiteralInteger", "name" : "'Scope Line'" }, + { "kind" : "IdRef", "name" : "'Function'" }, + { "kind" : "IdRef", "name" : "'Declaration'", "quantifier" : "?" } + ] + }, + { + "opname" : "DebugLexicalBlock", + "opcode" : 21, + "operands" : [ + { "kind" : "IdRef", "name" : "'Source'" }, + { "kind" : "LiteralInteger", "name" : "'Line'" }, + { "kind" : "LiteralInteger", "name" : "'Column'" }, + { "kind" : "IdRef", "name" : "'Parent'" }, + { "kind" : "IdRef", "name" : "'Name'", "quantifier" : "?" } + ] + }, + { + "opname" : "DebugLexicalBlockDiscriminator", + "opcode" : 22, + "operands" : [ + { "kind" : "IdRef", "name" : "'Scope'" }, + { "kind" : "LiteralInteger", "name" : "'Discriminator'" }, + { "kind" : "IdRef", "name" : "'Parent'" } + ] + }, + { + "opname" : "DebugScope", + "opcode" : 23, + "operands" : [ + { "kind" : "IdRef", "name" : "'Scope'" }, + { "kind" : "IdRef", "name" : "'Inlined At'", "quantifier" : "?" } + ] + }, + { + "opname" : "DebugNoScope", + "opcode" : 24 + }, + { + "opname" : "DebugInlinedAt", + "opcode" : 25, + "operands" : [ + { "kind" : "LiteralInteger", "name" : "'Line'" }, + { "kind" : "IdRef", "name" : "'Scope'" }, + { "kind" : "IdRef", "name" : "'Inlined'", "quantifier" : "?" } + ] + }, + { + "opname" : "DebugLocalVariable", + "opcode" : 26, + "operands" : [ + { "kind" : "IdRef", "name" : "'Name'" }, + { "kind" : "IdRef", "name" : "'Type'" }, + { "kind" : "IdRef", "name" : "'Source'" }, + { "kind" : "LiteralInteger", "name" : "'Line'" }, + { "kind" : "LiteralInteger", "name" : "'Column'" }, + { "kind" : "IdRef", "name" : "'Parent'" }, + { "kind" : "LiteralInteger", "name" : "'Arg Number'", "quantifier" : "?" } + ] + }, + { + "opname" : "DebugInlinedVariable", + "opcode" : 27, + "operands" : [ + { "kind" : "IdRef", "name" : "'Variable'" }, + { "kind" : "IdRef", "name" : "'Inlined'" } + ] + }, + { + "opname" : "DebugDeclare", + "opcode" : 28, + "operands" : [ + { "kind" : "IdRef", "name" : "'Local Variable'" }, + { "kind" : "IdRef", "name" : "'Variable'" }, + { "kind" : "IdRef", "name" : "'Expression'" } + ] + }, + { + "opname" : "DebugValue", + "opcode" : 29, + "operands" : [ + { "kind" : "IdRef", "name" : "'Value'" }, + { "kind" : "IdRef", "name" : "'Expression'" }, + { "kind" : "IdRef", "name" : "'Indexes'", "quantifier" : "*" } + ] + }, + { + "opname" : "DebugOperation", + "opcode" : 30, + "operands" : [ + { "kind" : "DebugOperation", "name" : "'OpCode'" }, + { "kind" : "LiteralInteger", "name" : "'Operands ...'", "quantifier" : "*" } + ] + }, + { + "opname" : "DebugExpression", + "opcode" : 31, + "operands" : [ + { "kind" : "IdRef", "name" : "'Operands ...'", "quantifier" : "*" } + ] + }, + { + "opname" : "DebugMacroDef", + "opcode" : 32, + "operands" : [ + { "kind" : "IdRef", "name" : "'Source'" }, + { "kind" : "LiteralInteger", "name" : "'Line'" }, + { "kind" : "IdRef", "name" : "'Name'" }, + { "kind" : "IdRef", "name" : "'Value'", "quantifier" : "?" } + ] + }, + { + "opname" : "DebugMacroUndef", + "opcode" : 33, + "operands" : [ + { "kind" : "IdRef", "name" : "'Source'" }, + { "kind" : "LiteralInteger", "name" : "'Line'" }, + { "kind" : "IdRef", "name" : "'Macro'" } + ] + } + ], + "operand_kinds" : [ + { + "category" : "BitEnum", + "kind" : "DebugInfoFlags", + "enumerants" : [ + { + "enumerant" : "FlagIsProtected", + "value" : "0x01" + }, + { + "enumerant" : "FlagIsPrivate", + "value" : "0x02" + }, + { + "enumerant" : "FlagIsPublic", + "value" : "0x03" + }, + { + "enumerant" : "FlagIsLocal", + "value" : "0x04" + }, + { + "enumerant" : "FlagIsDefinition", + "value" : "0x08" + }, + { + "enumerant" : "FlagFwdDecl", + "value" : "0x10" + }, + { + "enumerant" : "FlagArtificial", + "value" : "0x20" + }, + { + "enumerant" : "FlagExplicit", + "value" : "0x40" + }, + { + "enumerant" : "FlagPrototyped", + "value" : "0x80" + }, + { + "enumerant" : "FlagObjectPointer", + "value" : "0x100" + }, + { + "enumerant" : "FlagStaticMember", + "value" : "0x200" + }, + { + "enumerant" : "FlagIndirectVariable", + "value" : "0x400" + }, + { + "enumerant" : "FlagLValueReference", + "value" : "0x800" + }, + { + "enumerant" : "FlagRValueReference", + "value" : "0x1000" + }, + { + "enumerant" : "FlagIsOptimized", + "value" : "0x2000" + } + ] + }, + { + "category" : "ValueEnum", + "kind" : "DebugBaseTypeAttributeEncoding", + "enumerants" : [ + { + "enumerant" : "Unspecified", + "value" : "0" + }, + { + "enumerant" : "Address", + "value" : "1" + }, + { + "enumerant" : "Boolean", + "value" : "2" + }, + { + "enumerant" : "Float", + "value" : "4" + }, + { + "enumerant" : "Signed", + "value" : "5" + }, + { + "enumerant" : "SignedChar", + "value" : "6" + }, + { + "enumerant" : "Unsigned", + "value" : "7" + }, + { + "enumerant" : "UnsignedChar", + "value" : "8" + } + ] + }, + { + "category" : "ValueEnum", + "kind" : "DebugCompositeType", + "enumerants" : [ + { + "enumerant" : "Class", + "value" : "0" + }, + { + "enumerant" : "Structure", + "value" : "1" + }, + { + "enumerant" : "Union", + "value" : "2" + } + ] + }, + { + "category" : "ValueEnum", + "kind" : "DebugTypeQualifier", + "enumerants" : [ + { + "enumerant" : "ConstType", + "value" : "0" + }, + { + "enumerant" : "VolatileType", + "value" : "1" + }, + { + "enumerant" : "RestrictType", + "value" : "2" + } + ] + }, + { + "category" : "ValueEnum", + "kind" : "DebugOperation", + "enumerants" : [ + { + "enumerant" : "Deref", + "value" : "0" + }, + { + "enumerant" : "Plus", + "value" : "1" + }, + { + "enumerant" : "Minus", + "value" : "2" + }, + { + "enumerant" : "PlusUconst", + "value" : "3", + "parameters" : [ + { "kind" : "LiteralInteger" } + ] + }, + { + "enumerant" : "BitPiece", + "value" : "4", + "parameters" : [ + { "kind" : "LiteralInteger" }, + { "kind" : "LiteralInteger" } + ] + }, + { + "enumerant" : "Swap", + "value" : "5" + }, + { + "enumerant" : "Xderef", + "value" : "6" + }, + { + "enumerant" : "StackValue", + "value" : "7" + }, + { + "enumerant" : "Constu", + "value" : "8", + "parameters" : [ + { "kind" : "LiteralInteger" } + ] + } + ] + } + ] +} diff --git a/3rdparty/spirv-tools/source/extinst.spv-amd-gcn-shader.grammar.json b/3rdparty/spirv-tools/source/extinst.spv-amd-gcn-shader.grammar.json new file mode 100644 index 000000000..e18251bba --- /dev/null +++ b/3rdparty/spirv-tools/source/extinst.spv-amd-gcn-shader.grammar.json @@ -0,0 +1,26 @@ +{ + "revision" : 2, + "instructions" : [ + { + "opname" : "CubeFaceIndexAMD", + "opcode" : 1, + "operands" : [ + { "kind" : "IdRef", "name" : "'P'" } + ], + "extensions" : [ "SPV_AMD_gcn_shader" ] + }, + { + "opname" : "CubeFaceCoordAMD", + "opcode" : 2, + "operands" : [ + { "kind" : "IdRef", "name" : "'P'" } + ], + "extensions" : [ "SPV_AMD_gcn_shader" ] + }, + { + "opname" : "TimeAMD", + "opcode" : 3, + "extensions" : [ "SPV_AMD_gcn_shader" ] + } + ] +} diff --git a/3rdparty/spirv-tools/source/extinst.spv-amd-shader-ballot.grammar.json b/3rdparty/spirv-tools/source/extinst.spv-amd-shader-ballot.grammar.json new file mode 100644 index 000000000..62a470eeb --- /dev/null +++ b/3rdparty/spirv-tools/source/extinst.spv-amd-shader-ballot.grammar.json @@ -0,0 +1,41 @@ +{ + "revision" : 5, + "instructions" : [ + { + "opname" : "SwizzleInvocationsAMD", + "opcode" : 1, + "operands" : [ + { "kind" : "IdRef", "name" : "'data'" }, + { "kind" : "IdRef", "name" : "'offset'" } + ], + "extensions" : [ "SPV_AMD_shader_ballot" ] + }, + { + "opname" : "SwizzleInvocationsMaskedAMD", + "opcode" : 2, + "operands" : [ + { "kind" : "IdRef", "name" : "'data'" }, + { "kind" : "IdRef", "name" : "'mask'" } + ], + "extensions" : [ "SPV_AMD_shader_ballot" ] + }, + { + "opname" : "WriteInvocationAMD", + "opcode" : 3, + "operands" : [ + { "kind" : "IdRef", "name" : "'inputValue'" }, + { "kind" : "IdRef", "name" : "'writeValue'" }, + { "kind" : "IdRef", "name" : "'invocationIndex'" } + ], + "extensions" : [ "SPV_AMD_shader_ballot" ] + }, + { + "opname" : "MbcntAMD", + "opcode" : 4, + "operands" : [ + { "kind" : "IdRef", "name" : "'mask'" } + ], + "extensions" : [ "SPV_AMD_shader_ballot" ] + } + ] +} diff --git a/3rdparty/spirv-tools/source/extinst.spv-amd-shader-explicit-vertex-parameter.grammar.json b/3rdparty/spirv-tools/source/extinst.spv-amd-shader-explicit-vertex-parameter.grammar.json new file mode 100644 index 000000000..e156b1b6f --- /dev/null +++ b/3rdparty/spirv-tools/source/extinst.spv-amd-shader-explicit-vertex-parameter.grammar.json @@ -0,0 +1,14 @@ +{ + "revision" : 4, + "instructions" : [ + { + "opname" : "InterpolateAtVertexAMD", + "opcode" : 1, + "operands" : [ + { "kind" : "IdRef", "name" : "'interpolant'" }, + { "kind" : "IdRef", "name" : "'vertexIdx'" } + ], + "extensions" : [ "SPV_AMD_shader_explicit_vertex_parameter" ] + } + ] +} diff --git a/3rdparty/spirv-tools/source/extinst.spv-amd-shader-trinary-minmax.grammar.json b/3rdparty/spirv-tools/source/extinst.spv-amd-shader-trinary-minmax.grammar.json new file mode 100644 index 000000000..c681976fe --- /dev/null +++ b/3rdparty/spirv-tools/source/extinst.spv-amd-shader-trinary-minmax.grammar.json @@ -0,0 +1,95 @@ +{ + "revision" : 4, + "instructions" : [ + { + "opname" : "FMin3AMD", + "opcode" : 1, + "operands" : [ + { "kind" : "IdRef", "name" : "'x'" }, + { "kind" : "IdRef", "name" : "'y'" }, + { "kind" : "IdRef", "name" : "'z'" } + ], + "extensions" : [ "SPV_AMD_shader_trinary_minmax" ] + }, + { + "opname" : "UMin3AMD", + "opcode" : 2, + "operands" : [ + { "kind" : "IdRef", "name" : "'x'" }, + { "kind" : "IdRef", "name" : "'y'" }, + { "kind" : "IdRef", "name" : "'z'" } + ], + "extensions" : [ "SPV_AMD_shader_trinary_minmax" ] + }, + { + "opname" : "SMin3AMD", + "opcode" : 3, + "operands" : [ + { "kind" : "IdRef", "name" : "'x'" }, + { "kind" : "IdRef", "name" : "'y'" }, + { "kind" : "IdRef", "name" : "'z'" } + ], + "extensions" : [ "SPV_AMD_shader_trinary_minmax" ] + }, + { + "opname" : "FMax3AMD", + "opcode" : 4, + "operands" : [ + { "kind" : "IdRef", "name" : "'x'" }, + { "kind" : "IdRef", "name" : "'y'" }, + { "kind" : "IdRef", "name" : "'z'" } + ], + "extensions" : [ "SPV_AMD_shader_trinary_minmax" ] + }, + { + "opname" : "UMax3AMD", + "opcode" : 5, + "operands" : [ + { "kind" : "IdRef", "name" : "'x'" }, + { "kind" : "IdRef", "name" : "'y'" }, + { "kind" : "IdRef", "name" : "'z'" } + ], + "extensions" : [ "SPV_AMD_shader_trinary_minmax" ] + }, + { + "opname" : "SMax3AMD", + "opcode" : 6, + "operands" : [ + { "kind" : "IdRef", "name" : "'x'" }, + { "kind" : "IdRef", "name" : "'y'" }, + { "kind" : "IdRef", "name" : "'z'" } + ], + "extensions" : [ "SPV_AMD_shader_trinary_minmax" ] + }, + { + "opname" : "FMid3AMD", + "opcode" : 7, + "operands" : [ + { "kind" : "IdRef", "name" : "'x'" }, + { "kind" : "IdRef", "name" : "'y'" }, + { "kind" : "IdRef", "name" : "'z'" } + ], + "extensions" : [ "SPV_AMD_shader_trinary_minmax" ] + }, + { + "opname" : "UMid3AMD", + "opcode" : 8, + "operands" : [ + { "kind" : "IdRef", "name" : "'x'" }, + { "kind" : "IdRef", "name" : "'y'" }, + { "kind" : "IdRef", "name" : "'z'" } + ], + "extensions" : [ "SPV_AMD_shader_trinary_minmax" ] + }, + { + "opname" : "SMid3AMD", + "opcode" : 9, + "operands" : [ + { "kind" : "IdRef", "name" : "'x'" }, + { "kind" : "IdRef", "name" : "'y'" }, + { "kind" : "IdRef", "name" : "'z'" } + ], + "extensions" : [ "SPV_AMD_shader_trinary_minmax" ] + } + ] +} diff --git a/3rdparty/spirv-tools/source/id_descriptor.cpp b/3rdparty/spirv-tools/source/id_descriptor.cpp new file mode 100644 index 000000000..7697c2993 --- /dev/null +++ b/3rdparty/spirv-tools/source/id_descriptor.cpp @@ -0,0 +1,79 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "id_descriptor.h" + +#include +#include + +#include "opcode.h" +#include "operand.h" + +namespace libspirv { + +namespace { + +// Hashes an array of words. Order of words is important. +uint32_t HashU32Array(const std::vector& words) { + // The hash function is a sum of hashes of each word seeded by word index. + // Knuth's multiplicative hash is used to hash the words. + const uint32_t kKnuthMulHash = 2654435761; + uint32_t val = 0; + for (uint32_t i = 0; i < words.size(); ++i) { + val += (words[i] + i + 123) * kKnuthMulHash; + } + return val; +} + +} // namespace + +uint32_t IdDescriptorCollection::ProcessInstruction( + const spv_parsed_instruction_t& inst) { + if (!inst.result_id) return 0; + + assert(words_.empty()); + words_.push_back(inst.words[0]); + + for (size_t operand_index = 0; operand_index < inst.num_operands; + ++operand_index) { + const auto& operand = inst.operands[operand_index]; + if (spvIsIdType(operand.type)) { + const uint32_t id = inst.words[operand.offset]; + const auto it = id_to_descriptor_.find(id); + // Forward declared ids are not hashed. + if (it != id_to_descriptor_.end()) { + words_.push_back(it->second); + } + } else { + for (size_t operand_word_index = 0; + operand_word_index < operand.num_words; ++operand_word_index) { + words_.push_back(inst.words[operand.offset + operand_word_index]); + } + } + } + + uint32_t descriptor = + custom_hash_func_ ? custom_hash_func_(words_) : HashU32Array(words_); + if (descriptor == 0) descriptor = 1; + assert(descriptor); + + words_.clear(); + + const auto result = id_to_descriptor_.emplace(inst.result_id, descriptor); + assert(result.second); + (void)result; + return descriptor; +} + +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/id_descriptor.h b/3rdparty/spirv-tools/source/id_descriptor.h new file mode 100644 index 000000000..d27123a1e --- /dev/null +++ b/3rdparty/spirv-tools/source/id_descriptor.h @@ -0,0 +1,63 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_ID_DESCRIPTOR_H_ +#define LIBSPIRV_ID_DESCRIPTOR_H_ + +#include +#include + +#include "spirv-tools/libspirv.hpp" + +namespace libspirv { + +using CustomHashFunc = std::function&)>; + +// Computes and stores id descriptors. +// +// Descriptors are computed as hash of all words in the instruction where ids +// were substituted with previously computed descriptors. +class IdDescriptorCollection { + public: + explicit IdDescriptorCollection( + CustomHashFunc custom_hash_func = CustomHashFunc()) + : custom_hash_func_(custom_hash_func) { + words_.reserve(16); + } + + // Computes descriptor for the result id of the given instruction and + // registers it in id_to_descriptor_. Returns the computed descriptor. + // This function needs to be sequentially called for every instruction in the + // module. + uint32_t ProcessInstruction(const spv_parsed_instruction_t& inst); + + // Returns a previously computed descriptor id. + uint32_t GetDescriptor(uint32_t id) const { + const auto it = id_to_descriptor_.find(id); + if (it == id_to_descriptor_.end()) return 0; + return it->second; + } + + private: + std::unordered_map id_to_descriptor_; + + std::function&)> custom_hash_func_; + + // Scratch buffer used for hashing. Class member to optimize on allocation. + std::vector words_; +}; + +} // namespace libspirv + +#endif // LIBSPIRV_ID_DESCRIPTOR_H_ diff --git a/3rdparty/spirv-tools/source/instruction.h b/3rdparty/spirv-tools/source/instruction.h new file mode 100644 index 000000000..884276d7e --- /dev/null +++ b/3rdparty/spirv-tools/source/instruction.h @@ -0,0 +1,49 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_INSTRUCTION_H_ +#define LIBSPIRV_INSTRUCTION_H_ + +#include +#include + +#include "latest_version_spirv_header.h" +#include "spirv-tools/libspirv.h" + +// Describes an instruction. +struct spv_instruction_t { + // Normally, both opcode and extInstType contain valid data. + // However, when the assembler parses ! as the first word in + // an instruction and opcode and extInstType are invalid. + SpvOp opcode; + spv_ext_inst_type_t extInstType; + + // The Id of the result type, if this instruction has one. Zero otherwise. + uint32_t resultTypeId; + + // The instruction, as a sequence of 32-bit words. + // For a regular instruction the opcode and word count are combined + // in words[0], as described in the SPIR-V spec. + // Otherwise, the first token was !, and that number appears + // in words[0]. Subsequent elements are the result of parsing + // tokens in the alternate parsing mode as described in syntax.md. + std::vector words; +}; + +// Appends a word to an instruction, without checking for overflow. +inline void spvInstructionAddWord(spv_instruction_t* inst, uint32_t value) { + inst->words.push_back(value); +} + +#endif // LIBSPIRV_INSTRUCTION_H_ diff --git a/3rdparty/spirv-tools/source/latest_version_glsl_std_450_header.h b/3rdparty/spirv-tools/source/latest_version_glsl_std_450_header.h new file mode 100644 index 000000000..b9e9ae275 --- /dev/null +++ b/3rdparty/spirv-tools/source/latest_version_glsl_std_450_header.h @@ -0,0 +1,20 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_LATEST_VERSION_GLSL_STD_450_HEADER_H_ +#define LIBSPIRV_LATEST_VERSION_GLSL_STD_450_HEADER_H_ + +#include "spirv/unified1/GLSL.std.450.h" + +#endif // LIBSPIRV_LATEST_VERSION_GLSL_STD_450_HEADER_H_ diff --git a/3rdparty/spirv-tools/source/latest_version_opencl_std_header.h b/3rdparty/spirv-tools/source/latest_version_opencl_std_header.h new file mode 100644 index 000000000..9bb6e5028 --- /dev/null +++ b/3rdparty/spirv-tools/source/latest_version_opencl_std_header.h @@ -0,0 +1,20 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_LATEST_VERSION_OPENCL_STD_HEADER_H_ +#define LIBSPIRV_LATEST_VERSION_OPENCL_STD_HEADER_H_ + +#include "spirv/unified1/OpenCL.std.h" + +#endif // LIBSPIRV_LATEST_VERSION_OPENCL_STD_HEADER_H_ diff --git a/3rdparty/spirv-tools/source/latest_version_spirv_header.h b/3rdparty/spirv-tools/source/latest_version_spirv_header.h new file mode 100644 index 000000000..c328b6976 --- /dev/null +++ b/3rdparty/spirv-tools/source/latest_version_spirv_header.h @@ -0,0 +1,20 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_LATEST_VERSION_SPIRV_HEADER_H_ +#define LIBSPIRV_LATEST_VERSION_SPIRV_HEADER_H_ + +#include "spirv/unified1/spirv.h" + +#endif // LIBSPIRV_LATEST_VERSION_SPIRV_HEADER_H_ diff --git a/3rdparty/spirv-tools/source/libspirv.cpp b/3rdparty/spirv-tools/source/libspirv.cpp new file mode 100644 index 000000000..d0e3fe28c --- /dev/null +++ b/3rdparty/spirv-tools/source/libspirv.cpp @@ -0,0 +1,118 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "spirv-tools/libspirv.hpp" + +#include "table.h" + +namespace spvtools { + +Context::Context(spv_target_env env) : context_(spvContextCreate(env)) {} + +Context::Context(Context&& other) : context_(other.context_) { + other.context_ = nullptr; +} + +Context& Context::operator=(Context&& other) { + spvContextDestroy(context_); + context_ = other.context_; + other.context_ = nullptr; + + return *this; +} + +Context::~Context() { spvContextDestroy(context_); } + +void Context::SetMessageConsumer(MessageConsumer consumer) { + libspirv::SetContextMessageConsumer(context_, std::move(consumer)); +} + +spv_context& Context::CContext() { return context_; } + +const spv_context& Context::CContext() const { return context_; } + +// Structs for holding the data members for SpvTools. +struct SpirvTools::Impl { + explicit Impl(spv_target_env env) : context(spvContextCreate(env)) { + // The default consumer in spv_context_t is a null consumer, which provides + // equivalent functionality (from the user's perspective) as a real consumer + // does nothing. + } + ~Impl() { spvContextDestroy(context); } + + spv_context context; // C interface context object. +}; + +SpirvTools::SpirvTools(spv_target_env env) : impl_(new Impl(env)) {} + +SpirvTools::~SpirvTools() {} + +void SpirvTools::SetMessageConsumer(MessageConsumer consumer) { + libspirv::SetContextMessageConsumer(impl_->context, std::move(consumer)); +} + +bool SpirvTools::Assemble(const std::string& text, + std::vector* binary, + uint32_t options) const { + return Assemble(text.data(), text.size(), binary, options); +} + +bool SpirvTools::Assemble(const char* text, const size_t text_size, + std::vector* binary, + uint32_t options) const { + spv_binary spvbinary = nullptr; + spv_result_t status = spvTextToBinaryWithOptions( + impl_->context, text, text_size, options, &spvbinary, nullptr); + if (status == SPV_SUCCESS) { + binary->assign(spvbinary->code, spvbinary->code + spvbinary->wordCount); + } + spvBinaryDestroy(spvbinary); + return status == SPV_SUCCESS; +} + +bool SpirvTools::Disassemble(const std::vector& binary, + std::string* text, uint32_t options) const { + return Disassemble(binary.data(), binary.size(), text, options); +} + +bool SpirvTools::Disassemble(const uint32_t* binary, const size_t binary_size, + std::string* text, uint32_t options) const { + spv_text spvtext = nullptr; + spv_result_t status = spvBinaryToText(impl_->context, binary, binary_size, + options, &spvtext, nullptr); + if (status == SPV_SUCCESS) { + text->assign(spvtext->str, spvtext->str + spvtext->length); + } + spvTextDestroy(spvtext); + return status == SPV_SUCCESS; +} + +bool SpirvTools::Validate(const std::vector& binary) const { + return Validate(binary.data(), binary.size()); +} + +bool SpirvTools::Validate(const uint32_t* binary, + const size_t binary_size) const { + return spvValidateBinary(impl_->context, binary, binary_size, nullptr) == + SPV_SUCCESS; +} + +bool SpirvTools::Validate(const uint32_t* binary, const size_t binary_size, + const spvtools::ValidatorOptions& options) const { + spv_const_binary_t the_binary{binary, binary_size}; + return spvValidateWithOptions(impl_->context, options, &the_binary, + nullptr) == SPV_SUCCESS; +} + +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/link/CMakeLists.txt b/3rdparty/spirv-tools/source/link/CMakeLists.txt new file mode 100644 index 000000000..8ca4df39f --- /dev/null +++ b/3rdparty/spirv-tools/source/link/CMakeLists.txt @@ -0,0 +1,36 @@ +# Copyright (c) 2017 Pierre Moreau + +# 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. +add_library(SPIRV-Tools-link + linker.cpp +) + +spvtools_default_compile_options(SPIRV-Tools-link) +target_include_directories(SPIRV-Tools-link + PUBLIC ${spirv-tools_SOURCE_DIR}/include + PUBLIC ${SPIRV_HEADER_INCLUDE_DIR} + PRIVATE ${spirv-tools_BINARY_DIR} +) +# We need the IR functionnalities from the optimizer +target_link_libraries(SPIRV-Tools-link + PUBLIC SPIRV-Tools-opt) + +set_property(TARGET SPIRV-Tools-link PROPERTY FOLDER "SPIRV-Tools libraries") +spvtools_check_symbol_exports(SPIRV-Tools-link) + +if(ENABLE_SPIRV_TOOLS_INSTALL) + install(TARGETS SPIRV-Tools-link + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) +endif(ENABLE_SPIRV_TOOLS_INSTALL) diff --git a/3rdparty/spirv-tools/source/link/linker.cpp b/3rdparty/spirv-tools/source/link/linker.cpp new file mode 100644 index 000000000..49cda4bd3 --- /dev/null +++ b/3rdparty/spirv-tools/source/link/linker.cpp @@ -0,0 +1,786 @@ +// Copyright (c) 2017 Pierre Moreau +// +// 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 "spirv-tools/linker.hpp" + +#include +#include + +#include +#include +#include +#include +#include + +#include "assembly_grammar.h" +#include "diagnostic.h" +#include "opt/build_module.h" +#include "opt/compact_ids_pass.h" +#include "opt/decoration_manager.h" +#include "opt/ir_loader.h" +#include "opt/make_unique.h" +#include "opt/pass_manager.h" +#include "opt/remove_duplicates_pass.h" +#include "spirv-tools/libspirv.hpp" +#include "spirv_target_env.h" + +namespace spvtools { + +using ir::Instruction; +using ir::IRContext; +using ir::Module; +using ir::Operand; +using opt::PassManager; +using opt::RemoveDuplicatesPass; +using opt::analysis::DecorationManager; +using opt::analysis::DefUseManager; + +// Stores various information about an imported or exported symbol. +struct LinkageSymbolInfo { + SpvId id; // ID of the symbol + SpvId type_id; // ID of the type of the symbol + std::string name; // unique name defining the symbol and used for matching + // imports and exports together + std::vector parameter_ids; // ID of the parameters of the symbol, if + // it is a function +}; +struct LinkageEntry { + LinkageSymbolInfo imported_symbol; + LinkageSymbolInfo exported_symbol; + + LinkageEntry(const LinkageSymbolInfo& import_info, + const LinkageSymbolInfo& export_info) + : imported_symbol(import_info), exported_symbol(export_info) {} +}; +using LinkageTable = std::vector; + +// Shifts the IDs used in each binary of |modules| so that they occupy a +// disjoint range from the other binaries, and compute the new ID bound which +// is returned in |max_id_bound|. +// +// Both |modules| and |max_id_bound| should not be null, and |modules| should +// not be empty either. Furthermore |modules| should not contain any null +// pointers. +static spv_result_t ShiftIdsInModules(const MessageConsumer& consumer, + std::vector* modules, + uint32_t* max_id_bound); + +// Generates the header for the linked module and returns it in |header|. +// +// |header| should not be null, |modules| should not be empty and pointers +// should be non-null. |max_id_bound| should be strictly greater than 0. +// +// TODO(pierremoreau): What to do when binaries use different versions of +// SPIR-V? For now, use the max of all versions found in +// the input modules. +static spv_result_t GenerateHeader(const MessageConsumer& consumer, + const std::vector& modules, + uint32_t max_id_bound, + ir::ModuleHeader* header); + +// Merge all the modules from |in_modules| into a single module owned by +// |linked_context|. +// +// |linked_context| should not be null. +static spv_result_t MergeModules(const MessageConsumer& consumer, + const std::vector& in_modules, + const libspirv::AssemblyGrammar& grammar, + IRContext* linked_context); + +// Compute all pairs of import and export and return it in |linkings_to_do|. +// +// |linkings_to_do should not be null. Built-in symbols will be ignored. +// +// TODO(pierremoreau): Linkage attributes applied by a group decoration are +// currently not handled. (You could have a group being +// applied to a single ID.) +// TODO(pierremoreau): What should be the proper behaviour with built-in +// symbols? +static spv_result_t GetImportExportPairs( + const MessageConsumer& consumer, const ir::IRContext& linked_context, + const DefUseManager& def_use_manager, + const DecorationManager& decoration_manager, bool allow_partial_linkage, + LinkageTable* linkings_to_do); + +// Checks that for each pair of import and export, the import and export have +// the same type as well as the same decorations. +// +// TODO(pierremoreau): Decorations on functions parameters are currently not +// checked. +static spv_result_t CheckImportExportCompatibility( + const MessageConsumer& consumer, const LinkageTable& linkings_to_do, + ir::IRContext* context); + +// Remove linkage specific instructions, such as prototypes of imported +// functions, declarations of imported variables, import (and export if +// necessary) linkage attribtes. +// +// |linked_context| and |decoration_manager| should not be null, and the +// 'RemoveDuplicatePass' should be run first. +// +// TODO(pierremoreau): Linkage attributes applied by a group decoration are +// currently not handled. (You could have a group being +// applied to a single ID.) +// TODO(pierremoreau): Run a pass for removing dead instructions, for example +// OpName for prototypes of imported funcions. +static spv_result_t RemoveLinkageSpecificInstructions( + const MessageConsumer& consumer, const LinkerOptions& options, + const LinkageTable& linkings_to_do, DecorationManager* decoration_manager, + ir::IRContext* linked_context); + +// Verify that the unique ids of each instruction in |linked_context| (i.e. the +// merged module) are truly unique. Does not check the validity of other ids +static spv_result_t VerifyIds(const MessageConsumer& consumer, + ir::IRContext* linked_context); + +spv_result_t Link(const Context& context, + const std::vector>& binaries, + std::vector* linked_binary, + const LinkerOptions& options) { + std::vector binary_ptrs; + binary_ptrs.reserve(binaries.size()); + std::vector binary_sizes; + binary_sizes.reserve(binaries.size()); + + for (const auto& binary : binaries) { + binary_ptrs.push_back(binary.data()); + binary_sizes.push_back(binary.size()); + } + + return Link(context, binary_ptrs.data(), binary_sizes.data(), binaries.size(), + linked_binary, options); +} + +spv_result_t Link(const Context& context, const uint32_t* const* binaries, + const size_t* binary_sizes, size_t num_binaries, + std::vector* linked_binary, + const LinkerOptions& options) { + spv_position_t position = {}; + const spv_context& c_context = context.CContext(); + const MessageConsumer& consumer = c_context->consumer; + + linked_binary->clear(); + if (num_binaries == 0u) + return libspirv::DiagnosticStream(position, consumer, + SPV_ERROR_INVALID_BINARY) + << "No modules were given."; + + std::vector> ir_contexts; + std::vector modules; + modules.reserve(num_binaries); + for (size_t i = 0u; i < num_binaries; ++i) { + const uint32_t schema = binaries[i][4u]; + if (schema != 0u) { + position.index = 4u; + return libspirv::DiagnosticStream(position, consumer, + SPV_ERROR_INVALID_BINARY) + << "Schema is non-zero for module " << i << "."; + } + + std::unique_ptr ir_context = BuildModule( + c_context->target_env, consumer, binaries[i], binary_sizes[i]); + if (ir_context == nullptr) + return libspirv::DiagnosticStream(position, consumer, + SPV_ERROR_INVALID_BINARY) + << "Failed to build a module out of " << ir_contexts.size() << "."; + modules.push_back(ir_context->module()); + ir_contexts.push_back(std::move(ir_context)); + } + + // Phase 1: Shift the IDs used in each binary so that they occupy a disjoint + // range from the other binaries, and compute the new ID bound. + uint32_t max_id_bound = 0u; + spv_result_t res = ShiftIdsInModules(consumer, &modules, &max_id_bound); + if (res != SPV_SUCCESS) return res; + + // Phase 2: Generate the header + ir::ModuleHeader header; + res = GenerateHeader(consumer, modules, max_id_bound, &header); + if (res != SPV_SUCCESS) return res; + IRContext linked_context(c_context->target_env, consumer); + linked_context.module()->SetHeader(header); + + // Phase 3: Merge all the binaries into a single one. + libspirv::AssemblyGrammar grammar(c_context); + res = MergeModules(consumer, modules, grammar, &linked_context); + if (res != SPV_SUCCESS) return res; + + if (options.GetVerifyIds()) { + res = VerifyIds(consumer, &linked_context); + if (res != SPV_SUCCESS) return res; + } + + // Phase 4: Find the import/export pairs + LinkageTable linkings_to_do; + res = GetImportExportPairs(consumer, linked_context, + *linked_context.get_def_use_mgr(), + *linked_context.get_decoration_mgr(), + options.GetAllowPartialLinkage(), &linkings_to_do); + if (res != SPV_SUCCESS) return res; + + // Phase 5: Ensure the import and export have the same types and decorations. + res = + CheckImportExportCompatibility(consumer, linkings_to_do, &linked_context); + if (res != SPV_SUCCESS) return res; + + // Phase 6: Remove duplicates + PassManager manager; + manager.SetMessageConsumer(consumer); + manager.AddPass(); + opt::Pass::Status pass_res = manager.Run(&linked_context); + if (pass_res == opt::Pass::Status::Failure) return SPV_ERROR_INVALID_DATA; + + // Phase 7: Rematch import variables/functions to export variables/functions + for (const auto& linking_entry : linkings_to_do) + linked_context.ReplaceAllUsesWith(linking_entry.imported_symbol.id, + linking_entry.exported_symbol.id); + + // Phase 8: Remove linkage specific instructions, such as import/export + // attributes, linkage capability, etc. if applicable + res = RemoveLinkageSpecificInstructions(consumer, options, linkings_to_do, + linked_context.get_decoration_mgr(), + &linked_context); + if (res != SPV_SUCCESS) return res; + + // Phase 9: Compact the IDs used in the module + manager.AddPass(); + pass_res = manager.Run(&linked_context); + if (pass_res == opt::Pass::Status::Failure) return SPV_ERROR_INVALID_DATA; + + // Phase 10: Output the module + linked_context.module()->ToBinary(linked_binary, true); + + return SPV_SUCCESS; +} + +static spv_result_t ShiftIdsInModules(const MessageConsumer& consumer, + std::vector* modules, + uint32_t* max_id_bound) { + spv_position_t position = {}; + + if (modules == nullptr) + return libspirv::DiagnosticStream(position, consumer, + SPV_ERROR_INVALID_DATA) + << "|modules| of ShiftIdsInModules should not be null."; + if (modules->empty()) + return libspirv::DiagnosticStream(position, consumer, + SPV_ERROR_INVALID_DATA) + << "|modules| of ShiftIdsInModules should not be empty."; + if (max_id_bound == nullptr) + return libspirv::DiagnosticStream(position, consumer, + SPV_ERROR_INVALID_DATA) + << "|max_id_bound| of ShiftIdsInModules should not be null."; + + uint32_t id_bound = modules->front()->IdBound() - 1u; + for (auto module_iter = modules->begin() + 1; module_iter != modules->end(); + ++module_iter) { + Module* module = *module_iter; + module->ForEachInst([&id_bound](Instruction* insn) { + insn->ForEachId([&id_bound](uint32_t* id) { *id += id_bound; }); + }); + id_bound += module->IdBound() - 1u; + if (id_bound > 0x3FFFFF) + return libspirv::DiagnosticStream(position, consumer, + SPV_ERROR_INVALID_ID) + << "The limit of IDs, 4194303, was exceeded:" + << " " << id_bound << " is the current ID bound."; + + // Invalidate the DefUseManager + module->context()->InvalidateAnalyses(ir::IRContext::kAnalysisDefUse); + } + ++id_bound; + if (id_bound > 0x3FFFFF) + return libspirv::DiagnosticStream(position, consumer, SPV_ERROR_INVALID_ID) + << "The limit of IDs, 4194303, was exceeded:" + << " " << id_bound << " is the current ID bound."; + + *max_id_bound = id_bound; + + return SPV_SUCCESS; +} + +static spv_result_t GenerateHeader(const MessageConsumer& consumer, + const std::vector& modules, + uint32_t max_id_bound, + ir::ModuleHeader* header) { + spv_position_t position = {}; + + if (modules.empty()) + return libspirv::DiagnosticStream(position, consumer, + SPV_ERROR_INVALID_DATA) + << "|modules| of GenerateHeader should not be empty."; + if (max_id_bound == 0u) + return libspirv::DiagnosticStream(position, consumer, + SPV_ERROR_INVALID_DATA) + << "|max_id_bound| of GenerateHeader should not be null."; + + uint32_t version = 0u; + for (const auto& module : modules) + version = std::max(version, module->version()); + + header->magic_number = SpvMagicNumber; + header->version = version; + header->generator = 17u; + header->bound = max_id_bound; + header->reserved = 0u; + + return SPV_SUCCESS; +} + +static spv_result_t MergeModules(const MessageConsumer& consumer, + const std::vector& input_modules, + const libspirv::AssemblyGrammar& grammar, + IRContext* linked_context) { + spv_position_t position = {}; + + if (linked_context == nullptr) + return libspirv::DiagnosticStream(position, consumer, + SPV_ERROR_INVALID_DATA) + << "|linked_module| of MergeModules should not be null."; + Module* linked_module = linked_context->module(); + + if (input_modules.empty()) return SPV_SUCCESS; + + for (const auto& module : input_modules) + for (const auto& inst : module->capabilities()) + linked_module->AddCapability( + std::unique_ptr(inst.Clone(linked_context))); + + for (const auto& module : input_modules) + for (const auto& inst : module->extensions()) + linked_module->AddExtension( + std::unique_ptr(inst.Clone(linked_context))); + + for (const auto& module : input_modules) + for (const auto& inst : module->ext_inst_imports()) + linked_module->AddExtInstImport( + std::unique_ptr(inst.Clone(linked_context))); + + do { + const Instruction* memory_model_inst = input_modules[0]->GetMemoryModel(); + if (memory_model_inst == nullptr) break; + + uint32_t addressing_model = memory_model_inst->GetSingleWordOperand(0u); + uint32_t memory_model = memory_model_inst->GetSingleWordOperand(1u); + for (const auto& module : input_modules) { + memory_model_inst = module->GetMemoryModel(); + if (memory_model_inst == nullptr) continue; + + if (addressing_model != memory_model_inst->GetSingleWordOperand(0u)) { + spv_operand_desc initial_desc = nullptr, current_desc = nullptr; + grammar.lookupOperand(SPV_OPERAND_TYPE_ADDRESSING_MODEL, + addressing_model, &initial_desc); + grammar.lookupOperand(SPV_OPERAND_TYPE_ADDRESSING_MODEL, + memory_model_inst->GetSingleWordOperand(0u), + ¤t_desc); + return libspirv::DiagnosticStream(position, consumer, + SPV_ERROR_INTERNAL) + << "Conflicting addressing models: " << initial_desc->name + << " vs " << current_desc->name << "."; + } + if (memory_model != memory_model_inst->GetSingleWordOperand(1u)) { + spv_operand_desc initial_desc = nullptr, current_desc = nullptr; + grammar.lookupOperand(SPV_OPERAND_TYPE_MEMORY_MODEL, memory_model, + &initial_desc); + grammar.lookupOperand(SPV_OPERAND_TYPE_MEMORY_MODEL, + memory_model_inst->GetSingleWordOperand(1u), + ¤t_desc); + return libspirv::DiagnosticStream(position, consumer, + SPV_ERROR_INTERNAL) + << "Conflicting memory models: " << initial_desc->name << " vs " + << current_desc->name << "."; + } + } + + if (memory_model_inst != nullptr) + linked_module->SetMemoryModel(std::unique_ptr( + memory_model_inst->Clone(linked_context))); + } while (false); + + std::vector> entry_points; + for (const auto& module : input_modules) + for (const auto& inst : module->entry_points()) { + const uint32_t model = inst.GetSingleWordInOperand(0); + const char* const name = + reinterpret_cast(inst.GetInOperand(2).words.data()); + const auto i = std::find_if( + entry_points.begin(), entry_points.end(), + [model, name](const std::pair& v) { + return v.first == model && strcmp(name, v.second) == 0; + }); + if (i != entry_points.end()) { + spv_operand_desc desc = nullptr; + grammar.lookupOperand(SPV_OPERAND_TYPE_EXECUTION_MODEL, model, &desc); + return libspirv::DiagnosticStream(position, consumer, + SPV_ERROR_INTERNAL) + << "The entry point \"" << name << "\", with execution model " + << desc->name << ", was already defined."; + } + linked_module->AddEntryPoint( + std::unique_ptr(inst.Clone(linked_context))); + entry_points.emplace_back(model, name); + } + + for (const auto& module : input_modules) + for (const auto& inst : module->execution_modes()) + linked_module->AddExecutionMode( + std::unique_ptr(inst.Clone(linked_context))); + + for (const auto& module : input_modules) + for (const auto& inst : module->debugs1()) + linked_module->AddDebug1Inst( + std::unique_ptr(inst.Clone(linked_context))); + + for (const auto& module : input_modules) + for (const auto& inst : module->debugs2()) + linked_module->AddDebug2Inst( + std::unique_ptr(inst.Clone(linked_context))); + + for (const auto& module : input_modules) + for (const auto& inst : module->debugs3()) + linked_module->AddDebug3Inst( + std::unique_ptr(inst.Clone(linked_context))); + + // If the generated module uses SPIR-V 1.1 or higher, add an + // OpModuleProcessed instruction about the linking step. + if (linked_module->version() >= 0x10100) { + const std::string processed_string("Linked by SPIR-V Tools Linker"); + const size_t words_nb = + processed_string.size() / 4u + (processed_string.size() % 4u != 0u); + std::vector processed_words(words_nb, 0u); + std::memcpy(processed_words.data(), processed_string.data(), words_nb * 4u); + linked_module->AddDebug3Inst(std::unique_ptr( + new Instruction(linked_context, SpvOpModuleProcessed, 0u, 0u, + {{SPV_OPERAND_TYPE_LITERAL_STRING, processed_words}}))); + } + + for (const auto& module : input_modules) + for (const auto& inst : module->annotations()) + linked_module->AddAnnotationInst( + std::unique_ptr(inst.Clone(linked_context))); + + // TODO(pierremoreau): Since the modules have not been validate, should we + // expect SpvStorageClassFunction variables outside + // functions? + uint32_t num_global_values = 0u; + for (const auto& module : input_modules) { + for (const auto& inst : module->types_values()) { + linked_module->AddType( + std::unique_ptr(inst.Clone(linked_context))); + num_global_values += inst.opcode() == SpvOpVariable; + } + } + if (num_global_values > 0xFFFF) + return libspirv::DiagnosticStream(position, consumer, SPV_ERROR_INTERNAL) + << "The limit of global values, 65535, was exceeded;" + << " " << num_global_values << " global values were found."; + + // Process functions and their basic blocks + for (const auto& module : input_modules) { + for (const auto& func : *module) { + std::unique_ptr cloned_func(func.Clone(linked_context)); + cloned_func->SetParent(linked_module); + linked_module->AddFunction(std::move(cloned_func)); + } + } + + return SPV_SUCCESS; +} + +static spv_result_t GetImportExportPairs( + const MessageConsumer& consumer, const ir::IRContext& linked_context, + const DefUseManager& def_use_manager, + const DecorationManager& decoration_manager, bool allow_partial_linkage, + LinkageTable* linkings_to_do) { + spv_position_t position = {}; + + if (linkings_to_do == nullptr) + return libspirv::DiagnosticStream(position, consumer, + SPV_ERROR_INVALID_DATA) + << "|linkings_to_do| of GetImportExportPairs should not be empty."; + + std::vector imports; + std::unordered_map> exports; + + // Figure out the imports and exports + for (const auto& decoration : linked_context.annotations()) { + if (decoration.opcode() != SpvOpDecorate || + decoration.GetSingleWordInOperand(1u) != SpvDecorationLinkageAttributes) + continue; + + const SpvId id = decoration.GetSingleWordInOperand(0u); + // Ignore if the targeted symbol is a built-in + bool is_built_in = false; + for (const auto& id_decoration : + decoration_manager.GetDecorationsFor(id, false)) { + if (id_decoration->GetSingleWordInOperand(1u) == SpvDecorationBuiltIn) { + is_built_in = true; + break; + } + } + if (is_built_in) { + continue; + } + + const uint32_t type = decoration.GetSingleWordInOperand(3u); + + LinkageSymbolInfo symbol_info; + symbol_info.name = + reinterpret_cast(decoration.GetInOperand(2u).words.data()); + symbol_info.id = id; + symbol_info.type_id = 0u; + + // Retrieve the type of the current symbol. This information will be used + // when checking that the imported and exported symbols have the same + // types. + const Instruction* def_inst = def_use_manager.GetDef(id); + if (def_inst == nullptr) + return libspirv::DiagnosticStream(position, consumer, + SPV_ERROR_INVALID_BINARY) + << "ID " << id << " is never defined:\n"; + + if (def_inst->opcode() == SpvOpVariable) { + symbol_info.type_id = def_inst->type_id(); + } else if (def_inst->opcode() == SpvOpFunction) { + symbol_info.type_id = def_inst->GetSingleWordInOperand(1u); + + // range-based for loop calls begin()/end(), but never cbegin()/cend(), + // which will not work here. + for (auto func_iter = linked_context.module()->cbegin(); + func_iter != linked_context.module()->cend(); ++func_iter) { + if (func_iter->result_id() != id) continue; + func_iter->ForEachParam([&symbol_info](const Instruction* inst) { + symbol_info.parameter_ids.push_back(inst->result_id()); + }); + } + } else { + return libspirv::DiagnosticStream(position, consumer, + SPV_ERROR_INVALID_BINARY) + << "Only global variables and functions can be decorated using" + << " LinkageAttributes; " << id << " is neither of them.\n"; + } + + if (type == SpvLinkageTypeImport) + imports.push_back(symbol_info); + else if (type == SpvLinkageTypeExport) + exports[symbol_info.name].push_back(symbol_info); + } + + // Find the import/export pairs + for (const auto& import : imports) { + std::vector possible_exports; + const auto& exp = exports.find(import.name); + if (exp != exports.end()) possible_exports = exp->second; + if (possible_exports.empty() && !allow_partial_linkage) + return libspirv::DiagnosticStream(position, consumer, + SPV_ERROR_INVALID_BINARY) + << "Unresolved external reference to \"" << import.name << "\"."; + else if (possible_exports.size() > 1u) + return libspirv::DiagnosticStream(position, consumer, + SPV_ERROR_INVALID_BINARY) + << "Too many external references, " << possible_exports.size() + << ", were found for \"" << import.name << "\"."; + + if (!possible_exports.empty()) + linkings_to_do->emplace_back(import, possible_exports.front()); + } + + return SPV_SUCCESS; +} + +static spv_result_t CheckImportExportCompatibility( + const MessageConsumer& consumer, const LinkageTable& linkings_to_do, + ir::IRContext* context) { + spv_position_t position = {}; + + // Ensure th import and export types are the same. + const DefUseManager& def_use_manager = *context->get_def_use_mgr(); + const DecorationManager& decoration_manager = *context->get_decoration_mgr(); + for (const auto& linking_entry : linkings_to_do) { + if (!RemoveDuplicatesPass::AreTypesEqual( + *def_use_manager.GetDef(linking_entry.imported_symbol.type_id), + *def_use_manager.GetDef(linking_entry.exported_symbol.type_id), + context)) + return libspirv::DiagnosticStream(position, consumer, + SPV_ERROR_INVALID_BINARY) + << "Type mismatch on symbol \"" + << linking_entry.imported_symbol.name + << "\" between imported variable/function %" + << linking_entry.imported_symbol.id + << " and exported variable/function %" + << linking_entry.exported_symbol.id << "."; + } + + // Ensure the import and export decorations are similar + for (const auto& linking_entry : linkings_to_do) { + if (!decoration_manager.HaveTheSameDecorations( + linking_entry.imported_symbol.id, linking_entry.exported_symbol.id)) + return libspirv::DiagnosticStream(position, consumer, + SPV_ERROR_INVALID_BINARY) + << "Decorations mismatch on symbol \"" + << linking_entry.imported_symbol.name + << "\" between imported variable/function %" + << linking_entry.imported_symbol.id + << " and exported variable/function %" + << linking_entry.exported_symbol.id << "."; + // TODO(pierremoreau): Decorations on function parameters should probably + // match, except for FuncParamAttr if I understand the + // spec correctly. + // TODO(pierremoreau): Decorations on the function return type should + // match, except for FuncParamAttr. + } + + return SPV_SUCCESS; +} + +static spv_result_t RemoveLinkageSpecificInstructions( + const MessageConsumer& consumer, const LinkerOptions& options, + const LinkageTable& linkings_to_do, DecorationManager* decoration_manager, + ir::IRContext* linked_context) { + spv_position_t position = {}; + + if (decoration_manager == nullptr) + return libspirv::DiagnosticStream(position, consumer, + SPV_ERROR_INVALID_DATA) + << "|decoration_manager| of RemoveLinkageSpecificInstructions " + "should not be empty."; + if (linked_context == nullptr) + return libspirv::DiagnosticStream(position, consumer, + SPV_ERROR_INVALID_DATA) + << "|linked_module| of RemoveLinkageSpecificInstructions should not " + "be empty."; + + // TODO(pierremoreau): Remove FuncParamAttr decorations of imported + // functions' return type. + + // Remove FuncParamAttr decorations of imported functions' parameters. + // From the SPIR-V specification, Sec. 2.13: + // When resolving imported functions, the Function Control and all Function + // Parameter Attributes are taken from the function definition, and not + // from the function declaration. + for (const auto& linking_entry : linkings_to_do) { + for (const auto parameter_id : + linking_entry.imported_symbol.parameter_ids) { + decoration_manager->RemoveDecorationsFrom( + parameter_id, [](const Instruction& inst) { + return (inst.opcode() == SpvOpDecorate || + inst.opcode() == SpvOpMemberDecorate) && + inst.GetSingleWordInOperand(1u) == + SpvDecorationFuncParamAttr; + }); + } + } + + // Remove prototypes of imported functions + for (const auto& linking_entry : linkings_to_do) { + for (auto func_iter = linked_context->module()->begin(); + func_iter != linked_context->module()->end();) { + if (func_iter->result_id() == linking_entry.imported_symbol.id) + func_iter = func_iter.Erase(); + else + ++func_iter; + } + } + + // Remove declarations of imported variables + for (const auto& linking_entry : linkings_to_do) { + auto next = linked_context->types_values_begin(); + for (auto inst = next; inst != linked_context->types_values_end(); + inst = next) { + ++next; + if (inst->result_id() == linking_entry.imported_symbol.id) { + linked_context->KillInst(&*inst); + } + } + } + + // If partial linkage is allowed, we need an efficient way to check whether + // an imported ID had a corresponding export symbol. As uses of the imported + // symbol have already been replaced by the exported symbol, use the exported + // symbol ID. + // TODO(pierremoreau): This will not work if the decoration is applied + // through a group, but the linker does not support that + // either. + std::unordered_set imports; + if (options.GetAllowPartialLinkage()) { + imports.reserve(linkings_to_do.size()); + for (const auto& linking_entry : linkings_to_do) + imports.emplace(linking_entry.exported_symbol.id); + } + + // Remove import linkage attributes + auto next = linked_context->annotation_begin(); + for (auto inst = next; inst != linked_context->annotation_end(); + inst = next) { + ++next; + // If this is an import annotation: + // * if we do not allow partial linkage, remove all import annotations; + // * otherwise, remove the annotation only if there was a corresponding + // export. + if (inst->opcode() == SpvOpDecorate && + inst->GetSingleWordOperand(1u) == SpvDecorationLinkageAttributes && + inst->GetSingleWordOperand(3u) == SpvLinkageTypeImport && + (!options.GetAllowPartialLinkage() || + imports.find(inst->GetSingleWordOperand(0u)) != imports.end())) { + linked_context->KillInst(&*inst); + } + } + + // Remove export linkage attributes if making an executable + if (!options.GetCreateLibrary()) { + next = linked_context->annotation_begin(); + for (auto inst = next; inst != linked_context->annotation_end(); + inst = next) { + ++next; + if (inst->opcode() == SpvOpDecorate && + inst->GetSingleWordOperand(1u) == SpvDecorationLinkageAttributes && + inst->GetSingleWordOperand(3u) == SpvLinkageTypeExport) { + linked_context->KillInst(&*inst); + } + } + } + + // Remove Linkage capability if making an executable and partial linkage is + // not allowed + if (!options.GetCreateLibrary() && !options.GetAllowPartialLinkage()) { + for (auto& inst : linked_context->capabilities()) + if (inst.GetSingleWordInOperand(0u) == SpvCapabilityLinkage) { + linked_context->KillInst(&inst); + // The RemoveDuplicatesPass did remove duplicated capabilities, so we + // now there aren’t more SpvCapabilityLinkage further down. + break; + } + } + + return SPV_SUCCESS; +} + +spv_result_t VerifyIds(const MessageConsumer& consumer, + ir::IRContext* linked_context) { + std::unordered_set ids; + bool ok = true; + linked_context->module()->ForEachInst( + [&ids, &ok](const ir::Instruction* inst) { + ok &= ids.insert(inst->unique_id()).second; + }); + + if (!ok) { + consumer(SPV_MSG_INTERNAL_ERROR, "", {}, "Non-unique id in merged module"); + return SPV_ERROR_INVALID_ID; + } + + return SPV_SUCCESS; +} + +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/macro.h b/3rdparty/spirv-tools/source/macro.h new file mode 100644 index 000000000..810db6e61 --- /dev/null +++ b/3rdparty/spirv-tools/source/macro.h @@ -0,0 +1,25 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_MACRO_H_ +#define LIBSPIRV_MACRO_H_ + +// Evaluates to the number of elements of array A. +// +// If we could use constexpr, then we could make this a template function. +// If the source arrays were std::array, then we could have used +// std::array::size. +#define ARRAY_SIZE(A) (static_cast(sizeof(A) / sizeof(A[0]))) + +#endif // LIBSPIRV_MACRO_H_ diff --git a/3rdparty/spirv-tools/source/message.cpp b/3rdparty/spirv-tools/source/message.cpp new file mode 100644 index 000000000..030fa4e23 --- /dev/null +++ b/3rdparty/spirv-tools/source/message.cpp @@ -0,0 +1,54 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "message.h" + +#include + +namespace spvtools { + +std::string StringifyMessage(spv_message_level_t level, const char* source, + const spv_position_t& position, + const char* message) { + const char* level_string = nullptr; + switch (level) { + case SPV_MSG_FATAL: + level_string = "fatal"; + break; + case SPV_MSG_INTERNAL_ERROR: + level_string = "internal error"; + break; + case SPV_MSG_ERROR: + level_string = "error"; + break; + case SPV_MSG_WARNING: + level_string = "warning"; + break; + case SPV_MSG_INFO: + level_string = "info"; + break; + case SPV_MSG_DEBUG: + level_string = "debug"; + break; + } + std::ostringstream oss; + oss << level_string << ": "; + if (source) oss << source << ":"; + oss << position.line << ":" << position.column << ":"; + oss << position.index << ": "; + if (message) oss << message; + return oss.str(); +} + +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/message.h b/3rdparty/spirv-tools/source/message.h new file mode 100644 index 000000000..60f5d5632 --- /dev/null +++ b/3rdparty/spirv-tools/source/message.h @@ -0,0 +1,33 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SPIRV_TOOLS_MESSAGE_H_ +#define SPIRV_TOOLS_MESSAGE_H_ + +#include + +#include "spirv-tools/libspirv.h" + +namespace spvtools { + +// A helper function to compose and return a string from the message in the +// following format: +// ": :::: " +std::string StringifyMessage(spv_message_level_t level, const char* source, + const spv_position_t& position, + const char* message); + +} // namespace spvtools + +#endif // SPIRV_TOOLS_MESSAGE_H_ diff --git a/3rdparty/spirv-tools/source/name_mapper.cpp b/3rdparty/spirv-tools/source/name_mapper.cpp new file mode 100644 index 000000000..a8b5a7c90 --- /dev/null +++ b/3rdparty/spirv-tools/source/name_mapper.cpp @@ -0,0 +1,332 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "name_mapper.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "spirv-tools/libspirv.h" + +#include "latest_version_spirv_header.h" +#include "parsed_operand.h" + +namespace { + +// Converts a uint32_t to its string decimal representation. +std::string to_string(uint32_t id) { + // Use stringstream, since some versions of Android compilers lack + // std::to_string. + std::stringstream os; + os << id; + return os.str(); +} + +} // anonymous namespace + +namespace libspirv { + +NameMapper GetTrivialNameMapper() { return to_string; } + +FriendlyNameMapper::FriendlyNameMapper(const spv_const_context context, + const uint32_t* code, + const size_t wordCount) + : grammar_(libspirv::AssemblyGrammar(context)) { + spv_diagnostic diag = nullptr; + // We don't care if the parse fails. + spvBinaryParse(context, this, code, wordCount, nullptr, + ParseInstructionForwarder, &diag); + spvDiagnosticDestroy(diag); +} + +std::string FriendlyNameMapper::NameForId(uint32_t id) { + auto iter = name_for_id_.find(id); + if (iter == name_for_id_.end()) { + // It must have been an invalid module, so just return a trivial mapping. + // We don't care about uniqueness. + return to_string(id); + } else { + return iter->second; + } +} + +std::string FriendlyNameMapper::Sanitize(const std::string& suggested_name) { + if (suggested_name.empty()) return "_"; + // Otherwise, replace invalid characters by '_'. + std::string result; + std::string valid = + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "_0123456789"; + std::transform(suggested_name.begin(), suggested_name.end(), + std::back_inserter(result), [&valid](const char c) { + return (std::string::npos == valid.find(c)) ? '_' : c; + }); + return result; +} + +void FriendlyNameMapper::SaveName(uint32_t id, + const std::string& suggested_name) { + if (name_for_id_.find(id) != name_for_id_.end()) return; + + const std::string sanitized_suggested_name = Sanitize(suggested_name); + std::string name = sanitized_suggested_name; + auto inserted = used_names_.insert(name); + if (!inserted.second) { + const std::string base_name = sanitized_suggested_name + "_"; + for (uint32_t index = 0; !inserted.second; ++index) { + name = base_name + to_string(index); + inserted = used_names_.insert(name); + } + } + name_for_id_[id] = name; +} + +void FriendlyNameMapper::SaveBuiltInName(uint32_t target_id, + uint32_t built_in) { +#define GLCASE(name) \ + case SpvBuiltIn##name: \ + SaveName(target_id, "gl_" #name); \ + return; +#define GLCASE2(name, suggested) \ + case SpvBuiltIn##name: \ + SaveName(target_id, "gl_" #suggested); \ + return; +#define CASE(name) \ + case SpvBuiltIn##name: \ + SaveName(target_id, #name); \ + return; + switch (built_in) { + GLCASE(Position) + GLCASE(PointSize) + GLCASE(ClipDistance) + GLCASE(CullDistance) + GLCASE2(VertexId, VertexID) + GLCASE2(InstanceId, InstanceID) + GLCASE2(PrimitiveId, PrimitiveID) + GLCASE2(InvocationId, InvocationID) + GLCASE(Layer) + GLCASE(ViewportIndex) + GLCASE(TessLevelOuter) + GLCASE(TessLevelInner) + GLCASE(TessCoord) + GLCASE(PatchVertices) + GLCASE(FragCoord) + GLCASE(PointCoord) + GLCASE(FrontFacing) + GLCASE2(SampleId, SampleID) + GLCASE(SamplePosition) + GLCASE(SampleMask) + GLCASE(FragDepth) + GLCASE(HelperInvocation) + GLCASE2(NumWorkgroups, NumWorkGroups) + GLCASE2(WorkgroupSize, WorkGroupSize) + GLCASE2(WorkgroupId, WorkGroupID) + GLCASE2(LocalInvocationId, LocalInvocationID) + GLCASE2(GlobalInvocationId, GlobalInvocationID) + GLCASE(LocalInvocationIndex) + CASE(WorkDim) + CASE(GlobalSize) + CASE(EnqueuedWorkgroupSize) + CASE(GlobalOffset) + CASE(GlobalLinearId) + CASE(SubgroupSize) + CASE(SubgroupMaxSize) + CASE(NumSubgroups) + CASE(NumEnqueuedSubgroups) + CASE(SubgroupId) + CASE(SubgroupLocalInvocationId) + GLCASE(VertexIndex) + GLCASE(InstanceIndex) + CASE(SubgroupEqMaskKHR) + CASE(SubgroupGeMaskKHR) + CASE(SubgroupGtMaskKHR) + CASE(SubgroupLeMaskKHR) + CASE(SubgroupLtMaskKHR) + default: + break; + } +#undef GLCASE +#undef GLCASE2 +#undef CASE +} + +spv_result_t FriendlyNameMapper::ParseInstruction( + const spv_parsed_instruction_t& inst) { + const auto result_id = inst.result_id; + switch (inst.opcode) { + case SpvOpName: + SaveName(inst.words[1], reinterpret_cast(inst.words + 2)); + break; + case SpvOpDecorate: + // Decorations come after OpName. So OpName will take precedence over + // decorations. + // + // In theory, we should also handle OpGroupDecorate. But that's unlikely + // to occur. + if (inst.words[2] == SpvDecorationBuiltIn) { + assert(inst.num_words > 3); + SaveBuiltInName(inst.words[1], inst.words[3]); + } + break; + case SpvOpTypeVoid: + SaveName(result_id, "void"); + break; + case SpvOpTypeBool: + SaveName(result_id, "bool"); + break; + case SpvOpTypeInt: { + std::string signedness; + std::string root; + const auto bit_width = inst.words[2]; + switch (bit_width) { + case 8: + root = "char"; + break; + case 16: + root = "short"; + break; + case 32: + root = "int"; + break; + case 64: + root = "long"; + break; + default: + root = to_string(bit_width); + signedness = "i"; + break; + } + if (0 == inst.words[3]) signedness = "u"; + SaveName(result_id, signedness + root); + } break; + case SpvOpTypeFloat: { + const auto bit_width = inst.words[2]; + switch (bit_width) { + case 16: + SaveName(result_id, "half"); + break; + case 32: + SaveName(result_id, "float"); + break; + case 64: + SaveName(result_id, "double"); + break; + default: + SaveName(result_id, std::string("fp") + to_string(bit_width)); + break; + } + } break; + case SpvOpTypeVector: + SaveName(result_id, std::string("v") + to_string(inst.words[3]) + + NameForId(inst.words[2])); + break; + case SpvOpTypeMatrix: + SaveName(result_id, std::string("mat") + to_string(inst.words[3]) + + NameForId(inst.words[2])); + break; + case SpvOpTypeArray: + SaveName(result_id, std::string("_arr_") + NameForId(inst.words[2]) + + "_" + NameForId(inst.words[3])); + break; + case SpvOpTypeRuntimeArray: + SaveName(result_id, + std::string("_runtimearr_") + NameForId(inst.words[2])); + break; + case SpvOpTypePointer: + SaveName(result_id, std::string("_ptr_") + + NameForEnumOperand(SPV_OPERAND_TYPE_STORAGE_CLASS, + inst.words[2]) + + "_" + NameForId(inst.words[3])); + break; + case SpvOpTypePipe: + SaveName(result_id, + std::string("Pipe") + + NameForEnumOperand(SPV_OPERAND_TYPE_ACCESS_QUALIFIER, + inst.words[2])); + break; + case SpvOpTypeEvent: + SaveName(result_id, "Event"); + break; + case SpvOpTypeDeviceEvent: + SaveName(result_id, "DeviceEvent"); + break; + case SpvOpTypeReserveId: + SaveName(result_id, "ReserveId"); + break; + case SpvOpTypeQueue: + SaveName(result_id, "Queue"); + break; + case SpvOpTypeOpaque: + SaveName(result_id, + std::string("Opaque_") + + Sanitize(reinterpret_cast(inst.words + 2))); + break; + case SpvOpTypePipeStorage: + SaveName(result_id, "PipeStorage"); + break; + case SpvOpTypeNamedBarrier: + SaveName(result_id, "NamedBarrier"); + break; + case SpvOpTypeStruct: + // Structs are mapped rather simplisitically. Just indicate that they + // are a struct and then give the raw Id number. + SaveName(result_id, std::string("_struct_") + to_string(result_id)); + break; + case SpvOpConstantTrue: + SaveName(result_id, "true"); + break; + case SpvOpConstantFalse: + SaveName(result_id, "false"); + break; + case SpvOpConstant: { + std::ostringstream value; + EmitNumericLiteral(&value, inst, inst.operands[2]); + auto value_str = value.str(); + // Use 'n' to signify negative. Other invalid characters will be mapped + // to underscore. + for (auto& c : value_str) + if (c == '-') c = 'n'; + SaveName(result_id, NameForId(inst.type_id) + "_" + value_str); + } break; + default: + // If this instruction otherwise defines an Id, then save a mapping for + // it. This is needed to ensure uniqueness in there is an OpName with + // string something like "1" that might collide with this result_id. + // We should only do this if a name hasn't already been registered by some + // previous forward reference. + if (result_id && name_for_id_.find(result_id) == name_for_id_.end()) + SaveName(result_id, to_string(result_id)); + break; + } + return SPV_SUCCESS; +} + +std::string FriendlyNameMapper::NameForEnumOperand(spv_operand_type_t type, + uint32_t word) { + spv_operand_desc desc = nullptr; + if (SPV_SUCCESS == grammar_.lookupOperand(type, word, &desc)) { + return desc->name; + } else { + // Invalid input. Just give something sane. + return std::string("StorageClass") + to_string(word); + } +} + +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/name_mapper.h b/3rdparty/spirv-tools/source/name_mapper.h new file mode 100644 index 000000000..8afac4241 --- /dev/null +++ b/3rdparty/spirv-tools/source/name_mapper.h @@ -0,0 +1,122 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_NAME_MAPPER_H_ +#define LIBSPIRV_NAME_MAPPER_H_ + +#include +#include +#include +#include + +#include "assembly_grammar.h" +#include "spirv-tools/libspirv.h" + +namespace libspirv { + +// A NameMapper maps SPIR-V Id values to names. Each name is valid to use in +// SPIR-V assembly. The mapping is one-to-one, i.e. no two Ids map to the same +// name. +using NameMapper = std::function; + +// Returns a NameMapper which always maps an Id to its decimal representation. +NameMapper GetTrivialNameMapper(); + +// A FriendlyNameMapper parses a module upon construction. If the parse is +// successful, then the NameForId method maps an Id to a friendly name +// while also satisfying the constraints on a NameMapper. +// +// The mapping is friendly in the following sense: +// - If an Id has a debug name (via OpName), then that will be used when +// possible. +// - Well known scalar types map to friendly names. For example, +// OpTypeVoid should be %void. Scalar types map to their names in OpenCL +// when +// there is a correspondence, and otherwise as follows: +// - unsigned integer type of n bits map to "u" followed by n +// - signed integer type of n bits map to "i" followed by n +// - floating point type of n bits map to "fp" followed by n +// - Vector type names map to "v" followed by the number of components, +// followed by the friendly name for the base type. +// - Matrix type names map to "mat" followed by the number of columns, +// followed by the friendly name for the base vector type. +// - Pointer types map to "_ptr_", then the name of the storage class, then the +// name for the pointee type. +// - Exotic types like event, pipe, opaque, queue, reserve-id map to their own +// human readable names. +// - A struct type maps to "_struct_" followed by the raw Id number. That's +// pretty simplistic, but workable. +// - A built-in variable maps to its GLSL variable name. +// - Numeric literals in OpConstant map to a human-friendly name. +class FriendlyNameMapper { + public: + // Construct a friendly name mapper, and determine friendly names for each + // defined Id in the specified module. The module is specified by the code + // wordCount, and should be parseable in the specified context. + FriendlyNameMapper(const spv_const_context context, const uint32_t* code, + const size_t wordCount); + + // Returns a NameMapper which maps ids to the friendly names parsed from the + // module provided to the constructor. + NameMapper GetNameMapper() { + return [this](uint32_t id) { return this->NameForId(id); }; + } + + // Returns the friendly name for the given id. If the module parsed during + // construction is valid, then the mapping satisfies the rules for a + // NameMapper. + std::string NameForId(uint32_t id); + + private: + // Transforms the given string so that it is acceptable as an Id name in + // assembly language. Two distinct inputs can map to the same output. + std::string Sanitize(const std::string& suggested_name); + + // Records a name for the given id. If this id already has a name, then + // this is a no-op. If the id doesn't have a name, use the given + // suggested_name if it hasn't already been taken, and otherwise generate + // a new (unused) name based on the suggested name. + void SaveName(uint32_t id, const std::string& suggested_name); + + // Records a built-in variable name for target_id. If target_id already + // has a name then this is a no-op. + void SaveBuiltInName(uint32_t target_id, uint32_t built_in); + + // Collects information from the given parsed instruction to populate + // name_for_id_. Returns SPV_SUCCESS; + spv_result_t ParseInstruction(const spv_parsed_instruction_t& inst); + + // Forwards a parsed-instruction callback from the binary parser into the + // FriendlyNameMapper hidden inside the user_data parameter. + static spv_result_t ParseInstructionForwarder( + void* user_data, const spv_parsed_instruction_t* parsed_instruction) { + return reinterpret_cast(user_data)->ParseInstruction( + *parsed_instruction); + } + + // Returns the friendly name for an enumerant. + std::string NameForEnumOperand(spv_operand_type_t type, uint32_t word); + + // Maps an id to its friendly name. This will have an entry for each Id + // defined in the module. + std::unordered_map name_for_id_; + // The set of names that have a mapping in name_for_id_; + std::unordered_set used_names_; + // The assembly grammar for the current context. + const libspirv::AssemblyGrammar grammar_; +}; + +} // namespace libspirv + +#endif // _LIBSPIRV_NAME_MAPPER_H_ diff --git a/3rdparty/spirv-tools/source/opcode.cpp b/3rdparty/spirv-tools/source/opcode.cpp new file mode 100644 index 000000000..c73f14d3a --- /dev/null +++ b/3rdparty/spirv-tools/source/opcode.cpp @@ -0,0 +1,456 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "opcode.h" + +#include +#include + +#include +#include + +#include "instruction.h" +#include "macro.h" +#include "spirv-tools/libspirv.h" +#include "spirv_constant.h" +#include "spirv_endian.h" +#include "spirv_target_env.h" + +namespace { +struct OpcodeDescPtrLen { + const spv_opcode_desc_t* ptr; + uint32_t len; +}; + +#include "core.insts-unified1.inc" // defines kOpcodeTableEntries_1_3 + +static const spv_opcode_table_t kOpcodeTable = {ARRAY_SIZE(kOpcodeTableEntries), + kOpcodeTableEntries}; + +// Represents a vendor tool entry in the SPIR-V XML Regsitry. +struct VendorTool { + uint32_t value; + const char* vendor; + const char* tool; // Might be empty string. + const char* vendor_tool; // Combiantion of vendor and tool. +}; + +const VendorTool vendor_tools[] = { +#include "generators.inc" +}; + +} // anonymous namespace + +// TODO(dneto): Move this to another file. It doesn't belong with opcode +// processing. +const char* spvGeneratorStr(uint32_t generator) { + auto where = std::find_if( + std::begin(vendor_tools), std::end(vendor_tools), + [generator](const VendorTool& vt) { return generator == vt.value; }); + if (where != std::end(vendor_tools)) return where->vendor_tool; + return "Unknown"; +} + +uint32_t spvOpcodeMake(uint16_t wordCount, SpvOp opcode) { + return ((uint32_t)opcode) | (((uint32_t)wordCount) << 16); +} + +void spvOpcodeSplit(const uint32_t word, uint16_t* pWordCount, + uint16_t* pOpcode) { + if (pWordCount) { + *pWordCount = (uint16_t)((0xffff0000 & word) >> 16); + } + if (pOpcode) { + *pOpcode = 0x0000ffff & word; + } +} + +spv_result_t spvOpcodeTableGet(spv_opcode_table* pInstTable, spv_target_env) { + if (!pInstTable) return SPV_ERROR_INVALID_POINTER; + + // Descriptions of each opcode. Each entry describes the format of the + // instruction that follows a particular opcode. + + *pInstTable = &kOpcodeTable; + return SPV_SUCCESS; +} + +spv_result_t spvOpcodeTableNameLookup(spv_target_env env, + const spv_opcode_table table, + const char* name, + spv_opcode_desc* pEntry) { + if (!name || !pEntry) return SPV_ERROR_INVALID_POINTER; + if (!table) return SPV_ERROR_INVALID_TABLE; + + // TODO: This lookup of the Opcode table is suboptimal! Binary sort would be + // preferable but the table requires sorting on the Opcode name, but it's + // static const initialized and matches the order of the spec. + const size_t nameLength = strlen(name); + for (uint64_t opcodeIndex = 0; opcodeIndex < table->count; ++opcodeIndex) { + const spv_opcode_desc_t& entry = table->entries[opcodeIndex]; + // We considers the current opcode as available as long as + // 1. The target environment satisfies the minimal requirement of the + // opcode; or + // 2. There is at least one extension enabling this opcode. + // + // Note that the second rule assumes the extension enabling this instruction + // is indeed requested in the SPIR-V code; checking that should be + // validator's work. + if ((spvVersionForTargetEnv(env) >= entry.minVersion || + entry.numExtensions > 0u) && + nameLength == strlen(entry.name) && + !strncmp(name, entry.name, nameLength)) { + // NOTE: Found out Opcode! + *pEntry = &entry; + return SPV_SUCCESS; + } + } + + return SPV_ERROR_INVALID_LOOKUP; +} + +spv_result_t spvOpcodeTableValueLookup(spv_target_env env, + const spv_opcode_table table, + const SpvOp opcode, + spv_opcode_desc* pEntry) { + if (!table) return SPV_ERROR_INVALID_TABLE; + if (!pEntry) return SPV_ERROR_INVALID_POINTER; + + const auto beg = table->entries; + const auto end = table->entries + table->count; + + spv_opcode_desc_t needle = {"", opcode, 0, nullptr, 0, {}, + false, false, 0, nullptr, ~0u}; + + auto comp = [](const spv_opcode_desc_t& lhs, const spv_opcode_desc_t& rhs) { + return lhs.opcode < rhs.opcode; + }; + + // We need to loop here because there can exist multiple symbols for the same + // opcode value, and they can be introduced in different target environments, + // which means they can have different minimal version requirements. + // Assumes the underlying table is already sorted ascendingly according to + // opcode value. + for (auto it = std::lower_bound(beg, end, needle, comp); + it != end && it->opcode == opcode; ++it) { + // We considers the current opcode as available as long as + // 1. The target environment satisfies the minimal requirement of the + // opcode; or + // 2. There is at least one extension enabling this opcode. + // + // Note that the second rule assumes the extension enabling this instruction + // is indeed requested in the SPIR-V code; checking that should be + // validator's work. + if (spvVersionForTargetEnv(env) >= it->minVersion || + it->numExtensions > 0u) { + *pEntry = it; + return SPV_SUCCESS; + } + } + + return SPV_ERROR_INVALID_LOOKUP; +} + +void spvInstructionCopy(const uint32_t* words, const SpvOp opcode, + const uint16_t wordCount, const spv_endianness_t endian, + spv_instruction_t* pInst) { + pInst->opcode = opcode; + pInst->words.resize(wordCount); + for (uint16_t wordIndex = 0; wordIndex < wordCount; ++wordIndex) { + pInst->words[wordIndex] = spvFixWord(words[wordIndex], endian); + if (!wordIndex) { + uint16_t thisWordCount; + uint16_t thisOpcode; + spvOpcodeSplit(pInst->words[wordIndex], &thisWordCount, &thisOpcode); + assert(opcode == static_cast(thisOpcode) && + wordCount == thisWordCount && "Endianness failed!"); + } + } +} + +const char* spvOpcodeString(const SpvOp opcode) { + const auto beg = kOpcodeTableEntries; + const auto end = kOpcodeTableEntries + ARRAY_SIZE(kOpcodeTableEntries); + spv_opcode_desc_t needle = {"", opcode, 0, nullptr, 0, {}, + false, false, 0, nullptr, ~0u}; + auto comp = [](const spv_opcode_desc_t& lhs, const spv_opcode_desc_t& rhs) { + return lhs.opcode < rhs.opcode; + }; + auto it = std::lower_bound(beg, end, needle, comp); + if (it != end && it->opcode == opcode) { + return it->name; + } + + assert(0 && "Unreachable!"); + return "unknown"; +} + +int32_t spvOpcodeIsScalarType(const SpvOp opcode) { + switch (opcode) { + case SpvOpTypeInt: + case SpvOpTypeFloat: + case SpvOpTypeBool: + return true; + default: + return false; + } +} + +int32_t spvOpcodeIsSpecConstant(const SpvOp opcode) { + switch (opcode) { + case SpvOpSpecConstantTrue: + case SpvOpSpecConstantFalse: + case SpvOpSpecConstant: + case SpvOpSpecConstantComposite: + case SpvOpSpecConstantOp: + return true; + default: + return false; + } +} + +int32_t spvOpcodeIsConstant(const SpvOp opcode) { + switch (opcode) { + case SpvOpConstantTrue: + case SpvOpConstantFalse: + case SpvOpConstant: + case SpvOpConstantComposite: + case SpvOpConstantSampler: + case SpvOpConstantNull: + case SpvOpSpecConstantTrue: + case SpvOpSpecConstantFalse: + case SpvOpSpecConstant: + case SpvOpSpecConstantComposite: + case SpvOpSpecConstantOp: + return true; + default: + return false; + } +} + +bool spvOpcodeIsConstantOrUndef(const SpvOp opcode) { + return opcode == SpvOpUndef || spvOpcodeIsConstant(opcode); +} + +bool spvOpcodeIsScalarSpecConstant(const SpvOp opcode) { + switch (opcode) { + case SpvOpSpecConstantTrue: + case SpvOpSpecConstantFalse: + case SpvOpSpecConstant: + return true; + default: + return false; + } +} + +int32_t spvOpcodeIsComposite(const SpvOp opcode) { + switch (opcode) { + case SpvOpTypeVector: + case SpvOpTypeMatrix: + case SpvOpTypeArray: + case SpvOpTypeStruct: + return true; + default: + return false; + } +} + +bool spvOpcodeReturnsLogicalVariablePointer(const SpvOp opcode) { + switch (opcode) { + case SpvOpVariable: + case SpvOpAccessChain: + case SpvOpInBoundsAccessChain: + case SpvOpFunctionParameter: + case SpvOpImageTexelPointer: + case SpvOpCopyObject: + case SpvOpSelect: + case SpvOpPhi: + case SpvOpFunctionCall: + case SpvOpPtrAccessChain: + case SpvOpLoad: + case SpvOpConstantNull: + return true; + default: + return false; + } +} + +int32_t spvOpcodeReturnsLogicalPointer(const SpvOp opcode) { + switch (opcode) { + case SpvOpVariable: + case SpvOpAccessChain: + case SpvOpInBoundsAccessChain: + case SpvOpFunctionParameter: + case SpvOpImageTexelPointer: + case SpvOpCopyObject: + return true; + default: + return false; + } +} + +int32_t spvOpcodeGeneratesType(SpvOp op) { + switch (op) { + case SpvOpTypeVoid: + case SpvOpTypeBool: + case SpvOpTypeInt: + case SpvOpTypeFloat: + case SpvOpTypeVector: + case SpvOpTypeMatrix: + case SpvOpTypeImage: + case SpvOpTypeSampler: + case SpvOpTypeSampledImage: + case SpvOpTypeArray: + case SpvOpTypeRuntimeArray: + case SpvOpTypeStruct: + case SpvOpTypeOpaque: + case SpvOpTypePointer: + case SpvOpTypeFunction: + case SpvOpTypeEvent: + case SpvOpTypeDeviceEvent: + case SpvOpTypeReserveId: + case SpvOpTypeQueue: + case SpvOpTypePipe: + case SpvOpTypePipeStorage: + case SpvOpTypeNamedBarrier: + return true; + default: + // In particular, OpTypeForwardPointer does not generate a type, + // but declares a storage class for a pointer type generated + // by a different instruction. + break; + } + return 0; +} + +bool spvOpcodeIsDecoration(const SpvOp opcode) { + switch (opcode) { + case SpvOpDecorate: + case SpvOpDecorateId: + case SpvOpMemberDecorate: + case SpvOpGroupDecorate: + case SpvOpGroupMemberDecorate: + case SpvOpDecorateStringGOOGLE: + case SpvOpMemberDecorateStringGOOGLE: + return true; + default: + break; + } + return false; +} + +bool spvOpcodeIsLoad(const SpvOp opcode) { + switch (opcode) { + case SpvOpLoad: + case SpvOpImageSampleExplicitLod: + case SpvOpImageSampleImplicitLod: + case SpvOpImageSampleDrefImplicitLod: + case SpvOpImageSampleDrefExplicitLod: + case SpvOpImageSampleProjImplicitLod: + case SpvOpImageSampleProjExplicitLod: + case SpvOpImageSampleProjDrefImplicitLod: + case SpvOpImageSampleProjDrefExplicitLod: + case SpvOpImageFetch: + case SpvOpImageGather: + case SpvOpImageDrefGather: + case SpvOpImageRead: + case SpvOpImageSparseSampleImplicitLod: + case SpvOpImageSparseSampleExplicitLod: + case SpvOpImageSparseSampleDrefExplicitLod: + case SpvOpImageSparseSampleDrefImplicitLod: + case SpvOpImageSparseFetch: + case SpvOpImageSparseGather: + case SpvOpImageSparseDrefGather: + case SpvOpImageSparseRead: + return true; + default: + return false; + } +} + +bool spvOpcodeIsBranch(SpvOp opcode) { + switch (opcode) { + case SpvOpBranch: + case SpvOpBranchConditional: + case SpvOpSwitch: + return true; + default: + return false; + } +} + +bool spvOpcodeIsAtomicOp(const SpvOp opcode) { + switch (opcode) { + case SpvOpAtomicLoad: + case SpvOpAtomicStore: + case SpvOpAtomicExchange: + case SpvOpAtomicCompareExchange: + case SpvOpAtomicCompareExchangeWeak: + case SpvOpAtomicIIncrement: + case SpvOpAtomicIDecrement: + case SpvOpAtomicIAdd: + case SpvOpAtomicISub: + case SpvOpAtomicSMin: + case SpvOpAtomicUMin: + case SpvOpAtomicSMax: + case SpvOpAtomicUMax: + case SpvOpAtomicAnd: + case SpvOpAtomicOr: + case SpvOpAtomicXor: + case SpvOpAtomicFlagTestAndSet: + case SpvOpAtomicFlagClear: + return true; + default: + return false; + } +} + +bool spvOpcodeIsReturn(SpvOp opcode) { + switch (opcode) { + case SpvOpReturn: + case SpvOpReturnValue: + return true; + default: + return false; + } +} + +bool spvOpcodeIsReturnOrAbort(SpvOp opcode) { + return spvOpcodeIsReturn(opcode) || opcode == SpvOpKill || + opcode == SpvOpUnreachable; +} + +bool spvOpcodeIsBlockTerminator(SpvOp opcode) { + return spvOpcodeIsBranch(opcode) || spvOpcodeIsReturnOrAbort(opcode); +} + +bool spvOpcodeIsBaseOpaqueType(SpvOp opcode) { + switch (opcode) { + case SpvOpTypeImage: + case SpvOpTypeSampler: + case SpvOpTypeSampledImage: + case SpvOpTypeOpaque: + case SpvOpTypeEvent: + case SpvOpTypeDeviceEvent: + case SpvOpTypeReserveId: + case SpvOpTypeQueue: + case SpvOpTypePipe: + case SpvOpTypeForwardPointer: + case SpvOpTypePipeStorage: + case SpvOpTypeNamedBarrier: + return true; + default: + return false; + } +} diff --git a/3rdparty/spirv-tools/source/opcode.h b/3rdparty/spirv-tools/source/opcode.h new file mode 100644 index 000000000..9b585137e --- /dev/null +++ b/3rdparty/spirv-tools/source/opcode.h @@ -0,0 +1,121 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPCODE_H_ +#define LIBSPIRV_OPCODE_H_ + +#include "instruction.h" +#include "latest_version_spirv_header.h" +#include "spirv-tools/libspirv.h" +#include "table.h" + +// Returns the name of a registered SPIR-V generator as a null-terminated +// string. If the generator is not known, then returns the string "Unknown". +// The generator parameter should be most significant 16-bits of the generator +// word in the SPIR-V module header. +// +// See the registry at https://www.khronos.org/registry/spir-v/api/spir-v.xml. +const char* spvGeneratorStr(uint32_t generator); + +// Combines word_count and opcode enumerant in single word. +uint32_t spvOpcodeMake(uint16_t word_count, SpvOp opcode); + +// Splits word into into two constituent parts: word_count and opcode. +void spvOpcodeSplit(const uint32_t word, uint16_t* word_count, + uint16_t* opcode); + +// Finds the named opcode in the given opcode table. On success, returns +// SPV_SUCCESS and writes a handle of the table entry into *entry. +spv_result_t spvOpcodeTableNameLookup(spv_target_env, + const spv_opcode_table table, + const char* name, spv_opcode_desc* entry); + +// Finds the opcode by enumerant in the given opcode table. On success, returns +// SPV_SUCCESS and writes a handle of the table entry into *entry. +spv_result_t spvOpcodeTableValueLookup(spv_target_env, + const spv_opcode_table table, + const SpvOp opcode, + spv_opcode_desc* entry); + +// Copies an instruction's word and fixes the endianness to host native. The +// source instruction's stream/opcode/endianness is in the words/opcode/endian +// parameter. The word_count parameter specifies the number of words to copy. +// Writes copied instruction into *inst. +void spvInstructionCopy(const uint32_t* words, const SpvOp opcode, + const uint16_t word_count, + const spv_endianness_t endian, spv_instruction_t* inst); + +// Gets the name of an instruction, without the "Op" prefix. +const char* spvOpcodeString(const SpvOp opcode); + +// Determine if the given opcode is a scalar type. Returns zero if false, +// non-zero otherwise. +int32_t spvOpcodeIsScalarType(const SpvOp opcode); + +// Determines if the given opcode is a specialization constant. Returns zero if +// false, non-zero otherwise. +int32_t spvOpcodeIsSpecConstant(const SpvOp opcode); + +// Determines if the given opcode is a constant. Returns zero if false, non-zero +// otherwise. +int32_t spvOpcodeIsConstant(const SpvOp opcode); + +// Returns true if the given opcode is a constant or undef. +bool spvOpcodeIsConstantOrUndef(const SpvOp opcode); + +// Returns true if the given opcode is a scalar specialization constant. +bool spvOpcodeIsScalarSpecConstant(const SpvOp opcode); + +// Determines if the given opcode is a composite type. Returns zero if false, +// non-zero otherwise. +int32_t spvOpcodeIsComposite(const SpvOp opcode); + +// Determines if the given opcode results in a pointer when using the logical +// addressing model. Returns zero if false, non-zero otherwise. +int32_t spvOpcodeReturnsLogicalPointer(const SpvOp opcode); + +// Returns whether the given opcode could result in a pointer or a variable +// pointer when using the logical addressing model. +bool spvOpcodeReturnsLogicalVariablePointer(const SpvOp opcode); + +// Determines if the given opcode generates a type. Returns zero if false, +// non-zero otherwise. +int32_t spvOpcodeGeneratesType(SpvOp opcode); + +// Returns true if the opcode adds a decoration to an id. +bool spvOpcodeIsDecoration(const SpvOp opcode); + +// Returns true if the opcode is a load from memory into a result id. This +// function only considers core instructions. +bool spvOpcodeIsLoad(const SpvOp opcode); + +// Returns true if the opcode is an atomic operation. +bool spvOpcodeIsAtomicOp(const SpvOp opcode); + +// Returns true if the given opcode is a branch instruction. +bool spvOpcodeIsBranch(SpvOp opcode); + +// Returns true if the given opcode is a return instruction. +bool spvOpcodeIsReturn(SpvOp opcode); + +// Returns true if the given opcode is a return instruction or it aborts +// execution. +bool spvOpcodeIsReturnOrAbort(SpvOp opcode); + +// Returns true if the given opcode is a basic block terminator. +bool spvOpcodeIsBlockTerminator(SpvOp opcode); + +// Returns true if the given opcode always defines an opaque type. +bool spvOpcodeIsBaseOpaqueType(SpvOp opcode); +#endif // LIBSPIRV_OPCODE_H_ diff --git a/3rdparty/spirv-tools/source/operand.cpp b/3rdparty/spirv-tools/source/operand.cpp new file mode 100644 index 000000000..4a2a2d6c0 --- /dev/null +++ b/3rdparty/spirv-tools/source/operand.cpp @@ -0,0 +1,470 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "operand.h" + +#include +#include +#include + +#include "macro.h" +#include "spirv_constant.h" +#include "spirv_target_env.h" + +// For now, assume unified1 contains up to SPIR-V 1.3 and no later +// SPIR-V version. +// TODO(dneto): Make one set of tables, but with version tags on a +// per-item basis. https://github.com/KhronosGroup/SPIRV-Tools/issues/1195 + +#include "operand.kinds-unified1.inc" + +static const spv_operand_table_t kOperandTable = { + ARRAY_SIZE(pygen_variable_OperandInfoTable), + pygen_variable_OperandInfoTable}; + +spv_result_t spvOperandTableGet(spv_operand_table* pOperandTable, + spv_target_env) { + if (!pOperandTable) return SPV_ERROR_INVALID_POINTER; + + *pOperandTable = &kOperandTable; + return SPV_SUCCESS; +} + +spv_result_t spvOperandTableNameLookup(spv_target_env env, + const spv_operand_table table, + const spv_operand_type_t type, + const char* name, + const size_t nameLength, + spv_operand_desc* pEntry) { + if (!table) return SPV_ERROR_INVALID_TABLE; + if (!name || !pEntry) return SPV_ERROR_INVALID_POINTER; + + for (uint64_t typeIndex = 0; typeIndex < table->count; ++typeIndex) { + const auto& group = table->types[typeIndex]; + if (type != group.type) continue; + for (uint64_t index = 0; index < group.count; ++index) { + const auto& entry = group.entries[index]; + // We considers the current operand as available as long as + // 1. The target environment satisfies the minimal requirement of the + // operand; or + // 2. There is at least one extension enabling this operand. + // + // Note that the second rule assumes the extension enabling this operand + // is indeed requested in the SPIR-V code; checking that should be + // validator's work. + if ((spvVersionForTargetEnv(env) >= entry.minVersion || + entry.numExtensions > 0u) && + nameLength == strlen(entry.name) && + !strncmp(entry.name, name, nameLength)) { + *pEntry = &entry; + return SPV_SUCCESS; + } + } + } + + return SPV_ERROR_INVALID_LOOKUP; +} + +spv_result_t spvOperandTableValueLookup(spv_target_env env, + const spv_operand_table table, + const spv_operand_type_t type, + const uint32_t value, + spv_operand_desc* pEntry) { + if (!table) return SPV_ERROR_INVALID_TABLE; + if (!pEntry) return SPV_ERROR_INVALID_POINTER; + + spv_operand_desc_t needle = {"", value, 0, nullptr, 0, nullptr, {}, ~0u}; + + auto comp = [](const spv_operand_desc_t& lhs, const spv_operand_desc_t& rhs) { + return lhs.value < rhs.value; + }; + + for (uint64_t typeIndex = 0; typeIndex < table->count; ++typeIndex) { + const auto& group = table->types[typeIndex]; + if (type != group.type) continue; + + const auto beg = group.entries; + const auto end = group.entries + group.count; + + // We need to loop here because there can exist multiple symbols for the + // same operand value, and they can be introduced in different target + // environments, which means they can have different minimal version + // requirements. For example, SubgroupEqMaskKHR can exist in any SPIR-V + // version as long as the SPV_KHR_shader_ballot extension is there; but + // starting from SPIR-V 1.3, SubgroupEqMask, which has the same numeric + // value as SubgroupEqMaskKHR, is available in core SPIR-V without extension + // requirements. + // Assumes the underlying table is already sorted ascendingly according to + // opcode value. + for (auto it = std::lower_bound(beg, end, needle, comp); + it != end && it->value == value; ++it) { + // We considers the current operand as available as long as + // 1. The target environment satisfies the minimal requirement of the + // operand; or + // 2. There is at least one extension enabling this operand. + // + // Note that the second rule assumes the extension enabling this operand + // is indeed requested in the SPIR-V code; checking that should be + // validator's work. + if (spvVersionForTargetEnv(env) >= it->minVersion || + it->numExtensions > 0u) { + *pEntry = it; + return SPV_SUCCESS; + } + } + } + + return SPV_ERROR_INVALID_LOOKUP; +} + +const char* spvOperandTypeStr(spv_operand_type_t type) { + switch (type) { + case SPV_OPERAND_TYPE_ID: + case SPV_OPERAND_TYPE_OPTIONAL_ID: + return "ID"; + case SPV_OPERAND_TYPE_TYPE_ID: + return "type ID"; + case SPV_OPERAND_TYPE_RESULT_ID: + return "result ID"; + case SPV_OPERAND_TYPE_LITERAL_INTEGER: + case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_INTEGER: + case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_NUMBER: + return "literal number"; + case SPV_OPERAND_TYPE_OPTIONAL_TYPED_LITERAL_INTEGER: + return "possibly multi-word literal integer"; + case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER: + return "possibly multi-word literal number"; + case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER: + return "extension instruction number"; + case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: + return "OpSpecConstantOp opcode"; + case SPV_OPERAND_TYPE_LITERAL_STRING: + case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_STRING: + return "literal string"; + case SPV_OPERAND_TYPE_SOURCE_LANGUAGE: + return "source language"; + case SPV_OPERAND_TYPE_EXECUTION_MODEL: + return "execution model"; + case SPV_OPERAND_TYPE_ADDRESSING_MODEL: + return "addressing model"; + case SPV_OPERAND_TYPE_MEMORY_MODEL: + return "memory model"; + case SPV_OPERAND_TYPE_EXECUTION_MODE: + return "execution mode"; + case SPV_OPERAND_TYPE_STORAGE_CLASS: + return "storage class"; + case SPV_OPERAND_TYPE_DIMENSIONALITY: + return "dimensionality"; + case SPV_OPERAND_TYPE_SAMPLER_ADDRESSING_MODE: + return "sampler addressing mode"; + case SPV_OPERAND_TYPE_SAMPLER_FILTER_MODE: + return "sampler filter mode"; + case SPV_OPERAND_TYPE_SAMPLER_IMAGE_FORMAT: + return "image format"; + case SPV_OPERAND_TYPE_FP_FAST_MATH_MODE: + return "floating-point fast math mode"; + case SPV_OPERAND_TYPE_FP_ROUNDING_MODE: + return "floating-point rounding mode"; + case SPV_OPERAND_TYPE_LINKAGE_TYPE: + return "linkage type"; + case SPV_OPERAND_TYPE_ACCESS_QUALIFIER: + case SPV_OPERAND_TYPE_OPTIONAL_ACCESS_QUALIFIER: + return "access qualifier"; + case SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE: + return "function parameter attribute"; + case SPV_OPERAND_TYPE_DECORATION: + return "decoration"; + case SPV_OPERAND_TYPE_BUILT_IN: + return "built-in"; + case SPV_OPERAND_TYPE_SELECTION_CONTROL: + return "selection control"; + case SPV_OPERAND_TYPE_LOOP_CONTROL: + return "loop control"; + case SPV_OPERAND_TYPE_FUNCTION_CONTROL: + return "function control"; + case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID: + return "memory semantics ID"; + case SPV_OPERAND_TYPE_MEMORY_ACCESS: + case SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS: + return "memory access"; + case SPV_OPERAND_TYPE_SCOPE_ID: + return "scope ID"; + case SPV_OPERAND_TYPE_GROUP_OPERATION: + return "group operation"; + case SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS: + return "kernel enqeue flags"; + case SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO: + return "kernel profiling info"; + case SPV_OPERAND_TYPE_CAPABILITY: + return "capability"; + case SPV_OPERAND_TYPE_IMAGE: + case SPV_OPERAND_TYPE_OPTIONAL_IMAGE: + return "image"; + case SPV_OPERAND_TYPE_OPTIONAL_CIV: + return "context-insensitive value"; + case SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS: + return "debug info flags"; + case SPV_OPERAND_TYPE_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING: + return "debug base type encoding"; + case SPV_OPERAND_TYPE_DEBUG_COMPOSITE_TYPE: + return "debug composite type"; + case SPV_OPERAND_TYPE_DEBUG_TYPE_QUALIFIER: + return "debug type qualifier"; + case SPV_OPERAND_TYPE_DEBUG_OPERATION: + return "debug operation"; + + // The next values are for values returned from an instruction, not actually + // an operand. So the specific strings don't matter. But let's add them + // for completeness and ease of testing. + case SPV_OPERAND_TYPE_IMAGE_CHANNEL_ORDER: + return "image channel order"; + case SPV_OPERAND_TYPE_IMAGE_CHANNEL_DATA_TYPE: + return "image channel data type"; + + case SPV_OPERAND_TYPE_NONE: + return "NONE"; + default: + assert(0 && "Unhandled operand type!"); + break; + } + return "unknown"; +} + +void spvPushOperandTypes(const spv_operand_type_t* types, + spv_operand_pattern_t* pattern) { + const spv_operand_type_t* endTypes; + for (endTypes = types; *endTypes != SPV_OPERAND_TYPE_NONE; ++endTypes) + ; + while (endTypes-- != types) { + pattern->push_back(*endTypes); + } +} + +void spvPushOperandTypesForMask(spv_target_env env, + const spv_operand_table operandTable, + const spv_operand_type_t type, + const uint32_t mask, + spv_operand_pattern_t* pattern) { + // Scan from highest bits to lowest bits because we will append in LIFO + // fashion, and we need the operands for lower order bits to be consumed first + for (uint32_t candidate_bit = (1u << 31u); candidate_bit; + candidate_bit >>= 1) { + if (candidate_bit & mask) { + spv_operand_desc entry = nullptr; + if (SPV_SUCCESS == spvOperandTableValueLookup(env, operandTable, type, + candidate_bit, &entry)) { + spvPushOperandTypes(entry->operandTypes, pattern); + } + } + } +} + +bool spvOperandIsConcrete(spv_operand_type_t type) { + if (spvIsIdType(type) || spvOperandIsConcreteMask(type)) { + return true; + } + switch (type) { + case SPV_OPERAND_TYPE_LITERAL_INTEGER: + case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER: + case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: + case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER: + case SPV_OPERAND_TYPE_LITERAL_STRING: + case SPV_OPERAND_TYPE_SOURCE_LANGUAGE: + case SPV_OPERAND_TYPE_EXECUTION_MODEL: + case SPV_OPERAND_TYPE_ADDRESSING_MODEL: + case SPV_OPERAND_TYPE_MEMORY_MODEL: + case SPV_OPERAND_TYPE_EXECUTION_MODE: + case SPV_OPERAND_TYPE_STORAGE_CLASS: + case SPV_OPERAND_TYPE_DIMENSIONALITY: + case SPV_OPERAND_TYPE_SAMPLER_ADDRESSING_MODE: + case SPV_OPERAND_TYPE_SAMPLER_FILTER_MODE: + case SPV_OPERAND_TYPE_SAMPLER_IMAGE_FORMAT: + case SPV_OPERAND_TYPE_IMAGE_CHANNEL_ORDER: + case SPV_OPERAND_TYPE_IMAGE_CHANNEL_DATA_TYPE: + case SPV_OPERAND_TYPE_FP_ROUNDING_MODE: + case SPV_OPERAND_TYPE_LINKAGE_TYPE: + case SPV_OPERAND_TYPE_ACCESS_QUALIFIER: + case SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE: + case SPV_OPERAND_TYPE_DECORATION: + case SPV_OPERAND_TYPE_BUILT_IN: + case SPV_OPERAND_TYPE_GROUP_OPERATION: + case SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS: + case SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO: + case SPV_OPERAND_TYPE_CAPABILITY: + case SPV_OPERAND_TYPE_DEBUG_BASE_TYPE_ATTRIBUTE_ENCODING: + case SPV_OPERAND_TYPE_DEBUG_COMPOSITE_TYPE: + case SPV_OPERAND_TYPE_DEBUG_TYPE_QUALIFIER: + case SPV_OPERAND_TYPE_DEBUG_OPERATION: + return true; + default: + break; + } + return false; +} + +bool spvOperandIsConcreteMask(spv_operand_type_t type) { + switch (type) { + case SPV_OPERAND_TYPE_IMAGE: + case SPV_OPERAND_TYPE_FP_FAST_MATH_MODE: + case SPV_OPERAND_TYPE_SELECTION_CONTROL: + case SPV_OPERAND_TYPE_LOOP_CONTROL: + case SPV_OPERAND_TYPE_FUNCTION_CONTROL: + case SPV_OPERAND_TYPE_MEMORY_ACCESS: + case SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS: + return true; + default: + break; + } + return false; +} + +bool spvOperandIsOptional(spv_operand_type_t type) { + return SPV_OPERAND_TYPE_FIRST_OPTIONAL_TYPE <= type && + type <= SPV_OPERAND_TYPE_LAST_OPTIONAL_TYPE; +} + +bool spvOperandIsVariable(spv_operand_type_t type) { + return SPV_OPERAND_TYPE_FIRST_VARIABLE_TYPE <= type && + type <= SPV_OPERAND_TYPE_LAST_VARIABLE_TYPE; +} + +bool spvExpandOperandSequenceOnce(spv_operand_type_t type, + spv_operand_pattern_t* pattern) { + switch (type) { + case SPV_OPERAND_TYPE_VARIABLE_ID: + pattern->push_back(type); + pattern->push_back(SPV_OPERAND_TYPE_OPTIONAL_ID); + return true; + case SPV_OPERAND_TYPE_VARIABLE_LITERAL_INTEGER: + pattern->push_back(type); + pattern->push_back(SPV_OPERAND_TYPE_OPTIONAL_LITERAL_INTEGER); + return true; + case SPV_OPERAND_TYPE_VARIABLE_LITERAL_INTEGER_ID: + // Represents Zero or more (Literal number, Id) pairs, + // where the literal number must be a scalar integer. + pattern->push_back(type); + pattern->push_back(SPV_OPERAND_TYPE_ID); + pattern->push_back(SPV_OPERAND_TYPE_OPTIONAL_TYPED_LITERAL_INTEGER); + return true; + case SPV_OPERAND_TYPE_VARIABLE_ID_LITERAL_INTEGER: + // Represents Zero or more (Id, Literal number) pairs. + pattern->push_back(type); + pattern->push_back(SPV_OPERAND_TYPE_LITERAL_INTEGER); + pattern->push_back(SPV_OPERAND_TYPE_OPTIONAL_ID); + return true; + default: + break; + } + return false; +} + +spv_operand_type_t spvTakeFirstMatchableOperand( + spv_operand_pattern_t* pattern) { + assert(!pattern->empty()); + spv_operand_type_t result; + do { + result = pattern->back(); + pattern->pop_back(); + } while (spvExpandOperandSequenceOnce(result, pattern)); + return result; +} + +spv_operand_pattern_t spvAlternatePatternFollowingImmediate( + const spv_operand_pattern_t& pattern) { + auto it = + std::find(pattern.crbegin(), pattern.crend(), SPV_OPERAND_TYPE_RESULT_ID); + if (it != pattern.crend()) { + spv_operand_pattern_t alternatePattern(it - pattern.crbegin() + 2, + SPV_OPERAND_TYPE_OPTIONAL_CIV); + alternatePattern[1] = SPV_OPERAND_TYPE_RESULT_ID; + return alternatePattern; + } + + // No result-id found, so just expect CIVs. + return {SPV_OPERAND_TYPE_OPTIONAL_CIV}; +} + +bool spvIsIdType(spv_operand_type_t type) { + switch (type) { + case SPV_OPERAND_TYPE_ID: + case SPV_OPERAND_TYPE_TYPE_ID: + case SPV_OPERAND_TYPE_RESULT_ID: + case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID: + case SPV_OPERAND_TYPE_SCOPE_ID: + return true; + default: + return false; + } +} + +std::function spvOperandCanBeForwardDeclaredFunction( + SpvOp opcode) { + std::function out; + switch (opcode) { + case SpvOpExecutionMode: + case SpvOpEntryPoint: + case SpvOpName: + case SpvOpMemberName: + case SpvOpSelectionMerge: + case SpvOpDecorate: + case SpvOpMemberDecorate: + case SpvOpDecorateId: + case SpvOpDecorateStringGOOGLE: + case SpvOpMemberDecorateStringGOOGLE: + case SpvOpTypeStruct: + case SpvOpBranch: + case SpvOpLoopMerge: + out = [](unsigned) { return true; }; + break; + case SpvOpGroupDecorate: + case SpvOpGroupMemberDecorate: + case SpvOpBranchConditional: + case SpvOpSwitch: + out = [](unsigned index) { return index != 0; }; + break; + + case SpvOpFunctionCall: + // The Function parameter. + out = [](unsigned index) { return index == 2; }; + break; + + case SpvOpPhi: + out = [](unsigned index) { return index > 1; }; + break; + + case SpvOpEnqueueKernel: + // The Invoke parameter. + out = [](unsigned index) { return index == 8; }; + break; + + case SpvOpGetKernelNDrangeSubGroupCount: + case SpvOpGetKernelNDrangeMaxSubGroupSize: + // The Invoke parameter. + out = [](unsigned index) { return index == 3; }; + break; + + case SpvOpGetKernelWorkGroupSize: + case SpvOpGetKernelPreferredWorkGroupSizeMultiple: + // The Invoke parameter. + out = [](unsigned index) { return index == 2; }; + break; + case SpvOpTypeForwardPointer: + out = [](unsigned index) { return index == 0; }; + break; + default: + out = [](unsigned) { return false; }; + break; + } + return out; +} diff --git a/3rdparty/spirv-tools/source/operand.h b/3rdparty/spirv-tools/source/operand.h new file mode 100644 index 000000000..984a62328 --- /dev/null +++ b/3rdparty/spirv-tools/source/operand.h @@ -0,0 +1,141 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPERAND_H_ +#define LIBSPIRV_OPERAND_H_ + +#include +#include + +#include "spirv-tools/libspirv.h" +#include "table.h" + +// A sequence of operand types. +// +// A SPIR-V parser uses an operand pattern to describe what is expected +// next on the input. +// +// As we parse an instruction in text or binary form from left to right, +// we pop and push at the end of the pattern vector. Symbols later in the +// pattern vector are matched against the input before symbols earlier in the +// pattern vector are matched. + +// Using a vector in this way reduces memory traffic, which is good for +// performance. +using spv_operand_pattern_t = std::vector; + +// Finds the named operand in the table. The type parameter specifies the +// operand's group. A handle of the operand table entry for this operand will +// be written into *entry. +spv_result_t spvOperandTableNameLookup(spv_target_env, + const spv_operand_table table, + const spv_operand_type_t type, + const char* name, + const size_t name_length, + spv_operand_desc* entry); + +// Finds the operand with value in the table. The type parameter specifies the +// operand's group. A handle of the operand table entry for this operand will +// be written into *entry. +spv_result_t spvOperandTableValueLookup(spv_target_env, + const spv_operand_table table, + const spv_operand_type_t type, + const uint32_t value, + spv_operand_desc* entry); + +// Gets the name string of the non-variable operand type. +const char* spvOperandTypeStr(spv_operand_type_t type); + +// Returns true if the given type is concrete. +bool spvOperandIsConcrete(spv_operand_type_t type); + +// Returns true if the given type is concrete and also a mask. +bool spvOperandIsConcreteMask(spv_operand_type_t type); + +// Returns true if an operand of the given type is optional. +bool spvOperandIsOptional(spv_operand_type_t type); + +// Returns true if an operand type represents zero or more logical operands. +// +// Note that a single logical operand may still be a variable number of words. +// For example, a literal string may be many words, but is just one logical +// operand. +bool spvOperandIsVariable(spv_operand_type_t type); + +// Append a list of operand types to the end of the pattern vector. +// The types parameter specifies the source array of types, ending with +// SPV_OPERAND_TYPE_NONE. +void spvPushOperandTypes(const spv_operand_type_t* types, + spv_operand_pattern_t* pattern); + +// Appends the operands expected after the given typed mask onto the +// end of the given pattern. +// +// Each set bit in the mask represents zero or more operand types that should +// be appended onto the pattern. Operands for a less significant bit always +// appear after operands for a more significant bit. +// +// If a set bit is unknown, then we assume it has no operands. +void spvPushOperandTypesForMask(spv_target_env, + const spv_operand_table operand_table, + const spv_operand_type_t mask_type, + const uint32_t mask, + spv_operand_pattern_t* pattern); + +// Expands an operand type representing zero or more logical operands, +// exactly once. +// +// If the given type represents potentially several logical operands, +// then prepend the given pattern with the first expansion of the logical +// operands, followed by original type. Otherwise, don't modify the pattern. +// +// For example, the SPV_OPERAND_TYPE_VARIABLE_ID represents zero or more +// IDs. In that case we would prepend the pattern with SPV_OPERAND_TYPE_ID +// followed by SPV_OPERAND_TYPE_VARIABLE_ID again. +// +// This also applies to zero or more tuples of logical operands. In that case +// we prepend pattern with for the members of the tuple, followed by the +// original type argument. The pattern must encode the fact that if any part +// of the tuple is present, then all tuple members should be. So the first +// member of the tuple must be optional, and the remaining members +// non-optional. +// +// Returns true if we modified the pattern. +bool spvExpandOperandSequenceOnce(spv_operand_type_t type, + spv_operand_pattern_t* pattern); + +// Expands the first element in the pattern until it is a matchable operand +// type, then pops it off the front and returns it. The pattern must not be +// empty. +// +// A matchable operand type is anything other than a zero-or-more-items +// operand type. +spv_operand_type_t spvTakeFirstMatchableOperand(spv_operand_pattern_t* pattern); + +// Calculates the corresponding post-immediate alternate pattern, which allows +// a limited set of operand types. +spv_operand_pattern_t spvAlternatePatternFollowingImmediate( + const spv_operand_pattern_t& pattern); + +// Is the operand an ID? +bool spvIsIdType(spv_operand_type_t type); + +// Takes the opcode of an instruction and returns +// a function object that will return true if the index +// of the operand can be forward declared. This function will +// used in the SSA validation stage of the pipeline +std::function spvOperandCanBeForwardDeclaredFunction( + SpvOp opcode); + +#endif // LIBSPIRV_OPERAND_H_ diff --git a/3rdparty/spirv-tools/source/opt/CMakeLists.txt b/3rdparty/spirv-tools/source/opt/CMakeLists.txt new file mode 100644 index 000000000..60028096a --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/CMakeLists.txt @@ -0,0 +1,191 @@ +# Copyright (c) 2016 Google Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +add_library(SPIRV-Tools-opt + aggressive_dead_code_elim_pass.h + basic_block.h + block_merge_pass.h + build_module.h + ccp_pass.h + cfg_cleanup_pass.h + cfg.h + common_uniform_elim_pass.h + compact_ids_pass.h + composite.h + const_folding_rules.h + constants.h + copy_prop_arrays.h + dead_branch_elim_pass.h + dead_insert_elim_pass.h + dead_variable_elimination.h + decoration_manager.h + def_use_manager.h + dominator_analysis.h + dominator_tree.h + eliminate_dead_constant_pass.h + eliminate_dead_functions_pass.h + feature_manager.h + flatten_decoration_pass.h + fold.h + folding_rules.h + fold_spec_constant_op_and_composite_pass.h + freeze_spec_constant_value_pass.h + function.h + if_conversion.h + inline_exhaustive_pass.h + inline_opaque_pass.h + inline_pass.h + insert_extract_elim.h + instruction.h + instruction_list.h + ir_builder.h + ir_context.h + ir_loader.h + licm_pass.h + local_access_chain_convert_pass.h + local_redundancy_elimination.h + local_single_block_elim_pass.h + local_single_store_elim_pass.h + local_ssa_elim_pass.h + log.h + loop_descriptor.h + loop_peeling.h + loop_unroller.h + loop_utils.h + loop_unswitch_pass.h + make_unique.h + mem_pass.h + merge_return_pass.h + module.h + null_pass.h + passes.h + pass.h + pass_manager.h + private_to_local_pass.h + propagator.h + redundancy_elimination.h + reflect.h + remove_duplicates_pass.h + replace_invalid_opc.h + scalar_analysis.h + scalar_analysis_nodes.h + scalar_replacement_pass.h + set_spec_constant_default_value_pass.h + simplification_pass.h + ssa_rewrite_pass.h + strength_reduction_pass.h + strip_debug_info_pass.h + strip_reflect_info_pass.h + tree_iterator.h + type_manager.h + types.h + unify_const_pass.h + value_number_table.h + workaround1209.h + + aggressive_dead_code_elim_pass.cpp + basic_block.cpp + block_merge_pass.cpp + build_module.cpp + ccp_pass.cpp + cfg_cleanup_pass.cpp + cfg.cpp + common_uniform_elim_pass.cpp + compact_ids_pass.cpp + composite.cpp + const_folding_rules.cpp + constants.cpp + copy_prop_arrays.cpp + dead_branch_elim_pass.cpp + dead_insert_elim_pass.cpp + dead_variable_elimination.cpp + decoration_manager.cpp + def_use_manager.cpp + dominator_analysis.cpp + dominator_tree.cpp + eliminate_dead_constant_pass.cpp + eliminate_dead_functions_pass.cpp + feature_manager.cpp + flatten_decoration_pass.cpp + fold.cpp + folding_rules.cpp + fold_spec_constant_op_and_composite_pass.cpp + freeze_spec_constant_value_pass.cpp + function.cpp + if_conversion.cpp + inline_exhaustive_pass.cpp + inline_opaque_pass.cpp + inline_pass.cpp + insert_extract_elim.cpp + instruction.cpp + instruction_list.cpp + ir_context.cpp + ir_loader.cpp + licm_pass.cpp + local_access_chain_convert_pass.cpp + local_redundancy_elimination.cpp + local_single_block_elim_pass.cpp + local_single_store_elim_pass.cpp + local_ssa_elim_pass.cpp + loop_descriptor.cpp + loop_peeling.cpp + loop_utils.cpp + loop_unroller.cpp + loop_unswitch_pass.cpp + mem_pass.cpp + merge_return_pass.cpp + module.cpp + optimizer.cpp + pass.cpp + pass_manager.cpp + private_to_local_pass.cpp + propagator.cpp + redundancy_elimination.cpp + remove_duplicates_pass.cpp + replace_invalid_opc.cpp + scalar_analysis.cpp + scalar_analysis_simplification.cpp + scalar_replacement_pass.cpp + set_spec_constant_default_value_pass.cpp + simplification_pass.cpp + ssa_rewrite_pass.cpp + strength_reduction_pass.cpp + strip_debug_info_pass.cpp + strip_reflect_info_pass.cpp + type_manager.cpp + types.cpp + unify_const_pass.cpp + value_number_table.cpp + workaround1209.cpp +) + +spvtools_default_compile_options(SPIRV-Tools-opt) +target_include_directories(SPIRV-Tools-opt + PUBLIC ${spirv-tools_SOURCE_DIR}/include + PUBLIC ${SPIRV_HEADER_INCLUDE_DIR} + PRIVATE ${spirv-tools_BINARY_DIR} +) +# We need the assembling and disassembling functionalities in the main library. +target_link_libraries(SPIRV-Tools-opt + PUBLIC ${SPIRV_TOOLS}) + +set_property(TARGET SPIRV-Tools-opt PROPERTY FOLDER "SPIRV-Tools libraries") +spvtools_check_symbol_exports(SPIRV-Tools-opt) + +if(ENABLE_SPIRV_TOOLS_INSTALL) + install(TARGETS SPIRV-Tools-opt + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) +endif(ENABLE_SPIRV_TOOLS_INSTALL) + 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 new file mode 100644 index 000000000..014ce0062 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/aggressive_dead_code_elim_pass.cpp @@ -0,0 +1,721 @@ +// Copyright (c) 2017 The Khronos Group Inc. +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "aggressive_dead_code_elim_pass.h" + +#include "cfa.h" +#include "iterator.h" +#include "latest_version_glsl_std_450_header.h" +#include "reflect.h" + +#include + +namespace spvtools { +namespace opt { + +namespace { + +const uint32_t kTypePointerStorageClassInIdx = 0; +const uint32_t kEntryPointFunctionIdInIdx = 1; +const uint32_t kSelectionMergeMergeBlockIdInIdx = 0; +const uint32_t kLoopMergeMergeBlockIdInIdx = 0; +const uint32_t kLoopMergeContinueBlockIdInIdx = 1; + +// Sorting functor to present annotation instructions in an easy-to-process +// order. The functor orders by opcode first and falls back on unique id +// ordering if both instructions have the same opcode. +// +// Desired priority: +// SpvOpGroupDecorate +// SpvOpGroupMemberDecorate +// SpvOpDecorate +// SpvOpMemberDecorate +// SpvOpDecorateId +// SpvOpDecorationGroup +struct DecorationLess { + bool operator()(const ir::Instruction* lhs, + const ir::Instruction* rhs) const { + assert(lhs && rhs); + SpvOp lhsOp = lhs->opcode(); + SpvOp rhsOp = rhs->opcode(); + if (lhsOp != rhsOp) { + // OpGroupDecorate and OpGroupMember decorate are highest priority to + // eliminate dead targets early and simplify subsequent checks. + if (lhsOp == SpvOpGroupDecorate && rhsOp != SpvOpGroupDecorate) + return true; + if (rhsOp == SpvOpGroupDecorate && lhsOp != SpvOpGroupDecorate) + return false; + if (lhsOp == SpvOpGroupMemberDecorate && + rhsOp != SpvOpGroupMemberDecorate) + return true; + if (rhsOp == SpvOpGroupMemberDecorate && + lhsOp != SpvOpGroupMemberDecorate) + return false; + if (lhsOp == SpvOpDecorate && rhsOp != SpvOpDecorate) return true; + if (rhsOp == SpvOpDecorate && lhsOp != SpvOpDecorate) return false; + if (lhsOp == SpvOpMemberDecorate && rhsOp != SpvOpMemberDecorate) + return true; + if (rhsOp == SpvOpMemberDecorate && lhsOp != SpvOpMemberDecorate) + return false; + if (lhsOp == SpvOpDecorateId && rhsOp != SpvOpDecorateId) return true; + if (rhsOp == SpvOpDecorateId && lhsOp != SpvOpDecorateId) return false; + // OpDecorationGroup is lowest priority to ensure use/def chains remain + // usable for instructions that target this group. + if (lhsOp == SpvOpDecorationGroup && rhsOp != SpvOpDecorationGroup) + return true; + if (rhsOp == SpvOpDecorationGroup && lhsOp != SpvOpDecorationGroup) + return false; + } + + // Fall back to maintain total ordering (compare unique ids). + return *lhs < *rhs; + } +}; + +} // namespace + +bool AggressiveDCEPass::IsVarOfStorage(uint32_t varId, uint32_t storageClass) { + if (varId == 0) return false; + const ir::Instruction* varInst = get_def_use_mgr()->GetDef(varId); + const SpvOp op = varInst->opcode(); + if (op != SpvOpVariable) return false; + const uint32_t varTypeId = varInst->type_id(); + const ir::Instruction* varTypeInst = get_def_use_mgr()->GetDef(varTypeId); + if (varTypeInst->opcode() != SpvOpTypePointer) return false; + return varTypeInst->GetSingleWordInOperand(kTypePointerStorageClassInIdx) == + storageClass; +} + +bool AggressiveDCEPass::IsLocalVar(uint32_t varId) { + return IsVarOfStorage(varId, SpvStorageClassFunction) || + (IsVarOfStorage(varId, SpvStorageClassPrivate) && private_like_local_); +} + +void AggressiveDCEPass::AddStores(uint32_t ptrId) { + get_def_use_mgr()->ForEachUser(ptrId, [this](ir::Instruction* user) { + switch (user->opcode()) { + case SpvOpAccessChain: + case SpvOpInBoundsAccessChain: + case SpvOpCopyObject: + this->AddStores(user->result_id()); + break; + case SpvOpLoad: + break; + // If default, assume it stores e.g. frexp, modf, function call + case SpvOpStore: + default: + AddToWorklist(user); + break; + } + }); +} + +bool AggressiveDCEPass::AllExtensionsSupported() const { + // If any extension not in whitelist, return false + for (auto& ei : get_module()->extensions()) { + const char* extName = + reinterpret_cast(&ei.GetInOperand(0).words[0]); + if (extensions_whitelist_.find(extName) == extensions_whitelist_.end()) + return false; + } + return true; +} + +bool AggressiveDCEPass::IsDead(ir::Instruction* inst) { + if (IsLive(inst)) return false; + if (inst->IsBranch() && !IsStructuredHeader(context()->get_instr_block(inst), + nullptr, nullptr, nullptr)) + return false; + return true; +} + +bool AggressiveDCEPass::IsTargetDead(ir::Instruction* inst) { + const uint32_t tId = inst->GetSingleWordInOperand(0); + ir::Instruction* tInst = get_def_use_mgr()->GetDef(tId); + if (ir::IsAnnotationInst(tInst->opcode())) { + // This must be a decoration group. We go through annotations in a specific + // order. So if this is not used by any group or group member decorates, it + // is dead. + assert(tInst->opcode() == SpvOpDecorationGroup); + bool dead = true; + get_def_use_mgr()->ForEachUser(tInst, [&dead](ir::Instruction* user) { + if (user->opcode() == SpvOpGroupDecorate || + user->opcode() == SpvOpGroupMemberDecorate) + dead = false; + }); + return dead; + } + return IsDead(tInst); +} + +void AggressiveDCEPass::ProcessLoad(uint32_t varId) { + // Only process locals + if (!IsLocalVar(varId)) return; + // Return if already processed + if (live_local_vars_.find(varId) != live_local_vars_.end()) return; + // Mark all stores to varId as live + AddStores(varId); + // Cache varId as processed + live_local_vars_.insert(varId); +} + +bool AggressiveDCEPass::IsStructuredHeader(ir::BasicBlock* bp, + ir::Instruction** mergeInst, + ir::Instruction** branchInst, + uint32_t* mergeBlockId) { + if (!bp) return false; + ir::Instruction* mi = bp->GetMergeInst(); + if (mi == nullptr) return false; + ir::Instruction* bri = &*bp->tail(); + if (branchInst != nullptr) *branchInst = bri; + if (mergeInst != nullptr) *mergeInst = mi; + if (mergeBlockId != nullptr) *mergeBlockId = mi->GetSingleWordInOperand(0); + return true; +} + +void AggressiveDCEPass::ComputeBlock2HeaderMaps( + std::list& structuredOrder) { + block2headerBranch_.clear(); + branch2merge_.clear(); + structured_order_index_.clear(); + std::stack currentHeaderBranch; + currentHeaderBranch.push(nullptr); + uint32_t currentMergeBlockId = 0; + uint32_t index = 0; + for (auto bi = structuredOrder.begin(); bi != structuredOrder.end(); + ++bi, ++index) { + structured_order_index_[*bi] = index; + // If this block is the merge block of the current control construct, + // we are leaving the current construct so we must update state + if ((*bi)->id() == currentMergeBlockId) { + currentHeaderBranch.pop(); + ir::Instruction* chb = currentHeaderBranch.top(); + if (chb != nullptr) + currentMergeBlockId = branch2merge_[chb]->GetSingleWordInOperand(0); + } + ir::Instruction* mergeInst; + ir::Instruction* branchInst; + uint32_t mergeBlockId; + bool is_header = + IsStructuredHeader(*bi, &mergeInst, &branchInst, &mergeBlockId); + // If this is a loop header, update state first so the block will map to + // the loop. + if (is_header && mergeInst->opcode() == SpvOpLoopMerge) { + currentHeaderBranch.push(branchInst); + branch2merge_[branchInst] = mergeInst; + currentMergeBlockId = mergeBlockId; + } + // Map the block to the current construct. + block2headerBranch_[*bi] = currentHeaderBranch.top(); + // If this is an if header, update state so following blocks map to the if. + if (is_header && mergeInst->opcode() == SpvOpSelectionMerge) { + currentHeaderBranch.push(branchInst); + branch2merge_[branchInst] = mergeInst; + currentMergeBlockId = mergeBlockId; + } + } +} + +void AggressiveDCEPass::AddBranch(uint32_t labelId, ir::BasicBlock* bp) { + std::unique_ptr newBranch(new ir::Instruction( + context(), SpvOpBranch, 0, 0, + {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {labelId}}})); + get_def_use_mgr()->AnalyzeInstDefUse(&*newBranch); + bp->AddInstruction(std::move(newBranch)); +} + +void AggressiveDCEPass::AddBreaksAndContinuesToWorklist( + ir::Instruction* loopMerge) { + ir::BasicBlock* header = context()->get_instr_block(loopMerge); + uint32_t headerIndex = structured_order_index_[header]; + const uint32_t mergeId = + loopMerge->GetSingleWordInOperand(kLoopMergeMergeBlockIdInIdx); + ir::BasicBlock* merge = context()->get_instr_block(mergeId); + uint32_t mergeIndex = structured_order_index_[merge]; + get_def_use_mgr()->ForEachUser( + mergeId, [headerIndex, mergeIndex, this](ir::Instruction* user) { + if (!user->IsBranch()) return; + ir::BasicBlock* block = context()->get_instr_block(user); + uint32_t index = structured_order_index_[block]; + if (headerIndex < index && index < mergeIndex) { + // This is a break from the loop. + AddToWorklist(user); + // Add branch's merge if there is one. + ir::Instruction* userMerge = branch2merge_[user]; + if (userMerge != nullptr) AddToWorklist(userMerge); + } + }); + const uint32_t contId = + loopMerge->GetSingleWordInOperand(kLoopMergeContinueBlockIdInIdx); + get_def_use_mgr()->ForEachUser(contId, [&contId, + this](ir::Instruction* user) { + SpvOp op = user->opcode(); + if (op == SpvOpBranchConditional || op == SpvOpSwitch) { + // A conditional branch or switch can only be a continue if it does not + // have a merge instruction or its merge block is not the continue block. + ir::Instruction* hdrMerge = branch2merge_[user]; + if (hdrMerge != nullptr && hdrMerge->opcode() == SpvOpSelectionMerge) { + uint32_t hdrMergeId = + hdrMerge->GetSingleWordInOperand(kSelectionMergeMergeBlockIdInIdx); + if (hdrMergeId == contId) return; + // Need to mark merge instruction too + AddToWorklist(hdrMerge); + } + } else if (op == SpvOpBranch) { + // An unconditional branch can only be a continue if it is not + // branching to its own merge block. + ir::BasicBlock* blk = context()->get_instr_block(user); + ir::Instruction* hdrBranch = block2headerBranch_[blk]; + if (hdrBranch == nullptr) return; + ir::Instruction* hdrMerge = branch2merge_[hdrBranch]; + if (hdrMerge->opcode() == SpvOpLoopMerge) return; + uint32_t hdrMergeId = + hdrMerge->GetSingleWordInOperand(kSelectionMergeMergeBlockIdInIdx); + if (contId == hdrMergeId) return; + } else { + return; + } + AddToWorklist(user); + }); +} + +bool AggressiveDCEPass::AggressiveDCE(ir::Function* func) { + // Mark function parameters as live. + AddToWorklist(&func->DefInst()); + func->ForEachParam( + [this](const ir::Instruction* param) { + AddToWorklist(const_cast(param)); + }, + false); + + // Compute map from block to controlling conditional branch + std::list structuredOrder; + cfg()->ComputeStructuredOrder(func, &*func->begin(), &structuredOrder); + ComputeBlock2HeaderMaps(structuredOrder); + bool modified = false; + // Add instructions with external side effects to worklist. Also add branches + // EXCEPT those immediately contained in an "if" selection construct or a loop + // or continue construct. + // TODO(greg-lunarg): Handle Frexp, Modf more optimally + call_in_func_ = false; + func_is_entry_point_ = false; + private_stores_.clear(); + // Stacks to keep track of when we are inside an if- or loop-construct. + // When immediately inside an if- or loop-construct, we do not initially + // mark branches live. All other branches must be marked live. + std::stack assume_branches_live; + std::stack currentMergeBlockId; + // Push sentinel values on stack for when outside of any control flow. + assume_branches_live.push(true); + currentMergeBlockId.push(0); + for (auto bi = structuredOrder.begin(); bi != structuredOrder.end(); ++bi) { + // If exiting if or loop, update stacks + if ((*bi)->id() == currentMergeBlockId.top()) { + assume_branches_live.pop(); + currentMergeBlockId.pop(); + } + for (auto ii = (*bi)->begin(); ii != (*bi)->end(); ++ii) { + SpvOp op = ii->opcode(); + switch (op) { + case SpvOpStore: { + uint32_t varId; + (void)GetPtr(&*ii, &varId); + // Mark stores as live if their variable is not function scope + // and is not private scope. Remember private stores for possible + // later inclusion + if (IsVarOfStorage(varId, SpvStorageClassPrivate)) + private_stores_.push_back(&*ii); + else if (!IsVarOfStorage(varId, SpvStorageClassFunction)) + AddToWorklist(&*ii); + } break; + case SpvOpLoopMerge: { + assume_branches_live.push(false); + currentMergeBlockId.push( + ii->GetSingleWordInOperand(kLoopMergeMergeBlockIdInIdx)); + } break; + case SpvOpSelectionMerge: { + assume_branches_live.push(false); + currentMergeBlockId.push( + ii->GetSingleWordInOperand(kSelectionMergeMergeBlockIdInIdx)); + } break; + case SpvOpSwitch: + case SpvOpBranch: + case SpvOpBranchConditional: { + if (assume_branches_live.top()) AddToWorklist(&*ii); + } break; + default: { + // Function calls, atomics, function params, function returns, etc. + // TODO(greg-lunarg): function calls live only if write to non-local + if (!context()->IsCombinatorInstruction(&*ii)) { + AddToWorklist(&*ii); + } + // Remember function calls + if (op == SpvOpFunctionCall) call_in_func_ = true; + } break; + } + } + } + // See if current function is an entry point + for (auto& ei : get_module()->entry_points()) { + if (ei.GetSingleWordInOperand(kEntryPointFunctionIdInIdx) == + func->result_id()) { + func_is_entry_point_ = true; + break; + } + } + // If the current function is an entry point and has no function calls, + // we can optimize private variables as locals + private_like_local_ = func_is_entry_point_ && !call_in_func_; + // If privates are not like local, add their stores to worklist + if (!private_like_local_) + for (auto& ps : private_stores_) AddToWorklist(ps); + // Perform closure on live instruction set. + while (!worklist_.empty()) { + ir::Instruction* liveInst = worklist_.front(); + // Add all operand instructions if not already live + liveInst->ForEachInId([&liveInst, this](const uint32_t* iid) { + ir::Instruction* inInst = get_def_use_mgr()->GetDef(*iid); + // Do not add label if an operand of a branch. This is not needed + // as part of live code discovery and can create false live code, + // for example, the branch to a header of a loop. + if (inInst->opcode() == SpvOpLabel && liveInst->IsBranch()) return; + AddToWorklist(inInst); + }); + if (liveInst->type_id() != 0) { + AddToWorklist(get_def_use_mgr()->GetDef(liveInst->type_id())); + } + // If in a structured if or loop construct, add the controlling + // conditional branch and its merge. Any containing control construct + // is marked live when the merge and branch are processed out of the + // worklist. + ir::BasicBlock* blk = context()->get_instr_block(liveInst); + ir::Instruction* branchInst = block2headerBranch_[blk]; + if (branchInst != nullptr) { + AddToWorklist(branchInst); + ir::Instruction* mergeInst = branch2merge_[branchInst]; + AddToWorklist(mergeInst); + // If in a loop, mark all its break and continue instructions live + if (mergeInst->opcode() == SpvOpLoopMerge) + AddBreaksAndContinuesToWorklist(mergeInst); + } + // If local load, add all variable's stores if variable not already live + if (liveInst->opcode() == SpvOpLoad) { + uint32_t varId; + (void)GetPtr(liveInst, &varId); + if (varId != 0) { + ProcessLoad(varId); + } + } + // If function call, treat as if it loads from all pointer arguments + else if (liveInst->opcode() == SpvOpFunctionCall) { + liveInst->ForEachInId([this](const uint32_t* iid) { + // Skip non-ptr args + if (!IsPtr(*iid)) return; + uint32_t varId; + (void)GetPtr(*iid, &varId); + ProcessLoad(varId); + }); + } + // If function parameter, treat as if it's result id is loaded from + else if (liveInst->opcode() == SpvOpFunctionParameter) { + ProcessLoad(liveInst->result_id()); + } + // We treat an OpImageTexelPointer as a load of the pointer, and + // that value is manipulated to get the result. + else if (liveInst->opcode() == SpvOpImageTexelPointer) { + uint32_t varId; + (void)GetPtr(liveInst, &varId); + if (varId != 0) { + ProcessLoad(varId); + } + } + worklist_.pop(); + } + + // Kill dead instructions and remember dead blocks + for (auto bi = structuredOrder.begin(); bi != structuredOrder.end();) { + uint32_t mergeBlockId = 0; + (*bi)->ForEachInst([this, &modified, &mergeBlockId](ir::Instruction* inst) { + if (!IsDead(inst)) return; + if (inst->opcode() == SpvOpLabel) return; + // If dead instruction is selection merge, remember merge block + // for new branch at end of block + if (inst->opcode() == SpvOpSelectionMerge || + inst->opcode() == SpvOpLoopMerge) + mergeBlockId = inst->GetSingleWordInOperand(0); + to_kill_.push_back(inst); + modified = true; + }); + // If a structured if or loop was deleted, add a branch to its merge + // block, and traverse to the merge block and continue processing there. + // We know the block still exists because the label is not deleted. + if (mergeBlockId != 0) { + AddBranch(mergeBlockId, *bi); + for (++bi; (*bi)->id() != mergeBlockId; ++bi) { + } + } else { + ++bi; + } + } + + return modified; +} + +void AggressiveDCEPass::Initialize(ir::IRContext* c) { + InitializeProcessing(c); + + // Clear collections + worklist_ = std::queue{}; + live_insts_.clear(); + live_local_vars_.clear(); + + // Initialize extensions whitelist + InitExtensions(); +} + +void AggressiveDCEPass::InitializeModuleScopeLiveInstructions() { + // Keep all execution modes. + for (auto& exec : get_module()->execution_modes()) { + AddToWorklist(&exec); + } + // Keep all entry points. + for (auto& entry : get_module()->entry_points()) { + AddToWorklist(&entry); + } + // Keep workgroup size. + for (auto& anno : get_module()->annotations()) { + if (anno.opcode() == SpvOpDecorate) { + if (anno.GetSingleWordInOperand(1u) == SpvDecorationBuiltIn && + anno.GetSingleWordInOperand(2u) == SpvBuiltInWorkgroupSize) { + AddToWorklist(&anno); + } + } + } +} + +Pass::Status AggressiveDCEPass::ProcessImpl() { + // Current functionality assumes shader capability + // TODO(greg-lunarg): Handle additional capabilities + if (!context()->get_feature_mgr()->HasCapability(SpvCapabilityShader)) + return Status::SuccessWithoutChange; + // Current functionality assumes relaxed logical addressing (see + // instruction.h) + // TODO(greg-lunarg): Handle non-logical addressing + if (context()->get_feature_mgr()->HasCapability(SpvCapabilityAddresses)) + return Status::SuccessWithoutChange; + // If any extensions in the module are not explicitly supported, + // return unmodified. + if (!AllExtensionsSupported()) return Status::SuccessWithoutChange; + + // Eliminate Dead functions. + bool modified = EliminateDeadFunctions(); + + InitializeModuleScopeLiveInstructions(); + + // Process all entry point functions. + ProcessFunction pfn = [this](ir::Function* fp) { return AggressiveDCE(fp); }; + modified |= ProcessEntryPointCallTree(pfn, get_module()); + + // Process module-level instructions. Now that all live instructions have + // been marked, it is safe to remove dead global values. + modified |= ProcessGlobalValues(); + + // Kill all dead instructions. + for (auto inst : to_kill_) { + context()->KillInst(inst); + } + + // Cleanup all CFG including all unreachable blocks. + ProcessFunction cleanup = [this](ir::Function* f) { return CFGCleanup(f); }; + modified |= ProcessEntryPointCallTree(cleanup, get_module()); + + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; +} + +bool AggressiveDCEPass::EliminateDeadFunctions() { + // Identify live functions first. Those that are not live + // are dead. ADCE is disabled for non-shaders so we do not check for exported + // functions here. + std::unordered_set live_function_set; + ProcessFunction mark_live = [&live_function_set](ir::Function* fp) { + live_function_set.insert(fp); + return false; + }; + ProcessEntryPointCallTree(mark_live, get_module()); + + bool modified = false; + for (auto funcIter = get_module()->begin(); + funcIter != get_module()->end();) { + if (live_function_set.count(&*funcIter) == 0) { + modified = true; + EliminateFunction(&*funcIter); + funcIter = funcIter.Erase(); + } else { + ++funcIter; + } + } + + return modified; +} + +void AggressiveDCEPass::EliminateFunction(ir::Function* func) { + // Remove all of the instruction in the function body + func->ForEachInst( + [this](ir::Instruction* inst) { context()->KillInst(inst); }, true); +} + +bool AggressiveDCEPass::ProcessGlobalValues() { + // Remove debug and annotation statements referencing dead instructions. + // This must be done before killing the instructions, otherwise there are + // dead objects in the def/use database. + bool modified = false; + ir::Instruction* instruction = &*get_module()->debug2_begin(); + while (instruction) { + if (instruction->opcode() != SpvOpName) { + instruction = instruction->NextNode(); + continue; + } + + if (IsTargetDead(instruction)) { + instruction = context()->KillInst(instruction); + modified = true; + } else { + instruction = instruction->NextNode(); + } + } + + // This code removes all unnecessary decorations safely (see #1174). It also + // does so in a more efficient manner than deleting them only as the targets + // are deleted. + std::vector annotations; + for (auto& inst : get_module()->annotations()) annotations.push_back(&inst); + std::sort(annotations.begin(), annotations.end(), DecorationLess()); + for (auto annotation : annotations) { + switch (annotation->opcode()) { + case SpvOpDecorate: + case SpvOpMemberDecorate: + case SpvOpDecorateId: + if (IsTargetDead(annotation)) context()->KillInst(annotation); + break; + case SpvOpGroupDecorate: { + // Go through the targets of this group decorate. Remove each dead + // target. If all targets are dead, remove this decoration. + bool dead = true; + for (uint32_t i = 1; i < annotation->NumOperands();) { + ir::Instruction* opInst = + get_def_use_mgr()->GetDef(annotation->GetSingleWordOperand(i)); + if (IsDead(opInst)) { + // Don't increment |i|. + annotation->RemoveOperand(i); + } else { + i++; + dead = false; + } + } + if (dead) context()->KillInst(annotation); + break; + } + case SpvOpGroupMemberDecorate: { + // Go through the targets of this group member decorate. Remove each + // dead target (and member index). If all targets are dead, remove this + // decoration. + bool dead = true; + for (uint32_t i = 1; i < annotation->NumOperands();) { + ir::Instruction* opInst = + get_def_use_mgr()->GetDef(annotation->GetSingleWordOperand(i)); + if (IsDead(opInst)) { + // Don't increment |i|. + annotation->RemoveOperand(i + 1); + annotation->RemoveOperand(i); + } else { + i += 2; + dead = false; + } + } + if (dead) context()->KillInst(annotation); + break; + } + case SpvOpDecorationGroup: + // By the time we hit decoration groups we've checked everything that + // can target them. So if they have no uses they must be dead. + if (get_def_use_mgr()->NumUsers(annotation) == 0) + context()->KillInst(annotation); + break; + default: + assert(false); + break; + } + } + + // Since ADCE is disabled for non-shaders, we don't check for export linkage + // attributes here. + for (auto& val : get_module()->types_values()) { + if (IsDead(&val)) { + to_kill_.push_back(&val); + } + } + + return modified; +} + +AggressiveDCEPass::AggressiveDCEPass() {} + +Pass::Status AggressiveDCEPass::Process(ir::IRContext* c) { + Initialize(c); + return ProcessImpl(); +} + +void AggressiveDCEPass::InitExtensions() { + extensions_whitelist_.clear(); + extensions_whitelist_.insert({ + "SPV_AMD_shader_explicit_vertex_parameter", + "SPV_AMD_shader_trinary_minmax", + "SPV_AMD_gcn_shader", + "SPV_KHR_shader_ballot", + "SPV_AMD_shader_ballot", + "SPV_AMD_gpu_shader_half_float", + "SPV_KHR_shader_draw_parameters", + "SPV_KHR_subgroup_vote", + "SPV_KHR_16bit_storage", + "SPV_KHR_device_group", + "SPV_KHR_multiview", + "SPV_NVX_multiview_per_view_attributes", + "SPV_NV_viewport_array2", + "SPV_NV_stereo_view_rendering", + "SPV_NV_sample_mask_override_coverage", + "SPV_NV_geometry_shader_passthrough", + "SPV_AMD_texture_gather_bias_lod", + "SPV_KHR_storage_buffer_storage_class", + // SPV_KHR_variable_pointers + // Currently do not support extended pointer expressions + "SPV_AMD_gpu_shader_int16", + "SPV_KHR_post_depth_coverage", + "SPV_KHR_shader_atomic_counter_ops", + "SPV_EXT_shader_stencil_export", + "SPV_EXT_shader_viewport_index_layer", + "SPV_AMD_shader_image_load_store_lod", + "SPV_AMD_shader_fragment_mask", + "SPV_EXT_fragment_fully_covered", + "SPV_AMD_gpu_shader_half_float_fetch", + "SPV_GOOGLE_decorate_string", + "SPV_GOOGLE_hlsl_functionality1", + "SPV_NV_shader_subgroup_partitioned", + "SPV_EXT_descriptor_indexing", + }); +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/aggressive_dead_code_elim_pass.h b/3rdparty/spirv-tools/source/opt/aggressive_dead_code_elim_pass.h new file mode 100644 index 000000000..9c1749b19 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/aggressive_dead_code_elim_pass.h @@ -0,0 +1,187 @@ +// Copyright (c) 2017 The Khronos Group Inc. +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_AGGRESSIVE_DCE_PASS_H_ +#define LIBSPIRV_OPT_AGGRESSIVE_DCE_PASS_H_ + +#include +#include +#include +#include +#include +#include + +#include "basic_block.h" +#include "def_use_manager.h" +#include "mem_pass.h" +#include "module.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class AggressiveDCEPass : public MemPass { + using cbb_ptr = const ir::BasicBlock*; + + public: + using GetBlocksFunction = + std::function*(const ir::BasicBlock*)>; + + AggressiveDCEPass(); + const char* name() const override { return "eliminate-dead-code-aggressive"; } + Status Process(ir::IRContext* c) override; + + ir::IRContext::Analysis GetPreservedAnalyses() override { + return ir::IRContext::kAnalysisDefUse; + } + + private: + // Return true if |varId| is a variable of |storageClass|. |varId| must either + // be 0 or the result of an instruction. + bool IsVarOfStorage(uint32_t varId, uint32_t storageClass); + + // Return true if |varId| is variable of function storage class or is + // private variable and privates can be optimized like locals (see + // privates_like_local_). + bool IsLocalVar(uint32_t varId); + + // Return true if |inst| is marked live. + bool IsLive(const ir::Instruction* inst) const { + return live_insts_.find(inst) != live_insts_.end(); + } + + // Returns true if |inst| is dead. + bool IsDead(ir::Instruction* inst); + + // Adds entry points, execution modes and workgroup size decorations to the + // worklist for processing with the first function. + void InitializeModuleScopeLiveInstructions(); + + // Add |inst| to worklist_ and live_insts_. + void AddToWorklist(ir::Instruction* inst) { + if (live_insts_.insert(inst).second) worklist_.push(inst); + } + + // Add all store instruction which use |ptrId|, directly or indirectly, + // to the live instruction worklist. + void AddStores(uint32_t ptrId); + + // Initialize extensions whitelist + void InitExtensions(); + + // Return true if all extensions in this module are supported by this pass. + bool AllExtensionsSupported() const; + + // Returns true if the target of |inst| is dead. An instruction is dead if + // its result id is used in decoration or debug instructions only. |inst| is + // assumed to be OpName, OpMemberName or an annotation instruction. + bool IsTargetDead(ir::Instruction* inst); + + // If |varId| is local, mark all stores of varId as live. + void ProcessLoad(uint32_t varId); + + // If |bp| is structured header block, returns true and sets |mergeInst| to + // the merge instruction, |branchInst| to the branch and |mergeBlockId| to the + // merge block if they are not nullptr. Any of |mergeInst|, |branchInst| or + // |mergeBlockId| may be a null pointer. Returns false if |bp| is a null + // pointer. + bool IsStructuredHeader(ir::BasicBlock* bp, ir::Instruction** mergeInst, + ir::Instruction** branchInst, uint32_t* mergeBlockId); + + // Initialize block2headerBranch_ and branch2merge_ using |structuredOrder| + // to order blocks. + void ComputeBlock2HeaderMaps(std::list& structuredOrder); + + // Add branch to |labelId| to end of block |bp|. + void AddBranch(uint32_t labelId, ir::BasicBlock* bp); + + // Add all break and continue branches in the loop associated with + // |mergeInst| to worklist if not already live + void AddBreaksAndContinuesToWorklist(ir::Instruction* mergeInst); + + // Eliminates dead debug2 and annotation instructions. Marks dead globals for + // removal (e.g. types, constants and variables). + bool ProcessGlobalValues(); + + // Erases functions that are unreachable from the entry points of the module. + bool EliminateDeadFunctions(); + + // Removes |func| from the module and deletes all its instructions. + void EliminateFunction(ir::Function* func); + + // For function |func|, mark all Stores to non-function-scope variables + // and block terminating instructions as live. Recursively mark the values + // they use. When complete, mark any non-live instructions to be deleted. + // Returns true if the function has been modified. + // + // Note: This function does not delete useless control structures. All + // existing control structures will remain. This can leave not-insignificant + // sequences of ultimately useless code. + // TODO(): Remove useless control constructs. + bool AggressiveDCE(ir::Function* func); + + void Initialize(ir::IRContext* c); + Pass::Status ProcessImpl(); + + // True if current function has a call instruction contained in it + bool call_in_func_; + + // True if current function is an entry point + bool func_is_entry_point_; + + // True if current function is entry point and has no function calls. + bool private_like_local_; + + // Live Instruction Worklist. An instruction is added to this list + // if it might have a side effect, either directly or indirectly. + // If we don't know, then add it to this list. Instructions are + // removed from this list as the algorithm traces side effects, + // building up the live instructions set |live_insts_|. + std::queue worklist_; + + // Map from block to the branch instruction in the header of the most + // immediate controlling structured if or loop. A loop header block points + // to its own branch instruction. An if-selection block points to the branch + // of an enclosing construct's header, if one exists. + std::unordered_map block2headerBranch_; + + // Maps basic block to their index in the structured order traversal. + std::unordered_map structured_order_index_; + + // Map from branch to its associated merge instruction, if any + std::unordered_map branch2merge_; + + // Store instructions to variables of private storage + std::vector private_stores_; + + // Live Instructions + std::unordered_set live_insts_; + + // Live Local Variables + std::unordered_set live_local_vars_; + + // List of instructions to delete. Deletion is delayed until debug and + // annotation instructions are processed. + std::vector to_kill_; + + // Extensions supported by this pass. + std::unordered_set extensions_whitelist_; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_AGGRESSIVE_DCE_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/basic_block.cpp b/3rdparty/spirv-tools/source/opt/basic_block.cpp new file mode 100644 index 000000000..98b069585 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/basic_block.cpp @@ -0,0 +1,228 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "basic_block.h" +#include "function.h" +#include "ir_context.h" +#include "module.h" +#include "reflect.h" + +#include "make_unique.h" + +#include + +namespace spvtools { +namespace ir { + +namespace { + +const uint32_t kLoopMergeContinueBlockIdInIdx = 1; +const uint32_t kLoopMergeMergeBlockIdInIdx = 0; +const uint32_t kSelectionMergeMergeBlockIdInIdx = 0; + +} // namespace + +BasicBlock* BasicBlock::Clone(IRContext* context) const { + BasicBlock* clone = new BasicBlock( + std::unique_ptr(GetLabelInst()->Clone(context))); + for (const auto& inst : insts_) + // Use the incoming context + clone->AddInstruction(std::unique_ptr(inst.Clone(context))); + return clone; +} + +const Instruction* BasicBlock::GetMergeInst() const { + const Instruction* result = nullptr; + // If it exists, the merge instruction immediately precedes the + // terminator. + auto iter = ctail(); + if (iter != cbegin()) { + --iter; + const auto opcode = iter->opcode(); + if (opcode == SpvOpLoopMerge || opcode == SpvOpSelectionMerge) { + result = &*iter; + } + } + return result; +} + +Instruction* BasicBlock::GetMergeInst() { + Instruction* result = nullptr; + // If it exists, the merge instruction immediately precedes the + // terminator. + auto iter = tail(); + if (iter != begin()) { + --iter; + const auto opcode = iter->opcode(); + if (opcode == SpvOpLoopMerge || opcode == SpvOpSelectionMerge) { + result = &*iter; + } + } + return result; +} + +const Instruction* BasicBlock::GetLoopMergeInst() const { + if (auto* merge = GetMergeInst()) { + if (merge->opcode() == SpvOpLoopMerge) { + return merge; + } + } + return nullptr; +} + +Instruction* BasicBlock::GetLoopMergeInst() { + if (auto* merge = GetMergeInst()) { + if (merge->opcode() == SpvOpLoopMerge) { + return merge; + } + } + return nullptr; +} + +void BasicBlock::KillAllInsts(bool killLabel) { + ForEachInst([killLabel](ir::Instruction* ip) { + if (killLabel || ip->opcode() != SpvOpLabel) { + ip->context()->KillInst(ip); + } + }); +} + +void BasicBlock::ForEachSuccessorLabel( + const std::function& f) const { + const auto br = &insts_.back(); + switch (br->opcode()) { + case SpvOpBranch: { + f(br->GetOperand(0).words[0]); + } break; + case SpvOpBranchConditional: + case SpvOpSwitch: { + bool is_first = true; + br->ForEachInId([&is_first, &f](const uint32_t* idp) { + if (!is_first) f(*idp); + is_first = false; + }); + } break; + default: + break; + } +} + +void BasicBlock::ForEachSuccessorLabel( + const std::function& f) { + auto br = &insts_.back(); + switch (br->opcode()) { + case SpvOpBranch: { + uint32_t tmp_id = br->GetOperand(0).words[0]; + f(&tmp_id); + if (tmp_id != br->GetOperand(0).words[0]) br->SetOperand(0, {tmp_id}); + } break; + case SpvOpBranchConditional: + case SpvOpSwitch: { + bool is_first = true; + br->ForEachInId([&is_first, &f](uint32_t* idp) { + if (!is_first) f(idp); + is_first = false; + }); + } break; + default: + break; + } +} + +bool BasicBlock::IsSuccessor(const ir::BasicBlock* block) const { + uint32_t succId = block->id(); + bool isSuccessor = false; + ForEachSuccessorLabel([&isSuccessor, succId](const uint32_t label) { + if (label == succId) isSuccessor = true; + }); + return isSuccessor; +} + +void BasicBlock::ForMergeAndContinueLabel( + const std::function& f) { + auto ii = insts_.end(); + --ii; + if (ii == insts_.begin()) return; + --ii; + if (ii->opcode() == SpvOpSelectionMerge || ii->opcode() == SpvOpLoopMerge) { + ii->ForEachInId([&f](const uint32_t* idp) { f(*idp); }); + } +} + +uint32_t BasicBlock::MergeBlockIdIfAny() const { + auto merge_ii = cend(); + --merge_ii; + uint32_t mbid = 0; + if (merge_ii != cbegin()) { + --merge_ii; + if (merge_ii->opcode() == SpvOpLoopMerge) { + mbid = merge_ii->GetSingleWordInOperand(kLoopMergeMergeBlockIdInIdx); + } else if (merge_ii->opcode() == SpvOpSelectionMerge) { + mbid = merge_ii->GetSingleWordInOperand(kSelectionMergeMergeBlockIdInIdx); + } + } + + return mbid; +} + +uint32_t BasicBlock::ContinueBlockIdIfAny() const { + auto merge_ii = cend(); + --merge_ii; + uint32_t cbid = 0; + if (merge_ii != cbegin()) { + --merge_ii; + if (merge_ii->opcode() == SpvOpLoopMerge) { + cbid = merge_ii->GetSingleWordInOperand(kLoopMergeContinueBlockIdInIdx); + } + } + return cbid; +} + +std::ostream& operator<<(std::ostream& str, const BasicBlock& block) { + str << block.PrettyPrint(); + return str; +} + +std::string BasicBlock::PrettyPrint(uint32_t options) const { + std::ostringstream str; + ForEachInst([&str, options](const ir::Instruction* inst) { + str << inst->PrettyPrint(options); + if (!IsTerminatorInst(inst->opcode())) { + str << std::endl; + } + }); + return str.str(); +} + +BasicBlock* BasicBlock::SplitBasicBlock(IRContext* context, uint32_t label_id, + iterator iter) { + assert(!insts_.empty()); + + BasicBlock* new_block = new BasicBlock(MakeUnique( + context, SpvOpLabel, 0, label_id, std::initializer_list{})); + + new_block->insts_.Splice(new_block->end(), &insts_, iter, end()); + new_block->SetParent(GetParent()); + + if (context->AreAnalysesValid(ir::IRContext::kAnalysisInstrToBlockMapping)) { + new_block->ForEachInst([new_block, context](ir::Instruction* inst) { + context->set_instr_block(inst, new_block); + }); + } + + return new_block; +} + +} // namespace ir +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/basic_block.h b/3rdparty/spirv-tools/source/opt/basic_block.h new file mode 100644 index 000000000..0df1f76ae --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/basic_block.h @@ -0,0 +1,300 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file defines the language constructs for representing a SPIR-V +// module in memory. + +#ifndef LIBSPIRV_OPT_BASIC_BLOCK_H_ +#define LIBSPIRV_OPT_BASIC_BLOCK_H_ + +#include +#include +#include +#include +#include + +#include "instruction.h" +#include "instruction_list.h" +#include "iterator.h" + +namespace spvtools { +namespace ir { + +class Function; +class IRContext; + +// A SPIR-V basic block. +class BasicBlock { + public: + using iterator = InstructionList::iterator; + using const_iterator = InstructionList::const_iterator; + + // Creates a basic block with the given starting |label|. + inline explicit BasicBlock(std::unique_ptr label); + + explicit BasicBlock(const BasicBlock& bb) = delete; + + // Creates a clone of the basic block in the given |context| + // + // The parent function will default to null and needs to be explicitly set by + // the user. + BasicBlock* Clone(IRContext*) const; + + // Sets the enclosing function for this basic block. + void SetParent(Function* function) { function_ = function; } + + // Return the enclosing function + inline Function* GetParent() const { return function_; } + + // Appends an instruction to this basic block. + inline void AddInstruction(std::unique_ptr i); + + // Appends all of block's instructions (except label) to this block + inline void AddInstructions(BasicBlock* bp); + + // The label starting this basic block. + Instruction* GetLabelInst() { return label_.get(); } + const Instruction* GetLabelInst() const { return label_.get(); } + + // Returns the merge instruction in this basic block, if it exists. + // Otherwise return null. May be used whenever tail() can be used. + const Instruction* GetMergeInst() const; + Instruction* GetMergeInst(); + + // Returns the OpLoopMerge instruciton in this basic block, if it exists. + // Otherwise return null. May be used whenever tail() can be used. + const Instruction* GetLoopMergeInst() const; + Instruction* GetLoopMergeInst(); + + // Returns the id of the label at the top of this block + inline uint32_t id() const { return label_->result_id(); } + + iterator begin() { return insts_.begin(); } + iterator end() { return insts_.end(); } + const_iterator begin() const { return insts_.cbegin(); } + const_iterator end() const { return insts_.cend(); } + const_iterator cbegin() const { return insts_.cbegin(); } + const_iterator cend() const { return insts_.cend(); } + + // Returns an iterator pointing to the last instruction. This may only + // be used if this block has an instruction other than the OpLabel + // that defines it. + iterator tail() { + assert(!insts_.empty()); + return --end(); + } + + // Returns a const iterator, but othewrise similar to tail(). + const_iterator ctail() const { + assert(!insts_.empty()); + return --insts_.cend(); + } + + // Returns true if the basic block has at least one successor. + inline bool hasSuccessor() const { return ctail()->IsBranch(); } + + // Runs the given function |f| on each instruction in this basic block, and + // optionally on the debug line instructions that might precede them. + inline void ForEachInst(const std::function& f, + bool run_on_debug_line_insts = false); + inline void ForEachInst(const std::function& f, + bool run_on_debug_line_insts = false) const; + + // Runs the given function |f| on each instruction in this basic block, and + // optionally on the debug line instructions that might precede them. If |f| + // returns false, iteration is terminated and this function returns false. + inline bool WhileEachInst(const std::function& f, + bool run_on_debug_line_insts = false); + inline bool WhileEachInst(const std::function& f, + bool run_on_debug_line_insts = false) const; + + // Runs the given function |f| on each Phi instruction in this basic block, + // and optionally on the debug line instructions that might precede them. + inline void ForEachPhiInst(const std::function& f, + bool run_on_debug_line_insts = false); + + // Runs the given function |f| on each Phi instruction in this basic block, + // and optionally on the debug line instructions that might precede them. If + // |f| returns false, iteration is terminated and this function return false. + inline bool WhileEachPhiInst(const std::function& f, + bool run_on_debug_line_insts = false); + + // Runs the given function |f| on each label id of each successor block + void ForEachSuccessorLabel( + const std::function& f) const; + + // Runs the given function |f| on each label id of each successor block. + // Modifying the pointed value will change the branch taken by the basic + // block. It is the caller responsibility to update or invalidate the CFG. + void ForEachSuccessorLabel(const std::function& f); + + // Returns true if |block| is a direct successor of |this|. + bool IsSuccessor(const ir::BasicBlock* block) const; + + // Runs the given function |f| on the merge and continue label, if any + void ForMergeAndContinueLabel(const std::function& f); + + // Returns true if this basic block has any Phi instructions. + bool HasPhiInstructions() { + return !WhileEachPhiInst([](ir::Instruction*) { return false; }); + } + + // Return true if this block is a loop header block. + bool IsLoopHeader() const { return GetLoopMergeInst() != nullptr; } + + // Returns the ID of the merge block declared by a merge instruction in this + // block, if any. If none, returns zero. + uint32_t MergeBlockIdIfAny() const; + + // Returns the ID of the continue block declared by a merge instruction in + // this block, if any. If none, returns zero. + uint32_t ContinueBlockIdIfAny() const; + + // Returns the terminator instruction. Assumes the terminator exists. + Instruction* terminator() { return &*tail(); } + const Instruction* terminator() const { return &*ctail(); } + + // Returns true if this basic block exits this function and returns to its + // caller. + bool IsReturn() const { return ctail()->IsReturn(); } + + // Returns true if this basic block exits this function or aborts execution. + bool IsReturnOrAbort() const { return ctail()->IsReturnOrAbort(); } + + // Kill all instructions in this block. Whether or not to kill the label is + // indicated by |killLabel|. + void KillAllInsts(bool killLabel); + + // Splits this basic block into two. Returns a new basic block with label + // |labelId| containing the instructions from |iter| onwards. Instructions + // prior to |iter| remain in this basic block. + BasicBlock* SplitBasicBlock(IRContext* context, uint32_t label_id, + iterator iter); + + // Pretty-prints this basic block into a std::string by printing every + // instruction in it. + // + // |options| are the disassembly options. SPV_BINARY_TO_TEXT_OPTION_NO_HEADER + // is always added to |options|. + std::string PrettyPrint(uint32_t options = 0u) const; + + private: + // The enclosing function. + Function* function_; + // The label starting this basic block. + std::unique_ptr label_; + // Instructions inside this basic block, but not the OpLabel. + InstructionList insts_; +}; + +// Pretty-prints |block| to |str|. Returns |str|. +std::ostream& operator<<(std::ostream& str, const BasicBlock& block); + +inline BasicBlock::BasicBlock(std::unique_ptr label) + : function_(nullptr), label_(std::move(label)) {} + +inline void BasicBlock::AddInstruction(std::unique_ptr i) { + insts_.push_back(std::move(i)); +} + +inline void BasicBlock::AddInstructions(BasicBlock* bp) { + auto bEnd = end(); + (void)bEnd.MoveBefore(&bp->insts_); +} + +inline bool BasicBlock::WhileEachInst( + const std::function& f, bool run_on_debug_line_insts) { + if (label_) { + if (!label_->WhileEachInst(f, run_on_debug_line_insts)) return false; + } + if (insts_.empty()) { + return true; + } + + Instruction* inst = &insts_.front(); + while (inst != nullptr) { + Instruction* next_instruction = inst->NextNode(); + if (!inst->WhileEachInst(f, run_on_debug_line_insts)) return false; + inst = next_instruction; + } + return true; +} + +inline bool BasicBlock::WhileEachInst( + const std::function& f, + bool run_on_debug_line_insts) const { + if (label_) { + if (!static_cast(label_.get()) + ->WhileEachInst(f, run_on_debug_line_insts)) + return false; + } + for (const auto& inst : insts_) { + if (!static_cast(&inst)->WhileEachInst( + f, run_on_debug_line_insts)) + return false; + } + return true; +} + +inline void BasicBlock::ForEachInst(const std::function& f, + bool run_on_debug_line_insts) { + WhileEachInst( + [&f](Instruction* inst) { + f(inst); + return true; + }, + run_on_debug_line_insts); +} + +inline void BasicBlock::ForEachInst( + const std::function& f, + bool run_on_debug_line_insts) const { + WhileEachInst( + [&f](const Instruction* inst) { + f(inst); + return true; + }, + run_on_debug_line_insts); +} + +inline bool BasicBlock::WhileEachPhiInst( + const std::function& f, bool run_on_debug_line_insts) { + if (insts_.empty()) { + return true; + } + + Instruction* inst = &insts_.front(); + while (inst != nullptr) { + Instruction* next_instruction = inst->NextNode(); + if (inst->opcode() != SpvOpPhi) break; + if (!inst->WhileEachInst(f, run_on_debug_line_insts)) return false; + inst = next_instruction; + } + return true; +} + +inline void BasicBlock::ForEachPhiInst( + const std::function& f, bool run_on_debug_line_insts) { + WhileEachPhiInst( + [&f](Instruction* inst) { + f(inst); + return true; + }, + run_on_debug_line_insts); +} + +} // namespace ir +} // namespace spvtools + +#endif // LIBSPIRV_OPT_BASIC_BLOCK_H_ diff --git a/3rdparty/spirv-tools/source/opt/block_merge_pass.cpp b/3rdparty/spirv-tools/source/opt/block_merge_pass.cpp new file mode 100644 index 000000000..fb370d690 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/block_merge_pass.cpp @@ -0,0 +1,157 @@ +// Copyright (c) 2017 The Khronos Group Inc. +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "block_merge_pass.h" + +#include "ir_context.h" +#include "iterator.h" + +namespace spvtools { +namespace opt { + +void BlockMergePass::KillInstAndName(ir::Instruction* inst) { + std::vector to_kill; + get_def_use_mgr()->ForEachUser(inst, [&to_kill](ir::Instruction* user) { + if (user->opcode() == SpvOpName) { + to_kill.push_back(user); + } + }); + for (auto i : to_kill) { + context()->KillInst(i); + } + context()->KillInst(inst); +} + +bool BlockMergePass::MergeBlocks(ir::Function* func) { + bool modified = false; + for (auto bi = func->begin(); bi != func->end();) { + // Find block with single successor which has no other predecessors. + auto ii = bi->end(); + --ii; + ir::Instruction* br = &*ii; + if (br->opcode() != SpvOpBranch) { + ++bi; + continue; + } + + const uint32_t lab_id = br->GetSingleWordInOperand(0); + if (cfg()->preds(lab_id).size() != 1) { + ++bi; + continue; + } + + bool pred_is_header = IsHeader(&*bi); + bool succ_is_header = IsHeader(lab_id); + if (pred_is_header && succ_is_header) { + // Cannot merge two headers together. + ++bi; + continue; + } + + bool pred_is_merge = IsMerge(&*bi); + bool succ_is_merge = IsMerge(lab_id); + if (pred_is_merge && succ_is_merge) { + // Cannot merge two merges together. + ++bi; + continue; + } + + ir::Instruction* merge_inst = bi->GetMergeInst(); + if (pred_is_header && lab_id != merge_inst->GetSingleWordInOperand(0u)) { + // If this is a header block and the successor is not its merge, we must + // be careful about which blocks we are willing to merge together. + // OpLoopMerge must be followed by a conditional or unconditional branch. + // The merge must be a loop merge because a selection merge cannot be + // followed by an unconditional branch. + ir::BasicBlock* succ_block = context()->get_instr_block(lab_id); + SpvOp succ_term_op = succ_block->terminator()->opcode(); + assert(merge_inst->opcode() == SpvOpLoopMerge); + if (succ_term_op != SpvOpBranch && + succ_term_op != SpvOpBranchConditional) { + ++bi; + continue; + } + } + + // Merge blocks. + context()->KillInst(br); + auto sbi = bi; + for (; sbi != func->end(); ++sbi) + if (sbi->id() == lab_id) break; + // If bi is sbi's only predecessor, it dominates sbi and thus + // sbi must follow bi in func's ordering. + assert(sbi != func->end()); + bi->AddInstructions(&*sbi); + if (merge_inst) { + if (pred_is_header && lab_id == merge_inst->GetSingleWordInOperand(0u)) { + // Merging the header and merge blocks, so remove the structured control + // flow declaration. + context()->KillInst(merge_inst); + } else { + // Move the merge instruction to just before the terminator. + merge_inst->InsertBefore(bi->terminator()); + } + } + context()->ReplaceAllUsesWith(lab_id, bi->id()); + KillInstAndName(sbi->GetLabelInst()); + (void)sbi.Erase(); + // Reprocess block. + modified = true; + } + return modified; +} + +bool BlockMergePass::IsHeader(ir::BasicBlock* block) { + return block->GetMergeInst() != nullptr; +} + +bool BlockMergePass::IsHeader(uint32_t id) { + return IsHeader(context()->get_instr_block(get_def_use_mgr()->GetDef(id))); +} + +bool BlockMergePass::IsMerge(uint32_t id) { + return !get_def_use_mgr()->WhileEachUse(id, [](ir::Instruction* user, + uint32_t index) { + SpvOp op = user->opcode(); + if ((op == SpvOpLoopMerge || op == SpvOpSelectionMerge) && index == 0u) { + return false; + } + return true; + }); +} + +bool BlockMergePass::IsMerge(ir::BasicBlock* block) { + return IsMerge(block->id()); +} + +void BlockMergePass::Initialize(ir::IRContext* c) { InitializeProcessing(c); } + +Pass::Status BlockMergePass::ProcessImpl() { + // Process all entry point functions. + ProcessFunction pfn = [this](ir::Function* fp) { return MergeBlocks(fp); }; + bool modified = ProcessEntryPointCallTree(pfn, get_module()); + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; +} + +BlockMergePass::BlockMergePass() {} + +Pass::Status BlockMergePass::Process(ir::IRContext* c) { + Initialize(c); + return ProcessImpl(); +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/block_merge_pass.h b/3rdparty/spirv-tools/source/opt/block_merge_pass.h new file mode 100644 index 000000000..a56f245e1 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/block_merge_pass.h @@ -0,0 +1,67 @@ +// Copyright (c) 2017 The Khronos Group Inc. +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_BLOCK_MERGE_PASS_H_ +#define LIBSPIRV_OPT_BLOCK_MERGE_PASS_H_ + +#include +#include +#include +#include +#include +#include + +#include "basic_block.h" +#include "def_use_manager.h" +#include "ir_context.h" +#include "module.h" +#include "pass.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class BlockMergePass : public Pass { + public: + BlockMergePass(); + const char* name() const override { return "merge-blocks"; } + Status Process(ir::IRContext*) override; + + private: + // Kill any OpName instruction referencing |inst|, then kill |inst|. + void KillInstAndName(ir::Instruction* inst); + + // Search |func| for blocks which have a single Branch to a block + // with no other predecessors. Merge these blocks into a single block. + bool MergeBlocks(ir::Function* func); + + // Returns true if |block| (or |id|) contains a merge instruction. + bool IsHeader(ir::BasicBlock* block); + bool IsHeader(uint32_t id); + + // Returns true if |block| (or |id|) is the merge target of a merge + // instruction. + bool IsMerge(ir::BasicBlock* block); + bool IsMerge(uint32_t id); + + void Initialize(ir::IRContext* c); + Pass::Status ProcessImpl(); +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_BLOCK_MERGE_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/build_module.cpp b/3rdparty/spirv-tools/source/opt/build_module.cpp new file mode 100644 index 000000000..c441fcccd --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/build_module.cpp @@ -0,0 +1,77 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "build_module.h" + +#include "ir_context.h" +#include "ir_loader.h" +#include "make_unique.h" +#include "table.h" + +namespace spvtools { + +namespace { + +// Sets the module header for IrLoader. Meets the interface requirement of +// spvBinaryParse(). +spv_result_t SetSpvHeader(void* builder, spv_endianness_t, uint32_t magic, + uint32_t version, uint32_t generator, + uint32_t id_bound, uint32_t reserved) { + reinterpret_cast(builder)->SetModuleHeader( + magic, version, generator, id_bound, reserved); + return SPV_SUCCESS; +} + +// Processes a parsed instruction for IrLoader. Meets the interface requirement +// of spvBinaryParse(). +spv_result_t SetSpvInst(void* builder, const spv_parsed_instruction_t* inst) { + if (reinterpret_cast(builder)->AddInstruction(inst)) { + return SPV_SUCCESS; + } + return SPV_ERROR_INVALID_BINARY; +} + +} // namespace + +std::unique_ptr BuildModule(spv_target_env env, + MessageConsumer consumer, + const uint32_t* binary, + const size_t size) { + auto context = spvContextCreate(env); + libspirv::SetContextMessageConsumer(context, consumer); + + auto irContext = MakeUnique(env, consumer); + ir::IrLoader loader(consumer, irContext->module()); + + spv_result_t status = spvBinaryParse(context, &loader, binary, size, + SetSpvHeader, SetSpvInst, nullptr); + loader.EndModule(); + + spvContextDestroy(context); + + return status == SPV_SUCCESS ? std::move(irContext) : nullptr; +} + +std::unique_ptr BuildModule(spv_target_env env, + MessageConsumer consumer, + const std::string& text, + uint32_t assemble_options) { + SpirvTools t(env); + t.SetMessageConsumer(consumer); + std::vector binary; + if (!t.Assemble(text, &binary, assemble_options)) return nullptr; + return BuildModule(env, consumer, binary.data(), binary.size()); +} + +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/build_module.h b/3rdparty/spirv-tools/source/opt/build_module.h new file mode 100644 index 000000000..7e9493634 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/build_module.h @@ -0,0 +1,45 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SPIRV_TOOLS_OPT_BUILD_MODULE_H_ +#define SPIRV_TOOLS_OPT_BUILD_MODULE_H_ + +#include +#include + +#include "ir_context.h" +#include "module.h" +#include "spirv-tools/libspirv.hpp" + +namespace spvtools { + +// Builds an ir::Module returns the owning ir::IRContext from the given SPIR-V +// |binary|. |size| specifies number of words in |binary|. The |binary| will be +// decoded according to the given target |env|. Returns nullptr if errors occur +// and sends the errors to |consumer|. +std::unique_ptr BuildModule(spv_target_env env, + MessageConsumer consumer, + const uint32_t* binary, size_t size); + +// Builds an ir::Module and returns the owning ir::IRContext from the given +// SPIR-V assembly |text|. The |text| will be encoded according to the given +// target |env|. Returns nullptr if errors occur and sends the errors to +// |consumer|. +std::unique_ptr BuildModule( + spv_target_env env, MessageConsumer consumer, const std::string& text, + uint32_t assemble_options = SpirvTools::kDefaultAssembleOption); + +} // namespace spvtools + +#endif // SPIRV_TOOLS_OPT_BUILD_MODULE_H_ diff --git a/3rdparty/spirv-tools/source/opt/ccp_pass.cpp b/3rdparty/spirv-tools/source/opt/ccp_pass.cpp new file mode 100644 index 000000000..e9044ad41 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/ccp_pass.cpp @@ -0,0 +1,330 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file implements conditional constant propagation as described in +// +// Constant propagation with conditional branches, +// Wegman and Zadeck, ACM TOPLAS 13(2):181-210. +#include "ccp_pass.h" +#include "fold.h" +#include "function.h" +#include "module.h" +#include "propagator.h" + +#include + +namespace spvtools { +namespace opt { + +namespace { + +// This SSA id is never defined nor referenced in the IR. It is a special ID +// which represents varying values. When an ID is found to have a varying +// value, its entry in the |values_| table maps to kVaryingSSAId. +const uint32_t kVaryingSSAId = std::numeric_limits::max(); + +} // namespace + +bool CCPPass::IsVaryingValue(uint32_t id) const { return id == kVaryingSSAId; } + +SSAPropagator::PropStatus CCPPass::MarkInstructionVarying( + ir::Instruction* instr) { + assert(instr->result_id() != 0 && + "Instructions with no result cannot be marked varying."); + values_[instr->result_id()] = kVaryingSSAId; + return SSAPropagator::kVarying; +} + +SSAPropagator::PropStatus CCPPass::VisitPhi(ir::Instruction* phi) { + uint32_t meet_val_id = 0; + + // Implement the lattice meet operation. The result of this Phi instruction is + // interesting only if the meet operation over arguments coming through + // executable edges yields the same constant value. + for (uint32_t i = 2; i < phi->NumOperands(); i += 2) { + if (!propagator_->IsPhiArgExecutable(phi, i)) { + // Ignore arguments coming through non-executable edges. + continue; + } + uint32_t phi_arg_id = phi->GetSingleWordOperand(i); + auto it = values_.find(phi_arg_id); + if (it != values_.end()) { + // We found an argument with a constant value. Apply the meet operation + // with the previous arguments. + if (it->second == kVaryingSSAId) { + // The "constant" value is actually a placeholder for varying. Return + // varying for this phi. + return MarkInstructionVarying(phi); + } else if (meet_val_id == 0) { + // This is the first argument we find. Initialize the result to its + // constant value id. + meet_val_id = it->second; + } else if (it->second == meet_val_id) { + // The argument is the same constant value already computed. Continue + // looking. + continue; + } else { + // We either found a varying value, or another constant value different + // from the previous computed meet value. This Phi will never be + // constant. + return MarkInstructionVarying(phi); + } + } else { + // The incoming value has no recorded value and is therefore not + // interesting. A not interesting value joined with any other value is the + // other value. + continue; + } + } + + // If there are no incoming executable edges, the meet ID will still be 0. In + // that case, return not interesting to evaluate the Phi node again. + if (meet_val_id == 0) { + return SSAPropagator::kNotInteresting; + } + + // All the operands have the same constant value represented by |meet_val_id|. + // Set the Phi's result to that value and declare it interesting. + values_[phi->result_id()] = meet_val_id; + return SSAPropagator::kInteresting; +} + +SSAPropagator::PropStatus CCPPass::VisitAssignment(ir::Instruction* instr) { + assert(instr->result_id() != 0 && + "Expecting an instruction that produces a result"); + + // If this is a copy operation, and the RHS is a known constant, assign its + // value to the LHS. + if (instr->opcode() == SpvOpCopyObject) { + uint32_t rhs_id = instr->GetSingleWordInOperand(0); + auto it = values_.find(rhs_id); + if (it != values_.end()) { + if (IsVaryingValue(it->second)) { + return MarkInstructionVarying(instr); + } else { + values_[instr->result_id()] = it->second; + return SSAPropagator::kInteresting; + } + } + return SSAPropagator::kNotInteresting; + } + + // Instructions with a RHS that cannot produce a constant are always varying. + if (!instr->IsFoldable()) { + return MarkInstructionVarying(instr); + } + + // See if the RHS of the assignment folds into a constant value. + auto map_func = [this](uint32_t id) { + auto it = values_.find(id); + if (it == values_.end() || IsVaryingValue(it->second)) { + return id; + } + return it->second; + }; + ir::Instruction* folded_inst = + opt::FoldInstructionToConstant(instr, map_func); + if (folded_inst != nullptr) { + // We do not want to change the body of the function by adding new + // instructions. When folding we can only generate new constants. + assert(folded_inst->IsConstant() && "CCP is only interested in constant."); + values_[instr->result_id()] = folded_inst->result_id(); + return SSAPropagator::kInteresting; + } + + // Conservatively mark this instruction as varying if any input id is varying. + if (!instr->WhileEachInId([this](uint32_t* op_id) { + auto iter = values_.find(*op_id); + if (iter != values_.end() && IsVaryingValue(iter->second)) return false; + return true; + })) { + return MarkInstructionVarying(instr); + } + + // If not, see if there is a least one unknown operand to the instruction. If + // so, we might be able to fold it later. + if (!instr->WhileEachInId([this](uint32_t* op_id) { + auto it = values_.find(*op_id); + if (it == values_.end()) return false; + return true; + })) { + return SSAPropagator::kNotInteresting; + } + + // Otherwise, we will never be able to fold this instruction, so mark it + // varying. + return MarkInstructionVarying(instr); +} + +SSAPropagator::PropStatus CCPPass::VisitBranch(ir::Instruction* instr, + ir::BasicBlock** dest_bb) const { + assert(instr->IsBranch() && "Expected a branch instruction."); + + *dest_bb = nullptr; + uint32_t dest_label = 0; + if (instr->opcode() == SpvOpBranch) { + // An unconditional jump always goes to its unique destination. + dest_label = instr->GetSingleWordInOperand(0); + } else if (instr->opcode() == SpvOpBranchConditional) { + // For a conditional branch, determine whether the predicate selector has a + // known value in |values_|. If it does, set the destination block + // according to the selector's boolean value. + uint32_t pred_id = instr->GetSingleWordOperand(0); + auto it = values_.find(pred_id); + if (it == values_.end() || IsVaryingValue(it->second)) { + // The predicate has an unknown value, either branch could be taken. + return SSAPropagator::kVarying; + } + + // Get the constant value for the predicate selector from the value table. + // Use it to decide which branch will be taken. + uint32_t pred_val_id = it->second; + const analysis::Constant* c = const_mgr_->FindDeclaredConstant(pred_val_id); + assert(c && "Expected to find a constant declaration for a known value."); + // Undef values should have returned as varying above. + assert(c->AsBoolConstant() || c->AsNullConstant()); + if (c->AsNullConstant()) { + dest_label = instr->GetSingleWordOperand(2u); + } else { + const analysis::BoolConstant* val = c->AsBoolConstant(); + dest_label = val->value() ? instr->GetSingleWordOperand(1) + : instr->GetSingleWordOperand(2); + } + } else { + // For an OpSwitch, extract the value taken by the switch selector and check + // which of the target literals it matches. The branch associated with that + // literal is the taken branch. + assert(instr->opcode() == SpvOpSwitch); + if (instr->GetOperand(0).words.size() != 1) { + // If the selector is wider than 32-bits, return varying. TODO(dnovillo): + // Add support for wider constants. + return SSAPropagator::kVarying; + } + uint32_t select_id = instr->GetSingleWordOperand(0); + auto it = values_.find(select_id); + if (it == values_.end() || IsVaryingValue(it->second)) { + // The selector has an unknown value, any of the branches could be taken. + return SSAPropagator::kVarying; + } + + // Get the constant value for the selector from the value table. Use it to + // decide which branch will be taken. + uint32_t select_val_id = it->second; + const analysis::Constant* c = + const_mgr_->FindDeclaredConstant(select_val_id); + assert(c && "Expected to find a constant declaration for a known value."); + // TODO: support 64-bit integer switches. + uint32_t constant_cond = 0; + if (const analysis::IntConstant* val = c->AsIntConstant()) { + constant_cond = val->words()[0]; + } else { + // Undef values should have returned varying above. + assert(c->AsNullConstant()); + constant_cond = 0; + } + + // Start assuming that the selector will take the default value; + dest_label = instr->GetSingleWordOperand(1); + for (uint32_t i = 2; i < instr->NumOperands(); i += 2) { + if (constant_cond == instr->GetSingleWordOperand(i)) { + dest_label = instr->GetSingleWordOperand(i + 1); + break; + } + } + } + + assert(dest_label && "Destination label should be set at this point."); + *dest_bb = context()->cfg()->block(dest_label); + return SSAPropagator::kInteresting; +} + +SSAPropagator::PropStatus CCPPass::VisitInstruction(ir::Instruction* instr, + ir::BasicBlock** dest_bb) { + *dest_bb = nullptr; + if (instr->opcode() == SpvOpPhi) { + return VisitPhi(instr); + } else if (instr->IsBranch()) { + return VisitBranch(instr, dest_bb); + } else if (instr->result_id()) { + return VisitAssignment(instr); + } + return SSAPropagator::kVarying; +} + +bool CCPPass::ReplaceValues() { + bool retval = false; + for (const auto& it : values_) { + uint32_t id = it.first; + uint32_t cst_id = it.second; + if (!IsVaryingValue(cst_id) && id != cst_id) { + retval |= context()->ReplaceAllUsesWith(id, cst_id); + } + } + return retval; +} + +bool CCPPass::PropagateConstants(ir::Function* fp) { + // Mark function parameters as varying. + fp->ForEachParam([this](const ir::Instruction* inst) { + values_[inst->result_id()] = kVaryingSSAId; + }); + + const auto visit_fn = [this](ir::Instruction* instr, + ir::BasicBlock** dest_bb) { + return VisitInstruction(instr, dest_bb); + }; + + propagator_ = + std::unique_ptr(new SSAPropagator(context(), visit_fn)); + + if (propagator_->Run(fp)) { + return ReplaceValues(); + } + + return false; +} + +void CCPPass::Initialize(ir::IRContext* c) { + InitializeProcessing(c); + + const_mgr_ = context()->get_constant_mgr(); + + // Populate the constant table with values from constant declarations in the + // module. The values of each OpConstant declaration is the identity + // assignment (i.e., each constant is its own value). + for (const auto& inst : get_module()->types_values()) { + // Record compile time constant ids. Treat all other global values as + // varying. + if (inst.IsConstant()) { + values_[inst.result_id()] = inst.result_id(); + } else { + values_[inst.result_id()] = kVaryingSSAId; + } + } +} + +Pass::Status CCPPass::Process(ir::IRContext* c) { + Initialize(c); + + // Process all entry point functions. + ProcessFunction pfn = [this](ir::Function* fp) { + return PropagateConstants(fp); + }; + bool modified = ProcessReachableCallTree(pfn, context()); + return modified ? Pass::Status::SuccessWithChange + : Pass::Status::SuccessWithoutChange; +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/ccp_pass.h b/3rdparty/spirv-tools/source/opt/ccp_pass.h new file mode 100644 index 000000000..e96e731a7 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/ccp_pass.h @@ -0,0 +1,100 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_CCP_PASS_H_ +#define LIBSPIRV_OPT_CCP_PASS_H_ + +#include "constants.h" +#include "function.h" +#include "ir_context.h" +#include "mem_pass.h" +#include "module.h" +#include "propagator.h" + +namespace spvtools { +namespace opt { + +class CCPPass : public MemPass { + public: + CCPPass() = default; + const char* name() const override { return "ccp"; } + Status Process(ir::IRContext* c) override; + + private: + // Initializes the pass. + void Initialize(ir::IRContext* c); + + // Runs constant propagation on the given function |fp|. Returns true if any + // constants were propagated and the IR modified. + bool PropagateConstants(ir::Function* fp); + + // Visits a single instruction |instr|. If the instruction is a conditional + // branch that always jumps to the same basic block, it sets the destination + // block in |dest_bb|. + SSAPropagator::PropStatus VisitInstruction(ir::Instruction* instr, + ir::BasicBlock** dest_bb); + + // Visits an OpPhi instruction |phi|. This applies the meet operator for the + // CCP lattice. Essentially, if all the operands in |phi| have the same + // constant value C, the result for |phi| gets assigned the value C. + SSAPropagator::PropStatus VisitPhi(ir::Instruction* phi); + + // Visits an SSA assignment instruction |instr|. If the RHS of |instr| folds + // into a constant value C, then the LHS of |instr| is assigned the value C in + // |values_|. + SSAPropagator::PropStatus VisitAssignment(ir::Instruction* instr); + + // Visits a branch instruction |instr|. If the branch is conditional + // (OpBranchConditional or OpSwitch), and the value of its selector is known, + // |dest_bb| will be set to the corresponding destination block. Unconditional + // branches always set |dest_bb| to the single destination block. + SSAPropagator::PropStatus VisitBranch(ir::Instruction* instr, + ir::BasicBlock** dest_bb) const; + + // Replaces all operands used in |fp| with the corresponding constant values + // in |values_|. Returns true if any operands were replaced, and false + // otherwise. + bool ReplaceValues(); + + // Marks |instr| as varying by registering a varying value for its result + // into the |values_| table. Returns SSAPropagator::kVarying. + SSAPropagator::PropStatus MarkInstructionVarying(ir::Instruction* instr); + + // Returns true if |id| is the special SSA id that corresponds to a varying + // value. + bool IsVaryingValue(uint32_t id) const; + + // Constant manager for the parent IR context. Used to record new constants + // generated during propagation. + analysis::ConstantManager* const_mgr_; + + // Constant value table. Each entry in this map + // represents the compile-time constant value for |id| as declared by + // |const_decl_id|. Each |const_decl_id| in this table is an OpConstant + // declaration for the current module. + // + // Additionally, this table keeps track of SSA IDs with varying values. If an + // SSA ID is found to have a varying value, it will have an entry in this + // table that maps to the special SSA id kVaryingSSAId. These values are + // never replaced in the IR, they are used by CCP during propagation. + std::unordered_map values_; + + // Propagator engine used. + std::unique_ptr propagator_; +}; + +} // namespace opt +} // namespace spvtools + +#endif diff --git a/3rdparty/spirv-tools/source/opt/cfg.cpp b/3rdparty/spirv-tools/source/opt/cfg.cpp new file mode 100644 index 000000000..6767570a2 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/cfg.cpp @@ -0,0 +1,310 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cfg.h" +#include "cfa.h" +#include "ir_builder.h" +#include "ir_context.h" +#include "module.h" + +namespace spvtools { +namespace ir { + +namespace { + +// Universal Limit of ResultID + 1 +const int kInvalidId = 0x400000; + +} // namespace + +CFG::CFG(ir::Module* module) + : module_(module), + pseudo_entry_block_(std::unique_ptr( + new ir::Instruction(module->context(), SpvOpLabel, 0, 0, {}))), + pseudo_exit_block_(std::unique_ptr(new ir::Instruction( + module->context(), SpvOpLabel, 0, kInvalidId, {}))) { + for (auto& fn : *module) { + for (auto& blk : fn) { + RegisterBlock(&blk); + } + } +} + +void CFG::AddEdges(ir::BasicBlock* blk) { + uint32_t blk_id = blk->id(); + // Force the creation of an entry, not all basic block have predecessors + // (such as the entry blocks and some unreachables). + label2preds_[blk_id]; + const auto* const_blk = blk; + const_blk->ForEachSuccessorLabel( + [blk_id, this](const uint32_t succ_id) { AddEdge(blk_id, succ_id); }); +} + +void CFG::RemoveNonExistingEdges(uint32_t blk_id) { + std::vector updated_pred_list; + for (uint32_t id : preds(blk_id)) { + const ir::BasicBlock* pred_blk = block(id); + bool has_branch = false; + pred_blk->ForEachSuccessorLabel([&has_branch, blk_id](uint32_t succ) { + if (succ == blk_id) { + has_branch = true; + } + }); + if (has_branch) updated_pred_list.push_back(id); + } + + label2preds_.at(blk_id) = std::move(updated_pred_list); +} + +void CFG::ComputeStructuredOrder(ir::Function* func, ir::BasicBlock* root, + std::list* order) { + assert(module_->context()->get_feature_mgr()->HasCapability( + SpvCapabilityShader) && + "This only works on structured control flow"); + + // Compute structured successors and do DFS. + ComputeStructuredSuccessors(func); + auto ignore_block = [](cbb_ptr) {}; + auto ignore_edge = [](cbb_ptr, cbb_ptr) {}; + auto get_structured_successors = [this](const ir::BasicBlock* b) { + return &(block2structured_succs_[b]); + }; + + // TODO(greg-lunarg): Get rid of const_cast by making moving const + // out of the cfa.h prototypes and into the invoking code. + auto post_order = [&](cbb_ptr b) { + order->push_front(const_cast(b)); + }; + spvtools::CFA::DepthFirstTraversal( + root, get_structured_successors, ignore_block, post_order, ignore_edge); +} + +void CFG::ForEachBlockInReversePostOrder( + BasicBlock* bb, const std::function& f) { + std::vector po; + std::unordered_set seen; + ComputePostOrderTraversal(bb, &po, &seen); + + for (auto current_bb = po.rbegin(); current_bb != po.rend(); ++current_bb) { + if (!IsPseudoExitBlock(*current_bb) && !IsPseudoEntryBlock(*current_bb)) { + f(*current_bb); + } + } +} + +void CFG::ComputeStructuredSuccessors(ir::Function* func) { + block2structured_succs_.clear(); + for (auto& blk : *func) { + // If no predecessors in function, make successor to pseudo entry. + if (label2preds_[blk.id()].size() == 0) + block2structured_succs_[&pseudo_entry_block_].push_back(&blk); + + // If header, make merge block first successor and continue block second + // successor if there is one. + uint32_t mbid = blk.MergeBlockIdIfAny(); + if (mbid != 0) { + block2structured_succs_[&blk].push_back(block(mbid)); + uint32_t cbid = blk.ContinueBlockIdIfAny(); + if (cbid != 0) { + block2structured_succs_[&blk].push_back(block(cbid)); + } + } + + // Add true successors. + const auto& const_blk = blk; + const_blk.ForEachSuccessorLabel([&blk, this](const uint32_t sbid) { + block2structured_succs_[&blk].push_back(block(sbid)); + }); + } +} + +void CFG::ComputePostOrderTraversal(BasicBlock* bb, vector* order, + unordered_set* seen) { + seen->insert(bb); + static_cast(bb)->ForEachSuccessorLabel( + [&order, &seen, this](const uint32_t sbid) { + BasicBlock* succ_bb = id2block_[sbid]; + if (!seen->count(succ_bb)) { + ComputePostOrderTraversal(succ_bb, order, seen); + } + }); + order->push_back(bb); +} + +BasicBlock* CFG::SplitLoopHeader(ir::BasicBlock* bb) { + assert(bb->GetLoopMergeInst() && "Expecting bb to be the header of a loop."); + + Function* fn = bb->GetParent(); + IRContext* context = fn->context(); + + // Find the insertion point for the new bb. + Function::iterator header_it = std::find_if( + fn->begin(), fn->end(), + [bb](BasicBlock& block_in_func) { return &block_in_func == bb; }); + assert(header_it != fn->end()); + + const std::vector& pred = preds(bb->id()); + // Find the back edge + ir::BasicBlock* latch_block = nullptr; + Function::iterator latch_block_iter = header_it; + while (++latch_block_iter != fn->end()) { + // If blocks are in the proper order, then the only branch that appears + // after the header is the latch. + if (std::find(pred.begin(), pred.end(), latch_block_iter->id()) != + pred.end()) { + break; + } + } + assert(latch_block_iter != fn->end() && "Could not find the latch."); + latch_block = &*latch_block_iter; + + RemoveSuccessorEdges(bb); + + // Create the new header bb basic bb. + // Leave the phi instructions behind. + auto iter = bb->begin(); + while (iter->opcode() == SpvOpPhi) { + ++iter; + } + + std::unique_ptr newBlock( + bb->SplitBasicBlock(context, context->TakeNextId(), iter)); + + // Insert the new bb in the correct position + auto insert_pos = header_it; + ++insert_pos; + ir::BasicBlock* new_header = &*insert_pos.InsertBefore(std::move(newBlock)); + new_header->SetParent(fn); + uint32_t new_header_id = new_header->id(); + context->AnalyzeDefUse(new_header->GetLabelInst()); + + // Update cfg + RegisterBlock(new_header); + + // Update bb mappings. + context->set_instr_block(new_header->GetLabelInst(), new_header); + new_header->ForEachInst([new_header, context](ir::Instruction* inst) { + context->set_instr_block(inst, new_header); + }); + + // Adjust the OpPhi instructions as needed. + bb->ForEachPhiInst([latch_block, bb, new_header, context](Instruction* phi) { + std::vector preheader_phi_ops; + std::vector header_phi_ops; + + // Identify where the original inputs to original OpPhi belong: header or + // preheader. + for (uint32_t i = 0; i < phi->NumInOperands(); i += 2) { + uint32_t def_id = phi->GetSingleWordInOperand(i); + uint32_t branch_id = phi->GetSingleWordInOperand(i + 1); + if (branch_id == latch_block->id()) { + header_phi_ops.push_back({SPV_OPERAND_TYPE_ID, {def_id}}); + header_phi_ops.push_back({SPV_OPERAND_TYPE_ID, {branch_id}}); + } else { + preheader_phi_ops.push_back(def_id); + preheader_phi_ops.push_back(branch_id); + } + } + + // Create a phi instruction if and only if the preheader_phi_ops has more + // than one pair. + if (preheader_phi_ops.size() > 2) { + opt::InstructionBuilder builder( + context, &*bb->begin(), + ir::IRContext::kAnalysisDefUse | + ir::IRContext::kAnalysisInstrToBlockMapping); + + ir::Instruction* new_phi = + builder.AddPhi(phi->type_id(), preheader_phi_ops); + + // Add the OpPhi to the header bb. + header_phi_ops.push_back({SPV_OPERAND_TYPE_ID, {new_phi->result_id()}}); + header_phi_ops.push_back({SPV_OPERAND_TYPE_ID, {bb->id()}}); + } else { + // An OpPhi with a single entry is just a copy. In this case use the same + // instruction in the new header. + header_phi_ops.push_back({SPV_OPERAND_TYPE_ID, {preheader_phi_ops[0]}}); + header_phi_ops.push_back({SPV_OPERAND_TYPE_ID, {bb->id()}}); + } + + phi->RemoveFromList(); + std::unique_ptr phi_owner(phi); + phi->SetInOperands(std::move(header_phi_ops)); + new_header->begin()->InsertBefore(std::move(phi_owner)); + context->set_instr_block(phi, new_header); + context->AnalyzeUses(phi); + }); + + // Add a branch to the new header. + opt::InstructionBuilder branch_builder( + context, bb, + ir::IRContext::kAnalysisDefUse | + ir::IRContext::kAnalysisInstrToBlockMapping); + bb->AddInstruction(MakeUnique( + context, SpvOpBranch, 0, 0, + std::initializer_list{ + {SPV_OPERAND_TYPE_ID, {new_header->id()}}})); + context->AnalyzeUses(bb->terminator()); + context->set_instr_block(bb->terminator(), bb); + label2preds_[new_header->id()].push_back(bb->id()); + + // Update the latch to branch to the new header. + latch_block->ForEachSuccessorLabel([bb, new_header_id](uint32_t* id) { + if (*id == bb->id()) { + *id = new_header_id; + } + }); + ir::Instruction* latch_branch = latch_block->terminator(); + context->AnalyzeUses(latch_branch); + label2preds_[new_header->id()].push_back(latch_block->id()); + + auto& block_preds = label2preds_[bb->id()]; + auto latch_pos = + std::find(block_preds.begin(), block_preds.end(), latch_block->id()); + assert(latch_pos != block_preds.end() && "The cfg was invalid."); + block_preds.erase(latch_pos); + + // Update the loop descriptors + if (context->AreAnalysesValid(ir::IRContext::kAnalysisLoopAnalysis)) { + LoopDescriptor* loop_desc = context->GetLoopDescriptor(bb->GetParent()); + Loop* loop = (*loop_desc)[bb->id()]; + + loop->AddBasicBlock(new_header_id); + loop->SetHeaderBlock(new_header); + loop_desc->SetBasicBlockToLoop(new_header_id, loop); + + loop->RemoveBasicBlock(bb->id()); + loop->SetPreHeaderBlock(bb); + + Loop* parent_loop = loop->GetParent(); + if (parent_loop != nullptr) { + parent_loop->AddBasicBlock(bb->id()); + loop_desc->SetBasicBlockToLoop(bb->id(), parent_loop); + } else { + loop_desc->SetBasicBlockToLoop(bb->id(), nullptr); + } + } + return new_header; +} + +unordered_set CFG::FindReachableBlocks(BasicBlock* start) { + std::unordered_set reachable_blocks; + ForEachBlockInReversePostOrder(start, [&reachable_blocks](BasicBlock* bb) { + reachable_blocks.insert(bb); + }); + return reachable_blocks; +} + +} // namespace ir +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/cfg.h b/3rdparty/spirv-tools/source/opt/cfg.h new file mode 100644 index 000000000..ffff7e176 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/cfg.h @@ -0,0 +1,175 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_CFG_H_ +#define LIBSPIRV_OPT_CFG_H_ + +#include "basic_block.h" + +#include +#include +#include +#include + +namespace spvtools { +namespace ir { + +class CFG { + public: + CFG(ir::Module* module); + + // Return the module described by this CFG. + ir::Module* get_module() const { return module_; } + + // Return the list of predecesors for basic block with label |blkid|. + // TODO(dnovillo): Move this to ir::BasicBlock. + const std::vector& preds(uint32_t blk_id) const { + assert(label2preds_.count(blk_id)); + return label2preds_.at(blk_id); + } + + // Return a pointer to the basic block instance corresponding to the label + // |blk_id|. + ir::BasicBlock* block(uint32_t blk_id) const { return id2block_.at(blk_id); } + + // Return the pseudo entry and exit blocks. + const ir::BasicBlock* pseudo_entry_block() const { + return &pseudo_entry_block_; + } + ir::BasicBlock* pseudo_entry_block() { return &pseudo_entry_block_; } + + const ir::BasicBlock* pseudo_exit_block() const { + return &pseudo_exit_block_; + } + ir::BasicBlock* pseudo_exit_block() { return &pseudo_exit_block_; } + + // Return true if |block_ptr| is the pseudo-entry block. + bool IsPseudoEntryBlock(ir::BasicBlock* block_ptr) const { + return block_ptr == &pseudo_entry_block_; + } + + // Return true if |block_ptr| is the pseudo-exit block. + bool IsPseudoExitBlock(ir::BasicBlock* block_ptr) const { + return block_ptr == &pseudo_exit_block_; + } + + // Compute structured block order into |order| for |func| starting at |root|. + // This order has the property that dominators come before all blocks they + // dominate and merge blocks come after all blocks that are in the control + // constructs of their header. + void ComputeStructuredOrder(ir::Function* func, ir::BasicBlock* root, + std::list* order); + + // Applies |f| to the basic block in reverse post order starting with |bb|. + // Note that basic blocks that cannot be reached from |bb| node will not be + // processed. + void ForEachBlockInReversePostOrder( + BasicBlock* bb, const std::function& f); + + // Registers |blk| as a basic block in the cfg, this also updates the + // predecessor lists of each successor of |blk|. + void RegisterBlock(ir::BasicBlock* blk) { + uint32_t blk_id = blk->id(); + id2block_[blk_id] = blk; + AddEdges(blk); + } + + // Removes from the CFG any mapping for the basic block id |blk_id|. + void ForgetBlock(const ir::BasicBlock* blk) { + id2block_.erase(blk->id()); + label2preds_.erase(blk->id()); + RemoveSuccessorEdges(blk); + } + + void RemoveEdge(uint32_t pred_blk_id, uint32_t succ_blk_id) { + auto pred_it = label2preds_.find(succ_blk_id); + if (pred_it == label2preds_.end()) return; + auto& preds_list = pred_it->second; + auto it = std::find(preds_list.begin(), preds_list.end(), pred_blk_id); + if (it != preds_list.end()) preds_list.erase(it); + } + + // Registers |blk| to all of its successors. + void AddEdges(ir::BasicBlock* blk); + + // Registers the basic block id |pred_blk_id| as being a predecessor of the + // basic block id |succ_blk_id|. + void AddEdge(uint32_t pred_blk_id, uint32_t succ_blk_id) { + label2preds_[succ_blk_id].push_back(pred_blk_id); + } + + // Removes any edges that no longer exist from the predecessor mapping for + // the basic block id |blk_id|. + void RemoveNonExistingEdges(uint32_t blk_id); + + // Remove all edges that leave |bb|. + void RemoveSuccessorEdges(const ir::BasicBlock* bb) { + bb->ForEachSuccessorLabel( + [bb, this](uint32_t succ_id) { RemoveEdge(bb->id(), succ_id); }); + } + + // Divides |block| into two basic blocks. The first block will have the same + // id as |block| and will become a preheader for the loop. The other block + // is a new block that will be the new loop header. + // + // Returns a pointer to the new loop header. + BasicBlock* SplitLoopHeader(ir::BasicBlock* bb); + + std::unordered_set FindReachableBlocks(BasicBlock* start); + + private: + using cbb_ptr = const ir::BasicBlock*; + + // Compute structured successors for function |func|. A block's structured + // successors are the blocks it branches to together with its declared merge + // block and continue block if it has them. When order matters, the merge + // block and continue block always appear first. This assures correct depth + // first search in the presence of early returns and kills. If the successor + // vector contain duplicates of the merge or continue blocks, they are safely + // ignored by DFS. + void ComputeStructuredSuccessors(ir::Function* func); + + // Computes the post-order traversal of the cfg starting at |bb| skipping + // nodes in |seen|. The order of the traversal is appended to |order|, and + // all nodes in the traversal are added to |seen|. + void ComputePostOrderTraversal(BasicBlock* bb, + std::vector* order, + std::unordered_set* seen); + + // Module for this CFG. + ir::Module* module_; + + // Map from block to its structured successor blocks. See + // ComputeStructuredSuccessors() for definition. + std::unordered_map> + block2structured_succs_; + + // Extra block whose successors are all blocks with no predecessors + // in function. + ir::BasicBlock pseudo_entry_block_; + + // Augmented CFG Exit Block. + ir::BasicBlock pseudo_exit_block_; + + // Map from block's label id to its predecessor blocks ids + std::unordered_map> label2preds_; + + // Map from block's label id to block. + std::unordered_map id2block_; +}; + +} // namespace ir +} // namespace spvtools + +#endif // LIBSPIRV_OPT_CFG_H_ diff --git a/3rdparty/spirv-tools/source/opt/cfg_cleanup_pass.cpp b/3rdparty/spirv-tools/source/opt/cfg_cleanup_pass.cpp new file mode 100644 index 000000000..2565225eb --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/cfg_cleanup_pass.cpp @@ -0,0 +1,43 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file implements a pass to cleanup the CFG to remove superfluous +// constructs (e.g., unreachable basic blocks, empty control flow structures, +// etc) + +#include +#include + +#include "cfg_cleanup_pass.h" + +#include "function.h" +#include "module.h" + +namespace spvtools { +namespace opt { + +void CFGCleanupPass::Initialize(ir::IRContext* c) { InitializeProcessing(c); } + +Pass::Status CFGCleanupPass::Process(ir::IRContext* c) { + Initialize(c); + + // Process all entry point functions. + ProcessFunction pfn = [this](ir::Function* fp) { return CFGCleanup(fp); }; + bool modified = ProcessReachableCallTree(pfn, context()); + return modified ? Pass::Status::SuccessWithChange + : Pass::Status::SuccessWithoutChange; +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/cfg_cleanup_pass.h b/3rdparty/spirv-tools/source/opt/cfg_cleanup_pass.h new file mode 100644 index 000000000..116e11d1b --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/cfg_cleanup_pass.h @@ -0,0 +1,43 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_CFG_CLEANUP_PASS_H_ +#define LIBSPIRV_OPT_CFG_CLEANUP_PASS_H_ + +#include "function.h" +#include "mem_pass.h" +#include "module.h" + +namespace spvtools { +namespace opt { + +class CFGCleanupPass : public MemPass { + public: + CFGCleanupPass() = default; + const char* name() const override { return "cfg-cleanup"; } + Status Process(ir::IRContext* c) override; + + ir::IRContext::Analysis GetPreservedAnalyses() override { + return ir::IRContext::kAnalysisDefUse; + } + + private: + // Initialize the pass. + void Initialize(ir::IRContext* c); +}; + +} // namespace opt +} // namespace spvtools + +#endif diff --git a/3rdparty/spirv-tools/source/opt/common_uniform_elim_pass.cpp b/3rdparty/spirv-tools/source/opt/common_uniform_elim_pass.cpp new file mode 100644 index 000000000..b060ea9c5 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/common_uniform_elim_pass.cpp @@ -0,0 +1,581 @@ +// Copyright (c) 2017 The Khronos Group Inc. +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "common_uniform_elim_pass.h" +#include "cfa.h" +#include "ir_context.h" + +namespace spvtools { +namespace opt { + +namespace { + +const uint32_t kAccessChainPtrIdInIdx = 0; +const uint32_t kTypePointerStorageClassInIdx = 0; +const uint32_t kTypePointerTypeIdInIdx = 1; +const uint32_t kConstantValueInIdx = 0; +const uint32_t kExtractCompositeIdInIdx = 0; +const uint32_t kExtractIdx0InIdx = 1; +const uint32_t kStorePtrIdInIdx = 0; +const uint32_t kLoadPtrIdInIdx = 0; +const uint32_t kCopyObjectOperandInIdx = 0; +const uint32_t kTypeIntWidthInIdx = 0; + +} // anonymous namespace + +bool CommonUniformElimPass::IsNonPtrAccessChain(const SpvOp opcode) const { + return opcode == SpvOpAccessChain || opcode == SpvOpInBoundsAccessChain; +} + +bool CommonUniformElimPass::IsSamplerOrImageType( + const ir::Instruction* typeInst) const { + switch (typeInst->opcode()) { + case SpvOpTypeSampler: + case SpvOpTypeImage: + case SpvOpTypeSampledImage: + return true; + default: + break; + } + if (typeInst->opcode() != SpvOpTypeStruct) return false; + // Return true if any member is a sampler or image + return !typeInst->WhileEachInId([this](const uint32_t* tid) { + const ir::Instruction* compTypeInst = get_def_use_mgr()->GetDef(*tid); + if (IsSamplerOrImageType(compTypeInst)) { + return false; + } + return true; + }); +} + +bool CommonUniformElimPass::IsSamplerOrImageVar(uint32_t varId) const { + const ir::Instruction* varInst = get_def_use_mgr()->GetDef(varId); + assert(varInst->opcode() == SpvOpVariable); + const uint32_t varTypeId = varInst->type_id(); + const ir::Instruction* varTypeInst = get_def_use_mgr()->GetDef(varTypeId); + const uint32_t varPteTypeId = + varTypeInst->GetSingleWordInOperand(kTypePointerTypeIdInIdx); + ir::Instruction* varPteTypeInst = get_def_use_mgr()->GetDef(varPteTypeId); + return IsSamplerOrImageType(varPteTypeInst); +} + +ir::Instruction* CommonUniformElimPass::GetPtr(ir::Instruction* ip, + uint32_t* objId) { + const SpvOp op = ip->opcode(); + assert(op == SpvOpStore || op == SpvOpLoad); + *objId = ip->GetSingleWordInOperand(op == SpvOpStore ? kStorePtrIdInIdx + : kLoadPtrIdInIdx); + ir::Instruction* ptrInst = get_def_use_mgr()->GetDef(*objId); + while (ptrInst->opcode() == SpvOpCopyObject) { + *objId = ptrInst->GetSingleWordInOperand(kCopyObjectOperandInIdx); + ptrInst = get_def_use_mgr()->GetDef(*objId); + } + ir::Instruction* objInst = ptrInst; + while (objInst->opcode() != SpvOpVariable && + objInst->opcode() != SpvOpFunctionParameter) { + if (IsNonPtrAccessChain(objInst->opcode())) { + *objId = objInst->GetSingleWordInOperand(kAccessChainPtrIdInIdx); + } else { + assert(objInst->opcode() == SpvOpCopyObject); + *objId = objInst->GetSingleWordInOperand(kCopyObjectOperandInIdx); + } + objInst = get_def_use_mgr()->GetDef(*objId); + } + return ptrInst; +} + +bool CommonUniformElimPass::IsVolatileStruct(uint32_t type_id) { + assert(get_def_use_mgr()->GetDef(type_id)->opcode() == SpvOpTypeStruct); + return !get_decoration_mgr()->WhileEachDecoration( + type_id, SpvDecorationVolatile, + [](const ir::Instruction&) { return false; }); +} + +bool CommonUniformElimPass::IsAccessChainToVolatileStructType( + const ir::Instruction& AccessChainInst) { + assert(AccessChainInst.opcode() == SpvOpAccessChain); + + uint32_t ptr_id = AccessChainInst.GetSingleWordInOperand(0); + const ir::Instruction* ptr_inst = get_def_use_mgr()->GetDef(ptr_id); + uint32_t pointee_type_id = GetPointeeTypeId(ptr_inst); + const uint32_t num_operands = AccessChainInst.NumOperands(); + + // walk the type tree: + for (uint32_t idx = 3; idx < num_operands; ++idx) { + ir::Instruction* pointee_type = get_def_use_mgr()->GetDef(pointee_type_id); + + switch (pointee_type->opcode()) { + case SpvOpTypeMatrix: + case SpvOpTypeVector: + case SpvOpTypeArray: + case SpvOpTypeRuntimeArray: + pointee_type_id = pointee_type->GetSingleWordOperand(1); + break; + case SpvOpTypeStruct: + // check for volatile decorations: + if (IsVolatileStruct(pointee_type_id)) return true; + + if (idx < num_operands - 1) { + const uint32_t index_id = AccessChainInst.GetSingleWordOperand(idx); + const ir::Instruction* index_inst = + get_def_use_mgr()->GetDef(index_id); + uint32_t index_value = index_inst->GetSingleWordOperand( + 2); // TODO: replace with GetUintValueFromConstant() + pointee_type_id = pointee_type->GetSingleWordInOperand(index_value); + } + break; + default: + assert(false && "Unhandled pointee type."); + } + } + return false; +} + +bool CommonUniformElimPass::IsVolatileLoad(const ir::Instruction& loadInst) { + assert(loadInst.opcode() == SpvOpLoad); + // Check if this Load instruction has Volatile Memory Access flag + if (loadInst.NumOperands() == 4) { + uint32_t memory_access_mask = loadInst.GetSingleWordOperand(3); + if (memory_access_mask & SpvMemoryAccessVolatileMask) return true; + } + // If we load a struct directly (result type is struct), + // check if the struct is decorated volatile + uint32_t type_id = loadInst.type_id(); + if (get_def_use_mgr()->GetDef(type_id)->opcode() == SpvOpTypeStruct) + return IsVolatileStruct(type_id); + else + return false; +} + +bool CommonUniformElimPass::IsUniformVar(uint32_t varId) { + const ir::Instruction* varInst = + get_def_use_mgr()->id_to_defs().find(varId)->second; + if (varInst->opcode() != SpvOpVariable) return false; + const uint32_t varTypeId = varInst->type_id(); + const ir::Instruction* varTypeInst = + get_def_use_mgr()->id_to_defs().find(varTypeId)->second; + return varTypeInst->GetSingleWordInOperand(kTypePointerStorageClassInIdx) == + SpvStorageClassUniform || + varTypeInst->GetSingleWordInOperand(kTypePointerStorageClassInIdx) == + SpvStorageClassUniformConstant; +} + +bool CommonUniformElimPass::HasUnsupportedDecorates(uint32_t id) const { + return !get_def_use_mgr()->WhileEachUser(id, [this](ir::Instruction* user) { + if (IsNonTypeDecorate(user->opcode())) return false; + return true; + }); +} + +bool CommonUniformElimPass::HasOnlyNamesAndDecorates(uint32_t id) const { + return get_def_use_mgr()->WhileEachUser(id, [this](ir::Instruction* user) { + SpvOp op = user->opcode(); + if (op != SpvOpName && !IsNonTypeDecorate(op)) return false; + return true; + }); +} + +void CommonUniformElimPass::DeleteIfUseless(ir::Instruction* inst) { + const uint32_t resId = inst->result_id(); + assert(resId != 0); + if (HasOnlyNamesAndDecorates(resId)) { + context()->KillInst(inst); + } +} + +ir::Instruction* CommonUniformElimPass::ReplaceAndDeleteLoad( + ir::Instruction* loadInst, uint32_t replId, ir::Instruction* ptrInst) { + const uint32_t loadId = loadInst->result_id(); + context()->KillNamesAndDecorates(loadId); + (void)context()->ReplaceAllUsesWith(loadId, replId); + // remove load instruction + ir::Instruction* next_instruction = context()->KillInst(loadInst); + // if access chain, see if it can be removed as well + if (IsNonPtrAccessChain(ptrInst->opcode())) DeleteIfUseless(ptrInst); + return next_instruction; +} + +void CommonUniformElimPass::GenACLoadRepl( + const ir::Instruction* ptrInst, + std::vector>* newInsts, + uint32_t* resultId) { + // Build and append Load + const uint32_t ldResultId = TakeNextId(); + const uint32_t varId = + ptrInst->GetSingleWordInOperand(kAccessChainPtrIdInIdx); + const ir::Instruction* varInst = get_def_use_mgr()->GetDef(varId); + assert(varInst->opcode() == SpvOpVariable); + const uint32_t varPteTypeId = GetPointeeTypeId(varInst); + std::vector load_in_operands; + load_in_operands.push_back( + ir::Operand(spv_operand_type_t::SPV_OPERAND_TYPE_ID, + std::initializer_list{varId})); + std::unique_ptr newLoad(new ir::Instruction( + context(), SpvOpLoad, varPteTypeId, ldResultId, load_in_operands)); + get_def_use_mgr()->AnalyzeInstDefUse(&*newLoad); + newInsts->emplace_back(std::move(newLoad)); + + // Build and append Extract + const uint32_t extResultId = TakeNextId(); + const uint32_t ptrPteTypeId = GetPointeeTypeId(ptrInst); + std::vector ext_in_opnds; + ext_in_opnds.push_back( + ir::Operand(spv_operand_type_t::SPV_OPERAND_TYPE_ID, + std::initializer_list{ldResultId})); + uint32_t iidIdx = 0; + ptrInst->ForEachInId([&iidIdx, &ext_in_opnds, this](const uint32_t* iid) { + if (iidIdx > 0) { + const ir::Instruction* cInst = get_def_use_mgr()->GetDef(*iid); + uint32_t val = cInst->GetSingleWordInOperand(kConstantValueInIdx); + ext_in_opnds.push_back( + ir::Operand(spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, + std::initializer_list{val})); + } + ++iidIdx; + }); + std::unique_ptr newExt( + new ir::Instruction(context(), SpvOpCompositeExtract, ptrPteTypeId, + extResultId, ext_in_opnds)); + get_def_use_mgr()->AnalyzeInstDefUse(&*newExt); + newInsts->emplace_back(std::move(newExt)); + *resultId = extResultId; +} + +bool CommonUniformElimPass::IsConstantIndexAccessChain(ir::Instruction* acp) { + uint32_t inIdx = 0; + return acp->WhileEachInId([&inIdx, this](uint32_t* tid) { + if (inIdx > 0) { + ir::Instruction* opInst = get_def_use_mgr()->GetDef(*tid); + if (opInst->opcode() != SpvOpConstant) return false; + } + ++inIdx; + return true; + }); +} + +bool CommonUniformElimPass::UniformAccessChainConvert(ir::Function* func) { + bool modified = false; + for (auto bi = func->begin(); bi != func->end(); ++bi) { + for (ir::Instruction* inst = &*bi->begin(); inst; inst = inst->NextNode()) { + if (inst->opcode() != SpvOpLoad) continue; + uint32_t varId; + ir::Instruction* ptrInst = GetPtr(inst, &varId); + if (!IsNonPtrAccessChain(ptrInst->opcode())) continue; + // Do not convert nested access chains + if (ptrInst->GetSingleWordInOperand(kAccessChainPtrIdInIdx) != varId) + continue; + if (!IsUniformVar(varId)) continue; + if (!IsConstantIndexAccessChain(ptrInst)) continue; + if (HasUnsupportedDecorates(inst->result_id())) continue; + if (HasUnsupportedDecorates(ptrInst->result_id())) continue; + if (IsVolatileLoad(*inst)) continue; + if (IsAccessChainToVolatileStructType(*ptrInst)) continue; + std::vector> newInsts; + uint32_t replId; + GenACLoadRepl(ptrInst, &newInsts, &replId); + inst = ReplaceAndDeleteLoad(inst, replId, ptrInst); + inst = inst->InsertBefore(std::move(newInsts)); + modified = true; + }; + } + return modified; +} + +void CommonUniformElimPass::ComputeStructuredSuccessors(ir::Function* func) { + block2structured_succs_.clear(); + for (auto& blk : *func) { + // If header, make merge block first successor. + uint32_t mbid = blk.MergeBlockIdIfAny(); + if (mbid != 0) { + block2structured_succs_[&blk].push_back(cfg()->block(mbid)); + uint32_t cbid = blk.ContinueBlockIdIfAny(); + if (cbid != 0) { + block2structured_succs_[&blk].push_back(cfg()->block(mbid)); + } + } + // add true successors + const auto& const_blk = blk; + const_blk.ForEachSuccessorLabel([&blk, this](const uint32_t sbid) { + block2structured_succs_[&blk].push_back(cfg()->block(sbid)); + }); + } +} + +void CommonUniformElimPass::ComputeStructuredOrder( + ir::Function* func, std::list* order) { + // Compute structured successors and do DFS + ComputeStructuredSuccessors(func); + auto ignore_block = [](cbb_ptr) {}; + auto ignore_edge = [](cbb_ptr, cbb_ptr) {}; + auto get_structured_successors = [this](const ir::BasicBlock* block) { + return &(block2structured_succs_[block]); + }; + // TODO(greg-lunarg): Get rid of const_cast by making moving const + // out of the cfa.h prototypes and into the invoking code. + auto post_order = [&](cbb_ptr b) { + order->push_front(const_cast(b)); + }; + + order->clear(); + spvtools::CFA::DepthFirstTraversal( + &*func->begin(), get_structured_successors, ignore_block, post_order, + ignore_edge); +} + +bool CommonUniformElimPass::CommonUniformLoadElimination(ir::Function* func) { + // Process all blocks in structured order. This is just one way (the + // simplest?) to keep track of the most recent block outside of control + // flow, used to copy common instructions, guaranteed to dominate all + // following load sites. + std::list structuredOrder; + ComputeStructuredOrder(func, &structuredOrder); + uniform2load_id_.clear(); + bool modified = false; + // Find insertion point in first block to copy non-dominating loads. + auto insertItr = func->begin()->begin(); + while (insertItr->opcode() == SpvOpVariable || + insertItr->opcode() == SpvOpNop) + ++insertItr; + uint32_t mergeBlockId = 0; + for (auto bi = structuredOrder.begin(); bi != structuredOrder.end(); ++bi) { + ir::BasicBlock* bp = *bi; + // Check if we are exiting outermost control construct. If so, remember + // new load insertion point. Trying to keep register pressure down. + if (mergeBlockId == bp->id()) { + mergeBlockId = 0; + insertItr = bp->begin(); + } + for (ir::Instruction* inst = &*bp->begin(); inst; inst = inst->NextNode()) { + if (inst->opcode() != SpvOpLoad) continue; + uint32_t varId; + ir::Instruction* ptrInst = GetPtr(inst, &varId); + if (ptrInst->opcode() != SpvOpVariable) continue; + if (!IsUniformVar(varId)) continue; + if (IsSamplerOrImageVar(varId)) continue; + if (HasUnsupportedDecorates(inst->result_id())) continue; + if (IsVolatileLoad(*inst)) continue; + uint32_t replId; + const auto uItr = uniform2load_id_.find(varId); + if (uItr != uniform2load_id_.end()) { + replId = uItr->second; + } else { + if (mergeBlockId == 0) { + // Load is in dominating block; just remember it + uniform2load_id_[varId] = inst->result_id(); + continue; + } else { + // Copy load into most recent dominating block and remember it + replId = TakeNextId(); + std::unique_ptr newLoad(new ir::Instruction( + context(), SpvOpLoad, inst->type_id(), replId, + {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {varId}}})); + get_def_use_mgr()->AnalyzeInstDefUse(&*newLoad); + insertItr = insertItr.InsertBefore(std::move(newLoad)); + ++insertItr; + uniform2load_id_[varId] = replId; + } + } + inst = ReplaceAndDeleteLoad(inst, replId, ptrInst); + modified = true; + } + // If we are outside of any control construct and entering one, remember + // the id of the merge block + if (mergeBlockId == 0) { + mergeBlockId = bp->MergeBlockIdIfAny(); + } + } + return modified; +} + +bool CommonUniformElimPass::CommonUniformLoadElimBlock(ir::Function* func) { + bool modified = false; + for (auto& blk : *func) { + uniform2load_id_.clear(); + for (ir::Instruction* inst = &*blk.begin(); inst; inst = inst->NextNode()) { + if (inst->opcode() != SpvOpLoad) continue; + uint32_t varId; + ir::Instruction* ptrInst = GetPtr(inst, &varId); + if (ptrInst->opcode() != SpvOpVariable) continue; + if (!IsUniformVar(varId)) continue; + if (!IsSamplerOrImageVar(varId)) continue; + if (HasUnsupportedDecorates(inst->result_id())) continue; + if (IsVolatileLoad(*inst)) continue; + uint32_t replId; + const auto uItr = uniform2load_id_.find(varId); + if (uItr != uniform2load_id_.end()) { + replId = uItr->second; + } else { + uniform2load_id_[varId] = inst->result_id(); + continue; + } + inst = ReplaceAndDeleteLoad(inst, replId, ptrInst); + modified = true; + } + } + return modified; +} + +bool CommonUniformElimPass::CommonExtractElimination(ir::Function* func) { + // Find all composite ids with duplicate extracts. + for (auto bi = func->begin(); bi != func->end(); ++bi) { + for (auto ii = bi->begin(); ii != bi->end(); ++ii) { + if (ii->opcode() != SpvOpCompositeExtract) continue; + // TODO(greg-lunarg): Support multiple indices + if (ii->NumInOperands() > 2) continue; + if (HasUnsupportedDecorates(ii->result_id())) continue; + uint32_t compId = ii->GetSingleWordInOperand(kExtractCompositeIdInIdx); + uint32_t idx = ii->GetSingleWordInOperand(kExtractIdx0InIdx); + comp2idx2inst_[compId][idx].push_back(&*ii); + } + } + // For all defs of ids with duplicate extracts, insert new extracts + // after def, and replace and delete old extracts + bool modified = false; + for (auto bi = func->begin(); bi != func->end(); ++bi) { + for (auto ii = bi->begin(); ii != bi->end(); ++ii) { + const auto cItr = comp2idx2inst_.find(ii->result_id()); + if (cItr == comp2idx2inst_.end()) continue; + for (auto idxItr : cItr->second) { + if (idxItr.second.size() < 2) continue; + uint32_t replId = TakeNextId(); + std::unique_ptr newExtract( + idxItr.second.front()->Clone(context())); + newExtract->SetResultId(replId); + get_def_use_mgr()->AnalyzeInstDefUse(&*newExtract); + ++ii; + ii = ii.InsertBefore(std::move(newExtract)); + for (auto instItr : idxItr.second) { + uint32_t resId = instItr->result_id(); + context()->KillNamesAndDecorates(resId); + (void)context()->ReplaceAllUsesWith(resId, replId); + context()->KillInst(instItr); + } + modified = true; + } + } + } + return modified; +} + +bool CommonUniformElimPass::EliminateCommonUniform(ir::Function* func) { + bool modified = false; + modified |= UniformAccessChainConvert(func); + modified |= CommonUniformLoadElimination(func); + modified |= CommonExtractElimination(func); + + modified |= CommonUniformLoadElimBlock(func); + return modified; +} + +void CommonUniformElimPass::Initialize(ir::IRContext* c) { + InitializeProcessing(c); + + // Clear collections. + comp2idx2inst_.clear(); + + // Initialize extension whitelist + InitExtensions(); +} + +bool CommonUniformElimPass::AllExtensionsSupported() const { + // If any extension not in whitelist, return false + for (auto& ei : get_module()->extensions()) { + const char* extName = + reinterpret_cast(&ei.GetInOperand(0).words[0]); + if (extensions_whitelist_.find(extName) == extensions_whitelist_.end()) + return false; + } + return true; +} + +Pass::Status CommonUniformElimPass::ProcessImpl() { + // Assumes all control flow structured. + // TODO(greg-lunarg): Do SSA rewrite for non-structured control flow + if (!context()->get_feature_mgr()->HasCapability(SpvCapabilityShader)) + return Status::SuccessWithoutChange; + // Assumes logical addressing only + // TODO(greg-lunarg): Add support for physical addressing + if (context()->get_feature_mgr()->HasCapability(SpvCapabilityAddresses)) + return Status::SuccessWithoutChange; + // Do not process if any disallowed extensions are enabled + if (!AllExtensionsSupported()) return Status::SuccessWithoutChange; + // Do not process if module contains OpGroupDecorate. Additional + // support required in KillNamesAndDecorates(). + // TODO(greg-lunarg): Add support for OpGroupDecorate + for (auto& ai : get_module()->annotations()) + if (ai.opcode() == SpvOpGroupDecorate) return Status::SuccessWithoutChange; + // If non-32-bit integer type in module, terminate processing + // TODO(): Handle non-32-bit integer constants in access chains + for (const ir::Instruction& inst : get_module()->types_values()) + if (inst.opcode() == SpvOpTypeInt && + inst.GetSingleWordInOperand(kTypeIntWidthInIdx) != 32) + return Status::SuccessWithoutChange; + // Process entry point functions + ProcessFunction pfn = [this](ir::Function* fp) { + return EliminateCommonUniform(fp); + }; + bool modified = ProcessEntryPointCallTree(pfn, get_module()); + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; +} + +CommonUniformElimPass::CommonUniformElimPass() {} + +Pass::Status CommonUniformElimPass::Process(ir::IRContext* c) { + Initialize(c); + return ProcessImpl(); +} + +void CommonUniformElimPass::InitExtensions() { + extensions_whitelist_.clear(); + extensions_whitelist_.insert({ + "SPV_AMD_shader_explicit_vertex_parameter", + "SPV_AMD_shader_trinary_minmax", + "SPV_AMD_gcn_shader", + "SPV_KHR_shader_ballot", + "SPV_AMD_shader_ballot", + "SPV_AMD_gpu_shader_half_float", + "SPV_KHR_shader_draw_parameters", + "SPV_KHR_subgroup_vote", + "SPV_KHR_16bit_storage", + "SPV_KHR_device_group", + "SPV_KHR_multiview", + "SPV_NVX_multiview_per_view_attributes", + "SPV_NV_viewport_array2", + "SPV_NV_stereo_view_rendering", + "SPV_NV_sample_mask_override_coverage", + "SPV_NV_geometry_shader_passthrough", + "SPV_AMD_texture_gather_bias_lod", + "SPV_KHR_storage_buffer_storage_class", + // SPV_KHR_variable_pointers + // Currently do not support extended pointer expressions + "SPV_AMD_gpu_shader_int16", + "SPV_KHR_post_depth_coverage", + "SPV_KHR_shader_atomic_counter_ops", + "SPV_EXT_shader_stencil_export", + "SPV_EXT_shader_viewport_index_layer", + "SPV_AMD_shader_image_load_store_lod", + "SPV_AMD_shader_fragment_mask", + "SPV_EXT_fragment_fully_covered", + "SPV_AMD_gpu_shader_half_float_fetch", + "SPV_GOOGLE_decorate_string", + "SPV_GOOGLE_hlsl_functionality1", + "SPV_NV_shader_subgroup_partitioned", + "SPV_EXT_descriptor_indexing", + }); +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/common_uniform_elim_pass.h b/3rdparty/spirv-tools/source/opt/common_uniform_elim_pass.h new file mode 100644 index 000000000..641520892 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/common_uniform_elim_pass.h @@ -0,0 +1,197 @@ +// Copyright (c) 2016 The Khronos Group Inc. +// Copyright (c) 2016 Valve Corporation +// Copyright (c) 2016 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_COMMON_UNIFORM_ELIM_PASS_H_ +#define LIBSPIRV_OPT_COMMON_UNIFORM_ELIM_PASS_H_ + +#include +#include +#include +#include +#include +#include + +#include "basic_block.h" +#include "decoration_manager.h" +#include "def_use_manager.h" +#include "ir_context.h" +#include "module.h" +#include "pass.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class CommonUniformElimPass : public Pass { + using cbb_ptr = const ir::BasicBlock*; + + public: + using GetBlocksFunction = + std::function*(const ir::BasicBlock*)>; + + CommonUniformElimPass(); + const char* name() const override { return "eliminate-common-uniform"; } + Status Process(ir::IRContext*) override; + + private: + // Returns true if |opcode| is a non-ptr access chain op + bool IsNonPtrAccessChain(const SpvOp opcode) const; + + // Returns true if |typeInst| is a sampler or image type or a struct + // containing one, recursively. + bool IsSamplerOrImageType(const ir::Instruction* typeInst) const; + + // Returns true if |varId| is a variable containing a sampler or image. + bool IsSamplerOrImageVar(uint32_t varId) const; + + // Given a load or store pointed at by |ip|, return the top-most + // non-CopyObj in its pointer operand. Also return the base pointer + // in |objId|. + ir::Instruction* GetPtr(ir::Instruction* ip, uint32_t* objId); + + // Return true if variable is uniform + bool IsUniformVar(uint32_t varId); + + // Given the type id for a struct type, checks if the struct type + // or any struct member is volatile decorated + bool IsVolatileStruct(uint32_t type_id); + + // Given an OpAccessChain instruction, return true + // if the accessed variable belongs to a volatile + // decorated object or member of a struct type + bool IsAccessChainToVolatileStructType( + const ir::Instruction& AccessChainInst); + + // Given an OpLoad instruction, return true if + // OpLoad has a Volatile Memory Access flag or if + // the resulting type is a volatile decorated struct + bool IsVolatileLoad(const ir::Instruction& loadInst); + + // Return true if any uses of |id| are decorate ops. + bool HasUnsupportedDecorates(uint32_t id) const; + + // Return true if all uses of |id| are only name or decorate ops. + bool HasOnlyNamesAndDecorates(uint32_t id) const; + + // Delete inst if it has no uses. Assumes inst has a resultId. + void DeleteIfUseless(ir::Instruction* inst); + + // Replace all instances of load's id with replId and delete load + // and its access chain, if any + ir::Instruction* ReplaceAndDeleteLoad(ir::Instruction* loadInst, + uint32_t replId, + ir::Instruction* ptrInst); + + // For the (constant index) access chain ptrInst, create an + // equivalent load and extract + void GenACLoadRepl(const ir::Instruction* ptrInst, + std::vector>* newInsts, + uint32_t* resultId); + + // Return true if all indices are constant + bool IsConstantIndexAccessChain(ir::Instruction* acp); + + // Convert all uniform access chain loads into load/extract. + bool UniformAccessChainConvert(ir::Function* func); + + // Compute structured successors for function |func|. + // A block's structured successors are the blocks it branches to + // together with its declared merge block if it has one. + // When order matters, the merge block always appears first. + // This assures correct depth first search in the presence of early + // returns and kills. If the successor vector contain duplicates + // if the merge block, they are safely ignored by DFS. + // + // TODO(dnovillo): This pass computes structured successors slightly different + // than the implementation in class Pass. Can this be re-factored? + void ComputeStructuredSuccessors(ir::Function* func); + + // Compute structured block order for |func| into |structuredOrder|. This + // order has the property that dominators come before all blocks they + // dominate and merge blocks come after all blocks that are in the control + // constructs of their header. + // + // TODO(dnovillo): This pass computes structured order slightly different + // than the implementation in class Pass. Can this be re-factored? + void ComputeStructuredOrder(ir::Function* func, + std::list* order); + + // Eliminate loads of uniform variables which have previously been loaded. + // If first load is in control flow, move it to first block of function. + // Most effective if preceded by UniformAccessChainRemoval(). + bool CommonUniformLoadElimination(ir::Function* func); + + // Eliminate loads of uniform sampler and image variables which have + // previously + // been loaded in the same block for types whose loads cannot cross blocks. + bool CommonUniformLoadElimBlock(ir::Function* func); + + // Eliminate duplicated extracts of same id. Extract may be moved to same + // block as the id definition. This is primarily intended for extracts + // from uniform loads. Most effective if preceded by + // CommonUniformLoadElimination(). + bool CommonExtractElimination(ir::Function* func); + + // For function |func|, first change all uniform constant index + // access chain loads into equivalent composite extracts. Then consolidate + // identical uniform loads into one uniform load. Finally, consolidate + // identical uniform extracts into one uniform extract. This may require + // moving a load or extract to a point which dominates all uses. + // Return true if func is modified. + // + // This pass requires the function to have structured control flow ie shader + // capability. It also requires logical addressing ie Addresses capability + // is not enabled. It also currently does not support any extensions. + // + // This function currently only optimizes loads with a single index. + bool EliminateCommonUniform(ir::Function* func); + + // Initialize extensions whitelist + void InitExtensions(); + + // Return true if all extensions in this module are allowed by this pass. + bool AllExtensionsSupported() const; + + // Return true if |op| is a decorate for non-type instruction + inline bool IsNonTypeDecorate(uint32_t op) const { + return (op == SpvOpDecorate || op == SpvOpDecorateId); + } + + void Initialize(ir::IRContext* c); + Pass::Status ProcessImpl(); + + // Map from uniform variable id to its common load id + std::unordered_map uniform2load_id_; + + // Map of extract composite ids to map of indices to insts + // TODO(greg-lunarg): Consider std::vector. + std::unordered_map>> + comp2idx2inst_; + + // Extensions supported by this pass. + std::unordered_set extensions_whitelist_; + + // Map from block to its structured successor blocks. See + // ComputeStructuredSuccessors() for definition. + std::unordered_map> + block2structured_succs_; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_SSAMEM_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/compact_ids_pass.cpp b/3rdparty/spirv-tools/source/opt/compact_ids_pass.cpp new file mode 100644 index 000000000..98a207da7 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/compact_ids_pass.cpp @@ -0,0 +1,74 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "compact_ids_pass.h" +#include "ir_context.h" + +#include +#include + +namespace spvtools { +namespace opt { + +using ir::Instruction; +using ir::Operand; + +Pass::Status CompactIdsPass::Process(ir::IRContext* c) { + InitializeProcessing(c); + + bool modified = false; + std::unordered_map result_id_mapping; + + c->module()->ForEachInst( + [&result_id_mapping, &modified](Instruction* inst) { + auto operand = inst->begin(); + while (operand != inst->end()) { + const auto type = operand->type; + if (spvIsIdType(type)) { + assert(operand->words.size() == 1); + uint32_t& id = operand->words[0]; + auto it = result_id_mapping.find(id); + if (it == result_id_mapping.end()) { + const uint32_t new_id = + static_cast(result_id_mapping.size()) + 1; + const auto insertion_result = + result_id_mapping.emplace(id, new_id); + it = insertion_result.first; + assert(insertion_result.second); + } + if (id != it->second) { + modified = true; + id = it->second; + // Update data cached in the instruction object. + if (type == SPV_OPERAND_TYPE_RESULT_ID) { + inst->SetResultId(id); + } else if (type == SPV_OPERAND_TYPE_TYPE_ID) { + inst->SetResultType(id); + } + } + } + ++operand; + } + }, + true); + + if (modified) + c->module()->SetIdBound( + static_cast(result_id_mapping.size() + 1)); + + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/compact_ids_pass.h b/3rdparty/spirv-tools/source/opt/compact_ids_pass.h new file mode 100644 index 000000000..cf7c3fb72 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/compact_ids_pass.h @@ -0,0 +1,35 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_COMPACT_IDS_PASS_H_ +#define LIBSPIRV_OPT_COMPACT_IDS_PASS_H_ + +#include "ir_context.h" +#include "module.h" +#include "pass.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class CompactIdsPass : public Pass { + public: + const char* name() const override { return "compact-ids"; } + Status Process(ir::IRContext*) override; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_COMPACT_IDS_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/composite.cpp b/3rdparty/spirv-tools/source/opt/composite.cpp new file mode 100644 index 000000000..1fbb71f69 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/composite.cpp @@ -0,0 +1,52 @@ +// Copyright (c) 2018 The Khronos Group Inc. +// Copyright (c) 2018 Valve Corporation +// Copyright (c) 2018 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "composite.h" + +#include "ir_context.h" +#include "iterator.h" +#include "spirv/1.2/GLSL.std.450.h" + +#include + +namespace spvtools { +namespace opt { + +bool ExtInsMatch(const std::vector& extIndices, + const ir::Instruction* insInst, const uint32_t extOffset) { + uint32_t numIndices = static_cast(extIndices.size()) - extOffset; + if (numIndices != insInst->NumInOperands() - 2) return false; + for (uint32_t i = 0; i < numIndices; ++i) + if (extIndices[i + extOffset] != insInst->GetSingleWordInOperand(i + 2)) + return false; + return true; +} + +bool ExtInsConflict(const std::vector& extIndices, + const ir::Instruction* insInst, const uint32_t extOffset) { + if (extIndices.size() - extOffset == insInst->NumInOperands() - 2) + return false; + uint32_t extNumIndices = static_cast(extIndices.size()) - extOffset; + uint32_t insNumIndices = insInst->NumInOperands() - 2; + uint32_t numIndices = std::min(extNumIndices, insNumIndices); + for (uint32_t i = 0; i < numIndices; ++i) + if (extIndices[i + extOffset] != insInst->GetSingleWordInOperand(i + 2)) + return false; + return true; +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/composite.h b/3rdparty/spirv-tools/source/opt/composite.h new file mode 100644 index 000000000..2153c626c --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/composite.h @@ -0,0 +1,50 @@ +// Copyright (c) 2018 The Khronos Group Inc. +// Copyright (c) 2018 Valve Corporation +// Copyright (c) 2018 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_UTIL_COMPOSITE_PASS_H_ +#define LIBSPIRV_UTIL_COMPOSITE_PASS_H_ + +#include +#include +#include +#include +#include + +#include "basic_block.h" +#include "def_use_manager.h" +#include "ir_context.h" +#include "module.h" + +namespace spvtools { +namespace opt { + +// Return true if the extract indices in |extIndices| starting at |extOffset| +// match indices of insert |insInst|. +bool ExtInsMatch(const std::vector& extIndices, + const ir::Instruction* insInst, const uint32_t extOffset); + +// Return true if indices in |extIndices| starting at |extOffset| and +// indices of insert |insInst| conflict, specifically, if the insert +// changes bits specified by the extract, but changes either more bits +// or less bits than the extract specifies, meaning the exact value being +// inserted cannot be used to replace the extract. +bool ExtInsConflict(const std::vector& extIndices, + const ir::Instruction* insInst, const uint32_t extOffset); + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_UTIL_COMPOSITE_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/const_folding_rules.cpp b/3rdparty/spirv-tools/source/opt/const_folding_rules.cpp new file mode 100644 index 000000000..a2d23c172 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/const_folding_rules.cpp @@ -0,0 +1,488 @@ +// Copyright (c) 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "const_folding_rules.h" + +namespace spvtools { +namespace opt { + +namespace { +const uint32_t kExtractCompositeIdInIdx = 0; + +// Folds an OpcompositeExtract where input is a composite constant. +ConstantFoldingRule FoldExtractWithConstants() { + return [](ir::Instruction* inst, + const std::vector& constants) + -> const analysis::Constant* { + const analysis::Constant* c = constants[kExtractCompositeIdInIdx]; + if (c == nullptr) { + return nullptr; + } + + for (uint32_t i = 1; i < inst->NumInOperands(); ++i) { + uint32_t element_index = inst->GetSingleWordInOperand(i); + if (c->AsNullConstant()) { + // Return Null for the return type. + ir::IRContext* context = inst->context(); + analysis::ConstantManager* const_mgr = context->get_constant_mgr(); + analysis::TypeManager* type_mgr = context->get_type_mgr(); + return const_mgr->GetConstant(type_mgr->GetType(inst->type_id()), {}); + } + + auto cc = c->AsCompositeConstant(); + assert(cc != nullptr); + auto components = cc->GetComponents(); + c = components[element_index]; + } + return c; + }; +} + +ConstantFoldingRule FoldVectorShuffleWithConstants() { + return [](ir::Instruction* inst, + const std::vector& constants) + -> const analysis::Constant* { + assert(inst->opcode() == SpvOpVectorShuffle); + const analysis::Constant* c1 = constants[0]; + const analysis::Constant* c2 = constants[1]; + if (c1 == nullptr || c2 == nullptr) { + return nullptr; + } + + ir::IRContext* context = inst->context(); + analysis::ConstantManager* const_mgr = context->get_constant_mgr(); + const analysis::Type* element_type = c1->type()->AsVector()->element_type(); + + std::vector c1_components; + if (const analysis::VectorConstant* vec_const = c1->AsVectorConstant()) { + c1_components = vec_const->GetComponents(); + } else { + assert(c1->AsNullConstant()); + const analysis::Constant* element = + const_mgr->GetConstant(element_type, {}); + c1_components.resize(c1->type()->AsVector()->element_count(), element); + } + std::vector c2_components; + if (const analysis::VectorConstant* vec_const = c2->AsVectorConstant()) { + c2_components = vec_const->GetComponents(); + } else { + assert(c2->AsNullConstant()); + const analysis::Constant* element = + const_mgr->GetConstant(element_type, {}); + c2_components.resize(c2->type()->AsVector()->element_count(), element); + } + + std::vector ids; + for (uint32_t i = 2; i < inst->NumInOperands(); ++i) { + uint32_t index = inst->GetSingleWordInOperand(i); + if (index < c1_components.size()) { + ir::Instruction* member_inst = + const_mgr->GetDefiningInstruction(c1_components[index]); + ids.push_back(member_inst->result_id()); + } else { + ir::Instruction* member_inst = const_mgr->GetDefiningInstruction( + c2_components[index - c1_components.size()]); + ids.push_back(member_inst->result_id()); + } + } + + analysis::TypeManager* type_mgr = context->get_type_mgr(); + return const_mgr->GetConstant(type_mgr->GetType(inst->type_id()), ids); + }; +} // namespace + +ConstantFoldingRule FoldCompositeWithConstants() { + // Folds an OpCompositeConstruct where all of the inputs are constants to a + // constant. A new constant is created if necessary. + return [](ir::Instruction* inst, + const std::vector& constants) + -> const analysis::Constant* { + ir::IRContext* context = inst->context(); + analysis::ConstantManager* const_mgr = context->get_constant_mgr(); + analysis::TypeManager* type_mgr = context->get_type_mgr(); + const analysis::Type* new_type = type_mgr->GetType(inst->type_id()); + + std::vector ids; + for (const analysis::Constant* element_const : constants) { + if (element_const == nullptr) { + return nullptr; + } + uint32_t element_id = const_mgr->FindDeclaredConstant(element_const); + if (element_id == 0) { + return nullptr; + } + ids.push_back(element_id); + } + return const_mgr->GetConstant(new_type, ids); + }; +} + +// The interface for a function that returns the result of applying a scalar +// floating-point binary operation on |a| and |b|. The type of the return value +// will be |type|. The input constants must also be of type |type|. +using UnaryScalarFoldingRule = std::function; + +// The interface for a function that returns the result of applying a scalar +// floating-point binary operation on |a| and |b|. The type of the return value +// will be |type|. The input constants must also be of type |type|. +using BinaryScalarFoldingRule = std::function; + +// Returns an std::vector containing the elements of |constant|. The type of +// |constant| must be |Vector|. +std::vector GetVectorComponents( + const analysis::Constant* constant, analysis::ConstantManager* const_mgr) { + std::vector components; + const analysis::VectorConstant* a = constant->AsVectorConstant(); + const analysis::Vector* vector_type = constant->type()->AsVector(); + assert(vector_type != nullptr); + if (a != nullptr) { + for (uint32_t i = 0; i < vector_type->element_count(); ++i) { + components.push_back(a->GetComponents()[i]); + } + } else { + const analysis::Type* element_type = vector_type->element_type(); + const analysis::Constant* element_null_const = + const_mgr->GetConstant(element_type, {}); + for (uint32_t i = 0; i < vector_type->element_count(); ++i) { + components.push_back(element_null_const); + } + } + return components; +} + +// Returns a |ConstantFoldingRule| that folds unary floating point scalar ops +// using |scalar_rule| and unary float point vectors ops by applying +// |scalar_rule| to the elements of the vector. The |ConstantFoldingRule| +// that is returned assumes that |constants| contains 1 entry. If they are +// not |nullptr|, then their type is either |Float| or |Integer| or a |Vector| +// whose element type is |Float| or |Integer|. +ConstantFoldingRule FoldFPUnaryOp(UnaryScalarFoldingRule scalar_rule) { + return [scalar_rule](ir::Instruction* inst, + const std::vector& constants) + -> const analysis::Constant* { + ir::IRContext* context = inst->context(); + analysis::ConstantManager* const_mgr = context->get_constant_mgr(); + analysis::TypeManager* type_mgr = context->get_type_mgr(); + const analysis::Type* result_type = type_mgr->GetType(inst->type_id()); + const analysis::Vector* vector_type = result_type->AsVector(); + + if (!inst->IsFloatingPointFoldingAllowed()) { + return nullptr; + } + + if (constants[0] == nullptr) { + return nullptr; + } + + if (vector_type != nullptr) { + std::vector a_components; + std::vector results_components; + + a_components = GetVectorComponents(constants[0], const_mgr); + + // Fold each component of the vector. + for (uint32_t i = 0; i < a_components.size(); ++i) { + results_components.push_back(scalar_rule(vector_type->element_type(), + a_components[i], const_mgr)); + if (results_components[i] == nullptr) { + return nullptr; + } + } + + // Build the constant object and return it. + std::vector ids; + for (const analysis::Constant* member : results_components) { + ids.push_back(const_mgr->GetDefiningInstruction(member)->result_id()); + } + return const_mgr->GetConstant(vector_type, ids); + } else { + return scalar_rule(result_type, constants[0], const_mgr); + } + }; +} + +// Returns a |ConstantFoldingRule| that folds floating point scalars using +// |scalar_rule| and vectors of floating point by applying |scalar_rule| to the +// elements of the vector. The |ConstantFoldingRule| that is returned assumes +// that |constants| contains 2 entries. If they are not |nullptr|, then their +// type is either |Float| or a |Vector| whose element type is |Float|. +ConstantFoldingRule FoldFPBinaryOp(BinaryScalarFoldingRule scalar_rule) { + return [scalar_rule](ir::Instruction* inst, + const std::vector& constants) + -> const analysis::Constant* { + ir::IRContext* context = inst->context(); + analysis::ConstantManager* const_mgr = context->get_constant_mgr(); + analysis::TypeManager* type_mgr = context->get_type_mgr(); + const analysis::Type* result_type = type_mgr->GetType(inst->type_id()); + const analysis::Vector* vector_type = result_type->AsVector(); + + if (!inst->IsFloatingPointFoldingAllowed()) { + return nullptr; + } + + if (constants[0] == nullptr || constants[1] == nullptr) { + return nullptr; + } + + if (vector_type != nullptr) { + std::vector a_components; + std::vector b_components; + std::vector results_components; + + a_components = GetVectorComponents(constants[0], const_mgr); + b_components = GetVectorComponents(constants[1], const_mgr); + + // Fold each component of the vector. + for (uint32_t i = 0; i < a_components.size(); ++i) { + results_components.push_back(scalar_rule(vector_type->element_type(), + a_components[i], + b_components[i], const_mgr)); + if (results_components[i] == nullptr) { + return nullptr; + } + } + + // Build the constant object and return it. + std::vector ids; + for (const analysis::Constant* member : results_components) { + ids.push_back(const_mgr->GetDefiningInstruction(member)->result_id()); + } + return const_mgr->GetConstant(vector_type, ids); + } else { + return scalar_rule(result_type, constants[0], constants[1], const_mgr); + } + }; +} + +// This macro defines a |UnaryScalarFoldingRule| that performs float to +// integer conversion. +// TODO(greg-lunarg): Support for 64-bit integer types. +UnaryScalarFoldingRule FoldFToIOp() { + return [](const analysis::Type* result_type, const analysis::Constant* a, + analysis::ConstantManager* const_mgr) -> const analysis::Constant* { + assert(result_type != nullptr && a != nullptr); + const analysis::Integer* integer_type = result_type->AsInteger(); + const analysis::Float* float_type = a->type()->AsFloat(); + assert(float_type != nullptr); + assert(integer_type != nullptr); + if (integer_type->width() != 32) return nullptr; + if (float_type->width() == 32) { + float fa = a->GetFloat(); + uint32_t result = integer_type->IsSigned() + ? static_cast(static_cast(fa)) + : static_cast(fa); + std::vector words = {result}; + return const_mgr->GetConstant(result_type, words); + } else if (float_type->width() == 64) { + double fa = a->GetDouble(); + uint32_t result = integer_type->IsSigned() + ? static_cast(static_cast(fa)) + : static_cast(fa); + std::vector words = {result}; + return const_mgr->GetConstant(result_type, words); + } + return nullptr; + }; +} + +// This macro defines a |UnaryScalarFoldingRule| that performs integer to +// float conversion. +// TODO(greg-lunarg): Support for 64-bit integer types. +UnaryScalarFoldingRule FoldIToFOp() { + return [](const analysis::Type* result_type, const analysis::Constant* a, + analysis::ConstantManager* const_mgr) -> const analysis::Constant* { + assert(result_type != nullptr && a != nullptr); + const analysis::Integer* integer_type = a->type()->AsInteger(); + const analysis::Float* float_type = result_type->AsFloat(); + assert(float_type != nullptr); + assert(integer_type != nullptr); + if (integer_type->width() != 32) return nullptr; + uint32_t ua = a->GetU32(); + if (float_type->width() == 32) { + float result_val = integer_type->IsSigned() + ? static_cast(static_cast(ua)) + : static_cast(ua); + spvutils::FloatProxy result(result_val); + std::vector words = {result.data()}; + return const_mgr->GetConstant(result_type, words); + } else if (float_type->width() == 64) { + double result_val = integer_type->IsSigned() + ? static_cast(static_cast(ua)) + : static_cast(ua); + spvutils::FloatProxy result(result_val); + std::vector words = result.GetWords(); + return const_mgr->GetConstant(result_type, words); + } + return nullptr; + }; +} + +// This macro defines a |BinaryScalarFoldingRule| that applies |op|. The +// operator |op| must work for both float and double, and use syntax "f1 op f2". +#define FOLD_FPARITH_OP(op) \ + [](const analysis::Type* result_type, const analysis::Constant* a, \ + const analysis::Constant* b, \ + analysis::ConstantManager* const_mgr) -> const analysis::Constant* { \ + assert(result_type != nullptr && a != nullptr && b != nullptr); \ + assert(result_type == a->type() && result_type == b->type()); \ + const analysis::Float* float_type = result_type->AsFloat(); \ + assert(float_type != nullptr); \ + if (float_type->width() == 32) { \ + float fa = a->GetFloat(); \ + float fb = b->GetFloat(); \ + spvutils::FloatProxy result(fa op fb); \ + std::vector words = result.GetWords(); \ + return const_mgr->GetConstant(result_type, words); \ + } else if (float_type->width() == 64) { \ + double fa = a->GetDouble(); \ + double fb = b->GetDouble(); \ + spvutils::FloatProxy result(fa op fb); \ + std::vector words = result.GetWords(); \ + return const_mgr->GetConstant(result_type, words); \ + } \ + return nullptr; \ + } + +// Define the folding rule for conversion between floating point and integer +ConstantFoldingRule FoldFToI() { return FoldFPUnaryOp(FoldFToIOp()); } +ConstantFoldingRule FoldIToF() { return FoldFPUnaryOp(FoldIToFOp()); } + +// Define the folding rules for subtraction, addition, multiplication, and +// division for floating point values. +ConstantFoldingRule FoldFSub() { return FoldFPBinaryOp(FOLD_FPARITH_OP(-)); } +ConstantFoldingRule FoldFAdd() { return FoldFPBinaryOp(FOLD_FPARITH_OP(+)); } +ConstantFoldingRule FoldFMul() { return FoldFPBinaryOp(FOLD_FPARITH_OP(*)); } +ConstantFoldingRule FoldFDiv() { return FoldFPBinaryOp(FOLD_FPARITH_OP(/)); } + +bool CompareFloatingPoint(bool op_result, bool op_unordered, + bool need_ordered) { + if (need_ordered) { + // operands are ordered and Operand 1 is |op| Operand 2 + return !op_unordered && op_result; + } else { + // operands are unordered or Operand 1 is |op| Operand 2 + return op_unordered || op_result; + } +} + +// This macro defines a |BinaryScalarFoldingRule| that applies |op|. The +// operator |op| must work for both float and double, and use syntax "f1 op f2". +#define FOLD_FPCMP_OP(op, ord) \ + [](const analysis::Type* result_type, const analysis::Constant* a, \ + const analysis::Constant* b, \ + analysis::ConstantManager* const_mgr) -> const analysis::Constant* { \ + assert(result_type != nullptr && a != nullptr && b != nullptr); \ + assert(result_type->AsBool()); \ + assert(a->type() == b->type()); \ + const analysis::Float* float_type = a->type()->AsFloat(); \ + assert(float_type != nullptr); \ + if (float_type->width() == 32) { \ + float fa = a->GetFloat(); \ + float fb = b->GetFloat(); \ + bool result = CompareFloatingPoint( \ + fa op fb, std::isnan(fa) || std::isnan(fb), ord); \ + std::vector words = {uint32_t(result)}; \ + return const_mgr->GetConstant(result_type, words); \ + } else if (float_type->width() == 64) { \ + double fa = a->GetDouble(); \ + double fb = b->GetDouble(); \ + bool result = CompareFloatingPoint( \ + fa op fb, std::isnan(fa) || std::isnan(fb), ord); \ + std::vector words = {uint32_t(result)}; \ + return const_mgr->GetConstant(result_type, words); \ + } \ + return nullptr; \ + } + +// Define the folding rules for ordered and unordered comparison for floating +// point values. +ConstantFoldingRule FoldFOrdEqual() { + return FoldFPBinaryOp(FOLD_FPCMP_OP(==, true)); +} +ConstantFoldingRule FoldFUnordEqual() { + return FoldFPBinaryOp(FOLD_FPCMP_OP(==, false)); +} +ConstantFoldingRule FoldFOrdNotEqual() { + return FoldFPBinaryOp(FOLD_FPCMP_OP(!=, true)); +} +ConstantFoldingRule FoldFUnordNotEqual() { + return FoldFPBinaryOp(FOLD_FPCMP_OP(!=, false)); +} +ConstantFoldingRule FoldFOrdLessThan() { + return FoldFPBinaryOp(FOLD_FPCMP_OP(<, true)); +} +ConstantFoldingRule FoldFUnordLessThan() { + return FoldFPBinaryOp(FOLD_FPCMP_OP(<, false)); +} +ConstantFoldingRule FoldFOrdGreaterThan() { + return FoldFPBinaryOp(FOLD_FPCMP_OP(>, true)); +} +ConstantFoldingRule FoldFUnordGreaterThan() { + return FoldFPBinaryOp(FOLD_FPCMP_OP(>, false)); +} +ConstantFoldingRule FoldFOrdLessThanEqual() { + return FoldFPBinaryOp(FOLD_FPCMP_OP(<=, true)); +} +ConstantFoldingRule FoldFUnordLessThanEqual() { + return FoldFPBinaryOp(FOLD_FPCMP_OP(<=, false)); +} +ConstantFoldingRule FoldFOrdGreaterThanEqual() { + return FoldFPBinaryOp(FOLD_FPCMP_OP(>=, true)); +} +ConstantFoldingRule FoldFUnordGreaterThanEqual() { + return FoldFPBinaryOp(FOLD_FPCMP_OP(>=, false)); +} +} // namespace + +spvtools::opt::ConstantFoldingRules::ConstantFoldingRules() { + // Add all folding rules to the list for the opcodes to which they apply. + // Note that the order in which rules are added to the list matters. If a rule + // applies to the instruction, the rest of the rules will not be attempted. + // Take that into consideration. + + rules_[SpvOpCompositeConstruct].push_back(FoldCompositeWithConstants()); + + rules_[SpvOpCompositeExtract].push_back(FoldExtractWithConstants()); + + rules_[SpvOpConvertFToS].push_back(FoldFToI()); + rules_[SpvOpConvertFToU].push_back(FoldFToI()); + rules_[SpvOpConvertSToF].push_back(FoldIToF()); + rules_[SpvOpConvertUToF].push_back(FoldIToF()); + + rules_[SpvOpFAdd].push_back(FoldFAdd()); + rules_[SpvOpFDiv].push_back(FoldFDiv()); + rules_[SpvOpFMul].push_back(FoldFMul()); + rules_[SpvOpFSub].push_back(FoldFSub()); + + rules_[SpvOpFOrdEqual].push_back(FoldFOrdEqual()); + rules_[SpvOpFUnordEqual].push_back(FoldFUnordEqual()); + rules_[SpvOpFOrdNotEqual].push_back(FoldFOrdNotEqual()); + rules_[SpvOpFUnordNotEqual].push_back(FoldFUnordNotEqual()); + rules_[SpvOpFOrdLessThan].push_back(FoldFOrdLessThan()); + rules_[SpvOpFUnordLessThan].push_back(FoldFUnordLessThan()); + rules_[SpvOpFOrdGreaterThan].push_back(FoldFOrdGreaterThan()); + rules_[SpvOpFUnordGreaterThan].push_back(FoldFUnordGreaterThan()); + rules_[SpvOpFOrdLessThanEqual].push_back(FoldFOrdLessThanEqual()); + rules_[SpvOpFUnordLessThanEqual].push_back(FoldFUnordLessThanEqual()); + rules_[SpvOpFOrdGreaterThanEqual].push_back(FoldFOrdGreaterThanEqual()); + rules_[SpvOpFUnordGreaterThanEqual].push_back(FoldFUnordGreaterThanEqual()); + + rules_[SpvOpVectorShuffle].push_back(FoldVectorShuffleWithConstants()); +} +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/const_folding_rules.h b/3rdparty/spirv-tools/source/opt/const_folding_rules.h new file mode 100644 index 000000000..2d9ecbaa3 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/const_folding_rules.h @@ -0,0 +1,84 @@ +// Copyright (c) 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_CONST_FOLDING_RULES_H_ +#define LIBSPIRV_OPT_CONST_FOLDING_RULES_H_ + +#include + +#include "constants.h" +#include "def_use_manager.h" +#include "folding_rules.h" +#include "ir_builder.h" +#include "ir_context.h" +#include "latest_version_spirv_header.h" + +namespace spvtools { +namespace opt { + +// Constant Folding Rules: +// +// The folding mechanism is built around the concept of a |ConstantFoldingRule|. +// A constant folding rule is a function that implements a method of simplifying +// an instruction to a constant. +// +// The inputs to a folding rule are: +// |inst| - the instruction to be simplified. +// |constants| - if an in-operands is an id of a constant, then the +// corresponding value in |constants| contains that +// constant value. Otherwise, the corresponding entry in +// |constants| is |nullptr|. +// +// A constant folding rule returns a pointer to an Constant if |inst| can be +// simplified using this rule. Otherwise, it returns |nullptr|. +// +// See const_folding_rules.cpp for examples on how to write a constant folding +// rule. +// +// Be sure to add new constant folding rules to the table of constant folding +// rules in the constructor for ConstantFoldingRules. The new rule should be +// added to the list for every opcode that it applies to. Note that earlier +// rules in the list are given priority. That is, if an earlier rule is able to +// fold an instruction, the later rules will not be attempted. + +using ConstantFoldingRule = std::function& constants)>; + +class ConstantFoldingRules { + public: + ConstantFoldingRules(); + + // Returns true if there is at least 1 folding rule for |opcode|. + bool HasFoldingRule(SpvOp opcode) const { return rules_.count(opcode); } + + // Returns an vector of constant folding rules for |opcode|. + const std::vector& GetRulesForOpcode( + SpvOp opcode) const { + auto it = rules_.find(opcode); + if (it != rules_.end()) { + return it->second; + } + return empty_vector_; + } + + private: + std::unordered_map> rules_; + std::vector empty_vector_; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_CONST_FOLDING_RULES_H_ diff --git a/3rdparty/spirv-tools/source/opt/constants.cpp b/3rdparty/spirv-tools/source/opt/constants.cpp new file mode 100644 index 000000000..1eb9efe14 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/constants.cpp @@ -0,0 +1,311 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "constants.h" +#include "ir_context.h" + +#include +#include + +namespace spvtools { +namespace opt { +namespace analysis { + +float Constant::GetFloat() const { + assert(type()->AsFloat() != nullptr && type()->AsFloat()->width() == 32); + + if (const FloatConstant* fc = AsFloatConstant()) { + return fc->GetFloatValue(); + } else { + assert(AsNullConstant() && "Must be a floating point constant."); + return 0.0f; + } +} + +double Constant::GetDouble() const { + assert(type()->AsFloat() != nullptr && type()->AsFloat()->width() == 64); + + if (const FloatConstant* fc = AsFloatConstant()) { + return fc->GetDoubleValue(); + } else { + assert(AsNullConstant() && "Must be a floating point constant."); + return 0.0; + } +} + +uint32_t Constant::GetU32() const { + assert(type()->AsInteger() != nullptr); + assert(type()->AsInteger()->width() == 32); + + if (const IntConstant* ic = AsIntConstant()) { + return ic->GetU32BitValue(); + } else { + assert(AsNullConstant() && "Must be an integer constant."); + return 0u; + } +} + +uint64_t Constant::GetU64() const { + assert(type()->AsInteger() != nullptr); + assert(type()->AsInteger()->width() == 64); + + if (const IntConstant* ic = AsIntConstant()) { + return ic->GetU64BitValue(); + } else { + assert(AsNullConstant() && "Must be an integer constant."); + return 0u; + } +} + +int32_t Constant::GetS32() const { + assert(type()->AsInteger() != nullptr); + assert(type()->AsInteger()->width() == 32); + + if (const IntConstant* ic = AsIntConstant()) { + return ic->GetS32BitValue(); + } else { + assert(AsNullConstant() && "Must be an integer constant."); + return 0; + } +} + +int64_t Constant::GetS64() const { + assert(type()->AsInteger() != nullptr); + assert(type()->AsInteger()->width() == 64); + + if (const IntConstant* ic = AsIntConstant()) { + return ic->GetS64BitValue(); + } else { + assert(AsNullConstant() && "Must be an integer constant."); + return 0; + } +} + +ConstantManager::ConstantManager(ir::IRContext* ctx) : ctx_(ctx) { + // Populate the constant table with values from constant declarations in the + // module. The values of each OpConstant declaration is the identity + // assignment (i.e., each constant is its own value). + for (const auto& inst : ctx_->module()->GetConstants()) { + MapInst(inst); + } +} + +Type* ConstantManager::GetType(const ir::Instruction* inst) const { + return context()->get_type_mgr()->GetType(inst->type_id()); +} + +std::vector ConstantManager::GetOperandConstants( + ir::Instruction* inst) const { + std::vector constants; + for (uint32_t i = 0; i < inst->NumInOperands(); i++) { + const ir::Operand* operand = &inst->GetInOperand(i); + if (operand->type != SPV_OPERAND_TYPE_ID) { + constants.push_back(nullptr); + } else { + uint32_t id = operand->words[0]; + const analysis::Constant* constant = FindDeclaredConstant(id); + constants.push_back(constant); + } + } + return constants; +} + +std::vector ConstantManager::GetConstantsFromIds( + const std::vector& ids) const { + std::vector constants; + for (uint32_t id : ids) { + if (const Constant* c = FindDeclaredConstant(id)) { + constants.push_back(c); + } else { + return {}; + } + } + return constants; +} + +ir::Instruction* ConstantManager::BuildInstructionAndAddToModule( + const Constant* new_const, ir::Module::inst_iterator* pos, + uint32_t type_id) { + uint32_t new_id = context()->TakeNextId(); + auto new_inst = CreateInstruction(new_id, new_const, type_id); + if (!new_inst) { + return nullptr; + } + auto* new_inst_ptr = new_inst.get(); + *pos = pos->InsertBefore(std::move(new_inst)); + ++(*pos); + context()->get_def_use_mgr()->AnalyzeInstDefUse(new_inst_ptr); + MapConstantToInst(new_const, new_inst_ptr); + return new_inst_ptr; +} + +ir::Instruction* ConstantManager::GetDefiningInstruction( + const Constant* c, ir::Module::inst_iterator* pos) { + uint32_t decl_id = FindDeclaredConstant(c); + if (decl_id == 0) { + auto iter = context()->types_values_end(); + if (pos == nullptr) pos = &iter; + return BuildInstructionAndAddToModule(c, pos); + } else { + auto def = context()->get_def_use_mgr()->GetDef(decl_id); + assert(def != nullptr); + return def; + } +} + +const Constant* ConstantManager::CreateConstant( + const Type* type, const std::vector& literal_words_or_ids) const { + if (literal_words_or_ids.size() == 0) { + // Constant declared with OpConstantNull + return new NullConstant(type); + } else if (auto* bt = type->AsBool()) { + assert(literal_words_or_ids.size() == 1 && + "Bool constant should be declared with one operand"); + return new BoolConstant(bt, literal_words_or_ids.front()); + } else if (auto* it = type->AsInteger()) { + return new IntConstant(it, literal_words_or_ids); + } else if (auto* ft = type->AsFloat()) { + return new FloatConstant(ft, literal_words_or_ids); + } else if (auto* vt = type->AsVector()) { + auto components = GetConstantsFromIds(literal_words_or_ids); + if (components.empty()) return nullptr; + // All components of VectorConstant must be of type Bool, Integer or Float. + if (!std::all_of(components.begin(), components.end(), + [](const Constant* c) { + if (c->type()->AsBool() || c->type()->AsInteger() || + c->type()->AsFloat()) { + return true; + } else { + return false; + } + })) + return nullptr; + // All components of VectorConstant must be in the same type. + const auto* component_type = components.front()->type(); + if (!std::all_of(components.begin(), components.end(), + [&component_type](const Constant* c) { + if (c->type() == component_type) return true; + return false; + })) + return nullptr; + return new VectorConstant(vt, components); + } else if (auto* mt = type->AsMatrix()) { + auto components = GetConstantsFromIds(literal_words_or_ids); + if (components.empty()) return nullptr; + return new MatrixConstant(mt, components); + } else if (auto* st = type->AsStruct()) { + auto components = GetConstantsFromIds(literal_words_or_ids); + if (components.empty()) return nullptr; + return new StructConstant(st, components); + } else if (auto* at = type->AsArray()) { + auto components = GetConstantsFromIds(literal_words_or_ids); + if (components.empty()) return nullptr; + return new ArrayConstant(at, components); + } else { + return nullptr; + } +} + +const Constant* ConstantManager::GetConstantFromInst(ir::Instruction* inst) { + std::vector literal_words_or_ids; + + // Collect the constant defining literals or component ids. + for (uint32_t i = 0; i < inst->NumInOperands(); i++) { + literal_words_or_ids.insert(literal_words_or_ids.end(), + inst->GetInOperand(i).words.begin(), + inst->GetInOperand(i).words.end()); + } + + switch (inst->opcode()) { + // OpConstant{True|False} have the value embedded in the opcode. So they + // are not handled by the for-loop above. Here we add the value explicitly. + case SpvOp::SpvOpConstantTrue: + literal_words_or_ids.push_back(true); + break; + case SpvOp::SpvOpConstantFalse: + literal_words_or_ids.push_back(false); + break; + case SpvOp::SpvOpConstantNull: + case SpvOp::SpvOpConstant: + case SpvOp::SpvOpConstantComposite: + case SpvOp::SpvOpSpecConstantComposite: + break; + default: + return nullptr; + } + + return GetConstant(GetType(inst), literal_words_or_ids); +} + +std::unique_ptr ConstantManager::CreateInstruction( + uint32_t id, const Constant* c, uint32_t type_id) const { + uint32_t type = + (type_id == 0) ? context()->get_type_mgr()->GetId(c->type()) : type_id; + if (c->AsNullConstant()) { + return MakeUnique(context(), SpvOp::SpvOpConstantNull, + type, id, + std::initializer_list{}); + } else if (const BoolConstant* bc = c->AsBoolConstant()) { + return MakeUnique( + context(), + bc->value() ? SpvOp::SpvOpConstantTrue : SpvOp::SpvOpConstantFalse, + type, id, std::initializer_list{}); + } else if (const IntConstant* ic = c->AsIntConstant()) { + return MakeUnique( + context(), SpvOp::SpvOpConstant, type, id, + std::initializer_list{ir::Operand( + spv_operand_type_t::SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER, + ic->words())}); + } else if (const FloatConstant* fc = c->AsFloatConstant()) { + return MakeUnique( + context(), SpvOp::SpvOpConstant, type, id, + std::initializer_list{ir::Operand( + spv_operand_type_t::SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER, + fc->words())}); + } else if (const CompositeConstant* cc = c->AsCompositeConstant()) { + return CreateCompositeInstruction(id, cc, type_id); + } else { + return nullptr; + } +} + +std::unique_ptr ConstantManager::CreateCompositeInstruction( + uint32_t result_id, const CompositeConstant* cc, uint32_t type_id) const { + std::vector operands; + for (const Constant* component_const : cc->GetComponents()) { + uint32_t id = FindDeclaredConstant(component_const); + if (id == 0) { + // Cannot get the id of the component constant, while all components + // should have been added to the module prior to the composite constant. + // Cannot create OpConstantComposite instruction in this case. + return nullptr; + } + operands.emplace_back(spv_operand_type_t::SPV_OPERAND_TYPE_ID, + std::initializer_list{id}); + } + uint32_t type = + (type_id == 0) ? context()->get_type_mgr()->GetId(cc->type()) : type_id; + return MakeUnique(context(), SpvOp::SpvOpConstantComposite, + type, result_id, std::move(operands)); +} + +const Constant* ConstantManager::GetConstant( + const Type* type, const std::vector& literal_words_or_ids) { + auto cst = CreateConstant(type, literal_words_or_ids); + return cst ? RegisterConstant(cst) : nullptr; +} + +} // namespace analysis +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/constants.h b/3rdparty/spirv-tools/source/opt/constants.h new file mode 100644 index 000000000..d1c1fbeaa --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/constants.h @@ -0,0 +1,647 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_CONSTANTS_H_ +#define LIBSPIRV_OPT_CONSTANTS_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "make_unique.h" +#include "module.h" +#include "type_manager.h" +#include "types.h" +#include "util/hex_float.h" + +namespace spvtools { +namespace opt { +namespace analysis { + +// Class hierarchy to represent the normal constants defined through +// OpConstantTrue, OpConstantFalse, OpConstant, OpConstantNull and +// OpConstantComposite instructions. +// TODO(qining): Add class for constants defined with OpConstantSampler. +class Constant; +class ScalarConstant; +class IntConstant; +class FloatConstant; +class BoolConstant; +class CompositeConstant; +class StructConstant; +class VectorConstant; +class MatrixConstant; +class ArrayConstant; +class NullConstant; + +// Abstract class for a SPIR-V constant. It has a bunch of As methods, +// which is used as a way to probe the actual +class Constant { + public: + Constant() = delete; + virtual ~Constant() {} + + // Make a deep copy of this constant. + virtual std::unique_ptr Copy() const = 0; + + // reflections + virtual ScalarConstant* AsScalarConstant() { return nullptr; } + virtual IntConstant* AsIntConstant() { return nullptr; } + virtual FloatConstant* AsFloatConstant() { return nullptr; } + virtual BoolConstant* AsBoolConstant() { return nullptr; } + virtual CompositeConstant* AsCompositeConstant() { return nullptr; } + virtual StructConstant* AsStructConstant() { return nullptr; } + virtual VectorConstant* AsVectorConstant() { return nullptr; } + virtual MatrixConstant* AsMatrixConstant() { return nullptr; } + virtual ArrayConstant* AsArrayConstant() { return nullptr; } + virtual NullConstant* AsNullConstant() { return nullptr; } + + virtual const ScalarConstant* AsScalarConstant() const { return nullptr; } + virtual const IntConstant* AsIntConstant() const { return nullptr; } + virtual const FloatConstant* AsFloatConstant() const { return nullptr; } + virtual const BoolConstant* AsBoolConstant() const { return nullptr; } + virtual const CompositeConstant* AsCompositeConstant() const { + return nullptr; + } + virtual const StructConstant* AsStructConstant() const { return nullptr; } + virtual const VectorConstant* AsVectorConstant() const { return nullptr; } + virtual const MatrixConstant* AsMatrixConstant() const { return nullptr; } + virtual const ArrayConstant* AsArrayConstant() const { return nullptr; } + virtual const NullConstant* AsNullConstant() const { return nullptr; } + + // Returns the float representation of the constant. Must be a 32 bit + // Float type. + float GetFloat() const; + + // Returns the double representation of the constant. Must be a 64 bit + // Float type. + double GetDouble() const; + + // Returns uint32_t representation of the constant. Must be a 32 bit + // Integer type. + uint32_t GetU32() const; + + // Returns uint64_t representation of the constant. Must be a 64 bit + // Integer type. + uint64_t GetU64() const; + + // Returns int32_t representation of the constant. Must be a 32 bit + // Integer type. + int32_t GetS32() const; + + // Returns int64_t representation of the constant. Must be a 64 bit + // Integer type. + int64_t GetS64() const; + + const Type* type() const { return type_; } + + protected: + Constant(const Type* ty) : type_(ty) {} + + // The type of this constant. + const Type* type_; +}; + +// Abstract class for scalar type constants. +class ScalarConstant : public Constant { + public: + ScalarConstant() = delete; + ScalarConstant* AsScalarConstant() override { return this; } + const ScalarConstant* AsScalarConstant() const override { return this; } + + // Returns a const reference of the value of this constant in 32-bit words. + virtual const std::vector& words() const { return words_; } + + // Returns true if the value is zero. + bool IsZero() const { + bool is_zero = true; + for (uint32_t v : words()) { + if (v != 0) { + is_zero = false; + break; + } + } + return is_zero; + } + + protected: + ScalarConstant(const Type* ty, const std::vector& w) + : Constant(ty), words_(w) {} + ScalarConstant(const Type* ty, std::vector&& w) + : Constant(ty), words_(std::move(w)) {} + std::vector words_; +}; + +// Integer type constant. +class IntConstant : public ScalarConstant { + public: + IntConstant(const Integer* ty, const std::vector& w) + : ScalarConstant(ty, w) {} + IntConstant(const Integer* ty, std::vector&& w) + : ScalarConstant(ty, std::move(w)) {} + + IntConstant* AsIntConstant() override { return this; } + const IntConstant* AsIntConstant() const override { return this; } + + int32_t GetS32BitValue() const { + // Relies on signed values smaller than 32-bit being sign extended. See + // section 2.2.1 of the SPIR-V spec. + assert(words().size() == 1); + return words()[0]; + } + + uint32_t GetU32BitValue() const { + // Relies on unsigned values smaller than 32-bit being zero extended. See + // section 2.2.1 of the SPIR-V spec. + assert(words().size() == 1); + return words()[0]; + } + + int64_t GetS64BitValue() const { + // Relies on unsigned values smaller than 64-bit being sign extended. See + // section 2.2.1 of the SPIR-V spec. + assert(words().size() == 2); + return static_cast(words()[1]) << 32 | + static_cast(words()[0]); + } + + uint64_t GetU64BitValue() const { + // Relies on unsigned values smaller than 64-bit being zero extended. See + // section 2.2.1 of the SPIR-V spec. + assert(words().size() == 2); + return static_cast(words()[1]) << 32 | + static_cast(words()[0]); + } + + // Make a copy of this IntConstant instance. + std::unique_ptr CopyIntConstant() const { + return MakeUnique(type_->AsInteger(), words_); + } + std::unique_ptr Copy() const override { + return std::unique_ptr(CopyIntConstant().release()); + } +}; + +// Float type constant. +class FloatConstant : public ScalarConstant { + public: + FloatConstant(const Float* ty, const std::vector& w) + : ScalarConstant(ty, w) {} + FloatConstant(const Float* ty, std::vector&& w) + : ScalarConstant(ty, std::move(w)) {} + + FloatConstant* AsFloatConstant() override { return this; } + const FloatConstant* AsFloatConstant() const override { return this; } + + // Make a copy of this FloatConstant instance. + std::unique_ptr CopyFloatConstant() const { + return MakeUnique(type_->AsFloat(), words_); + } + std::unique_ptr Copy() const override { + return std::unique_ptr(CopyFloatConstant().release()); + } + + // Returns the float value of |this|. The type of |this| must be |Float| with + // width of 32. + float GetFloatValue() const { + assert(type()->AsFloat()->width() == 32 && + "Not a 32-bit floating point value."); + spvutils::FloatProxy a(words()[0]); + return a.getAsFloat(); + } + + // Returns the double value of |this|. The type of |this| must be |Float| + // with width of 64. + double GetDoubleValue() const { + assert(type()->AsFloat()->width() == 64 && + "Not a 32-bit floating point value."); + uint64_t combined_words = words()[1]; + combined_words = combined_words << 32; + combined_words |= words()[0]; + spvutils::FloatProxy a(combined_words); + return a.getAsFloat(); + } +}; + +// Bool type constant. +class BoolConstant : public ScalarConstant { + public: + BoolConstant(const Bool* ty, bool v) + : ScalarConstant(ty, {static_cast(v)}), value_(v) {} + + BoolConstant* AsBoolConstant() override { return this; } + const BoolConstant* AsBoolConstant() const override { return this; } + + // Make a copy of this BoolConstant instance. + std::unique_ptr CopyBoolConstant() const { + return MakeUnique(type_->AsBool(), value_); + } + std::unique_ptr Copy() const override { + return std::unique_ptr(CopyBoolConstant().release()); + } + + bool value() const { return value_; } + + private: + bool value_; +}; + +// Abstract class for composite constants. +class CompositeConstant : public Constant { + public: + CompositeConstant() = delete; + CompositeConstant* AsCompositeConstant() override { return this; } + const CompositeConstant* AsCompositeConstant() const override { return this; } + + // Returns a const reference of the components held in this composite + // constant. + virtual const std::vector& GetComponents() const { + return components_; + } + + protected: + CompositeConstant(const Type* ty) : Constant(ty), components_() {} + CompositeConstant(const Type* ty, + const std::vector& components) + : Constant(ty), components_(components) {} + CompositeConstant(const Type* ty, std::vector&& components) + : Constant(ty), components_(std::move(components)) {} + std::vector components_; +}; + +// Struct type constant. +class StructConstant : public CompositeConstant { + public: + StructConstant(const Struct* ty) : CompositeConstant(ty) {} + StructConstant(const Struct* ty, + const std::vector& components) + : CompositeConstant(ty, components) {} + StructConstant(const Struct* ty, std::vector&& components) + : CompositeConstant(ty, std::move(components)) {} + + StructConstant* AsStructConstant() override { return this; } + const StructConstant* AsStructConstant() const override { return this; } + + // Make a copy of this StructConstant instance. + std::unique_ptr CopyStructConstant() const { + return MakeUnique(type_->AsStruct(), components_); + } + std::unique_ptr Copy() const override { + return std::unique_ptr(CopyStructConstant().release()); + } +}; + +// Vector type constant. +class VectorConstant : public CompositeConstant { + public: + VectorConstant(const Vector* ty) + : CompositeConstant(ty), component_type_(ty->element_type()) {} + VectorConstant(const Vector* ty, + const std::vector& components) + : CompositeConstant(ty, components), + component_type_(ty->element_type()) {} + VectorConstant(const Vector* ty, std::vector&& components) + : CompositeConstant(ty, std::move(components)), + component_type_(ty->element_type()) {} + + VectorConstant* AsVectorConstant() override { return this; } + const VectorConstant* AsVectorConstant() const override { return this; } + + // Make a copy of this VectorConstant instance. + std::unique_ptr CopyVectorConstant() const { + auto another = MakeUnique(type_->AsVector()); + another->components_.insert(another->components_.end(), components_.begin(), + components_.end()); + return another; + } + std::unique_ptr Copy() const override { + return std::unique_ptr(CopyVectorConstant().release()); + } + + const Type* component_type() const { return component_type_; } + + private: + const Type* component_type_; +}; + +// Matrix type constant. +class MatrixConstant : public CompositeConstant { + public: + MatrixConstant(const Matrix* ty) + : CompositeConstant(ty), component_type_(ty->element_type()) {} + MatrixConstant(const Matrix* ty, + const std::vector& components) + : CompositeConstant(ty, components), + component_type_(ty->element_type()) {} + MatrixConstant(const Vector* ty, std::vector&& components) + : CompositeConstant(ty, std::move(components)), + component_type_(ty->element_type()) {} + + MatrixConstant* AsMatrixConstant() override { return this; } + const MatrixConstant* AsMatrixConstant() const override { return this; } + + // Make a copy of this MatrixConstant instance. + std::unique_ptr CopyMatrixConstant() const { + auto another = MakeUnique(type_->AsMatrix()); + another->components_.insert(another->components_.end(), components_.begin(), + components_.end()); + return another; + } + std::unique_ptr Copy() const override { + return std::unique_ptr(CopyMatrixConstant().release()); + } + + const Type* component_type() { return component_type_; } + + private: + const Type* component_type_; +}; + +// Array type constant. +class ArrayConstant : public CompositeConstant { + public: + ArrayConstant(const Array* ty) : CompositeConstant(ty) {} + ArrayConstant(const Array* ty, const std::vector& components) + : CompositeConstant(ty, components) {} + ArrayConstant(const Array* ty, std::vector&& components) + : CompositeConstant(ty, std::move(components)) {} + + ArrayConstant* AsArrayConstant() override { return this; } + const ArrayConstant* AsArrayConstant() const override { return this; } + + // Make a copy of this ArrayConstant instance. + std::unique_ptr CopyArrayConstant() const { + return MakeUnique(type_->AsArray(), components_); + } + std::unique_ptr Copy() const override { + return std::unique_ptr(CopyArrayConstant().release()); + } +}; + +// Null type constant. +class NullConstant : public Constant { + public: + NullConstant(const Type* ty) : Constant(ty) {} + NullConstant* AsNullConstant() override { return this; } + const NullConstant* AsNullConstant() const override { return this; } + + // Make a copy of this NullConstant instance. + std::unique_ptr CopyNullConstant() const { + return MakeUnique(type_); + } + std::unique_ptr Copy() const override { + return std::unique_ptr(CopyNullConstant().release()); + } +}; + +class IRContext; + +// Hash function for Constant instances. Use the structure of the constant as +// the key. +struct ConstantHash { + void add_pointer(std::u32string* h, const void* p) const { + uint64_t ptr_val = reinterpret_cast(p); + h->push_back(static_cast(ptr_val >> 32)); + h->push_back(static_cast(ptr_val)); + } + + size_t operator()(const Constant* const_val) const { + std::u32string h; + add_pointer(&h, const_val->type()); + if (const auto scalar = const_val->AsScalarConstant()) { + for (const auto& w : scalar->words()) { + h.push_back(w); + } + } else if (const auto composite = const_val->AsCompositeConstant()) { + for (const auto& c : composite->GetComponents()) { + add_pointer(&h, c); + } + } else if (const_val->AsNullConstant()) { + h.push_back(0); + } else { + assert( + false && + "Tried to compute the hash value of an invalid Constant instance."); + } + + return std::hash()(h); + } +}; + +// Equality comparison structure for two constants. +struct ConstantEqual { + bool operator()(const Constant* c1, const Constant* c2) const { + if (c1->type() != c2->type()) { + return false; + } + + if (const auto& s1 = c1->AsScalarConstant()) { + const auto& s2 = c2->AsScalarConstant(); + return s2 && s1->words() == s2->words(); + } else if (const auto& composite1 = c1->AsCompositeConstant()) { + const auto& composite2 = c2->AsCompositeConstant(); + return composite2 && + composite1->GetComponents() == composite2->GetComponents(); + } else if (c1->AsNullConstant()) + return c2->AsNullConstant() != nullptr; + else + assert(false && "Tried to compare two invalid Constant instances."); + return false; + } +}; + +// This class represents a pool of constants. +class ConstantManager { + public: + ConstantManager(ir::IRContext* ctx); + + ir::IRContext* context() const { return ctx_; } + + // Gets or creates a unique Constant instance of type |type| and a vector of + // constant defining words |words|. If a Constant instance existed already in + // the constant pool, it returns a pointer to it. Otherwise, it creates one + // using CreateConstant. If a new Constant instance cannot be created, it + // returns nullptr. + const Constant* GetConstant( + const Type* type, const std::vector& literal_words_or_ids); + + // Gets or creates a Constant instance to hold the constant value of the given + // instruction. It returns a pointer to a Constant instance or nullptr if it + // could not create the constant. + const Constant* GetConstantFromInst(ir::Instruction* inst); + + // Gets or creates a constant defining instruction for the given Constant |c|. + // If |c| had already been defined, it returns a pointer to the existing + // declaration. Otherwise, it calls BuildInstructionAndAddToModule. If the + // optional |pos| is given, it will insert any newly created instructions at + // the given instruction iterator position. Otherwise, it inserts the new + // instruction at the end of the current module's types section. + ir::Instruction* GetDefiningInstruction( + const Constant* c, ir::Module::inst_iterator* pos = nullptr); + + // Creates a constant defining instruction for the given Constant instance + // and inserts the instruction at the position specified by the given + // instruction iterator. Returns a pointer to the created instruction if + // succeeded, otherwise returns a null pointer. The instruction iterator + // points to the same instruction before and after the insertion. This is the + // only method that actually manages id creation/assignment and instruction + // creation/insertion for a new Constant instance. + // + // |type_id| is an optional argument for disambiguating equivalent types. If + // |type_id| is specified, it is used as the type of the constant. Otherwise + // the type of the constant is derived by getting an id from the type manager + // for |c|. + ir::Instruction* BuildInstructionAndAddToModule( + const Constant* c, ir::Module::inst_iterator* pos, uint32_t type_id = 0); + + // A helper function to get the result type of the given instruction. Returns + // nullptr if the instruction does not have a type id (type id is 0). + Type* GetType(const ir::Instruction* inst) const; + + // A helper function to get the collected normal constant with the given id. + // Returns the pointer to the Constant instance in case it is found. + // Otherwise, it returns a null pointer. + const Constant* FindDeclaredConstant(uint32_t id) const { + auto iter = id_to_const_val_.find(id); + return (iter != id_to_const_val_.end()) ? iter->second : nullptr; + } + + // A helper function to get the id of a collected constant with the pointer + // to the Constant instance. Returns 0 in case the constant is not found. + uint32_t FindDeclaredConstant(const Constant* c) const { + auto iter = const_val_to_id_.find(c); + return (iter != const_val_to_id_.end()) ? iter->second : 0; + } + + // Returns the canonical constant that has the same structure and value as the + // given Constant |cst|. If none is found, it returns nullptr. + const Constant* FindConstant(const Constant* c) const { + auto it = const_pool_.find(c); + return (it != const_pool_.end()) ? *it : nullptr; + } + + // Registers a new constant |cst| in the constant pool. If the constant + // existed already, it returns a pointer to the previously existing Constant + // in the pool. Otherwise, it returns |cst|. + const Constant* RegisterConstant(const Constant* cst) { + auto ret = const_pool_.insert(cst); + return *ret.first; + } + + // A helper function to get a vector of Constant instances with the specified + // ids. If it can not find the Constant instance for any one of the ids, + // it returns an empty vector. + std::vector GetConstantsFromIds( + const std::vector& ids) const; + + // Returns a vector of constants representing each in operand. If an operand + // is not constant its entry is nullptr. + std::vector GetOperandConstants(ir::Instruction* inst) const; + + // Records a mapping between |inst| and the constant value generated by it. + // It returns true if a new Constant was successfully mapped, false if |inst| + // generates no constant values. + bool MapInst(ir::Instruction* inst) { + if (auto cst = GetConstantFromInst(inst)) { + MapConstantToInst(cst, inst); + return true; + } + return false; + } + + void RemoveId(uint32_t id) { + auto it = id_to_const_val_.find(id); + if (it != id_to_const_val_.end()) { + const_val_to_id_.erase(it->second); + id_to_const_val_.erase(it); + } + } + + // Records a new mapping between |inst| and |const_value|. This updates the + // two mappings |id_to_const_val_| and |const_val_to_id_|. + void MapConstantToInst(const Constant* const_value, ir::Instruction* inst) { + const_val_to_id_[const_value] = inst->result_id(); + id_to_const_val_[inst->result_id()] = const_value; + } + + private: + // Creates a Constant instance with the given type and a vector of constant + // defining words. Returns a unique pointer to the created Constant instance + // if the Constant instance can be created successfully. To create scalar + // type constants, the vector should contain the constant value in 32 bit + // words and the given type must be of type Bool, Integer or Float. To create + // composite type constants, the vector should contain the component ids, and + // those component ids should have been recorded before as Normal Constants. + // And the given type must be of type Struct, Vector or Array. When creating + // VectorType Constant instance, the components must be scalars of the same + // type, either Bool, Integer or Float. If any of the rules above failed, the + // creation will fail and nullptr will be returned. If the vector is empty, + // a NullConstant instance will be created with the given type. + const Constant* CreateConstant( + const Type* type, + const std::vector& literal_words_or_ids) const; + + // Creates an instruction with the given result id to declare a constant + // represented by the given Constant instance. Returns an unique pointer to + // the created instruction if the instruction can be created successfully. + // Otherwise, returns a null pointer. + // + // |type_id| is an optional argument for disambiguating equivalent types. If + // |type_id| is specified, it is used as the type of the constant. Otherwise + // the type of the constant is derived by getting an id from the type manager + // for |c|. + std::unique_ptr CreateInstruction( + uint32_t result_id, const Constant* c, uint32_t type_id = 0) const; + + // Creates an OpConstantComposite instruction with the given result id and + // the CompositeConst instance which represents a composite constant. Returns + // an unique pointer to the created instruction if succeeded. Otherwise + // returns a null pointer. + // + // |type_id| is an optional argument for disambiguating equivalent types. If + // |type_id| is specified, it is used as the type of the constant. Otherwise + // the type of the constant is derived by getting an id from the type manager + // for |c|. + std::unique_ptr CreateCompositeInstruction( + uint32_t result_id, const CompositeConstant* cc, + uint32_t type_id = 0) const; + + // IR context that owns this constant manager. + ir::IRContext* ctx_; + + // A mapping from the result ids of Normal Constants to their + // Constant instances. All Normal Constants in the module, either + // existing ones before optimization or the newly generated ones, should have + // their Constant instance stored and their result id registered in this map. + std::unordered_map id_to_const_val_; + + // A mapping from the Constant instance of Normal Constants to their + // result id in the module. This is a mirror map of |id_to_const_val_|. All + // Normal Constants that defining instructions in the module should have + // their Constant and their result id registered here. + std::unordered_map const_val_to_id_; + + // The constant pool. All created constants are registered here. + std::unordered_set const_pool_; +}; + +} // namespace analysis +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_CONSTANTS_H_ diff --git a/3rdparty/spirv-tools/source/opt/copy_prop_arrays.cpp b/3rdparty/spirv-tools/source/opt/copy_prop_arrays.cpp new file mode 100644 index 000000000..eb4694f22 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/copy_prop_arrays.cpp @@ -0,0 +1,848 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "copy_prop_arrays.h" +#include "ir_builder.h" + +namespace { +const uint32_t kLoadPointerInOperand = 0; +const uint32_t kStorePointerInOperand = 0; +const uint32_t kStoreObjectInOperand = 1; +const uint32_t kCompositeExtractObjectInOperand = 0; +} // namespace + +namespace spvtools { +namespace opt { + +Pass::Status CopyPropagateArrays::Process(ir::IRContext* ctx) { + InitializeProcessing(ctx); + + bool modified = false; + for (ir::Function& function : *get_module()) { + ir::BasicBlock* entry_bb = &*function.begin(); + + for (auto var_inst = entry_bb->begin(); var_inst->opcode() == SpvOpVariable; + ++var_inst) { + if (!IsPointerToArrayType(var_inst->type_id())) { + continue; + } + + // Find the only store to the entire memory location, if it exists. + ir::Instruction* store_inst = FindStoreInstruction(&*var_inst); + + if (!store_inst) { + continue; + } + + std::unique_ptr source_object = + FindSourceObjectIfPossible(&*var_inst, store_inst); + + if (source_object != nullptr) { + if (CanUpdateUses(&*var_inst, source_object->GetPointerTypeId())) { + modified = true; + PropagateObject(&*var_inst, source_object.get(), store_inst); + } + } + } + } + return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange); +} + +std::unique_ptr +CopyPropagateArrays::FindSourceObjectIfPossible(ir::Instruction* var_inst, + ir::Instruction* store_inst) { + assert(var_inst->opcode() == SpvOpVariable && "Expecting a variable."); + + // Check that the variable is a composite object where |store_inst| + // dominates all of its loads. + if (!store_inst) { + return nullptr; + } + + // Look at the loads to ensure they are dominated by the store. + if (!HasValidReferencesOnly(var_inst, store_inst)) { + return nullptr; + } + + // If so, look at the store to see if it is the copy of an object. + std::unique_ptr source = GetSourceObjectIfAny( + store_inst->GetSingleWordInOperand(kStoreObjectInOperand)); + + if (!source) { + return nullptr; + } + + // Ensure that |source| does not change between the point at which it is + // loaded, and the position in which |var_inst| is loaded. + // + // For now we will go with the easy to implement approach, and check that the + // entire variable (not just the specific component) is never written to. + + if (!HasNoStores(source->GetVariable())) { + return nullptr; + } + return source; +} + +ir::Instruction* CopyPropagateArrays::FindStoreInstruction( + const ir::Instruction* var_inst) const { + ir::Instruction* store_inst = nullptr; + get_def_use_mgr()->WhileEachUser( + var_inst, [&store_inst, var_inst](ir::Instruction* use) { + if (use->opcode() == SpvOpStore && + use->GetSingleWordInOperand(kStorePointerInOperand) == + var_inst->result_id()) { + if (store_inst == nullptr) { + store_inst = use; + } else { + store_inst = nullptr; + return false; + } + } + return true; + }); + return store_inst; +} + +void CopyPropagateArrays::PropagateObject(ir::Instruction* var_inst, + MemoryObject* source, + ir::Instruction* insertion_point) { + assert(var_inst->opcode() == SpvOpVariable && + "This function propagates variables."); + + ir::Instruction* new_access_chain = + BuildNewAccessChain(insertion_point, source); + context()->KillNamesAndDecorates(var_inst); + UpdateUses(var_inst, new_access_chain); +} + +ir::Instruction* CopyPropagateArrays::BuildNewAccessChain( + ir::Instruction* insertion_point, + CopyPropagateArrays::MemoryObject* source) const { + InstructionBuilder builder(context(), insertion_point, + ir::IRContext::kAnalysisDefUse | + ir::IRContext::kAnalysisInstrToBlockMapping); + + if (source->AccessChain().size() == 0) { + return source->GetVariable(); + } + + return builder.AddAccessChain(source->GetPointerTypeId(), + source->GetVariable()->result_id(), + source->AccessChain()); +} + +bool CopyPropagateArrays::HasNoStores(ir::Instruction* ptr_inst) { + return get_def_use_mgr()->WhileEachUser( + ptr_inst, [this](ir::Instruction* use) { + if (use->opcode() == SpvOpLoad) { + return true; + } else if (use->opcode() == SpvOpAccessChain) { + return HasNoStores(use); + } else if (use->IsDecoration() || use->opcode() == SpvOpName) { + return true; + } else if (use->opcode() == SpvOpStore) { + return false; + } else if (use->opcode() == SpvOpImageTexelPointer) { + return true; + } + // Some other instruction. Be conservative. + return false; + }); +} + +bool CopyPropagateArrays::HasValidReferencesOnly(ir::Instruction* ptr_inst, + ir::Instruction* store_inst) { + ir::BasicBlock* store_block = context()->get_instr_block(store_inst); + opt::DominatorAnalysis* dominator_analysis = + context()->GetDominatorAnalysis(store_block->GetParent(), *cfg()); + + return get_def_use_mgr()->WhileEachUser( + ptr_inst, + [this, store_inst, dominator_analysis, ptr_inst](ir::Instruction* use) { + if (use->opcode() == SpvOpLoad || + use->opcode() == SpvOpImageTexelPointer) { + // TODO: If there are many load in the same BB as |store_inst| the + // time to do the multiple traverses can add up. Consider collecting + // those loads and doing a single traversal. + return dominator_analysis->Dominates(store_inst, use); + } else if (use->opcode() == SpvOpAccessChain) { + return HasValidReferencesOnly(use, store_inst); + } else if (use->IsDecoration() || use->opcode() == SpvOpName) { + return true; + } else if (use->opcode() == SpvOpStore) { + // If we are storing to part of the object it is not an candidate. + return ptr_inst->opcode() == SpvOpVariable && + store_inst->GetSingleWordInOperand(kStorePointerInOperand) == + ptr_inst->result_id(); + } + // Some other instruction. Be conservative. + return false; + }); +} + +std::unique_ptr +CopyPropagateArrays::GetSourceObjectIfAny(uint32_t result) { + ir::Instruction* result_inst = context()->get_def_use_mgr()->GetDef(result); + + switch (result_inst->opcode()) { + case SpvOpLoad: + return BuildMemoryObjectFromLoad(result_inst); + case SpvOpCompositeExtract: + return BuildMemoryObjectFromExtract(result_inst); + case SpvOpCompositeConstruct: + return BuildMemoryObjectFromCompositeConstruct(result_inst); + case SpvOpCopyObject: + return GetSourceObjectIfAny(result_inst->GetSingleWordInOperand(0)); + case SpvOpCompositeInsert: + return BuildMemoryObjectFromInsert(result_inst); + default: + return nullptr; + } +} + +std::unique_ptr +CopyPropagateArrays::BuildMemoryObjectFromLoad(ir::Instruction* load_inst) { + std::vector components_in_reverse; + analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); + + ir::Instruction* current_inst = def_use_mgr->GetDef( + load_inst->GetSingleWordInOperand(kLoadPointerInOperand)); + + // Build the access chain for the memory object by collecting the indices used + // in the OpAccessChain instructions. If we find a variable index, then + // return |nullptr| because we cannot know for sure which memory location is + // used. + // + // It is built in reverse order because the different |OpAccessChain| + // instructions are visited in reverse order from which they are applied. + while (current_inst->opcode() == SpvOpAccessChain) { + for (uint32_t i = current_inst->NumInOperands() - 1; i >= 1; --i) { + uint32_t element_index_id = current_inst->GetSingleWordInOperand(i); + components_in_reverse.push_back(element_index_id); + } + current_inst = def_use_mgr->GetDef(current_inst->GetSingleWordInOperand(0)); + } + + // If the address in the load is not constructed from an |OpVariable| + // instruction followed by a series of |OpAccessChain| instructions, then + // return |nullptr| because we cannot identify the owner or access chain + // exactly. + if (current_inst->opcode() != SpvOpVariable) { + return nullptr; + } + + // Build the memory object. Use |rbegin| and |rend| to put the access chain + // back in the correct order. + return std::unique_ptr( + new MemoryObject(current_inst, components_in_reverse.rbegin(), + components_in_reverse.rend())); +} + +std::unique_ptr +CopyPropagateArrays::BuildMemoryObjectFromExtract( + ir::Instruction* extract_inst) { + assert(extract_inst->opcode() == SpvOpCompositeExtract && + "Expecting an OpCompositeExtract instruction."); + analysis::ConstantManager* const_mgr = context()->get_constant_mgr(); + + std::unique_ptr result = GetSourceObjectIfAny( + extract_inst->GetSingleWordInOperand(kCompositeExtractObjectInOperand)); + + if (result) { + analysis::Integer int_type(32, false); + const analysis::Type* uint32_type = + context()->get_type_mgr()->GetRegisteredType(&int_type); + + std::vector components; + // Convert the indices in the extract instruction to a series of ids that + // can be used by the |OpAccessChain| instruction. + for (uint32_t i = 1; i < extract_inst->NumInOperands(); ++i) { + uint32_t index = extract_inst->GetSingleWordInOperand(1); + const analysis::Constant* index_const = + const_mgr->GetConstant(uint32_type, {index}); + components.push_back( + const_mgr->GetDefiningInstruction(index_const)->result_id()); + } + result->GetMember(components); + return result; + } + return nullptr; +} + +std::unique_ptr +CopyPropagateArrays::BuildMemoryObjectFromCompositeConstruct( + ir::Instruction* conststruct_inst) { + assert(conststruct_inst->opcode() == SpvOpCompositeConstruct && + "Expecting an OpCompositeConstruct instruction."); + + // If every operand in the instruction are part of the same memory object, and + // are being combined in the same order, then the result is the same as the + // parent. + + std::unique_ptr memory_object = + GetSourceObjectIfAny(conststruct_inst->GetSingleWordInOperand(0)); + + if (!memory_object) { + return nullptr; + } + + if (!memory_object->IsMember()) { + return nullptr; + } + + analysis::ConstantManager* const_mgr = context()->get_constant_mgr(); + const analysis::Constant* last_access = + const_mgr->FindDeclaredConstant(memory_object->AccessChain().back()); + if (!last_access || + (!last_access->AsIntConstant() && !last_access->AsNullConstant())) { + return nullptr; + } + + if (last_access->GetU32() != 0) { + return nullptr; + } + + memory_object->GetParent(); + + if (memory_object->GetNumberOfMembers() != + conststruct_inst->NumInOperands()) { + return nullptr; + } + + for (uint32_t i = 1; i < conststruct_inst->NumInOperands(); ++i) { + std::unique_ptr member_object = + GetSourceObjectIfAny(conststruct_inst->GetSingleWordInOperand(i)); + + if (!member_object->IsMember()) { + return nullptr; + } + + if (!memory_object->Contains(member_object.get())) { + return nullptr; + } + + last_access = + const_mgr->FindDeclaredConstant(member_object->AccessChain().back()); + if (!last_access || !last_access->AsIntConstant()) { + return nullptr; + } + + if (last_access->GetU32() != i) { + return nullptr; + } + } + return memory_object; +} + +std::unique_ptr +CopyPropagateArrays::BuildMemoryObjectFromInsert(ir::Instruction* insert_inst) { + assert(insert_inst->opcode() == SpvOpCompositeInsert && + "Expecting an OpCompositeInsert instruction."); + + analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); + analysis::TypeManager* type_mgr = context()->get_type_mgr(); + analysis::ConstantManager* const_mgr = context()->get_constant_mgr(); + const analysis::Type* result_type = type_mgr->GetType(insert_inst->type_id()); + + uint32_t number_of_elements = 0; + if (const analysis::Struct* struct_type = result_type->AsStruct()) { + number_of_elements = + static_cast(struct_type->element_types().size()); + } else if (const analysis::Array* array_type = result_type->AsArray()) { + const analysis::Constant* length_const = + const_mgr->FindDeclaredConstant(array_type->LengthId()); + assert(length_const->AsIntConstant()); + number_of_elements = length_const->AsIntConstant()->GetU32(); + } else if (const analysis::Vector* vector_type = result_type->AsVector()) { + number_of_elements = vector_type->element_count(); + } else if (const analysis::Matrix* matrix_type = result_type->AsMatrix()) { + number_of_elements = matrix_type->element_count(); + } + + if (number_of_elements == 0) { + return nullptr; + } + + if (insert_inst->NumInOperands() != 3) { + return nullptr; + } + + if (insert_inst->GetSingleWordInOperand(2) != number_of_elements - 1) { + return nullptr; + } + + std::unique_ptr memory_object = + GetSourceObjectIfAny(insert_inst->GetSingleWordInOperand(0)); + + if (!memory_object) { + return nullptr; + } + + if (!memory_object->IsMember()) { + return nullptr; + } + + const analysis::Constant* last_access = + const_mgr->FindDeclaredConstant(memory_object->AccessChain().back()); + if (!last_access || !last_access->AsIntConstant()) { + return nullptr; + } + + if (last_access->GetU32() != number_of_elements - 1) { + return nullptr; + } + + memory_object->GetParent(); + + ir::Instruction* current_insert = + def_use_mgr->GetDef(insert_inst->GetSingleWordInOperand(1)); + for (uint32_t i = number_of_elements - 1; i > 0; --i) { + if (current_insert->opcode() != SpvOpCompositeInsert) { + return nullptr; + } + + if (current_insert->NumInOperands() != 3) { + return nullptr; + } + + if (current_insert->GetSingleWordInOperand(2) != i - 1) { + return nullptr; + } + + std::unique_ptr current_memory_object = + GetSourceObjectIfAny(current_insert->GetSingleWordInOperand(0)); + + if (!current_memory_object) { + return nullptr; + } + + if (!current_memory_object->IsMember()) { + return nullptr; + } + + if (memory_object->AccessChain().size() + 1 != + current_memory_object->AccessChain().size()) { + return nullptr; + } + + if (!memory_object->Contains(current_memory_object.get())) { + return nullptr; + } + + const analysis::Constant* current_last_access = + const_mgr->FindDeclaredConstant( + current_memory_object->AccessChain().back()); + if (!current_last_access || !current_last_access->AsIntConstant()) { + return nullptr; + } + + if (current_last_access->GetU32() != i - 1) { + return nullptr; + } + current_insert = + def_use_mgr->GetDef(current_insert->GetSingleWordInOperand(1)); + } + + return memory_object; +} + +bool CopyPropagateArrays::IsPointerToArrayType(uint32_t type_id) { + analysis::TypeManager* type_mgr = context()->get_type_mgr(); + analysis::Pointer* pointer_type = type_mgr->GetType(type_id)->AsPointer(); + if (pointer_type) { + return pointer_type->pointee_type()->kind() == analysis::Type::kArray || + pointer_type->pointee_type()->kind() == analysis::Type::kImage; + } + return false; +} + +bool CopyPropagateArrays::CanUpdateUses(ir::Instruction* original_ptr_inst, + uint32_t type_id) { + analysis::TypeManager* type_mgr = context()->get_type_mgr(); + analysis::ConstantManager* const_mgr = context()->get_constant_mgr(); + analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); + + analysis::Type* type = type_mgr->GetType(type_id); + if (type->AsRuntimeArray()) { + return false; + } + + if (!type->AsStruct() && !type->AsArray() && !type->AsPointer()) { + // If the type is not an aggregate, then the desired type must be the + // same as the current type. No work to do, and we can do that. + return true; + } + + return def_use_mgr->WhileEachUse( + original_ptr_inst, + [this, type_mgr, const_mgr, type](ir::Instruction* use, uint32_t) { + switch (use->opcode()) { + case SpvOpLoad: { + analysis::Pointer* pointer_type = type->AsPointer(); + uint32_t new_type_id = + type_mgr->GetId(pointer_type->pointee_type()); + + if (new_type_id != use->type_id()) { + return CanUpdateUses(use, new_type_id); + } + return true; + } + case SpvOpAccessChain: { + analysis::Pointer* pointer_type = type->AsPointer(); + const analysis::Type* pointee_type = pointer_type->pointee_type(); + + std::vector access_chain; + for (uint32_t i = 1; i < use->NumInOperands(); ++i) { + const analysis::Constant* index_const = + const_mgr->FindDeclaredConstant( + use->GetSingleWordInOperand(i)); + if (index_const) { + access_chain.push_back(index_const->AsIntConstant()->GetU32()); + } else { + // Variable index means the type is a type where every element + // is the same type. Use element 0 to get the type. + access_chain.push_back(0); + } + } + + const analysis::Type* new_pointee_type = + type_mgr->GetMemberType(pointee_type, access_chain); + opt::analysis::Pointer pointerTy(new_pointee_type, + pointer_type->storage_class()); + uint32_t new_pointer_type_id = + context()->get_type_mgr()->GetTypeInstruction(&pointerTy); + + if (new_pointer_type_id != use->type_id()) { + return CanUpdateUses(use, new_pointer_type_id); + } + return true; + } + case SpvOpCompositeExtract: { + std::vector access_chain; + for (uint32_t i = 1; i < use->NumInOperands(); ++i) { + access_chain.push_back(use->GetSingleWordInOperand(i)); + } + + const analysis::Type* new_type = + type_mgr->GetMemberType(type, access_chain); + uint32_t new_type_id = type_mgr->GetTypeInstruction(new_type); + + if (new_type_id != use->type_id()) { + return CanUpdateUses(use, new_type_id); + } + return true; + } + case SpvOpStore: + // Can't handle changing the type of a store. There are too many + // other things that might need to change as well. Not worth the + // effort. Punting for now. + + // TODO (s-perron): This can be handled by expanding the store into + // a series of extracts, composite constructs, and a store. + return true; + case SpvOpImageTexelPointer: + case SpvOpName: + return true; + default: + return use->IsDecoration(); + } + }); +} +void CopyPropagateArrays::UpdateUses(ir::Instruction* original_ptr_inst, + ir::Instruction* new_ptr_inst) { + // TODO (s-perron): Keep the def-use manager up to date. Not done now because + // it can cause problems for the |ForEachUse| traversals. Can be use by + // keeping a list of instructions that need updating, and then updating them + // in |PropagateObject|. + + analysis::TypeManager* type_mgr = context()->get_type_mgr(); + analysis::ConstantManager* const_mgr = context()->get_constant_mgr(); + analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); + + std::vector > uses; + def_use_mgr->ForEachUse(original_ptr_inst, + [&uses](ir::Instruction* use, uint32_t index) { + uses.push_back({use, index}); + }); + + for (auto pair : uses) { + ir::Instruction* use = pair.first; + uint32_t index = pair.second; + analysis::Pointer* pointer_type = nullptr; + switch (use->opcode()) { + case SpvOpLoad: { + // Replace the actual use. + context()->ForgetUses(use); + use->SetOperand(index, {new_ptr_inst->result_id()}); + + // Update the type. + pointer_type = type_mgr->GetType(new_ptr_inst->type_id())->AsPointer(); + uint32_t new_type_id = type_mgr->GetId(pointer_type->pointee_type()); + if (new_type_id != use->type_id()) { + use->SetResultType(new_type_id); + context()->AnalyzeUses(use); + UpdateUses(use, use); + } else { + context()->AnalyzeUses(use); + } + } break; + case SpvOpAccessChain: { + // Update the actual use. + context()->ForgetUses(use); + use->SetOperand(index, {new_ptr_inst->result_id()}); + + // Update the result type. + pointer_type = type_mgr->GetType(new_ptr_inst->type_id())->AsPointer(); + const analysis::Type* pointee_type = pointer_type->pointee_type(); + + // Convert the ids on the OpAccessChain to indices that can be used to + // get the specific member. + std::vector access_chain; + for (uint32_t i = 1; i < use->NumInOperands(); ++i) { + const analysis::Constant* index_const = + const_mgr->FindDeclaredConstant(use->GetSingleWordInOperand(i)); + if (index_const) { + access_chain.push_back(index_const->AsIntConstant()->GetU32()); + } else { + // Variable index means the type is an type where every element + // is the same type. Use element 0 to get the type. + access_chain.push_back(0); + } + } + const analysis::Type* new_pointee_type = + type_mgr->GetMemberType(pointee_type, access_chain); + + // Now build a pointer to the type of the member. + opt::analysis::Pointer new_pointer_type(new_pointee_type, + pointer_type->storage_class()); + uint32_t new_pointer_type_id = + context()->get_type_mgr()->GetTypeInstruction(&new_pointer_type); + + if (new_pointer_type_id != use->type_id()) { + use->SetResultType(new_pointer_type_id); + context()->AnalyzeUses(use); + UpdateUses(use, use); + } else { + context()->AnalyzeUses(use); + } + } break; + case SpvOpCompositeExtract: { + // Update the actual use. + context()->ForgetUses(use); + use->SetOperand(index, {new_ptr_inst->result_id()}); + + std::vector access_chain; + for (uint32_t i = 1; i < use->NumInOperands(); ++i) { + access_chain.push_back(use->GetSingleWordInOperand(i)); + } + + const analysis::Type* type = type_mgr->GetType(new_ptr_inst->type_id()); + const analysis::Type* new_type = + type_mgr->GetMemberType(type, access_chain); + uint32_t new_type_id = type_mgr->GetTypeInstruction(new_type); + + if (new_type_id != use->type_id()) { + use->SetResultType(new_type_id); + context()->AnalyzeUses(use); + UpdateUses(use, use); + } else { + context()->AnalyzeUses(use); + } + } break; + case SpvOpStore: + // If the use is the pointer, then it is the single store to that + // variable. We do not want to replace it. Instead, it will become + // dead after all of the loads are removed, and ADCE will get rid of it. + // + // If the use is the object being stored, we will create a copy of the + // object turning it into the correct type. The copy is done by + // decomposing the object into the base type, which must be the same, + // and then rebuilding them. + if (index == 1) { + ir::Instruction* target_pointer = def_use_mgr->GetDef( + use->GetSingleWordInOperand(kStorePointerInOperand)); + pointer_type = + type_mgr->GetType(target_pointer->type_id())->AsPointer(); + uint32_t copy = + GenerateCopy(original_ptr_inst, + type_mgr->GetId(pointer_type->pointee_type()), use); + + context()->ForgetUses(use); + use->SetInOperand(index, {copy}); + context()->AnalyzeUses(use); + } + break; + case SpvOpImageTexelPointer: + // We treat an OpImageTexelPointer as a load. The result type should + // always have the Image storage class, and should not need to be + // updated. + + // Replace the actual use. + context()->ForgetUses(use); + use->SetOperand(index, {new_ptr_inst->result_id()}); + context()->AnalyzeUses(use); + break; + default: + assert(false && "Don't know how to rewrite instruction"); + break; + } + } +} + +uint32_t CopyPropagateArrays::GenerateCopy( + ir::Instruction* object_inst, uint32_t new_type_id, + ir::Instruction* insertion_position) { + analysis::TypeManager* type_mgr = context()->get_type_mgr(); + analysis::ConstantManager* const_mgr = context()->get_constant_mgr(); + + uint32_t original_type_id = object_inst->type_id(); + if (original_type_id == new_type_id) { + return object_inst->result_id(); + } + + opt::InstructionBuilder ir_builder( + context(), insertion_position, + ir::IRContext::kAnalysisInstrToBlockMapping | + ir::IRContext::kAnalysisDefUse); + + analysis::Type* original_type = type_mgr->GetType(original_type_id); + analysis::Type* new_type = type_mgr->GetType(new_type_id); + + if (const analysis::Array* original_array_type = original_type->AsArray()) { + uint32_t original_element_type_id = + type_mgr->GetId(original_array_type->element_type()); + + analysis::Array* new_array_type = new_type->AsArray(); + assert(new_array_type != nullptr && "Can't copy an array to a non-array."); + uint32_t new_element_type_id = + type_mgr->GetId(new_array_type->element_type()); + + std::vector element_ids; + const analysis::Constant* length_const = + const_mgr->FindDeclaredConstant(original_array_type->LengthId()); + assert(length_const->AsIntConstant()); + uint32_t array_length = length_const->AsIntConstant()->GetU32(); + for (uint32_t i = 0; i < array_length; i++) { + ir::Instruction* extract = ir_builder.AddCompositeExtract( + original_element_type_id, object_inst->result_id(), {i}); + element_ids.push_back( + GenerateCopy(extract, new_element_type_id, insertion_position)); + } + + return ir_builder.AddCompositeConstruct(new_type_id, element_ids) + ->result_id(); + } else if (const analysis::Struct* original_struct_type = + original_type->AsStruct()) { + analysis::Struct* new_struct_type = new_type->AsStruct(); + + const std::vector& original_types = + original_struct_type->element_types(); + const std::vector& new_types = + new_struct_type->element_types(); + std::vector element_ids; + for (uint32_t i = 0; i < original_types.size(); i++) { + ir::Instruction* extract = ir_builder.AddCompositeExtract( + type_mgr->GetId(original_types[i]), object_inst->result_id(), {i}); + element_ids.push_back(GenerateCopy(extract, type_mgr->GetId(new_types[i]), + insertion_position)); + } + return ir_builder.AddCompositeConstruct(new_type_id, element_ids) + ->result_id(); + } else { + // If we do not have an aggregate type, then we have a problem. Either we + // found multiple instances of the same type, or we are copying to an + // incompatible type. Either way the code is illegal. + assert(false && + "Don't know how to copy this type. Code is likely illegal."); + } + return 0; +} + +void CopyPropagateArrays::MemoryObject::GetMember( + const std::vector& access_chain) { + access_chain_.insert(access_chain_.end(), access_chain.begin(), + access_chain.end()); +} + +uint32_t CopyPropagateArrays::MemoryObject::GetNumberOfMembers() { + ir::IRContext* context = variable_inst_->context(); + analysis::TypeManager* type_mgr = context->get_type_mgr(); + + const analysis::Type* type = type_mgr->GetType(variable_inst_->type_id()); + type = type->AsPointer()->pointee_type(); + + std::vector access_indices = GetAccessIds(); + type = type_mgr->GetMemberType(type, access_indices); + + if (const analysis::Struct* struct_type = type->AsStruct()) { + return static_cast(struct_type->element_types().size()); + } else if (const analysis::Array* array_type = type->AsArray()) { + const analysis::Constant* length_const = + context->get_constant_mgr()->FindDeclaredConstant( + array_type->LengthId()); + assert(length_const->AsIntConstant()); + return length_const->AsIntConstant()->GetU32(); + } else if (const analysis::Vector* vector_type = type->AsVector()) { + return vector_type->element_count(); + } else if (const analysis::Matrix* matrix_type = type->AsMatrix()) { + return matrix_type->element_count(); + } else { + return 0; + } +} + +template +CopyPropagateArrays::MemoryObject::MemoryObject(ir::Instruction* var_inst, + iterator begin, iterator end) + : variable_inst_(var_inst), access_chain_(begin, end) {} + +std::vector CopyPropagateArrays::MemoryObject::GetAccessIds() const { + analysis::ConstantManager* const_mgr = + variable_inst_->context()->get_constant_mgr(); + + std::vector access_indices; + for (uint32_t id : AccessChain()) { + const analysis::Constant* element_index_const = + const_mgr->FindDeclaredConstant(id); + if (!element_index_const) { + access_indices.push_back(0); + } else { + assert(element_index_const->AsIntConstant()); + access_indices.push_back(element_index_const->AsIntConstant()->GetU32()); + } + } + return access_indices; +} + +bool CopyPropagateArrays::MemoryObject::Contains( + CopyPropagateArrays::MemoryObject* other) { + if (this->GetVariable() != other->GetVariable()) { + return false; + } + + if (AccessChain().size() > other->AccessChain().size()) { + return false; + } + + for (uint32_t i = 0; i < AccessChain().size(); i++) { + if (AccessChain()[i] != other->AccessChain()[i]) { + return false; + } + } + return true; +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/copy_prop_arrays.h b/3rdparty/spirv-tools/source/opt/copy_prop_arrays.h new file mode 100644 index 000000000..db6156a2b --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/copy_prop_arrays.h @@ -0,0 +1,231 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_COPY_PROP_H_ +#define LIBSPIRV_OPT_COPY_PROP_H_ + +#include "mem_pass.h" + +namespace spvtools { +namespace opt { + +// This pass implements a simple array copy propagation. It does not do a full +// array data flow. It looks for simple cases that meet the following +// conditions: +// +// 1) The source must never be stored to. +// 2) The target must be stored to exactly once. +// 3) The store to the target must be a store to the entire array, and be a +// copy of the entire source. +// 4) All loads of the target must be dominated by the store. +// +// The hard part is keeping all of the types correct. We do not want to +// have to do too large a search to update everything, which may not be +// possible, do we give up if we see any instruction that might be hard to +// update. + +class CopyPropagateArrays : public MemPass { + public: + const char* name() const override { return "copy-propagate-arrays"; } + Status Process(ir::IRContext*) override; + + ir::IRContext::Analysis GetPreservedAnalyses() override { + return ir::IRContext::kAnalysisDefUse | ir::IRContext::kAnalysisCFG | + ir::IRContext::kAnalysisInstrToBlockMapping | + ir::IRContext::kAnalysisLoopAnalysis | + ir::IRContext::kAnalysisDecorations | + ir::IRContext::kAnalysisDominatorAnalysis | + ir::IRContext::kAnalysisNameMap; + } + + private: + // The class used to identify a particular memory object. This memory object + // will be owned by a particular variable, meaning that the memory is part of + // that variable. It could be the entire variable or a member of the + // variable. + class MemoryObject { + public: + // Construction a memory object that is owned by |var_inst|. The iterator + // |begin| and |end| traverse a container of integers that identify which + // member of |var_inst| this memory object will represent. These integers + // are interpreted the same way they would be in an |OpAccessChain| + // instruction. + template + MemoryObject(ir::Instruction* var_inst, iterator begin, iterator end); + + // Change |this| to now point to the member identified by |access_chain| + // (starting from the current member). The elements in |access_chain| are + // interpreted the same as the indices in the |OpAccessChain| + // instruction. + void GetMember(const std::vector& access_chain); + + // Change |this| to now represent the first enclosing object to which it + // belongs. (Remove the last element off the access_chain). It is invalid + // to call this function if |this| does not represent a member of its owner. + void GetParent() { + assert(IsMember()); + access_chain_.pop_back(); + } + + // Returns true if |this| represents a member of its owner, and not the + // entire variable. + bool IsMember() const { return !access_chain_.empty(); } + + // Returns the number of members in the object represented by |this|. If + // |this| does not represent a composite type, the return value will be 0. + uint32_t GetNumberOfMembers(); + + // Returns the owning variable that the memory object is contained in. + ir::Instruction* GetVariable() const { return variable_inst_; } + + // Returns a vector of integers that can be used to access the specific + // member that |this| represents starting from the owning variable. These + // values are to be interpreted the same way the indices are in an + // |OpAccessChain| instruction. + const std::vector& AccessChain() const { return access_chain_; } + + // Returns the type id of the pointer type that can be used to point to this + // memory object. + uint32_t GetPointerTypeId() const { + analysis::TypeManager* type_mgr = + GetVariable()->context()->get_type_mgr(); + const analysis::Pointer* pointer_type = + type_mgr->GetType(GetVariable()->type_id())->AsPointer(); + const analysis::Type* var_type = pointer_type->pointee_type(); + const analysis::Type* member_type = + type_mgr->GetMemberType(var_type, GetAccessIds()); + uint32_t member_type_id = type_mgr->GetId(member_type); + assert(member_type != 0); + uint32_t member_pointer_type_id = type_mgr->FindPointerToType( + member_type_id, pointer_type->storage_class()); + return member_pointer_type_id; + } + + // Returns the storage class of the memory object. + SpvStorageClass GetStorageClass() const { + analysis::TypeManager* type_mgr = + GetVariable()->context()->get_type_mgr(); + const analysis::Pointer* pointer_type = + type_mgr->GetType(GetVariable()->type_id())->AsPointer(); + return pointer_type->storage_class(); + } + + // Returns true if |other| represents memory that is contains inside of the + // memory represented by |this|. + bool Contains(MemoryObject* other); + + private: + // The variable that owns this memory object. + ir::Instruction* variable_inst_; + + // The access chain to reach the particular member the memory object + // represents. It should be interpreted the same way the indices in an + // |OpAccessChain| are interpreted. + std::vector access_chain_; + std::vector GetAccessIds() const; + }; + + // Returns the memory object being stored to |var_inst| in the store + // instruction |store_inst|, if one exists, that can be used in place of + // |var_inst| in all of the loads of |var_inst|. This code is conservative + // and only identifies very simple cases. If no such memory object can be + // found, the return value is |nullptr|. + std::unique_ptr FindSourceObjectIfPossible( + ir::Instruction* var_inst, ir::Instruction* store_inst); + + // Replaces all loads of |var_inst| with a load from |source| instead. + // |insertion_pos| is a position where it is possible to construct the + // address of |source| and also dominates all of the loads of |var_inst|. + void PropagateObject(ir::Instruction* var_inst, MemoryObject* source, + ir::Instruction* insertion_pos); + + // Returns true if all of the references to |ptr_inst| can be rewritten and + // are dominated by |store_inst|. + bool HasValidReferencesOnly(ir::Instruction* ptr_inst, + ir::Instruction* store_inst); + + // Returns a memory object that at one time was equivalent to the value in + // |result|. If no such memory object exists, the return value is |nullptr|. + std::unique_ptr GetSourceObjectIfAny(uint32_t result); + + // Returns the memory object that is loaded by |load_inst|. If a memory + // object cannot be identified, the return value is |nullptr|. The opcode of + // |load_inst| must be |OpLoad|. + std::unique_ptr BuildMemoryObjectFromLoad( + ir::Instruction* load_inst); + + // Returns the memory object that at some point was equivalent to the result + // of |extract_inst|. If a memory object cannot be identified, the return + // value is |nullptr|. The opcode of |extract_inst| must be + // |OpCompositeExtract|. + std::unique_ptr BuildMemoryObjectFromExtract( + ir::Instruction* extract_inst); + + // Returns the memory object that at some point was equivalent to the result + // of |construct_inst|. If a memory object cannot be identified, the return + // value is |nullptr|. The opcode of |constuct_inst| must be + // |OpCompositeConstruct|. + std::unique_ptr BuildMemoryObjectFromCompositeConstruct( + ir::Instruction* conststruct_inst); + + // Returns the memory object that at some point was equivalent to the result + // of |insert_inst|. If a memory object cannot be identified, the return + // value is |nullptr\. The opcode of |insert_inst| must be + // |OpCompositeInsert|. This function looks for a series of + // |OpCompositeInsert| instructions that insert the elements one at a time in + // order from beginning to end. + std::unique_ptr BuildMemoryObjectFromInsert( + ir::Instruction* insert_inst); + + // Return true if |type_id| is a pointer type whose pointee type is an array. + bool IsPointerToArrayType(uint32_t type_id); + + // Returns true of there are not stores using |ptr_inst| or something derived + // from it. + bool HasNoStores(ir::Instruction* ptr_inst); + + // Creates an |OpAccessChain| instruction whose result is a pointer the memory + // represented by |source|. The new instruction will be placed before + // |insertion_point|. |insertion_point| must be part of a function. Returns + // the new instruction. + ir::Instruction* BuildNewAccessChain(ir::Instruction* insertion_point, + MemoryObject* source) const; + + // Rewrites all uses of |original_ptr| to use |new_pointer_inst| updating + // types of other instructions as needed. This function should not be called + // if |CanUpdateUses(original_ptr_inst, new_pointer_inst->type_id())| returns + // false. + void UpdateUses(ir::Instruction* original_ptr_inst, + ir::Instruction* new_pointer_inst); + + // Return true if |UpdateUses| is able to change all of the uses of + // |original_ptr_inst| to |type_id| and still have valid code. + bool CanUpdateUses(ir::Instruction* original_ptr_inst, uint32_t type_id); + + // Returns the id whose value is the same as |object_to_copy| except its type + // is |new_type_id|. Any instructions need to generate this value will be + // inserted before |insertion_position|. + uint32_t GenerateCopy(ir::Instruction* object_to_copy, uint32_t new_type_id, + ir::Instruction* insertion_position); + + // Returns a store to |var_inst| that writes to the entire variable, and is + // the only store that does so. Note it does not look through OpAccessChain + // instruction, so partial stores are not considered. + ir::Instruction* FindStoreInstruction(const ir::Instruction* var_inst) const; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_COPY_PROP_H_ diff --git a/3rdparty/spirv-tools/source/opt/dead_branch_elim_pass.cpp b/3rdparty/spirv-tools/source/opt/dead_branch_elim_pass.cpp new file mode 100644 index 000000000..5dfe42e61 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/dead_branch_elim_pass.cpp @@ -0,0 +1,394 @@ +// Copyright (c) 2017 The Khronos Group Inc. +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// Copyright (c) 2018 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dead_branch_elim_pass.h" + +#include "cfa.h" +#include "ir_context.h" +#include "iterator.h" +#include "make_unique.h" + +namespace spvtools { +namespace opt { + +namespace { + +const uint32_t kBranchCondTrueLabIdInIdx = 1; +const uint32_t kBranchCondFalseLabIdInIdx = 2; + +} // anonymous namespace + +bool DeadBranchElimPass::GetConstCondition(uint32_t condId, bool* condVal) { + bool condIsConst; + ir::Instruction* cInst = get_def_use_mgr()->GetDef(condId); + switch (cInst->opcode()) { + case SpvOpConstantFalse: { + *condVal = false; + condIsConst = true; + } break; + case SpvOpConstantTrue: { + *condVal = true; + condIsConst = true; + } break; + case SpvOpLogicalNot: { + bool negVal; + condIsConst = + GetConstCondition(cInst->GetSingleWordInOperand(0), &negVal); + if (condIsConst) *condVal = !negVal; + } break; + default: { condIsConst = false; } break; + } + return condIsConst; +} + +bool DeadBranchElimPass::GetConstInteger(uint32_t selId, uint32_t* selVal) { + ir::Instruction* sInst = get_def_use_mgr()->GetDef(selId); + uint32_t typeId = sInst->type_id(); + ir::Instruction* typeInst = get_def_use_mgr()->GetDef(typeId); + if (!typeInst || (typeInst->opcode() != SpvOpTypeInt)) return false; + // TODO(greg-lunarg): Support non-32 bit ints + if (typeInst->GetSingleWordInOperand(0) != 32) return false; + if (sInst->opcode() == SpvOpConstant) { + *selVal = sInst->GetSingleWordInOperand(0); + return true; + } else if (sInst->opcode() == SpvOpConstantNull) { + *selVal = 0; + return true; + } + return false; +} + +void DeadBranchElimPass::AddBranch(uint32_t labelId, ir::BasicBlock* bp) { + assert(get_def_use_mgr()->GetDef(labelId) != nullptr); + std::unique_ptr newBranch(new ir::Instruction( + context(), SpvOpBranch, 0, 0, + {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {labelId}}})); + get_def_use_mgr()->AnalyzeInstDefUse(&*newBranch); + bp->AddInstruction(std::move(newBranch)); +} + +ir::BasicBlock* DeadBranchElimPass::GetParentBlock(uint32_t id) { + return context()->get_instr_block(get_def_use_mgr()->GetDef(id)); +} + +bool DeadBranchElimPass::MarkLiveBlocks( + ir::Function* func, std::unordered_set* live_blocks) { + std::unordered_set continues; + std::vector stack; + stack.push_back(&*func->begin()); + bool modified = false; + while (!stack.empty()) { + ir::BasicBlock* block = stack.back(); + stack.pop_back(); + + // Live blocks doubles as visited set. + if (!live_blocks->insert(block).second) continue; + + uint32_t cont_id = block->ContinueBlockIdIfAny(); + if (cont_id != 0) continues.insert(GetParentBlock(cont_id)); + + ir::Instruction* terminator = block->terminator(); + uint32_t live_lab_id = 0; + // Check if the terminator has a single valid successor. + if (terminator->opcode() == SpvOpBranchConditional) { + bool condVal; + if (GetConstCondition(terminator->GetSingleWordInOperand(0u), &condVal)) { + live_lab_id = terminator->GetSingleWordInOperand( + condVal ? kBranchCondTrueLabIdInIdx : kBranchCondFalseLabIdInIdx); + } + } else if (terminator->opcode() == SpvOpSwitch) { + uint32_t sel_val; + if (GetConstInteger(terminator->GetSingleWordInOperand(0u), &sel_val)) { + // Search switch operands for selector value, set live_lab_id to + // corresponding label, use default if not found. + uint32_t icnt = 0; + uint32_t case_val; + terminator->WhileEachInOperand( + [&icnt, &case_val, &sel_val, &live_lab_id](const uint32_t* idp) { + if (icnt == 1) { + // Start with default label. + live_lab_id = *idp; + } else if (icnt > 1) { + if (icnt % 2 == 0) { + case_val = *idp; + } else { + if (case_val == sel_val) { + live_lab_id = *idp; + return false; + } + } + } + ++icnt; + return true; + }); + } + } + + // Don't simplify branches of continue blocks. A path from the continue to + // the header is required. + // TODO(alan-baker): They can be simplified iff there remains a path to the + // backedge. Structured control flow should guarantee one path hits the + // backedge, but I've removed the requirement for structured control flow + // from this pass. + bool simplify = live_lab_id != 0 && !continues.count(block); + + if (simplify) { + modified = true; + // Replace with unconditional branch. + // Remove the merge instruction if it is a selection merge. + AddBranch(live_lab_id, block); + context()->KillInst(terminator); + ir::Instruction* mergeInst = block->GetMergeInst(); + if (mergeInst && mergeInst->opcode() == SpvOpSelectionMerge) { + context()->KillInst(mergeInst); + } + stack.push_back(GetParentBlock(live_lab_id)); + } else { + // All successors are live. + const auto* const_block = block; + const_block->ForEachSuccessorLabel([&stack, this](const uint32_t label) { + stack.push_back(GetParentBlock(label)); + }); + } + } + + return modified; +} + +void DeadBranchElimPass::MarkUnreachableStructuredTargets( + const std::unordered_set& live_blocks, + std::unordered_set* unreachable_merges, + std::unordered_map* + unreachable_continues) { + for (auto block : live_blocks) { + if (auto merge_id = block->MergeBlockIdIfAny()) { + ir::BasicBlock* merge_block = GetParentBlock(merge_id); + if (!live_blocks.count(merge_block)) { + unreachable_merges->insert(merge_block); + } + if (auto cont_id = block->ContinueBlockIdIfAny()) { + ir::BasicBlock* cont_block = GetParentBlock(cont_id); + if (!live_blocks.count(cont_block)) { + (*unreachable_continues)[cont_block] = block; + } + } + } + } +} + +bool DeadBranchElimPass::FixPhiNodesInLiveBlocks( + ir::Function* func, const std::unordered_set& live_blocks, + const std::unordered_map& + unreachable_continues) { + bool modified = false; + for (auto& block : *func) { + if (live_blocks.count(&block)) { + for (auto iter = block.begin(); iter != block.end();) { + if (iter->opcode() != SpvOpPhi) { + break; + } + + bool changed = false; + bool backedge_added = false; + ir::Instruction* inst = &*iter; + std::vector operands; + // Build a complete set of operands (not just input operands). Start + // with type and result id operands. + operands.push_back(inst->GetOperand(0u)); + operands.push_back(inst->GetOperand(1u)); + // Iterate through the incoming labels and determine which to keep + // and/or modify. If there in an unreachable continue block, there will + // be an edge from that block to the header. We need to keep it to + // maintain the structured control flow. If the header has more that 2 + // incoming edges, then the OpPhi must have an entry for that edge. + // However, if there is only one other incoming edge, the OpPhi can be + // eliminated. + for (uint32_t i = 1; i < inst->NumInOperands(); i += 2) { + ir::BasicBlock* inc = GetParentBlock(inst->GetSingleWordInOperand(i)); + auto cont_iter = unreachable_continues.find(inc); + if (cont_iter != unreachable_continues.end() && + cont_iter->second == &block && inst->NumInOperands() > 4) { + if (get_def_use_mgr() + ->GetDef(inst->GetSingleWordInOperand(i - 1)) + ->opcode() == SpvOpUndef) { + // Already undef incoming value, no change necessary. + operands.push_back(inst->GetInOperand(i - 1)); + operands.push_back(inst->GetInOperand(i)); + backedge_added = true; + } else { + // Replace incoming value with undef if this phi exists in the + // loop header. Otherwise, this edge is not live since the + // unreachable continue block will be replaced with an + // unconditional branch to the header only. + operands.emplace_back( + SPV_OPERAND_TYPE_ID, + std::initializer_list{Type2Undef(inst->type_id())}); + operands.push_back(inst->GetInOperand(i)); + changed = true; + backedge_added = true; + } + } else if (live_blocks.count(inc) && inc->IsSuccessor(&block)) { + // Keep live incoming edge. + operands.push_back(inst->GetInOperand(i - 1)); + operands.push_back(inst->GetInOperand(i)); + } else { + // Remove incoming edge. + changed = true; + } + } + + if (changed) { + modified = true; + uint32_t continue_id = block.ContinueBlockIdIfAny(); + if (!backedge_added && continue_id != 0 && + unreachable_continues.count(GetParentBlock(continue_id)) && + operands.size() > 4) { + // Changed the backedge to branch from the continue block instead + // of a successor of the continue block. Add an entry to the phi to + // provide an undef for the continue block. Since the successor of + // the continue must also be unreachable (dominated by the continue + // block), any entry for the original backedge has been removed + // from the phi operands. + operands.emplace_back( + SPV_OPERAND_TYPE_ID, + std::initializer_list{Type2Undef(inst->type_id())}); + operands.emplace_back(SPV_OPERAND_TYPE_ID, + std::initializer_list{continue_id}); + } + + // Either replace the phi with a single value or rebuild the phi out + // of |operands|. + // + // We always have type and result id operands. So this phi has a + // single source if there are two more operands beyond those. + if (operands.size() == 4) { + // First input data operands is at index 2. + uint32_t replId = operands[2u].words[0]; + context()->ReplaceAllUsesWith(inst->result_id(), replId); + iter = context()->KillInst(&*inst); + } else { + // We've rewritten the operands, so first instruct the def/use + // manager to forget uses in the phi before we replace them. After + // replacing operands update the def/use manager by re-analyzing + // the used ids in this phi. + get_def_use_mgr()->EraseUseRecordsOfOperandIds(inst); + inst->ReplaceOperands(operands); + get_def_use_mgr()->AnalyzeInstUse(inst); + ++iter; + } + } else { + ++iter; + } + } + } + } + + return modified; +} + +bool DeadBranchElimPass::EraseDeadBlocks( + ir::Function* func, const std::unordered_set& live_blocks, + const std::unordered_set& unreachable_merges, + const std::unordered_map& + unreachable_continues) { + bool modified = false; + for (auto ebi = func->begin(); ebi != func->end();) { + if (unreachable_merges.count(&*ebi)) { + if (ebi->begin() != ebi->tail() || + ebi->terminator()->opcode() != SpvOpUnreachable) { + // Make unreachable, but leave the label. + KillAllInsts(&*ebi, false); + // Add unreachable terminator. + ebi->AddInstruction( + MakeUnique(context(), SpvOpUnreachable, 0, 0, + std::initializer_list{})); + modified = true; + } + ++ebi; + } else if (unreachable_continues.count(&*ebi)) { + uint32_t cont_id = unreachable_continues.find(&*ebi)->second->id(); + if (ebi->begin() != ebi->tail() || + ebi->terminator()->opcode() != SpvOpBranch || + ebi->terminator()->GetSingleWordInOperand(0u) != cont_id) { + // Make unreachable, but leave the label. + KillAllInsts(&*ebi, false); + // Add unconditional branch to header. + assert(unreachable_continues.count(&*ebi)); + ebi->AddInstruction( + MakeUnique(context(), SpvOpBranch, 0, 0, + std::initializer_list{ + {SPV_OPERAND_TYPE_ID, {cont_id}}})); + get_def_use_mgr()->AnalyzeInstUse(&*ebi->tail()); + modified = true; + } + ++ebi; + } else if (!live_blocks.count(&*ebi)) { + // Kill this block. + KillAllInsts(&*ebi); + ebi = ebi.Erase(); + modified = true; + } else { + ++ebi; + } + } + + return modified; +} + +bool DeadBranchElimPass::EliminateDeadBranches(ir::Function* func) { + bool modified = false; + std::unordered_set live_blocks; + modified |= MarkLiveBlocks(func, &live_blocks); + + std::unordered_set unreachable_merges; + std::unordered_map unreachable_continues; + MarkUnreachableStructuredTargets(live_blocks, &unreachable_merges, + &unreachable_continues); + modified |= FixPhiNodesInLiveBlocks(func, live_blocks, unreachable_continues); + modified |= EraseDeadBlocks(func, live_blocks, unreachable_merges, + unreachable_continues); + + return modified; +} + +void DeadBranchElimPass::Initialize(ir::IRContext* c) { + InitializeProcessing(c); +} + +Pass::Status DeadBranchElimPass::ProcessImpl() { + // Do not process if module contains OpGroupDecorate. Additional + // support required in KillNamesAndDecorates(). + // TODO(greg-lunarg): Add support for OpGroupDecorate + for (auto& ai : get_module()->annotations()) + if (ai.opcode() == SpvOpGroupDecorate) return Status::SuccessWithoutChange; + // Process all entry point functions + ProcessFunction pfn = [this](ir::Function* fp) { + return EliminateDeadBranches(fp); + }; + bool modified = ProcessReachableCallTree(pfn, context()); + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; +} + +DeadBranchElimPass::DeadBranchElimPass() {} + +Pass::Status DeadBranchElimPass::Process(ir::IRContext* module) { + Initialize(module); + return ProcessImpl(); +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/dead_branch_elim_pass.h b/3rdparty/spirv-tools/source/opt/dead_branch_elim_pass.h new file mode 100644 index 000000000..62ec582ca --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/dead_branch_elim_pass.h @@ -0,0 +1,138 @@ +// Copyright (c) 2017 The Khronos Group Inc. +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_DEAD_BRANCH_ELIM_PASS_H_ +#define LIBSPIRV_OPT_DEAD_BRANCH_ELIM_PASS_H_ + +#include +#include +#include +#include +#include +#include + +#include "basic_block.h" +#include "def_use_manager.h" +#include "mem_pass.h" +#include "module.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class DeadBranchElimPass : public MemPass { + using cbb_ptr = const ir::BasicBlock*; + + public: + DeadBranchElimPass(); + const char* name() const override { return "eliminate-dead-branches"; } + Status Process(ir::IRContext* context) override; + + ir::IRContext::Analysis GetPreservedAnalyses() override { + return ir::IRContext::kAnalysisDefUse; + } + + private: + // If |condId| is boolean constant, return conditional value in |condVal| and + // return true, otherwise return false. + bool GetConstCondition(uint32_t condId, bool* condVal); + + // If |valId| is a 32-bit integer constant, return value via |value| and + // return true, otherwise return false. + bool GetConstInteger(uint32_t valId, uint32_t* value); + + // Add branch to |labelId| to end of block |bp|. + void AddBranch(uint32_t labelId, ir::BasicBlock* bp); + + // For function |func|, look for BranchConditionals with constant condition + // and convert to a Branch to the indicated label. Delete resulting dead + // blocks. Note some such branches and blocks may be left to avoid creating + // invalid control flow. + // TODO(greg-lunarg): Remove remaining constant conditional branches and dead + // blocks. + bool EliminateDeadBranches(ir::Function* func); + + // Returns the basic block containing |id|. + // Note: this pass only requires correct instruction block mappings for the + // input. This pass does not preserve the block mapping, so it is not kept + // up-to-date during processing. + ir::BasicBlock* GetParentBlock(uint32_t id); + + // Marks live blocks reachable from the entry of |func|. Simplifies constant + // branches and switches as it proceeds, to limit the number of live blocks. + // It is careful not to eliminate backedges even if they are dead, but the + // header is live. Likewise, unreachable merge blocks named in live merge + // instruction must be retained (though they may be clobbered). + bool MarkLiveBlocks(ir::Function* func, + std::unordered_set* live_blocks); + + // Checks for unreachable merge and continue blocks with live headers; those + // blocks must be retained. Continues are tracked separately so that a live + // phi can be updated to take an undef value from any of its predecessors + // that are unreachable continues. + // + // |unreachable_continues| maps the id of an unreachable continue target to + // the header block that declares it. + void MarkUnreachableStructuredTargets( + const std::unordered_set& live_blocks, + std::unordered_set* unreachable_merges, + std::unordered_map* + unreachable_continues); + + // Fix phis in reachable blocks so that only live (or unremovable) incoming + // edges are present. If the block now only has a single live incoming edge, + // remove the phi and replace its uses with its data input. If the single + // remaining incoming edge is from the phi itself, the the phi is in an + // unreachable single block loop. Either the block is dead and will be + // removed, or it's reachable from an unreachable continue target. In the + // latter case that continue target block will be collapsed into a block that + // only branches back to its header and we'll eliminate the block with the + // phi. + // + // |unreachable_continues| maps continue targets that cannot be reached to + // merge instruction that declares them. + bool FixPhiNodesInLiveBlocks( + ir::Function* func, + const std::unordered_set& live_blocks, + const std::unordered_map& + unreachable_continues); + + // Erases dead blocks. Any block captured in |unreachable_merges| or + // |unreachable_continues| is a dead block that is required to remain due to + // a live merge instruction in the corresponding header. These blocks will + // have their instructions clobbered and will become a label and terminator. + // Unreachable merge blocks are terminated by OpUnreachable, while + // unreachable continue blocks are terminated by an unconditional branch to + // the header. Otherwise, blocks are dead if not explicitly captured in + // |live_blocks| and are totally removed. + // + // |unreachable_continues| maps continue targets that cannot be reached to + // corresponding header block that declares them. + bool EraseDeadBlocks( + ir::Function* func, + const std::unordered_set& live_blocks, + const std::unordered_set& unreachable_merges, + const std::unordered_map& + unreachable_continues); + + void Initialize(ir::IRContext* c); + Pass::Status ProcessImpl(); +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_DEAD_BRANCH_ELIM_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/dead_insert_elim_pass.cpp b/3rdparty/spirv-tools/source/opt/dead_insert_elim_pass.cpp new file mode 100644 index 000000000..55f4efe7b --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/dead_insert_elim_pass.cpp @@ -0,0 +1,279 @@ +// Copyright (c) 2018 The Khronos Group Inc. +// Copyright (c) 2018 Valve Corporation +// Copyright (c) 2018 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dead_insert_elim_pass.h" + +#include "composite.h" +#include "ir_context.h" +#include "iterator.h" +#include "spirv/1.2/GLSL.std.450.h" + +#include + +namespace spvtools { +namespace opt { + +namespace { + +const uint32_t kTypeVectorCountInIdx = 1; +const uint32_t kTypeMatrixCountInIdx = 1; +const uint32_t kTypeArrayLengthIdInIdx = 1; +const uint32_t kTypeIntWidthInIdx = 0; +const uint32_t kConstantValueInIdx = 0; +const uint32_t kInsertObjectIdInIdx = 0; +const uint32_t kInsertCompositeIdInIdx = 1; + +} // anonymous namespace + +uint32_t DeadInsertElimPass::NumComponents(ir::Instruction* typeInst) { + switch (typeInst->opcode()) { + case SpvOpTypeVector: { + return typeInst->GetSingleWordInOperand(kTypeVectorCountInIdx); + } break; + case SpvOpTypeMatrix: { + return typeInst->GetSingleWordInOperand(kTypeMatrixCountInIdx); + } break; + case SpvOpTypeArray: { + uint32_t lenId = + typeInst->GetSingleWordInOperand(kTypeArrayLengthIdInIdx); + ir::Instruction* lenInst = get_def_use_mgr()->GetDef(lenId); + if (lenInst->opcode() != SpvOpConstant) return 0; + uint32_t lenTypeId = lenInst->type_id(); + ir::Instruction* lenTypeInst = get_def_use_mgr()->GetDef(lenTypeId); + // TODO(greg-lunarg): Support non-32-bit array length + if (lenTypeInst->GetSingleWordInOperand(kTypeIntWidthInIdx) != 32) + return 0; + return lenInst->GetSingleWordInOperand(kConstantValueInIdx); + } break; + case SpvOpTypeStruct: { + return typeInst->NumInOperands(); + } break; + default: { return 0; } break; + } +} + +void DeadInsertElimPass::MarkInsertChain(ir::Instruction* insertChain, + std::vector* pExtIndices, + uint32_t extOffset) { + // Not currently optimizing array inserts. + ir::Instruction* typeInst = get_def_use_mgr()->GetDef(insertChain->type_id()); + if (typeInst->opcode() == SpvOpTypeArray) return; + // Insert chains are only composed of inserts and phis + if (insertChain->opcode() != SpvOpCompositeInsert && + insertChain->opcode() != SpvOpPhi) + return; + // If extract indices are empty, mark all subcomponents if type + // is constant length. + if (pExtIndices == nullptr) { + uint32_t cnum = NumComponents(typeInst); + if (cnum > 0) { + std::vector extIndices; + for (uint32_t i = 0; i < cnum; i++) { + extIndices.clear(); + extIndices.push_back(i); + MarkInsertChain(insertChain, &extIndices, 0); + } + return; + } + } + ir::Instruction* insInst = insertChain; + while (insInst->opcode() == SpvOpCompositeInsert) { + // If no extract indices, mark insert and inserted object (which might + // also be an insert chain) and continue up the chain though the input + // composite. + // + // Note: We mark inserted objects in this function (rather than in + // EliminateDeadInsertsOnePass) because in some cases, we can do it + // more accurately here. + if (pExtIndices == nullptr) { + liveInserts_.insert(insInst->result_id()); + uint32_t objId = insInst->GetSingleWordInOperand(kInsertObjectIdInIdx); + MarkInsertChain(get_def_use_mgr()->GetDef(objId), nullptr, 0); + } + // If extract indices match insert, we are done. Mark insert and + // inserted object. + else if (ExtInsMatch(*pExtIndices, insInst, extOffset)) { + liveInserts_.insert(insInst->result_id()); + uint32_t objId = insInst->GetSingleWordInOperand(kInsertObjectIdInIdx); + MarkInsertChain(get_def_use_mgr()->GetDef(objId), nullptr, 0); + break; + } + // If non-matching intersection, mark insert + else if (ExtInsConflict(*pExtIndices, insInst, extOffset)) { + liveInserts_.insert(insInst->result_id()); + // If more extract indices than insert, we are done. Use remaining + // extract indices to mark inserted object. + uint32_t numInsertIndices = insInst->NumInOperands() - 2; + if (pExtIndices->size() - extOffset > numInsertIndices) { + uint32_t objId = insInst->GetSingleWordInOperand(kInsertObjectIdInIdx); + MarkInsertChain(get_def_use_mgr()->GetDef(objId), pExtIndices, + extOffset + numInsertIndices); + break; + } + // If fewer extract indices than insert, also mark inserted object and + // continue up chain. + else { + uint32_t objId = insInst->GetSingleWordInOperand(kInsertObjectIdInIdx); + MarkInsertChain(get_def_use_mgr()->GetDef(objId), nullptr, 0); + } + } + // Get next insert in chain + const uint32_t compId = + insInst->GetSingleWordInOperand(kInsertCompositeIdInIdx); + insInst = get_def_use_mgr()->GetDef(compId); + } + // If insert chain ended with phi, do recursive call on each operand + if (insInst->opcode() != SpvOpPhi) return; + // Mark phi visited to prevent potential infinite loop. If phi is already + // visited, return to avoid infinite loop. + auto iter = visitedPhis_.find(insInst->result_id()); + if (iter == visitedPhis_.end()) { + iter = visitedPhis_.emplace(insInst->result_id(), true).first; + } else if (iter->second) { + return; + } else { + iter->second = true; + } + + // Phis may have duplicate inputs values for different edges, prune incoming + // ids lists before recursing. + std::vector ids; + for (uint32_t i = 0; i < insInst->NumInOperands(); i += 2) { + ids.push_back(insInst->GetSingleWordInOperand(i)); + } + std::sort(ids.begin(), ids.end()); + auto new_end = std::unique(ids.begin(), ids.end()); + for (auto id_iter = ids.begin(); id_iter != new_end; ++id_iter) { + ir::Instruction* pi = get_def_use_mgr()->GetDef(*id_iter); + MarkInsertChain(pi, pExtIndices, extOffset); + } + + // Unmark phi when done visiting. + iter->second = false; +} + +bool DeadInsertElimPass::EliminateDeadInserts(ir::Function* func) { + bool modified = false; + bool lastmodified = true; + // Each pass can delete dead instructions, thus potentially revealing + // new dead insertions ie insertions with no uses. + while (lastmodified) { + lastmodified = EliminateDeadInsertsOnePass(func); + modified |= lastmodified; + } + return modified; +} + +bool DeadInsertElimPass::EliminateDeadInsertsOnePass(ir::Function* func) { + bool modified = false; + liveInserts_.clear(); + visitedPhis_.clear(); + // Mark all live inserts + for (auto bi = func->begin(); bi != func->end(); ++bi) { + for (auto ii = bi->begin(); ii != bi->end(); ++ii) { + // Only process Inserts and composite Phis + SpvOp op = ii->opcode(); + ir::Instruction* typeInst = get_def_use_mgr()->GetDef(ii->type_id()); + if (op != SpvOpCompositeInsert && + (op != SpvOpPhi || !spvOpcodeIsComposite(typeInst->opcode()))) + continue; + // The marking algorithm can be expensive for large arrays and the + // efficacy of eliminating dead inserts into arrays is questionable. + // Skip optimizing array inserts for now. Just mark them live. + // TODO(greg-lunarg): Eliminate dead array inserts + if (op == SpvOpCompositeInsert) { + if (typeInst->opcode() == SpvOpTypeArray) { + liveInserts_.insert(ii->result_id()); + continue; + } + } + const uint32_t id = ii->result_id(); + get_def_use_mgr()->ForEachUser(id, [&ii, this](ir::Instruction* user) { + switch (user->opcode()) { + case SpvOpCompositeInsert: + case SpvOpPhi: + // Use by insert or phi does not initiate marking + break; + case SpvOpCompositeExtract: { + // Capture extract indices + std::vector extIndices; + uint32_t icnt = 0; + user->ForEachInOperand([&icnt, &extIndices](const uint32_t* idp) { + if (icnt > 0) extIndices.push_back(*idp); + ++icnt; + }); + // Mark all inserts in chain that intersect with extract + MarkInsertChain(&*ii, &extIndices, 0); + } break; + default: { + // Mark inserts in chain for all components + MarkInsertChain(&*ii, nullptr, 0); + } break; + } + }); + } + } + // Find and disconnect dead inserts + std::vector dead_instructions; + for (auto bi = func->begin(); bi != func->end(); ++bi) { + for (auto ii = bi->begin(); ii != bi->end(); ++ii) { + if (ii->opcode() != SpvOpCompositeInsert) continue; + const uint32_t id = ii->result_id(); + if (liveInserts_.find(id) != liveInserts_.end()) continue; + const uint32_t replId = + ii->GetSingleWordInOperand(kInsertCompositeIdInIdx); + (void)context()->ReplaceAllUsesWith(id, replId); + dead_instructions.push_back(&*ii); + modified = true; + } + } + // DCE dead inserts + while (!dead_instructions.empty()) { + ir::Instruction* inst = dead_instructions.back(); + dead_instructions.pop_back(); + DCEInst(inst, [&dead_instructions](ir::Instruction* other_inst) { + auto i = std::find(dead_instructions.begin(), dead_instructions.end(), + other_inst); + if (i != dead_instructions.end()) { + dead_instructions.erase(i); + } + }); + } + return modified; +} + +void DeadInsertElimPass::Initialize(ir::IRContext* c) { + InitializeProcessing(c); +} + +Pass::Status DeadInsertElimPass::ProcessImpl() { + // Process all entry point functions. + ProcessFunction pfn = [this](ir::Function* fp) { + return EliminateDeadInserts(fp); + }; + bool modified = ProcessEntryPointCallTree(pfn, get_module()); + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; +} + +DeadInsertElimPass::DeadInsertElimPass() {} + +Pass::Status DeadInsertElimPass::Process(ir::IRContext* c) { + Initialize(c); + return ProcessImpl(); +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/dead_insert_elim_pass.h b/3rdparty/spirv-tools/source/opt/dead_insert_elim_pass.h new file mode 100644 index 000000000..97a725d9c --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/dead_insert_elim_pass.h @@ -0,0 +1,82 @@ +// Copyright (c) 2018 The Khronos Group Inc. +// Copyright (c) 2018 Valve Corporation +// Copyright (c) 2018 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_DEAD_INSERT_ELIM_PASS_H_ +#define LIBSPIRV_OPT_DEAD_INSERT_ELIM_PASS_H_ + +#include +#include +#include +#include +#include + +#include "basic_block.h" +#include "def_use_manager.h" +#include "ir_context.h" +#include "mem_pass.h" +#include "module.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class DeadInsertElimPass : public MemPass { + public: + DeadInsertElimPass(); + const char* name() const override { return "eliminate-dead-inserts"; } + Status Process(ir::IRContext*) override; + + private: + // Return the number of subcomponents in the composite type |typeId|. + // Return 0 if not a composite type or number of components is not a + // 32-bit constant. + uint32_t NumComponents(ir::Instruction* typeInst); + + // Mark all inserts in instruction chain ending at |insertChain| with + // indices that intersect with extract indices |extIndices| starting with + // index at |extOffset|. Chains are composed solely of Inserts and Phis. + // Mark all inserts in chain if |extIndices| is nullptr. + void MarkInsertChain(ir::Instruction* insertChain, + std::vector* extIndices, uint32_t extOffset); + + // Perform EliminateDeadInsertsOnePass(|func|) until no modification is + // made. Return true if modified. + bool EliminateDeadInserts(ir::Function* func); + + // DCE all dead struct, matrix and vector inserts in |func|. An insert is + // dead if the value it inserts is never used. Replace any reference to the + // insert with its original composite. Return true if modified. Dead inserts + // in dependence cycles are not currently eliminated. Dead inserts into + // arrays are not currently eliminated. + bool EliminateDeadInsertsOnePass(ir::Function* func); + + // Return true if all extensions in this module are allowed by this pass. + bool AllExtensionsSupported() const; + + void Initialize(ir::IRContext* c); + Pass::Status ProcessImpl(); + + // Live inserts + std::unordered_set liveInserts_; + + // Visited phis as insert chain is traversed; used to avoid infinite loop + std::unordered_map visitedPhis_; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_DEAD_INSERT_ELIM_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/dead_variable_elimination.cpp b/3rdparty/spirv-tools/source/opt/dead_variable_elimination.cpp new file mode 100644 index 000000000..1fec3a4f8 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/dead_variable_elimination.cpp @@ -0,0 +1,114 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dead_variable_elimination.h" + +#include "ir_context.h" +#include "reflect.h" + +namespace spvtools { +namespace opt { + +// This optimization removes global variables that are not needed because they +// are definitely not accessed. +Pass::Status DeadVariableElimination::Process(ir::IRContext* c) { + // The algorithm will compute the reference count for every global variable. + // Anything with a reference count of 0 will then be deleted. For variables + // that might have references that are not explicit in this context, we use + // the + // value kMustKeep as the reference count. + InitializeProcessing(c); + + std::vector ids_to_remove; + + // Get the reference count for all of the global OpVariable instructions. + for (auto& inst : context()->types_values()) { + if (inst.opcode() != SpvOp::SpvOpVariable) { + continue; + } + + size_t count = 0; + uint32_t result_id = inst.result_id(); + + // Check the linkage. If it is exported, it could be reference somewhere + // else, so we must keep the variable around. + get_decoration_mgr()->ForEachDecoration( + result_id, SpvDecorationLinkageAttributes, + [&count](const ir::Instruction& linkage_instruction) { + uint32_t last_operand = linkage_instruction.NumOperands() - 1; + if (linkage_instruction.GetSingleWordOperand(last_operand) == + SpvLinkageTypeExport) { + count = kMustKeep; + } + }); + + if (count != kMustKeep) { + // If we don't have to keep the instruction for other reasons, then look + // at the uses and count the number of real references. + count = 0; + get_def_use_mgr()->ForEachUser( + result_id, [&count](ir::Instruction* user) { + if (!ir::IsAnnotationInst(user->opcode()) && + user->opcode() != SpvOpName) { + ++count; + } + }); + } + reference_count_[result_id] = count; + if (count == 0) { + ids_to_remove.push_back(result_id); + } + } + + // Remove all of the variables that have a reference count of 0. + bool modified = false; + if (!ids_to_remove.empty()) { + modified = true; + for (auto result_id : ids_to_remove) { + DeleteVariable(result_id); + } + } + return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange); +} + +void DeadVariableElimination::DeleteVariable(uint32_t result_id) { + ir::Instruction* inst = get_def_use_mgr()->GetDef(result_id); + assert(inst->opcode() == SpvOpVariable && + "Should not be trying to delete anything other than an OpVariable."); + + // Look for an initializer that references another variable. We need to know + // if that variable can be deleted after the reference is removed. + if (inst->NumOperands() == 4) { + ir::Instruction* initializer = + get_def_use_mgr()->GetDef(inst->GetSingleWordOperand(3)); + + // TODO: Handle OpSpecConstantOP which might be defined in terms of other + // variables. Will probably require a unified dead code pass that does all + // instruction types. (Issue 906) + if (initializer->opcode() == SpvOpVariable) { + uint32_t initializer_id = initializer->result_id(); + size_t& count = reference_count_[initializer_id]; + if (count != kMustKeep) { + --count; + } + + if (count == 0) { + DeleteVariable(initializer_id); + } + } + } + context()->KillDef(result_id); +} +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/dead_variable_elimination.h b/3rdparty/spirv-tools/source/opt/dead_variable_elimination.h new file mode 100644 index 000000000..f016e78b2 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/dead_variable_elimination.h @@ -0,0 +1,55 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SPIRV_TOOLS_DEAD_VARIABLE_ELIMINATION_H +#define SPIRV_TOOLS_DEAD_VARIABLE_ELIMINATION_H + +#include +#include + +#include "decoration_manager.h" +#include "mem_pass.h" + +namespace spvtools { +namespace opt { + +class DeadVariableElimination : public MemPass { + public: + const char* name() const override { return "dead-variable-elimination"; } + Status Process(ir::IRContext* c) override; + + ir::IRContext::Analysis GetPreservedAnalyses() override { + return ir::IRContext::kAnalysisDefUse; + } + + private: + // Deletes the OpVariable instruction who result id is |result_id|. + void DeleteVariable(uint32_t result_id); + + // Keeps track of the number of references of an id. Once that value is 0, it + // is safe to remove the corresponding instruction. + // + // Note that the special value kMustKeep is used to indicate that the + // instruction cannot be deleted for reasons other that is being explicitly + // referenced. + std::unordered_map reference_count_; + + // Special value used to indicate that an id cannot be safely deleted. + enum { kMustKeep = INT_MAX }; +}; + +} // namespace opt +} // namespace spvtools + +#endif // SPIRV_TOOLS_DEAD_VARIABLE_ELIMINATION_H diff --git a/3rdparty/spirv-tools/source/opt/decoration_manager.cpp b/3rdparty/spirv-tools/source/opt/decoration_manager.cpp new file mode 100644 index 000000000..f382d7846 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/decoration_manager.cpp @@ -0,0 +1,437 @@ +// Copyright (c) 2017 Pierre Moreau +// +// 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 "decoration_manager.h" + +#include +#include +#include + +#include "ir_context.h" + +namespace spvtools { +namespace opt { +namespace analysis { + +void DecorationManager::RemoveDecorationsFrom( + uint32_t id, std::function pred) { + const auto ids_iter = id_to_decoration_insts_.find(id); + if (ids_iter == id_to_decoration_insts_.end()) return; + + TargetData& decorations_info = ids_iter->second; + auto context = module_->context(); + std::vector insts_to_kill; + const bool is_group = !decorations_info.decorate_insts.empty(); + + // Schedule all direct decorations for removal if instructed as such by + // |pred|. + for (ir::Instruction* inst : decorations_info.direct_decorations) + if (pred(*inst)) insts_to_kill.push_back(inst); + + // For all groups being directly applied to |id|, remove |id| (and the + // literal if |inst| is an OpGroupMemberDecorate) from the instruction + // applying the group. + std::unordered_set indirect_decorations_to_remove; + for (ir::Instruction* inst : decorations_info.indirect_decorations) { + assert(inst->opcode() == SpvOpGroupDecorate || + inst->opcode() == SpvOpGroupMemberDecorate); + + std::vector group_decorations_to_keep; + const uint32_t group_id = inst->GetSingleWordInOperand(0u); + const auto group_iter = id_to_decoration_insts_.find(group_id); + assert(group_iter != id_to_decoration_insts_.end() && + "Unknown decoration group"); + const auto& group_decorations = group_iter->second.direct_decorations; + for (ir::Instruction* decoration : group_decorations) { + if (!pred(*decoration)) group_decorations_to_keep.push_back(decoration); + } + + // If all decorations should be kept, move to the next group + if (group_decorations_to_keep.size() == group_decorations.size()) continue; + + // Otherwise, remove |id| from the targets of |group_id| + const uint32_t stride = inst->opcode() == SpvOpGroupDecorate ? 1u : 2u; + bool was_modified = false; + for (uint32_t i = 1u; i < inst->NumInOperands();) { + if (inst->GetSingleWordInOperand(i) != id) { + i += stride; + continue; + } + + const uint32_t last_operand_index = inst->NumInOperands() - stride; + if (i < last_operand_index) + inst->GetInOperand(i) = inst->GetInOperand(last_operand_index); + // Remove the associated literal, if it exists. + if (stride == 2u) { + if (i < last_operand_index) + inst->GetInOperand(i + 1u) = + inst->GetInOperand(last_operand_index + 1u); + inst->RemoveInOperand(last_operand_index + 1u); + } + inst->RemoveInOperand(last_operand_index); + was_modified = true; + } + + // If the instruction has no targets left, remove the instruction + // altogether. + if (inst->NumInOperands() == 1u) { + indirect_decorations_to_remove.emplace(inst); + insts_to_kill.push_back(inst); + } else if (was_modified) { + context->ForgetUses(inst); + indirect_decorations_to_remove.emplace(inst); + context->AnalyzeUses(inst); + } + + // If only some of the decorations should be kept, clone them and apply + // them directly to |id|. + if (!group_decorations_to_keep.empty()) { + for (ir::Instruction* decoration : group_decorations_to_keep) { + // simply clone decoration and change |group_id| to |id| + std::unique_ptr new_inst( + decoration->Clone(module_->context())); + new_inst->SetInOperand(0, {id}); + module_->AddAnnotationInst(std::move(new_inst)); + auto decoration_iter = --module_->annotation_end(); + context->AnalyzeUses(&*decoration_iter); + } + } + } + + auto& indirect_decorations = decorations_info.indirect_decorations; + indirect_decorations.erase( + std::remove_if( + indirect_decorations.begin(), indirect_decorations.end(), + [&indirect_decorations_to_remove](const ir::Instruction* inst) { + return indirect_decorations_to_remove.count(inst); + }), + indirect_decorations.end()); + + for (ir::Instruction* inst : insts_to_kill) context->KillInst(inst); + insts_to_kill.clear(); + + // Schedule all instructions applying the group for removal if this group no + // longer applies decorations, either directly or indirectly. + if (is_group && decorations_info.direct_decorations.empty() && + decorations_info.indirect_decorations.empty()) { + for (ir::Instruction* inst : decorations_info.decorate_insts) + insts_to_kill.push_back(inst); + } + for (ir::Instruction* inst : insts_to_kill) context->KillInst(inst); + + if (decorations_info.direct_decorations.empty() && + decorations_info.indirect_decorations.empty() && + decorations_info.decorate_insts.empty()) { + id_to_decoration_insts_.erase(ids_iter); + + // Remove the OpDecorationGroup defining this group. + if (is_group) context->KillInst(context->get_def_use_mgr()->GetDef(id)); + } +} + +std::vector DecorationManager::GetDecorationsFor( + uint32_t id, bool include_linkage) { + return InternalGetDecorationsFor(id, include_linkage); +} + +std::vector DecorationManager::GetDecorationsFor( + uint32_t id, bool include_linkage) const { + return const_cast(this) + ->InternalGetDecorationsFor(id, include_linkage); +} + +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); + + // 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, + DecorationSet* decorate_id_set, DecorationSet* decorate_string_set, + DecorationSet* member_decorate_set) { + for (const ir::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 = decorate_set_for1 == decorate_set_for2 && + decorate_id_set_for1 == decorate_id_set_for2 && + member_decorate_set_for1 == member_decorate_set_for2 && + // Compare string sets last in case the strings are long. + 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. +bool DecorationManager::AreDecorationsTheSame(const ir::Instruction* inst1, + const ir::Instruction* inst2, + bool ignore_target) const { + switch (inst1->opcode()) { + case SpvOpDecorate: + case SpvOpMemberDecorate: + case SpvOpDecorateId: + case SpvOpDecorateStringGOOGLE: + break; + default: + return false; + } + + if (inst1->opcode() != inst2->opcode() || + inst1->NumInOperands() != inst2->NumInOperands()) + return false; + + for (uint32_t i = ignore_target ? 1u : 0u; i < inst1->NumInOperands(); ++i) + if (inst1->GetInOperand(i) != inst2->GetInOperand(i)) return false; + + return true; +} + +void DecorationManager::AnalyzeDecorations() { + if (!module_) return; + + // For each group and instruction, collect all their decoration instructions. + for (ir::Instruction& inst : module_->annotations()) { + AddDecoration(&inst); + } +} +void DecorationManager::AddDecoration(ir::Instruction* inst) { + switch (inst->opcode()) { + case SpvOpDecorate: + case SpvOpDecorateId: + case SpvOpDecorateStringGOOGLE: + case SpvOpMemberDecorate: { + const auto target_id = inst->GetSingleWordInOperand(0u); + id_to_decoration_insts_[target_id].direct_decorations.push_back(inst); + break; + } + case SpvOpGroupDecorate: + case SpvOpGroupMemberDecorate: { + const uint32_t start = inst->opcode() == SpvOpGroupDecorate ? 1u : 2u; + const uint32_t stride = start; + for (uint32_t i = start; i < inst->NumInOperands(); i += stride) { + const auto target_id = inst->GetSingleWordInOperand(i); + TargetData& target_data = id_to_decoration_insts_[target_id]; + target_data.indirect_decorations.push_back(inst); + } + const auto target_id = inst->GetSingleWordInOperand(0u); + id_to_decoration_insts_[target_id].decorate_insts.push_back(inst); + break; + } + default: + break; + } +} + +template +std::vector DecorationManager::InternalGetDecorationsFor( + uint32_t id, bool include_linkage) { + std::vector decorations; + + const auto ids_iter = id_to_decoration_insts_.find(id); + // |id| has no decorations + if (ids_iter == id_to_decoration_insts_.end()) return decorations; + + const TargetData& target_data = ids_iter->second; + + const auto process_direct_decorations = + [include_linkage, + &decorations](const std::vector& direct_decorations) { + for (ir::Instruction* inst : direct_decorations) { + const bool is_linkage = inst->opcode() == SpvOpDecorate && + inst->GetSingleWordInOperand(1u) == + SpvDecorationLinkageAttributes; + if (include_linkage || !is_linkage) decorations.push_back(inst); + } + }; + + // Process |id|'s decorations. + process_direct_decorations(ids_iter->second.direct_decorations); + + // Process the decorations of all groups applied to |id|. + for (const ir::Instruction* inst : target_data.indirect_decorations) { + const uint32_t group_id = inst->GetSingleWordInOperand(0u); + const auto group_iter = id_to_decoration_insts_.find(group_id); + assert(group_iter != id_to_decoration_insts_.end() && "Unknown group ID"); + process_direct_decorations(group_iter->second.direct_decorations); + } + + return decorations; +} + +bool DecorationManager::WhileEachDecoration( + uint32_t id, uint32_t decoration, + std::function f) { + for (const ir::Instruction* inst : GetDecorationsFor(id, true)) { + switch (inst->opcode()) { + case SpvOpMemberDecorate: + if (inst->GetSingleWordInOperand(2) == decoration) { + if (!f(*inst)) return false; + } + break; + case SpvOpDecorate: + case SpvOpDecorateId: + case SpvOpDecorateStringGOOGLE: + if (inst->GetSingleWordInOperand(1) == decoration) { + if (!f(*inst)) return false; + } + break; + default: + assert(false && "Unexpected decoration instruction"); + } + } + return true; +} + +void DecorationManager::ForEachDecoration( + uint32_t id, uint32_t decoration, + std::function f) { + WhileEachDecoration(id, decoration, [&f](const ir::Instruction& inst) { + f(inst); + return true; + }); +} + +void DecorationManager::CloneDecorations(uint32_t from, uint32_t to) { + const auto decoration_list = id_to_decoration_insts_.find(from); + if (decoration_list == id_to_decoration_insts_.end()) return; + auto context = module_->context(); + for (ir::Instruction* inst : decoration_list->second.direct_decorations) { + // simply clone decoration and change |target-id| to |to| + std::unique_ptr new_inst(inst->Clone(module_->context())); + new_inst->SetInOperand(0, {to}); + module_->AddAnnotationInst(std::move(new_inst)); + auto decoration_iter = --module_->annotation_end(); + context->AnalyzeUses(&*decoration_iter); + } + // We need to copy the list of instructions as ForgetUses and AnalyzeUses are + // going to modify it. + std::vector indirect_decorations = + decoration_list->second.indirect_decorations; + for (ir::Instruction* inst : indirect_decorations) { + switch (inst->opcode()) { + case SpvOpGroupDecorate: + context->ForgetUses(inst); + // add |to| to list of decorated id's + inst->AddOperand( + ir::Operand(spv_operand_type_t::SPV_OPERAND_TYPE_ID, {to})); + context->AnalyzeUses(inst); + break; + case SpvOpGroupMemberDecorate: { + context->ForgetUses(inst); + // for each (id == from), add (to, literal) as operands + const uint32_t num_operands = inst->NumOperands(); + for (uint32_t i = 1; i < num_operands; i += 2) { + ir::Operand op = inst->GetOperand(i); + if (op.words[0] == from) { // add new pair of operands: (to, literal) + inst->AddOperand( + ir::Operand(spv_operand_type_t::SPV_OPERAND_TYPE_ID, {to})); + op = inst->GetOperand(i + 1); + inst->AddOperand(std::move(op)); + } + } + context->AnalyzeUses(inst); + break; + } + default: + assert(false && "Unexpected decoration instruction"); + } + } +} + +void DecorationManager::RemoveDecoration(ir::Instruction* inst) { + const auto remove_from_container = [inst](std::vector& v) { + v.erase(std::remove(v.begin(), v.end(), inst), v.end()); + }; + + switch (inst->opcode()) { + case SpvOpDecorate: + case SpvOpDecorateId: + case SpvOpDecorateStringGOOGLE: + case SpvOpMemberDecorate: { + const auto target_id = inst->GetSingleWordInOperand(0u); + auto const iter = id_to_decoration_insts_.find(target_id); + if (iter == id_to_decoration_insts_.end()) return; + remove_from_container(iter->second.direct_decorations); + } break; + case SpvOpGroupDecorate: + case SpvOpGroupMemberDecorate: { + const uint32_t stride = inst->opcode() == SpvOpGroupDecorate ? 1u : 2u; + for (uint32_t i = 1u; i < inst->NumInOperands(); i += stride) { + const auto target_id = inst->GetSingleWordInOperand(i); + auto const iter = id_to_decoration_insts_.find(target_id); + if (iter == id_to_decoration_insts_.end()) continue; + remove_from_container(iter->second.indirect_decorations); + } + const auto group_id = inst->GetSingleWordInOperand(0u); + auto const iter = id_to_decoration_insts_.find(group_id); + if (iter == id_to_decoration_insts_.end()) return; + remove_from_container(iter->second.decorate_insts); + } break; + default: + break; + } +} + +} // namespace analysis +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/decoration_manager.h b/3rdparty/spirv-tools/source/opt/decoration_manager.h new file mode 100644 index 000000000..40ba13eca --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/decoration_manager.h @@ -0,0 +1,137 @@ +// Copyright (c) 2017 Pierre Moreau +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_DECORATION_MANAGER_H_ +#define LIBSPIRV_OPT_DECORATION_MANAGER_H_ + +#include +#include +#include +#include + +#include "instruction.h" +#include "module.h" + +namespace spvtools { +namespace opt { +namespace analysis { + +// A class for analyzing and managing decorations in an ir::Module. +class DecorationManager { + public: + // Constructs a decoration manager from the given |module| + explicit DecorationManager(ir::Module* module) : module_(module) { + AnalyzeDecorations(); + } + DecorationManager() = delete; + + // Removes all decorations from |id| (either directly or indirectly) for + // which |pred| returns true. + // If |id| is a group ID, OpGroupDecorate and OpGroupMemberDecorate will be + // removed if they have no targets left, and OpDecorationGroup will be + // removed if the group is not applied to anyone and contains no decorations. + void RemoveDecorationsFrom(uint32_t id, + std::function pred = + [](const ir::Instruction&) { return true; }); + + // Removes all decorations from the result id of |inst|. + // + // NOTE: This is only meant to be called from ir_context, as only metadata + // will be removed, and no actual instruction. + void RemoveDecoration(ir::Instruction* inst); + + // Returns a vector of all decorations affecting |id|. If a group is applied + // to |id|, the decorations of that group are returned rather than the group + // decoration instruction. If |include_linkage| is not set, linkage + // decorations won't be returned. + std::vector GetDecorationsFor(uint32_t id, + bool include_linkage); + std::vector GetDecorationsFor( + uint32_t id, bool include_linkage) 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 HaveTheSameDecorations(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. + // + // This is only valid for OpDecorate, OpMemberDecorate and OpDecorateId; it + // will return false for other opcodes. + bool AreDecorationsTheSame(const ir::Instruction* inst1, + const ir::Instruction* inst2, + bool ignore_target) const; + + // |f| is run on each decoration instruction for |id| with decoration + // |decoration|. Processed are all decorations which target |id| either + // directly or indirectly by Decoration Groups. + void ForEachDecoration(uint32_t id, uint32_t decoration, + std::function f); + + // |f| is run on each decoration instruction for |id| with decoration + // |decoration|. Processes all decoration which target |id| either directly or + // indirectly through decoration groups. If |f| returns false, iteration is + // terminated and this function returns false. + bool WhileEachDecoration(uint32_t id, uint32_t decoration, + std::function f); + + // Clone all decorations from one id |from|. + // The cloned decorations are assigned to the given id |to| and are + // added to the module. The purpose is to decorate cloned instructions. + // This function does not check if the id |to| is already decorated. + void CloneDecorations(uint32_t from, uint32_t to); + + // Informs the decoration manager of a new decoration that it needs to track. + void AddDecoration(ir::Instruction* inst); + + private: + // Analyzes the defs and uses in the given |module| and populates data + // structures in this class. Does nothing if |module| is nullptr. + void AnalyzeDecorations(); + + template + std::vector InternalGetDecorationsFor(uint32_t id, bool include_linkage); + + // Tracks decoration information of an ID. + struct TargetData { + std::vector direct_decorations; // All decorate + // instructions applied + // to the tracked ID. + std::vector indirect_decorations; // All instructions + // applying a group to + // the tracked ID. + std::vector decorate_insts; // All decorate instructions + // applying the decorations + // of the tracked ID to + // targets. + // It is empty if the + // tracked ID is not a + // group. + }; + + // Mapping from ids to the instructions applying a decoration to those ids. + // In other words, for each id you get all decoration instructions + // referencing that id, be it directly (SpvOpDecorate, SpvOpMemberDecorate + // and SpvOpDecorateId), or indirectly (SpvOpGroupDecorate, + // SpvOpMemberGroupDecorate). + std::unordered_map id_to_decoration_insts_; + // The enclosing module. + ir::Module* module_; +}; + +} // namespace analysis +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_DECORATION_MANAGER_H_ diff --git a/3rdparty/spirv-tools/source/opt/def_use_manager.cpp b/3rdparty/spirv-tools/source/opt/def_use_manager.cpp new file mode 100644 index 000000000..6e83b4197 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/def_use_manager.cpp @@ -0,0 +1,293 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "def_use_manager.h" + +#include + +#include "log.h" +#include "reflect.h" + +namespace spvtools { +namespace opt { +namespace analysis { + +void DefUseManager::AnalyzeInstDef(ir::Instruction* inst) { + const uint32_t def_id = inst->result_id(); + if (def_id != 0) { + auto iter = id_to_def_.find(def_id); + if (iter != id_to_def_.end()) { + // Clear the original instruction that defining the same result id of the + // new instruction. + ClearInst(iter->second); + } + id_to_def_[def_id] = inst; + } else { + ClearInst(inst); + } +} + +void DefUseManager::AnalyzeInstUse(ir::Instruction* inst) { + // Create entry for the given instruction. Note that the instruction may + // not have any in-operands. In such cases, we still need a entry for those + // instructions so this manager knows it has seen the instruction later. + auto* used_ids = &inst_to_used_ids_[inst]; + if (used_ids->size()) { + EraseUseRecordsOfOperandIds(inst); + used_ids = &inst_to_used_ids_[inst]; + } + used_ids->clear(); // It might have existed before. + + for (uint32_t i = 0; i < inst->NumOperands(); ++i) { + switch (inst->GetOperand(i).type) { + // For any id type but result id type + case SPV_OPERAND_TYPE_ID: + case SPV_OPERAND_TYPE_TYPE_ID: + case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID: + case SPV_OPERAND_TYPE_SCOPE_ID: { + uint32_t use_id = inst->GetSingleWordOperand(i); + ir::Instruction* def = GetDef(use_id); + assert(def && "Definition is not registered."); + id_to_users_.insert(UserEntry(def, inst)); + used_ids->push_back(use_id); + } break; + default: + break; + } + } +} + +void DefUseManager::AnalyzeInstDefUse(ir::Instruction* inst) { + AnalyzeInstDef(inst); + AnalyzeInstUse(inst); +} + +void DefUseManager::UpdateDefUse(ir::Instruction* inst) { + const uint32_t def_id = inst->result_id(); + if (def_id != 0) { + auto iter = id_to_def_.find(def_id); + if (iter == id_to_def_.end()) { + AnalyzeInstDef(inst); + } + } + AnalyzeInstUse(inst); +} + +ir::Instruction* DefUseManager::GetDef(uint32_t id) { + auto iter = id_to_def_.find(id); + if (iter == id_to_def_.end()) return nullptr; + return iter->second; +} + +const ir::Instruction* DefUseManager::GetDef(uint32_t id) const { + const auto iter = id_to_def_.find(id); + if (iter == id_to_def_.end()) return nullptr; + return iter->second; +} + +DefUseManager::IdToUsersMap::const_iterator DefUseManager::UsersBegin( + const ir::Instruction* def) const { + return id_to_users_.lower_bound( + UserEntry(const_cast(def), nullptr)); +} + +bool DefUseManager::UsersNotEnd(const IdToUsersMap::const_iterator& iter, + const IdToUsersMap::const_iterator& cached_end, + const ir::Instruction* inst) const { + return (iter != cached_end && iter->first == inst); +} + +bool DefUseManager::UsersNotEnd(const IdToUsersMap::const_iterator& iter, + const ir::Instruction* inst) const { + return UsersNotEnd(iter, id_to_users_.end(), inst); +} + +bool DefUseManager::WhileEachUser( + const ir::Instruction* def, + const std::function& f) const { + // Ensure that |def| has been registered. + assert(def && (!def->HasResultId() || def == GetDef(def->result_id())) && + "Definition is not registered."); + if (!def->HasResultId()) return true; + + auto end = id_to_users_.end(); + for (auto iter = UsersBegin(def); UsersNotEnd(iter, end, def); ++iter) { + if (!f(iter->second)) return false; + } + return true; +} + +bool DefUseManager::WhileEachUser( + uint32_t id, const std::function& f) const { + return WhileEachUser(GetDef(id), f); +} + +void DefUseManager::ForEachUser( + const ir::Instruction* def, + const std::function& f) const { + WhileEachUser(def, [&f](ir::Instruction* user) { + f(user); + return true; + }); +} + +void DefUseManager::ForEachUser( + uint32_t id, const std::function& f) const { + ForEachUser(GetDef(id), f); +} + +bool DefUseManager::WhileEachUse( + const ir::Instruction* def, + const std::function& f) const { + // Ensure that |def| has been registered. + assert(def && (!def->HasResultId() || def == GetDef(def->result_id())) && + "Definition is not registered."); + if (!def->HasResultId()) return true; + + auto end = id_to_users_.end(); + for (auto iter = UsersBegin(def); UsersNotEnd(iter, end, def); ++iter) { + ir::Instruction* user = iter->second; + for (uint32_t idx = 0; idx != user->NumOperands(); ++idx) { + const ir::Operand& op = user->GetOperand(idx); + if (op.type != SPV_OPERAND_TYPE_RESULT_ID && spvIsIdType(op.type)) { + if (def->result_id() == op.words[0]) { + if (!f(user, idx)) return false; + } + } + } + } + return true; +} + +bool DefUseManager::WhileEachUse( + uint32_t id, + const std::function& f) const { + return WhileEachUse(GetDef(id), f); +} + +void DefUseManager::ForEachUse( + const ir::Instruction* def, + const std::function& f) const { + WhileEachUse(def, [&f](ir::Instruction* user, uint32_t index) { + f(user, index); + return true; + }); +} + +void DefUseManager::ForEachUse( + uint32_t id, + const std::function& f) const { + ForEachUse(GetDef(id), f); +} + +uint32_t DefUseManager::NumUsers(const ir::Instruction* def) const { + uint32_t count = 0; + ForEachUser(def, [&count](ir::Instruction*) { ++count; }); + return count; +} + +uint32_t DefUseManager::NumUsers(uint32_t id) const { + return NumUsers(GetDef(id)); +} + +uint32_t DefUseManager::NumUses(const ir::Instruction* def) const { + uint32_t count = 0; + ForEachUse(def, [&count](ir::Instruction*, uint32_t) { ++count; }); + return count; +} + +uint32_t DefUseManager::NumUses(uint32_t id) const { + return NumUses(GetDef(id)); +} + +std::vector DefUseManager::GetAnnotations(uint32_t id) const { + std::vector annos; + const ir::Instruction* def = GetDef(id); + if (!def) return annos; + + ForEachUser(def, [&annos](ir::Instruction* user) { + if (ir::IsAnnotationInst(user->opcode())) { + annos.push_back(user); + } + }); + return annos; +} + +void DefUseManager::AnalyzeDefUse(ir::Module* module) { + if (!module) return; + // Analyze all the defs before any uses to catch forward references. + module->ForEachInst( + std::bind(&DefUseManager::AnalyzeInstDef, this, std::placeholders::_1)); + module->ForEachInst( + std::bind(&DefUseManager::AnalyzeInstUse, this, std::placeholders::_1)); +} + +void DefUseManager::ClearInst(ir::Instruction* inst) { + auto iter = inst_to_used_ids_.find(inst); + if (iter != inst_to_used_ids_.end()) { + EraseUseRecordsOfOperandIds(inst); + if (inst->result_id() != 0) { + // Remove all uses of this inst. + auto users_begin = UsersBegin(inst); + auto end = id_to_users_.end(); + auto new_end = users_begin; + for (; UsersNotEnd(new_end, end, inst); ++new_end) { + } + id_to_users_.erase(users_begin, new_end); + id_to_def_.erase(inst->result_id()); + } + } +} + +void DefUseManager::EraseUseRecordsOfOperandIds(const ir::Instruction* inst) { + // Go through all ids used by this instruction, remove this instruction's + // uses of them. + auto iter = inst_to_used_ids_.find(inst); + if (iter != inst_to_used_ids_.end()) { + for (auto use_id : iter->second) { + id_to_users_.erase( + UserEntry(GetDef(use_id), const_cast(inst))); + } + inst_to_used_ids_.erase(inst); + } +} + +bool operator==(const DefUseManager& lhs, const DefUseManager& rhs) { + if (lhs.id_to_def_ != rhs.id_to_def_) { + return false; + } + + if (lhs.id_to_users_ != rhs.id_to_users_) { + for (auto p : lhs.id_to_users_) { + if (rhs.id_to_users_.count(p) == 0) { + return false; + } + } + for (auto p : rhs.id_to_users_) { + if (lhs.id_to_users_.count(p) == 0) { + return false; + } + } + return false; + } + + if (lhs.inst_to_used_ids_ != lhs.inst_to_used_ids_) { + return false; + } + return true; +} + +} // namespace analysis +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/def_use_manager.h b/3rdparty/spirv-tools/source/opt/def_use_manager.h new file mode 100644 index 000000000..206170398 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/def_use_manager.h @@ -0,0 +1,255 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_DEF_USE_MANAGER_H_ +#define LIBSPIRV_OPT_DEF_USE_MANAGER_H_ + +#include +#include +#include +#include + +#include "instruction.h" +#include "module.h" +#include "spirv-tools/libspirv.hpp" + +namespace spvtools { +namespace opt { +namespace analysis { + +// Class for representing a use of id. Note that: +// * Result type id is a use. +// * Ids referenced in OpSectionMerge & OpLoopMerge are considered as use. +// * Ids referenced in OpPhi's in operands are considered as use. +struct Use { + ir::Instruction* inst; // Instruction using the id. + uint32_t operand_index; // logical operand index of the id use. This can be + // the index of result type id. +}; + +inline bool operator==(const Use& lhs, const Use& rhs) { + return lhs.inst == rhs.inst && lhs.operand_index == rhs.operand_index; +} + +inline bool operator!=(const Use& lhs, const Use& rhs) { return !(lhs == rhs); } + +inline bool operator<(const Use& lhs, const Use& rhs) { + if (lhs.inst < rhs.inst) return true; + if (lhs.inst > rhs.inst) return false; + return lhs.operand_index < rhs.operand_index; +} + +// Definition and user pair. +// +// The first element of the pair is the definition. +// The second element of the pair is the user. +// +// Definition should never be null. User can be null, however, such an entry +// should be used only for searching (e.g. all users of a particular definition) +// and never stored in a container. +using UserEntry = std::pair; + +// Orders UserEntry for use in associative containers (i.e. less than ordering). +// +// The definition of an UserEntry is treated as the major key and the users as +// the minor key so that all the users of a particular definition are +// consecutive in a container. +// +// A null user always compares less than a real user. This is done to provide +// easy values to search for the beginning of the users of a particular +// definition (i.e. using {def, nullptr}). +struct UserEntryLess { + bool operator()(const UserEntry& lhs, const UserEntry& rhs) const { + // If lhs.first and rhs.first are both null, fall through to checking the + // second entries. + if (!lhs.first && rhs.first) return true; + if (lhs.first && !rhs.first) return false; + + // If neither definition is null, then compare unique ids. + if (lhs.first && rhs.first) { + if (lhs.first->unique_id() < rhs.first->unique_id()) return true; + if (rhs.first->unique_id() < lhs.first->unique_id()) return false; + } + + // Return false on equality. + if (!lhs.second && !rhs.second) return false; + if (!lhs.second) return true; + if (!rhs.second) return false; + + // If neither user is null then compare unique ids. + return lhs.second->unique_id() < rhs.second->unique_id(); + } +}; + +// A class for analyzing and managing defs and uses in an ir::Module. +class DefUseManager { + public: + using IdToDefMap = std::unordered_map; + using IdToUsersMap = std::set; + + // Constructs a def-use manager from the given |module|. All internal messages + // will be communicated to the outside via the given message |consumer|. This + // instance only keeps a reference to the |consumer|, so the |consumer| should + // outlive this instance. + DefUseManager(ir::Module* module) { AnalyzeDefUse(module); } + + DefUseManager(const DefUseManager&) = delete; + DefUseManager(DefUseManager&&) = delete; + DefUseManager& operator=(const DefUseManager&) = delete; + DefUseManager& operator=(DefUseManager&&) = delete; + + // Analyzes the defs in the given |inst|. + void AnalyzeInstDef(ir::Instruction* inst); + + // Analyzes the uses in the given |inst|. + // + // All operands of |inst| must be analyzed as defs. + void AnalyzeInstUse(ir::Instruction* inst); + + // Analyzes the defs and uses in the given |inst|. + void AnalyzeInstDefUse(ir::Instruction* inst); + + // Returns the def instruction for the given |id|. If there is no instruction + // defining |id|, returns nullptr. + ir::Instruction* GetDef(uint32_t id); + const ir::Instruction* GetDef(uint32_t id) const; + + // Runs the given function |f| on each unique user instruction of |def| (or + // |id|). + // + // If one instruction uses |def| in multiple operands, that instruction will + // only be visited once. + // + // |def| (or |id|) must be registered as a definition. + void ForEachUser(const ir::Instruction* def, + const std::function& f) const; + void ForEachUser(uint32_t id, + const std::function& f) const; + + // Runs the given function |f| on each unique user instruction of |def| (or + // |id|). If |f| returns false, iteration is terminated and this function + // returns false. + // + // If one instruction uses |def| in multiple operands, that instruction will + // be only be visited once. + // + // |def| (or |id|) must be registered as a definition. + bool WhileEachUser(const ir::Instruction* def, + const std::function& f) const; + bool WhileEachUser(uint32_t id, + const std::function& f) const; + + // Runs the given function |f| on each unique use of |def| (or + // |id|). + // + // If one instruction uses |def| in multiple operands, each operand will be + // visited separately. + // + // |def| (or |id|) must be registered as a definition. + void ForEachUse(const ir::Instruction* def, + const std::function& f) const; + void ForEachUse(uint32_t id, + const std::function& f) const; + + // Runs the given function |f| on each unique use of |def| (or + // |id|). If |f| returns false, iteration is terminated and this function + // returns false. + // + // If one instruction uses |def| in multiple operands, each operand will be + // visited separately. + // + // |def| (or |id|) must be registered as a definition. + bool WhileEachUse(const ir::Instruction* def, + const std::function& f) const; + bool WhileEachUse(uint32_t id, + const std::function& f) const; + + // Returns the number of users of |def| (or |id|). + uint32_t NumUsers(const ir::Instruction* def) const; + uint32_t NumUsers(uint32_t id) const; + + // Returns the number of uses of |def| (or |id|). + uint32_t NumUses(const ir::Instruction* def) const; + uint32_t NumUses(uint32_t id) const; + + // Returns the annotation instrunctions which are a direct use of the given + // |id|. This means when the decorations are applied through decoration + // group(s), this function will just return the OpGroupDecorate + // instrcution(s) which refer to the given id as an operand. The OpDecorate + // instructions which decorate the decoration group will not be returned. + std::vector GetAnnotations(uint32_t id) const; + + // Returns the map from ids to their def instructions. + const IdToDefMap& id_to_defs() const { return id_to_def_; } + // Returns the map from instructions to their users. + const IdToUsersMap& id_to_users() const { return id_to_users_; } + + // Clear the internal def-use record of the given instruction |inst|. This + // method will update the use information of the operand ids of |inst|. The + // record: |inst| uses an |id|, will be removed from the use records of |id|. + // If |inst| defines an result id, the use record of this result id will also + // be removed. Does nothing if |inst| was not analyzed before. + void ClearInst(ir::Instruction* inst); + + // Erases the records that a given instruction uses its operand ids. + void EraseUseRecordsOfOperandIds(const ir::Instruction* inst); + + friend bool operator==(const DefUseManager&, const DefUseManager&); + friend bool operator!=(const DefUseManager& lhs, const DefUseManager& rhs) { + return !(lhs == rhs); + } + + // If |inst| has not already been analysed, then analyses its defintion and + // uses. + void UpdateDefUse(ir::Instruction* inst); + + private: + using InstToUsedIdsMap = + std::unordered_map>; + + // Returns the first location that {|def|, nullptr} could be inserted into the + // users map without violating ordering. + IdToUsersMap::const_iterator UsersBegin(const ir::Instruction* def) const; + + // Returns true if |iter| has not reached the end of |def|'s users. + // + // In the first version |iter| is compared against the end of the map for + // validity before other checks. In the second version, |iter| is compared + // against |cached_end| for validity before other checks. This allows caching + // the map's end which is a performance improvement on some platforms. + bool UsersNotEnd(const IdToUsersMap::const_iterator& iter, + const ir::Instruction* def) const; + bool UsersNotEnd(const IdToUsersMap::const_iterator& iter, + const IdToUsersMap::const_iterator& cached_end, + const ir::Instruction* def) const; + + // Analyzes the defs and uses in the given |module| and populates data + // structures in this class. Does nothing if |module| is nullptr. + void AnalyzeDefUse(ir::Module* module); + + IdToDefMap id_to_def_; // Mapping from ids to their definitions + IdToUsersMap id_to_users_; // Mapping from ids to their users + // Mapping from instructions to the ids used in the instruction. + InstToUsedIdsMap inst_to_used_ids_; +}; + +} // namespace analysis +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_DEF_USE_MANAGER_H_ diff --git a/3rdparty/spirv-tools/source/opt/dominator_analysis.cpp b/3rdparty/spirv-tools/source/opt/dominator_analysis.cpp new file mode 100644 index 000000000..95f2aa420 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/dominator_analysis.cpp @@ -0,0 +1,73 @@ +// Copyright (c) 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "dominator_analysis.h" + +#include + +#include "ir_context.h" + +namespace spvtools { +namespace opt { + +ir::BasicBlock* DominatorAnalysisBase::CommonDominator( + ir::BasicBlock* b1, ir::BasicBlock* b2) const { + if (!b1 || !b2) return nullptr; + + std::unordered_set seen; + ir::BasicBlock* block = b1; + while (block && seen.insert(block).second) { + block = ImmediateDominator(block); + } + + block = b2; + while (block && !seen.count(block)) { + block = ImmediateDominator(block); + } + + return block; +} + +bool DominatorAnalysisBase::Dominates(ir::Instruction* a, + ir::Instruction* b) const { + if (!a || !b) { + return false; + } + + if (a == b) { + return true; + } + + ir::BasicBlock* bb_a = a->context()->get_instr_block(a); + ir::BasicBlock* bb_b = b->context()->get_instr_block(b); + + if (bb_a != bb_b) { + return tree_.Dominates(bb_a, bb_b); + } + + for (ir::Instruction& inst : *bb_a) { + if (&inst == a) { + return true; + } else if (&inst == b) { + return false; + } + } + assert(false && + "We did not find the load or store in the block they are " + "supposed to be in."); + return false; +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/dominator_analysis.h b/3rdparty/spirv-tools/source/opt/dominator_analysis.h new file mode 100644 index 000000000..ea10ce385 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/dominator_analysis.h @@ -0,0 +1,140 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_DOMINATOR_ANALYSIS_PASS_H_ +#define LIBSPIRV_OPT_DOMINATOR_ANALYSIS_PASS_H_ + +#include +#include + +#include "dominator_tree.h" +#include "module.h" + +namespace spvtools { +namespace opt { + +// Interface to perform dominator or postdominator analysis on a given function. +class DominatorAnalysisBase { + public: + explicit DominatorAnalysisBase(bool is_post_dom) : tree_(is_post_dom) {} + + // Calculates the dominator (or postdominator) tree for given function |f|. + inline void InitializeTree(const ir::Function* f, const ir::CFG& cfg) { + tree_.InitializeTree(f, cfg); + } + + // Returns true if BasicBlock |a| dominates BasicBlock |b|. + inline bool Dominates(const ir::BasicBlock* a, + const ir::BasicBlock* b) const { + if (!a || !b) return false; + return Dominates(a->id(), b->id()); + } + + // Returns true if BasicBlock |a| dominates BasicBlock |b|. Same as above only + // using the BasicBlock IDs. + inline bool Dominates(uint32_t a, uint32_t b) const { + return tree_.Dominates(a, b); + } + + // Returns true if instruction |a| dominates instruction |b|. + bool Dominates(ir::Instruction* a, ir::Instruction* b) const; + + // Returns true if BasicBlock |a| strictly dominates BasicBlock |b|. + inline bool StrictlyDominates(const ir::BasicBlock* a, + const ir::BasicBlock* b) const { + if (!a || !b) return false; + return StrictlyDominates(a->id(), b->id()); + } + + // Returns true if BasicBlock |a| strictly dominates BasicBlock |b|. Same as + // above only using the BasicBlock IDs. + inline bool StrictlyDominates(uint32_t a, uint32_t b) const { + return tree_.StrictlyDominates(a, b); + } + + // Returns the immediate dominator of |node| or returns nullptr if it is has + // no dominator. + inline ir::BasicBlock* ImmediateDominator(const ir::BasicBlock* node) const { + if (!node) return nullptr; + return tree_.ImmediateDominator(node); + } + + // Returns the immediate dominator of |node_id| or returns nullptr if it is + // has no dominator. Same as above but operates on IDs. + inline ir::BasicBlock* ImmediateDominator(uint32_t node_id) const { + return tree_.ImmediateDominator(node_id); + } + + // Returns true if |node| is reachable from the entry. + inline bool IsReachable(const ir::BasicBlock* node) const { + if (!node) return false; + return tree_.ReachableFromRoots(node->id()); + } + + // Returns true if |node_id| is reachable from the entry. + inline bool IsReachable(uint32_t node_id) const { + return tree_.ReachableFromRoots(node_id); + } + + // Dump the tree structure into the given |out| stream in the dot format. + inline void DumpAsDot(std::ostream& out) const { tree_.DumpTreeAsDot(out); } + + // Returns true if this is a postdomiator tree. + inline bool IsPostDominator() const { return tree_.IsPostDominator(); } + + // Returns the tree itself for manual operations, such as traversing the + // roots. + // For normal dominance relationships the methods above should be used. + inline DominatorTree& GetDomTree() { return tree_; } + inline const DominatorTree& GetDomTree() const { return tree_; } + + // Force the dominator tree to be removed + inline void ClearTree() { tree_.ClearTree(); } + + // Applies the std::function |func| to dominator tree nodes in dominator + // order. + void Visit(std::function func) { + tree_.Visit(func); + } + + // Applies the std::function |func| to dominator tree nodes in dominator + // order. + void Visit(std::function func) const { + tree_.Visit(func); + } + + // Returns the most immediate basic block that dominates both |b1| and |b2|. + // If there is no such basic block, nullptr is returned. + ir::BasicBlock* CommonDominator(ir::BasicBlock* b1, ir::BasicBlock* b2) const; + + protected: + DominatorTree tree_; +}; + +// Derived class for normal dominator analysis. +class DominatorAnalysis : public DominatorAnalysisBase { + public: + DominatorAnalysis() : DominatorAnalysisBase(false) {} +}; + +// Derived class for postdominator analysis. +class PostDominatorAnalysis : public DominatorAnalysisBase { + public: + PostDominatorAnalysis() : DominatorAnalysisBase(true) {} +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_DOMINATOR_ANALYSIS_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/dominator_tree.cpp b/3rdparty/spirv-tools/source/opt/dominator_tree.cpp new file mode 100644 index 000000000..39339d06b --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/dominator_tree.cpp @@ -0,0 +1,399 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "cfa.h" +#include "dominator_tree.h" + +using namespace spvtools; +using namespace spvtools::opt; + +// Calculates the dominator or postdominator tree for a given function. +// 1 - Compute the successors and predecessors for each BasicBlock. We add a +// dummy node for the start node or for postdominators the exit. This node will +// point to all entry or all exit nodes. +// 2 - Using the CFA::DepthFirstTraversal get a depth first postordered list of +// all BasicBlocks. Using the successors (or for postdominator, predecessors) +// calculated in step 1 to traverse the tree. +// 3 - Pass the list calculated in step 2 to the CFA::CalculateDominators using +// the predecessors list (or for postdominator, successors). This will give us a +// vector of BB pairs. Each BB and its immediate dominator. +// 4 - Using the list from 3 use those edges to build a tree of +// DominatorTreeNodes. Each node containing a link to the parent dominator and +// children which are dominated. +// 5 - Using the tree from 4, perform a depth first traversal to calculate the +// preorder and postorder index of each node. We use these indexes to compare +// nodes against each other for domination checks. + +namespace { + +// Wrapper around CFA::DepthFirstTraversal to provide an interface to perform +// depth first search on generic BasicBlock types. Will call post and pre order +// user defined functions during traversal +// +// BBType - BasicBlock type. Will either be ir::BasicBlock or DominatorTreeNode +// SuccessorLambda - Lamdba matching the signature of 'const +// std::vector*(const BBType *A)'. Will return a vector of the nodes +// succeding BasicBlock A. +// PostLambda - Lamdba matching the signature of 'void (const BBType*)' will be +// called on each node traversed AFTER their children. +// PreLambda - Lamdba matching the signature of 'void (const BBType*)' will be +// called on each node traversed BEFORE their children. +template +static void DepthFirstSearch(const BBType* bb, SuccessorLambda successors, + PreLambda pre, PostLambda post) { + // Ignore backedge operation. + auto nop_backedge = [](const BBType*, const BBType*) {}; + CFA::DepthFirstTraversal(bb, successors, pre, post, nop_backedge); +} + +// Wrapper around CFA::DepthFirstTraversal to provide an interface to perform +// depth first search on generic BasicBlock types. This overload is for only +// performing user defined post order. +// +// BBType - BasicBlock type. Will either be ir::BasicBlock or DominatorTreeNode +// SuccessorLambda - Lamdba matching the signature of 'const +// std::vector*(const BBType *A)'. Will return a vector of the nodes +// succeding BasicBlock A. +// PostLambda - Lamdba matching the signature of 'void (const BBType*)' will be +// called on each node traversed after their children. +template +static void DepthFirstSearchPostOrder(const BBType* bb, + SuccessorLambda successors, + PostLambda post) { + // Ignore preorder operation. + auto nop_preorder = [](const BBType*) {}; + DepthFirstSearch(bb, successors, nop_preorder, post); +} + +// Small type trait to get the function class type. +template +struct GetFunctionClass { + using FunctionType = ir::Function; +}; + +// Helper class to compute predecessors and successors for each Basic Block in a +// function. Through GetPredFunctor and GetSuccessorFunctor it provides an +// interface to get the successor and predecessor lists for each basic +// block. This is required by the DepthFirstTraversal and ComputeDominator +// functions which take as parameter an std::function returning the successors +// and predecessors respectively. +// +// When computing the post-dominator tree, all edges are inverted. So successors +// returned by this class will be predecessors in the original CFG. +template +class BasicBlockSuccessorHelper { + // This should eventually become const ir::BasicBlock. + using BasicBlock = BBType; + using Function = typename GetFunctionClass::FunctionType; + + using BasicBlockListTy = std::vector; + using BasicBlockMapTy = std::map; + + public: + // For compliance with the dominance tree computation, entry nodes are + // connected to a single dummy node. + BasicBlockSuccessorHelper(Function& func, const BasicBlock* dummy_start_node, + bool post); + + // CFA::CalculateDominators requires std::vector. + using GetBlocksFunction = + std::function*(const BasicBlock*)>; + + // Returns the list of predecessor functions. + GetBlocksFunction GetPredFunctor() { + return [this](const BasicBlock* bb) { + BasicBlockListTy* v = &this->predecessors_[bb]; + return v; + }; + } + + // Returns a vector of the list of successor nodes from a given node. + GetBlocksFunction GetSuccessorFunctor() { + return [this](const BasicBlock* bb) { + BasicBlockListTy* v = &this->successors_[bb]; + return v; + }; + } + + private: + bool invert_graph_; + BasicBlockMapTy successors_; + BasicBlockMapTy predecessors_; + + // Build the successors and predecessors map for each basic blocks |f|. + // If |invert_graph_| is true, all edges are reversed (successors becomes + // predecessors and vice versa). + // For convenience, the start of the graph is |dummy_start_node|. + // The dominator tree construction requires a unique entry node, which cannot + // be guaranteed for the postdominator graph. The |dummy_start_node| BB is + // here to gather all entry nodes. + void CreateSuccessorMap(Function& f, const BasicBlock* dummy_start_node); +}; + +template +BasicBlockSuccessorHelper::BasicBlockSuccessorHelper( + Function& func, const BasicBlock* dummy_start_node, bool invert) + : invert_graph_(invert) { + CreateSuccessorMap(func, dummy_start_node); +} + +template +void BasicBlockSuccessorHelper::CreateSuccessorMap( + Function& f, const BasicBlock* dummy_start_node) { + std::map id_to_BB_map; + auto GetSuccessorBasicBlock = [&f, &id_to_BB_map](uint32_t successor_id) { + BasicBlock*& Succ = id_to_BB_map[successor_id]; + if (!Succ) { + for (BasicBlock& BBIt : f) { + if (successor_id == BBIt.id()) { + Succ = &BBIt; + break; + } + } + } + return Succ; + }; + + if (invert_graph_) { + // For the post dominator tree, we see the inverted graph. + // successors_ in the inverted graph are the predecessors in the CFG. + // The tree construction requires 1 entry point, so we add a dummy node + // that is connected to all function exiting basic blocks. + // An exiting basic block is a block with an OpKill, OpUnreachable, + // OpReturn or OpReturnValue as terminator instruction. + for (BasicBlock& bb : f) { + if (bb.hasSuccessor()) { + BasicBlockListTy& pred_list = predecessors_[&bb]; + const auto& const_bb = bb; + const_bb.ForEachSuccessorLabel( + [this, &pred_list, &bb, + &GetSuccessorBasicBlock](const uint32_t successor_id) { + BasicBlock* succ = GetSuccessorBasicBlock(successor_id); + // Inverted graph: our successors in the CFG + // are our predecessors in the inverted graph. + this->successors_[succ].push_back(&bb); + pred_list.push_back(succ); + }); + } else { + successors_[dummy_start_node].push_back(&bb); + predecessors_[&bb].push_back(const_cast(dummy_start_node)); + } + } + } else { + successors_[dummy_start_node].push_back(f.entry().get()); + predecessors_[f.entry().get()].push_back( + const_cast(dummy_start_node)); + for (BasicBlock& bb : f) { + BasicBlockListTy& succ_list = successors_[&bb]; + + const auto& const_bb = bb; + const_bb.ForEachSuccessorLabel([&](const uint32_t successor_id) { + BasicBlock* succ = GetSuccessorBasicBlock(successor_id); + succ_list.push_back(succ); + predecessors_[succ].push_back(&bb); + }); + } + } +} + +} // namespace + +namespace spvtools { +namespace opt { + +bool DominatorTree::StrictlyDominates(uint32_t a, uint32_t b) const { + if (a == b) return false; + return Dominates(a, b); +} + +bool DominatorTree::StrictlyDominates(const ir::BasicBlock* a, + const ir::BasicBlock* b) const { + return DominatorTree::StrictlyDominates(a->id(), b->id()); +} + +bool DominatorTree::StrictlyDominates(const DominatorTreeNode* a, + const DominatorTreeNode* b) const { + if (a == b) return false; + return Dominates(a, b); +} + +bool DominatorTree::Dominates(uint32_t a, uint32_t b) const { + // Check that both of the inputs are actual nodes. + const DominatorTreeNode* a_node = GetTreeNode(a); + const DominatorTreeNode* b_node = GetTreeNode(b); + if (!a_node || !b_node) return false; + + return Dominates(a_node, b_node); +} + +bool DominatorTree::Dominates(const DominatorTreeNode* a, + const DominatorTreeNode* b) const { + // Node A dominates node B if they are the same. + if (a == b) return true; + + return a->dfs_num_pre_ < b->dfs_num_pre_ && + a->dfs_num_post_ > b->dfs_num_post_; +} + +bool DominatorTree::Dominates(const ir::BasicBlock* A, + const ir::BasicBlock* B) const { + return Dominates(A->id(), B->id()); +} + +ir::BasicBlock* DominatorTree::ImmediateDominator( + const ir::BasicBlock* A) const { + return ImmediateDominator(A->id()); +} + +ir::BasicBlock* DominatorTree::ImmediateDominator(uint32_t a) const { + // Check that A is a valid node in the tree. + auto a_itr = nodes_.find(a); + if (a_itr == nodes_.end()) return nullptr; + + const DominatorTreeNode* node = &a_itr->second; + + if (node->parent_ == nullptr) { + return nullptr; + } + + return node->parent_->bb_; +} + +DominatorTreeNode* DominatorTree::GetOrInsertNode(ir::BasicBlock* bb) { + DominatorTreeNode* dtn = nullptr; + + std::map::iterator node_iter = + nodes_.find(bb->id()); + if (node_iter == nodes_.end()) { + dtn = &nodes_.emplace(std::make_pair(bb->id(), DominatorTreeNode{bb})) + .first->second; + } else + dtn = &node_iter->second; + + return dtn; +} + +void DominatorTree::GetDominatorEdges( + const ir::Function* f, const ir::BasicBlock* dummy_start_node, + std::vector>* edges) { + // Each time the depth first traversal calls the postorder callback + // std::function we push that node into the postorder vector to create our + // postorder list. + std::vector postorder; + auto postorder_function = [&](const ir::BasicBlock* b) { + postorder.push_back(b); + }; + + // CFA::CalculateDominators requires std::vector + // BB are derived from F, so we need to const cast it at some point + // no modification is made on F. + BasicBlockSuccessorHelper helper{ + *const_cast(f), dummy_start_node, postdominator_}; + + // The successor function tells DepthFirstTraversal how to move to successive + // nodes by providing an interface to get a list of successor nodes from any + // given node. + auto successor_functor = helper.GetSuccessorFunctor(); + + // The predecessor functor does the same as the successor functor + // but for all nodes preceding a given node. + auto predecessor_functor = helper.GetPredFunctor(); + + // If we're building a post dominator tree we traverse the tree in reverse + // using the predecessor function in place of the successor function and vice + // versa. + DepthFirstSearchPostOrder(dummy_start_node, successor_functor, + postorder_function); + *edges = + CFA::CalculateDominators(postorder, predecessor_functor); +} + +void DominatorTree::InitializeTree(const ir::Function* f, const ir::CFG& cfg) { + ClearTree(); + + // Skip over empty functions. + if (f->cbegin() == f->cend()) { + return; + } + + const ir::BasicBlock* dummy_start_node = + postdominator_ ? cfg.pseudo_exit_block() : cfg.pseudo_entry_block(); + + // Get the immediate dominator for each node. + std::vector> edges; + GetDominatorEdges(f, dummy_start_node, &edges); + + // Transform the vector into the tree structure which we can use to + // efficiently query dominance. + for (auto edge : edges) { + DominatorTreeNode* first = GetOrInsertNode(edge.first); + + if (edge.first == edge.second) { + if (std::find(roots_.begin(), roots_.end(), first) == roots_.end()) + roots_.push_back(first); + continue; + } + + DominatorTreeNode* second = GetOrInsertNode(edge.second); + + first->parent_ = second; + second->children_.push_back(first); + } + ResetDFNumbering(); +} + +void DominatorTree::ResetDFNumbering() { + int index = 0; + auto preFunc = [&index](const DominatorTreeNode* node) { + const_cast(node)->dfs_num_pre_ = ++index; + }; + + auto postFunc = [&index](const DominatorTreeNode* node) { + const_cast(node)->dfs_num_post_ = ++index; + }; + + auto getSucc = [](const DominatorTreeNode* node) { return &node->children_; }; + + for (auto root : roots_) DepthFirstSearch(root, getSucc, preFunc, postFunc); +} + +void DominatorTree::DumpTreeAsDot(std::ostream& out_stream) const { + out_stream << "digraph {\n"; + Visit([&out_stream](const DominatorTreeNode* node) { + // Print the node. + if (node->bb_) { + out_stream << node->bb_->id() << "[label=\"" << node->bb_->id() + << "\"];\n"; + } + + // Print the arrow from the parent to this node. Entry nodes will not have + // parents so draw them as children from the dummy node. + if (node->parent_) { + out_stream << node->parent_->bb_->id() << " -> " << node->bb_->id() + << ";\n"; + } + + // Return true to continue the traversal. + return true; + }); + out_stream << "}\n"; +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/dominator_tree.h b/3rdparty/spirv-tools/source/opt/dominator_tree.h new file mode 100644 index 000000000..39d5e0297 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/dominator_tree.h @@ -0,0 +1,306 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_DOMINATOR_ANALYSIS_TREE_H_ +#define LIBSPIRV_OPT_DOMINATOR_ANALYSIS_TREE_H_ + +#include +#include +#include +#include +#include + +#include "cfg.h" +#include "module.h" +#include "tree_iterator.h" + +namespace spvtools { +namespace opt { +// This helper struct forms the nodes in the tree, with each node containing its +// children. It also contains two values, for the pre and post indexes in the +// tree which are used to compare two nodes. +struct DominatorTreeNode { + explicit DominatorTreeNode(ir::BasicBlock* bb) + : bb_(bb), + parent_(nullptr), + children_({}), + dfs_num_pre_(-1), + dfs_num_post_(-1) {} + + using iterator = std::vector::iterator; + using const_iterator = std::vector::const_iterator; + + // depth first preorder iterator. + using df_iterator = TreeDFIterator; + using const_df_iterator = TreeDFIterator; + // depth first postorder iterator. + using post_iterator = PostOrderTreeDFIterator; + using const_post_iterator = PostOrderTreeDFIterator; + + iterator begin() { return children_.begin(); } + iterator end() { return children_.end(); } + const_iterator begin() const { return cbegin(); } + const_iterator end() const { return cend(); } + const_iterator cbegin() const { return children_.begin(); } + const_iterator cend() const { return children_.end(); } + + // Depth first preorder iterator using this node as root. + df_iterator df_begin() { return df_iterator(this); } + df_iterator df_end() { return df_iterator(); } + const_df_iterator df_begin() const { return df_cbegin(); } + const_df_iterator df_end() const { return df_cend(); } + const_df_iterator df_cbegin() const { return const_df_iterator(this); } + const_df_iterator df_cend() const { return const_df_iterator(); } + + // Depth first postorder iterator using this node as root. + post_iterator post_begin() { return post_iterator::begin(this); } + post_iterator post_end() { return post_iterator::end(nullptr); } + const_post_iterator post_begin() const { return post_cbegin(); } + const_post_iterator post_end() const { return post_cend(); } + const_post_iterator post_cbegin() const { + return const_post_iterator::begin(this); + } + const_post_iterator post_cend() const { + return const_post_iterator::end(nullptr); + } + + inline uint32_t id() const { return bb_->id(); } + + ir::BasicBlock* bb_; + DominatorTreeNode* parent_; + std::vector children_; + + // These indexes are used to compare two given nodes. A node is a child or + // grandchild of another node if its preorder index is greater than the + // first nodes preorder index AND if its postorder index is less than the + // first nodes postorder index. + int dfs_num_pre_; + int dfs_num_post_; +}; + +// A class representing a tree of BasicBlocks in a given function, where each +// node is dominated by its parent. +class DominatorTree { + public: + // Map OpLabel ids to dominator tree nodes + using DominatorTreeNodeMap = std::map; + using iterator = TreeDFIterator; + using const_iterator = TreeDFIterator; + using post_iterator = PostOrderTreeDFIterator; + using const_post_iterator = PostOrderTreeDFIterator; + + // List of DominatorTreeNode to define the list of roots + using DominatorTreeNodeList = std::vector; + using roots_iterator = DominatorTreeNodeList::iterator; + using roots_const_iterator = DominatorTreeNodeList::const_iterator; + + DominatorTree() : postdominator_(false) {} + explicit DominatorTree(bool post) : postdominator_(post) {} + + // Depth first iterators. + // Traverse the dominator tree in a depth first pre-order. + // The pseudo-block is ignored. + iterator begin() { return ++iterator(GetRoot()); } + iterator end() { return iterator(); } + const_iterator begin() const { return cbegin(); } + const_iterator end() const { return cend(); } + const_iterator cbegin() const { return ++const_iterator(GetRoot()); } + const_iterator cend() const { return const_iterator(); } + + // Traverse the dominator tree in a depth first post-order. + // The pseudo-block is ignored. + post_iterator post_begin() { return post_iterator::begin(GetRoot()); } + post_iterator post_end() { return post_iterator::end(GetRoot()); } + const_post_iterator post_begin() const { return post_cbegin(); } + const_post_iterator post_end() const { return post_cend(); } + const_post_iterator post_cbegin() const { + return const_post_iterator::begin(GetRoot()); + } + const_post_iterator post_cend() const { + return const_post_iterator::end(GetRoot()); + } + + roots_iterator roots_begin() { return roots_.begin(); } + roots_iterator roots_end() { return roots_.end(); } + roots_const_iterator roots_begin() const { return roots_cbegin(); } + roots_const_iterator roots_end() const { return roots_cend(); } + roots_const_iterator roots_cbegin() const { return roots_.begin(); } + roots_const_iterator roots_cend() const { return roots_.end(); } + + // Get the unique root of the tree. + // It is guaranteed to work on a dominator tree. + // post-dominator might have a list. + DominatorTreeNode* GetRoot() { + assert(roots_.size() == 1); + return *roots_.begin(); + } + + const DominatorTreeNode* GetRoot() const { + assert(roots_.size() == 1); + return *roots_.begin(); + } + + const DominatorTreeNodeList& Roots() const { return roots_; } + + // Dumps the tree in the graphvis dot format into the |out_stream|. + void DumpTreeAsDot(std::ostream& out_stream) const; + + // Build the (post-)dominator tree for the function |f| + // Any existing data will be overwritten + void InitializeTree(const ir::Function* f, const ir::CFG& cfg); + + // Check if the basic block |a| dominates the basic block |b|. + bool Dominates(const ir::BasicBlock* a, const ir::BasicBlock* b) const; + + // Check if the basic block id |a| dominates the basic block id |b|. + bool Dominates(uint32_t a, uint32_t b) const; + + // Check if the dominator tree node |a| dominates the dominator tree node |b|. + bool Dominates(const DominatorTreeNode* a, const DominatorTreeNode* b) const; + + // Check if the basic block |a| strictly dominates the basic block |b|. + bool StrictlyDominates(const ir::BasicBlock* a, + const ir::BasicBlock* b) const; + + // Check if the basic block id |a| strictly dominates the basic block id |b|. + bool StrictlyDominates(uint32_t a, uint32_t b) const; + + // Check if the dominator tree node |a| strictly dominates the dominator tree + // node |b|. + bool StrictlyDominates(const DominatorTreeNode* a, + const DominatorTreeNode* b) const; + + // Returns the immediate dominator of basic block |a|. + ir::BasicBlock* ImmediateDominator(const ir::BasicBlock* A) const; + + // Returns the immediate dominator of basic block id |a|. + ir::BasicBlock* ImmediateDominator(uint32_t a) const; + + // Returns true if the basic block |a| is reachable by this tree. A node would + // be unreachable if it cannot be reached by traversal from the start node or + // for a postdominator tree, cannot be reached from the exit nodes. + inline bool ReachableFromRoots(const ir::BasicBlock* a) const { + if (!a) return false; + return ReachableFromRoots(a->id()); + } + + // Returns true if the basic block id |a| is reachable by this tree. + bool ReachableFromRoots(uint32_t a) const { + return GetTreeNode(a) != nullptr; + } + + // Returns true if this tree is a post dominator tree. + bool IsPostDominator() const { return postdominator_; } + + // Clean up the tree. + void ClearTree() { + nodes_.clear(); + roots_.clear(); + } + + // Applies the std::function |func| to all nodes in the dominator tree. + // Tree nodes are visited in a depth first pre-order. + bool Visit(std::function func) { + for (auto n : *this) { + if (!func(&n)) return false; + } + return true; + } + + // Applies the std::function |func| to all nodes in the dominator tree. + // Tree nodes are visited in a depth first pre-order. + bool Visit(std::function func) const { + for (auto n : *this) { + if (!func(&n)) return false; + } + return true; + } + + // Applies the std::function |func| to all nodes in the dominator tree from + // |node| downwards. The boolean return from |func| is used to determine + // whether or not the children should also be traversed. Tree nodes are + // visited in a depth first pre-order. + void VisitChildrenIf(std::function func, + iterator node) { + if (func(&*node)) { + for (auto n : *node) { + VisitChildrenIf(func, n->df_begin()); + } + } + } + + // Returns the DominatorTreeNode associated with the basic block |bb|. + // If the |bb| is unknown to the dominator tree, it returns null. + inline DominatorTreeNode* GetTreeNode(ir::BasicBlock* bb) { + return GetTreeNode(bb->id()); + } + // Returns the DominatorTreeNode associated with the basic block |bb|. + // If the |bb| is unknown to the dominator tree, it returns null. + inline const DominatorTreeNode* GetTreeNode(ir::BasicBlock* bb) const { + return GetTreeNode(bb->id()); + } + + // Returns the DominatorTreeNode associated with the basic block id |id|. + // If the id |id| is unknown to the dominator tree, it returns null. + inline DominatorTreeNode* GetTreeNode(uint32_t id) { + DominatorTreeNodeMap::iterator node_iter = nodes_.find(id); + if (node_iter == nodes_.end()) { + return nullptr; + } + return &node_iter->second; + } + // Returns the DominatorTreeNode associated with the basic block id |id|. + // If the id |id| is unknown to the dominator tree, it returns null. + inline const DominatorTreeNode* GetTreeNode(uint32_t id) const { + DominatorTreeNodeMap::const_iterator node_iter = nodes_.find(id); + if (node_iter == nodes_.end()) { + return nullptr; + } + return &node_iter->second; + } + + // Adds the basic block |bb| to the tree structure if it doesn't already + // exist. + DominatorTreeNode* GetOrInsertNode(ir::BasicBlock* bb); + + // Recomputes the DF numbering of the tree. + void ResetDFNumbering(); + + private: + // Wrapper function which gets the list of pairs of each BasicBlocks to its + // immediately dominating BasicBlock and stores the result in the the edges + // parameter. + // + // The |edges| vector will contain the dominator tree as pairs of nodes. + // The first node in the pair is a node in the graph. The second node in the + // pair is its immediate dominator. + // The root of the tree has themself as immediate dominator. + void GetDominatorEdges( + const ir::Function* f, const ir::BasicBlock* dummy_start_node, + std::vector>* edges); + + // The roots of the tree. + std::vector roots_; + + // Pairs each basic block id to the tree node containing that basic block. + DominatorTreeNodeMap nodes_; + + // True if this is a post dominator tree. + bool postdominator_; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_DOMINATOR_ANALYSIS_TREE_H_ diff --git a/3rdparty/spirv-tools/source/opt/eliminate_dead_constant_pass.cpp b/3rdparty/spirv-tools/source/opt/eliminate_dead_constant_pass.cpp new file mode 100644 index 000000000..5e299c6d2 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/eliminate_dead_constant_pass.cpp @@ -0,0 +1,103 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "eliminate_dead_constant_pass.h" + +#include +#include +#include + +#include "def_use_manager.h" +#include "ir_context.h" +#include "log.h" +#include "reflect.h" + +namespace spvtools { +namespace opt { + +Pass::Status EliminateDeadConstantPass::Process(ir::IRContext* irContext) { + std::unordered_set working_list; + // Traverse all the instructions to get the initial set of dead constants as + // working list and count number of real uses for constants. Uses in + // annotation instructions do not count. + std::unordered_map use_counts; + std::vector constants = irContext->GetConstants(); + for (auto* c : constants) { + uint32_t const_id = c->result_id(); + size_t count = 0; + irContext->get_def_use_mgr()->ForEachUse( + const_id, [&count](ir::Instruction* user, uint32_t index) { + (void)index; + SpvOp op = user->opcode(); + if (!(ir::IsAnnotationInst(op) || ir::IsDebug1Inst(op) || + ir::IsDebug2Inst(op) || ir::IsDebug3Inst(op))) { + ++count; + } + }); + use_counts[c] = count; + if (!count) { + working_list.insert(c); + } + } + + // Start from the constants with 0 uses, back trace through the def-use chain + // to find all dead constants. + std::unordered_set dead_consts; + while (!working_list.empty()) { + ir::Instruction* inst = *working_list.begin(); + // Back propagate if the instruction contains IDs in its operands. + switch (inst->opcode()) { + case SpvOp::SpvOpConstantComposite: + case SpvOp::SpvOpSpecConstantComposite: + case SpvOp::SpvOpSpecConstantOp: + for (uint32_t i = 0; i < inst->NumInOperands(); i++) { + // SpecConstantOp instruction contains 'opcode' as its operand. Need + // to exclude such operands when decreasing uses. + if (inst->GetInOperand(i).type != SPV_OPERAND_TYPE_ID) { + continue; + } + uint32_t operand_id = inst->GetSingleWordInOperand(i); + ir::Instruction* def_inst = + irContext->get_def_use_mgr()->GetDef(operand_id); + // If the use_count does not have any count for the def_inst, + // def_inst must not be a constant, and should be ignored here. + if (!use_counts.count(def_inst)) { + continue; + } + // The number of uses should never be less then 0, so it can not be + // less than 1 before it decreases. + SPIRV_ASSERT(consumer(), use_counts[def_inst] > 0); + --use_counts[def_inst]; + if (!use_counts[def_inst]) { + working_list.insert(def_inst); + } + } + break; + default: + break; + } + dead_consts.insert(inst); + working_list.erase(inst); + } + + // Turn all dead instructions and uses of them to nop + for (auto* dc : dead_consts) { + irContext->KillDef(dc->result_id()); + } + return dead_consts.empty() ? Status::SuccessWithoutChange + : Status::SuccessWithChange; +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/eliminate_dead_constant_pass.h b/3rdparty/spirv-tools/source/opt/eliminate_dead_constant_pass.h new file mode 100644 index 000000000..3ff69f5c7 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/eliminate_dead_constant_pass.h @@ -0,0 +1,35 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_ELIMINATE_DEAD_CONSTANT_PASS_H_ +#define LIBSPIRV_OPT_ELIMINATE_DEAD_CONSTANT_PASS_H_ + +#include "ir_context.h" +#include "module.h" +#include "pass.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class EliminateDeadConstantPass : public Pass { + public: + const char* name() const override { return "eliminate-dead-const"; } + Status Process(ir::IRContext*) override; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_ELIMINATE_DEAD_CONSTANT_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/eliminate_dead_functions_pass.cpp b/3rdparty/spirv-tools/source/opt/eliminate_dead_functions_pass.cpp new file mode 100644 index 000000000..8f9748a67 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/eliminate_dead_functions_pass.cpp @@ -0,0 +1,57 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "eliminate_dead_functions_pass.h" +#include "ir_context.h" + +#include + +namespace spvtools { +namespace opt { + +Pass::Status EliminateDeadFunctionsPass::Process(ir::IRContext* c) { + InitializeProcessing(c); + + // Identify live functions first. Those that are not live + // are dead. + std::unordered_set live_function_set; + ProcessFunction mark_live = [&live_function_set](ir::Function* fp) { + live_function_set.insert(fp); + return false; + }; + ProcessReachableCallTree(mark_live, context()); + + bool modified = false; + for (auto funcIter = get_module()->begin(); + funcIter != get_module()->end();) { + if (live_function_set.count(&*funcIter) == 0) { + modified = true; + EliminateFunction(&*funcIter); + funcIter = funcIter.Erase(); + } else { + ++funcIter; + } + } + + return modified ? Pass::Status::SuccessWithChange + : Pass::Status::SuccessWithoutChange; +} + +void EliminateDeadFunctionsPass::EliminateFunction(ir::Function* func) { + // Remove all of the instruction in the function body + func->ForEachInst( + [this](ir::Instruction* inst) { context()->KillInst(inst); }, true); +} +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/eliminate_dead_functions_pass.h b/3rdparty/spirv-tools/source/opt/eliminate_dead_functions_pass.h new file mode 100644 index 000000000..adb41bb39 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/eliminate_dead_functions_pass.h @@ -0,0 +1,43 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_ELIMINATE_DEAD_FUNCTIONS_PASS_H_ +#define LIBSPIRV_OPT_ELIMINATE_DEAD_FUNCTIONS_PASS_H_ + +#include "def_use_manager.h" +#include "function.h" +#include "mem_pass.h" +#include "module.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class EliminateDeadFunctionsPass : public MemPass { + public: + const char* name() const override { return "eliminate-dead-functions"; } + Status Process(ir::IRContext* c) override; + + ir::IRContext::Analysis GetPreservedAnalyses() override { + return ir::IRContext::kAnalysisDefUse; + } + + private: + void EliminateFunction(ir::Function* func); +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_ELIMINATE_DEAD_FUNCTIONS_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/feature_manager.cpp b/3rdparty/spirv-tools/source/opt/feature_manager.cpp new file mode 100644 index 000000000..f9b91bdbe --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/feature_manager.cpp @@ -0,0 +1,65 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "feature_manager.h" +#include +#include + +#include "enum_string_mapping.h" + +namespace spvtools { +namespace opt { + +void FeatureManager::Analyze(ir::Module* module) { + AddExtensions(module); + AddCapabilities(module); + AddExtInstImportIds(module); +} + +void FeatureManager::AddExtensions(ir::Module* module) { + for (auto ext : module->extensions()) { + const std::string name = + reinterpret_cast(ext.GetInOperand(0u).words.data()); + libspirv::Extension extension; + if (libspirv::GetExtensionFromString(name.c_str(), &extension)) { + extensions_.Add(extension); + } + } +} + +void FeatureManager::AddCapability(SpvCapability cap) { + if (capabilities_.Contains(cap)) return; + + capabilities_.Add(cap); + + spv_operand_desc desc = {}; + if (SPV_SUCCESS == + grammar_.lookupOperand(SPV_OPERAND_TYPE_CAPABILITY, cap, &desc)) { + libspirv::CapabilitySet(desc->numCapabilities, desc->capabilities) + .ForEach([this](SpvCapability c) { AddCapability(c); }); + } +} + +void FeatureManager::AddCapabilities(ir::Module* module) { + for (ir::Instruction& inst : module->capabilities()) { + AddCapability(static_cast(inst.GetSingleWordInOperand(0))); + } +} + +void FeatureManager::AddExtInstImportIds(ir::Module* module) { + extinst_importid_GLSLstd450_ = module->GetExtInstImportId("GLSL.std.450"); +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/feature_manager.h b/3rdparty/spirv-tools/source/opt/feature_manager.h new file mode 100644 index 000000000..b99a776fc --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/feature_manager.h @@ -0,0 +1,83 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_FEATURE_MANAGER_H_ +#define LIBSPIRV_OPT_FEATURE_MANAGER_H_ + +#include "assembly_grammar.h" +#include "extensions.h" +#include "module.h" + +namespace spvtools { +namespace opt { + +// Tracks features enabled by a module. The IRContext has a FeatureManager. +class FeatureManager { + public: + explicit FeatureManager(const libspirv::AssemblyGrammar& grammar) + : grammar_(grammar) {} + + // Returns true if |ext| is an enabled extension in the module. + bool HasExtension(libspirv::Extension ext) const { + return extensions_.Contains(ext); + } + + // Returns true if |cap| is an enabled capability in the module. + bool HasCapability(SpvCapability cap) const { + return capabilities_.Contains(cap); + } + + // Analyzes |module| and records enabled extensions and capabilities. + void Analyze(ir::Module* module); + + libspirv::CapabilitySet* GetCapabilities() { return &capabilities_; } + const libspirv::CapabilitySet* GetCapabilities() const { + return &capabilities_; + } + + uint32_t GetExtInstImportId_GLSLstd450() const { + return extinst_importid_GLSLstd450_; + } + + private: + // Analyzes |module| and records enabled extensions. + void AddExtensions(ir::Module* module); + + // Adds the given |capability| and all implied capabilities into the current + // FeatureManager. + void AddCapability(SpvCapability capability); + + // Analyzes |module| and records enabled capabilities. + void AddCapabilities(ir::Module* module); + + // Analyzes |module| and records imported external instruction sets. + void AddExtInstImportIds(ir::Module* module); + + // Auxiliary object for querying SPIR-V grammar facts. + const libspirv::AssemblyGrammar& grammar_; + + // The enabled extensions. + libspirv::ExtensionSet extensions_; + + // The enabled capabilities. + libspirv::CapabilitySet capabilities_; + + // Common external instruction import ids, cached for performance. + uint32_t extinst_importid_GLSLstd450_ = 0; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_FEATURE_MANAGER_H_ diff --git a/3rdparty/spirv-tools/source/opt/flatten_decoration_pass.cpp b/3rdparty/spirv-tools/source/opt/flatten_decoration_pass.cpp new file mode 100644 index 000000000..eac829733 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/flatten_decoration_pass.cpp @@ -0,0 +1,167 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "flatten_decoration_pass.h" +#include "ir_context.h" + +#include +#include +#include +#include + +namespace spvtools { +namespace opt { + +using ir::Instruction; +using ir::Operand; + +using Words = std::vector; +using OrderedUsesMap = std::unordered_map; + +Pass::Status FlattenDecorationPass::Process(ir::IRContext* c) { + InitializeProcessing(c); + + bool modified = false; + + // The target Id of OpDecorationGroup instructions. + // We have to track this separately from its uses, in case it + // has no uses. + std::unordered_set group_ids; + // Maps a decoration group Id to its GroupDecorate targets, in order + // of appearance. + OrderedUsesMap normal_uses; + // Maps a decoration group Id to its GroupMemberDecorate targets and + // their indices, in of appearance. + OrderedUsesMap member_uses; + + auto annotations = context()->annotations(); + + // On the first pass, record each OpDecorationGroup with its ordered uses. + // Rely on unordered_map::operator[] to create its entries on first access. + for (const auto& inst : annotations) { + switch (inst.opcode()) { + case SpvOp::SpvOpDecorationGroup: + group_ids.insert(inst.result_id()); + break; + case SpvOp::SpvOpGroupDecorate: { + Words& words = normal_uses[inst.GetSingleWordInOperand(0)]; + for (uint32_t i = 1; i < inst.NumInOperandWords(); i++) { + words.push_back(inst.GetSingleWordInOperand(i)); + } + } break; + case SpvOp::SpvOpGroupMemberDecorate: { + Words& words = member_uses[inst.GetSingleWordInOperand(0)]; + for (uint32_t i = 1; i < inst.NumInOperandWords(); i++) { + words.push_back(inst.GetSingleWordInOperand(i)); + } + } break; + default: + break; + } + } + + // On the second pass, replace OpDecorationGroup and its uses with + // equivalent normal and struct member uses. + auto inst_iter = annotations.begin(); + // We have to re-evaluate the end pointer + while (inst_iter != context()->annotations().end()) { + // Should we replace this instruction? + bool replace = false; + switch (inst_iter->opcode()) { + case SpvOp::SpvOpDecorationGroup: + case SpvOp::SpvOpGroupDecorate: + case SpvOp::SpvOpGroupMemberDecorate: + replace = true; + break; + case SpvOp::SpvOpDecorate: { + // If this decoration targets a group, then replace it + // by sets of normal and member decorations. + const uint32_t group = inst_iter->GetSingleWordOperand(0); + const auto normal_uses_iter = normal_uses.find(group); + if (normal_uses_iter != normal_uses.end()) { + for (auto target : normal_uses[group]) { + std::unique_ptr new_inst(inst_iter->Clone(context())); + new_inst->SetInOperand(0, Words{target}); + inst_iter = inst_iter.InsertBefore(std::move(new_inst)); + ++inst_iter; + replace = true; + } + } + const auto member_uses_iter = member_uses.find(group); + if (member_uses_iter != member_uses.end()) { + const Words& member_id_pairs = (*member_uses_iter).second; + // The collection is a sequence of pairs. + assert((member_id_pairs.size() % 2) == 0); + for (size_t i = 0; i < member_id_pairs.size(); i += 2) { + // Make an OpMemberDecorate instruction for each (target, member) + // pair. + const uint32_t target = member_id_pairs[i]; + const uint32_t member = member_id_pairs[i + 1]; + std::vector operands; + operands.push_back(Operand(SPV_OPERAND_TYPE_ID, {target})); + operands.push_back( + Operand(SPV_OPERAND_TYPE_LITERAL_INTEGER, {member})); + auto decoration_operands_iter = inst_iter->begin(); + decoration_operands_iter++; // Skip the group target. + operands.insert(operands.end(), decoration_operands_iter, + inst_iter->end()); + std::unique_ptr new_inst(new Instruction( + context(), SpvOp::SpvOpMemberDecorate, 0, 0, operands)); + inst_iter = inst_iter.InsertBefore(std::move(new_inst)); + ++inst_iter; + replace = true; + } + } + // If this is an OpDecorate targeting the OpDecorationGroup itself, + // remove it even if that decoration group itself is not the target of + // any OpGroupDecorate or OpGroupMemberDecorate. + if (!replace && group_ids.count(group)) { + replace = true; + } + } break; + default: + break; + } + if (replace) { + inst_iter = inst_iter.Erase(); + modified = true; + } else { + // Handle the case of decorations unrelated to decoration groups. + ++inst_iter; + } + } + + // Remove OpName instructions which reference the removed group decorations. + // An OpDecorationGroup instruction might not have been used by an + // OpGroupDecorate or OpGroupMemberDecorate instruction. + if (!group_ids.empty()) { + for (auto debug_inst_iter = context()->debug2_begin(); + debug_inst_iter != context()->debug2_end();) { + if (debug_inst_iter->opcode() == SpvOp::SpvOpName) { + const uint32_t target = debug_inst_iter->GetSingleWordOperand(0); + if (group_ids.count(target)) { + debug_inst_iter = debug_inst_iter.Erase(); + modified = true; + } else { + ++debug_inst_iter; + } + } + } + } + + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/flatten_decoration_pass.h b/3rdparty/spirv-tools/source/opt/flatten_decoration_pass.h new file mode 100644 index 000000000..7db6f8640 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/flatten_decoration_pass.h @@ -0,0 +1,35 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_FLATTEN_DECORATION_PASS_H_ +#define LIBSPIRV_OPT_FLATTEN_DECORATION_PASS_H_ + +#include "ir_context.h" +#include "module.h" +#include "pass.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class FlattenDecorationPass : public Pass { + public: + const char* name() const override { return "flatten-decoration"; } + Status Process(ir::IRContext*) override; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_FLATTEN_DECORATION_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/fold.cpp b/3rdparty/spirv-tools/source/opt/fold.cpp new file mode 100644 index 000000000..678c4566e --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/fold.cpp @@ -0,0 +1,683 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "fold.h" + +#include +#include +#include + +#include "const_folding_rules.h" +#include "def_use_manager.h" +#include "folding_rules.h" +#include "ir_builder.h" +#include "ir_context.h" + +namespace spvtools { +namespace opt { + +namespace { + +#ifndef INT32_MIN +#define INT32_MIN (-2147483648) +#endif + +#ifndef INT32_MAX +#define INT32_MAX 2147483647 +#endif + +#ifndef UINT32_MAX +#define UINT32_MAX 0xffffffff /* 4294967295U */ +#endif + +// Returns the single-word result from performing the given unary operation on +// the operand value which is passed in as a 32-bit word. +uint32_t UnaryOperate(SpvOp opcode, uint32_t operand) { + switch (opcode) { + // Arthimetics + case SpvOp::SpvOpSNegate: + return -static_cast(operand); + case SpvOp::SpvOpNot: + return ~operand; + case SpvOp::SpvOpLogicalNot: + return !static_cast(operand); + default: + assert(false && + "Unsupported unary operation for OpSpecConstantOp instruction"); + return 0u; + } +} + +// Returns the single-word result from performing the given binary operation on +// the operand values which are passed in as two 32-bit word. +uint32_t BinaryOperate(SpvOp opcode, uint32_t a, uint32_t b) { + switch (opcode) { + // Arthimetics + case SpvOp::SpvOpIAdd: + return a + b; + case SpvOp::SpvOpISub: + return a - b; + case SpvOp::SpvOpIMul: + return a * b; + case SpvOp::SpvOpUDiv: + assert(b != 0); + return a / b; + case SpvOp::SpvOpSDiv: + assert(b != 0u); + return (static_cast(a)) / (static_cast(b)); + case SpvOp::SpvOpSRem: { + // The sign of non-zero result comes from the first operand: a. This is + // guaranteed by C++11 rules for integer division operator. The division + // result is rounded toward zero, so the result of '%' has the sign of + // the first operand. + assert(b != 0u); + return static_cast(a) % static_cast(b); + } + case SpvOp::SpvOpSMod: { + // The sign of non-zero result comes from the second operand: b + assert(b != 0u); + int32_t rem = BinaryOperate(SpvOp::SpvOpSRem, a, b); + int32_t b_prim = static_cast(b); + return (rem + b_prim) % b_prim; + } + case SpvOp::SpvOpUMod: + assert(b != 0u); + return (a % b); + + // Shifting + case SpvOp::SpvOpShiftRightLogical: { + return a >> b; + } + case SpvOp::SpvOpShiftRightArithmetic: + return (static_cast(a)) >> b; + case SpvOp::SpvOpShiftLeftLogical: + return a << b; + + // Bitwise operations + case SpvOp::SpvOpBitwiseOr: + return a | b; + case SpvOp::SpvOpBitwiseAnd: + return a & b; + case SpvOp::SpvOpBitwiseXor: + return a ^ b; + + // Logical + case SpvOp::SpvOpLogicalEqual: + return (static_cast(a)) == (static_cast(b)); + case SpvOp::SpvOpLogicalNotEqual: + return (static_cast(a)) != (static_cast(b)); + case SpvOp::SpvOpLogicalOr: + return (static_cast(a)) || (static_cast(b)); + case SpvOp::SpvOpLogicalAnd: + return (static_cast(a)) && (static_cast(b)); + + // Comparison + case SpvOp::SpvOpIEqual: + return a == b; + case SpvOp::SpvOpINotEqual: + return a != b; + case SpvOp::SpvOpULessThan: + return a < b; + case SpvOp::SpvOpSLessThan: + return (static_cast(a)) < (static_cast(b)); + case SpvOp::SpvOpUGreaterThan: + return a > b; + case SpvOp::SpvOpSGreaterThan: + return (static_cast(a)) > (static_cast(b)); + case SpvOp::SpvOpULessThanEqual: + return a <= b; + case SpvOp::SpvOpSLessThanEqual: + return (static_cast(a)) <= (static_cast(b)); + case SpvOp::SpvOpUGreaterThanEqual: + return a >= b; + case SpvOp::SpvOpSGreaterThanEqual: + return (static_cast(a)) >= (static_cast(b)); + default: + assert(false && + "Unsupported binary operation for OpSpecConstantOp instruction"); + return 0u; + } +} + +// Returns the single-word result from performing the given ternary operation +// on the operand values which are passed in as three 32-bit word. +uint32_t TernaryOperate(SpvOp opcode, uint32_t a, uint32_t b, uint32_t c) { + switch (opcode) { + case SpvOp::SpvOpSelect: + return (static_cast(a)) ? b : c; + default: + assert(false && + "Unsupported ternary operation for OpSpecConstantOp instruction"); + return 0u; + } +} + +// Returns the single-word result from performing the given operation on the +// operand words. This only works with 32-bit operations and uses boolean +// convention that 0u is false, and anything else is boolean true. +// TODO(qining): Support operands other than 32-bit wide. +uint32_t OperateWords(SpvOp opcode, + const std::vector& operand_words) { + switch (operand_words.size()) { + case 1: + return UnaryOperate(opcode, operand_words.front()); + case 2: + return BinaryOperate(opcode, operand_words.front(), operand_words.back()); + case 3: + return TernaryOperate(opcode, operand_words[0], operand_words[1], + operand_words[2]); + default: + assert(false && "Invalid number of operands"); + return 0; + } +} + +bool FoldInstructionInternal(ir::Instruction* inst) { + ir::IRContext* context = inst->context(); + auto identity_map = [](uint32_t id) { return id; }; + ir::Instruction* folded_inst = FoldInstructionToConstant(inst, identity_map); + if (folded_inst != nullptr) { + inst->SetOpcode(SpvOpCopyObject); + inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {folded_inst->result_id()}}}); + return true; + } + + SpvOp opcode = inst->opcode(); + analysis::ConstantManager* const_manager = context->get_constant_mgr(); + + std::vector constants = + const_manager->GetOperandConstants(inst); + + static FoldingRules* rules = new FoldingRules(); + for (FoldingRule rule : rules->GetRulesForOpcode(opcode)) { + if (rule(inst, constants)) { + return true; + } + } + return false; +} + +} // namespace + +const ConstantFoldingRules& GetConstantFoldingRules() { + static ConstantFoldingRules* rules = new ConstantFoldingRules(); + return *rules; +} + +// Returns the result of performing an operation on scalar constant operands. +// This function extracts the operand values as 32 bit words and returns the +// result in 32 bit word. Scalar constants with longer than 32-bit width are +// not accepted in this function. +uint32_t FoldScalars(SpvOp opcode, + const std::vector& operands) { + assert(IsFoldableOpcode(opcode) && + "Unhandled instruction opcode in FoldScalars"); + std::vector operand_values_in_raw_words; + for (const auto& operand : operands) { + if (const analysis::ScalarConstant* scalar = operand->AsScalarConstant()) { + const auto& scalar_words = scalar->words(); + assert(scalar_words.size() == 1 && + "Scalar constants with longer than 32-bit width are not allowed " + "in FoldScalars()"); + operand_values_in_raw_words.push_back(scalar_words.front()); + } else if (operand->AsNullConstant()) { + operand_values_in_raw_words.push_back(0u); + } else { + assert(false && + "FoldScalars() only accepts ScalarConst or NullConst type of " + "constant"); + } + } + return OperateWords(opcode, operand_values_in_raw_words); +} + +// Returns true if |inst| is a binary operation that takes two integers as +// parameters and folds to a constant that can be represented as an unsigned +// 32-bit value when the ids have been replaced by |id_map|. If |inst| can be +// folded, the resulting value is returned in |*result|. Valid result types for +// the instruction are any integer (signed or unsigned) with 32-bits or less, or +// a boolean value. +bool FoldBinaryIntegerOpToConstant(ir::Instruction* inst, + std::function id_map, + uint32_t* result) { + SpvOp opcode = inst->opcode(); + ir::IRContext* context = inst->context(); + analysis::ConstantManager* const_manger = context->get_constant_mgr(); + + uint32_t ids[2]; + const analysis::IntConstant* constants[2]; + for (uint32_t i = 0; i < 2; i++) { + const ir::Operand* operand = &inst->GetInOperand(i); + if (operand->type != SPV_OPERAND_TYPE_ID) { + return false; + } + ids[i] = id_map(operand->words[0]); + const analysis::Constant* constant = + const_manger->FindDeclaredConstant(ids[i]); + constants[i] = (constant != nullptr ? constant->AsIntConstant() : nullptr); + } + + switch (opcode) { + // Arthimetics + case SpvOp::SpvOpIMul: + for (uint32_t i = 0; i < 2; i++) { + if (constants[i] != nullptr && constants[i]->IsZero()) { + *result = 0; + return true; + } + } + break; + case SpvOp::SpvOpUDiv: + case SpvOp::SpvOpSDiv: + case SpvOp::SpvOpSRem: + case SpvOp::SpvOpSMod: + case SpvOp::SpvOpUMod: + // This changes undefined behaviour (ie divide by 0) into a 0. + for (uint32_t i = 0; i < 2; i++) { + if (constants[i] != nullptr && constants[i]->IsZero()) { + *result = 0; + return true; + } + } + break; + + // Shifting + case SpvOp::SpvOpShiftRightLogical: + case SpvOp::SpvOpShiftLeftLogical: + if (constants[1] != nullptr) { + // When shifting by a value larger than the size of the result, the + // result is undefined. We are setting the undefined behaviour to a + // result of 0. + uint32_t shift_amount = constants[1]->GetU32BitValue(); + if (shift_amount >= 32) { + *result = 0; + return true; + } + } + break; + + // Bitwise operations + case SpvOp::SpvOpBitwiseOr: + for (uint32_t i = 0; i < 2; i++) { + if (constants[i] != nullptr) { + // TODO: Change the mask against a value based on the bit width of the + // instruction result type. This way we can handle say 16-bit values + // as well. + uint32_t mask = constants[i]->GetU32BitValue(); + if (mask == 0xFFFFFFFF) { + *result = 0xFFFFFFFF; + return true; + } + } + } + break; + case SpvOp::SpvOpBitwiseAnd: + for (uint32_t i = 0; i < 2; i++) { + if (constants[i] != nullptr) { + if (constants[i]->IsZero()) { + *result = 0; + return true; + } + } + } + break; + + // Comparison + case SpvOp::SpvOpULessThan: + if (constants[0] != nullptr && + constants[0]->GetU32BitValue() == UINT32_MAX) { + *result = false; + return true; + } + if (constants[1] != nullptr && constants[1]->GetU32BitValue() == 0) { + *result = false; + return true; + } + break; + case SpvOp::SpvOpSLessThan: + if (constants[0] != nullptr && + constants[0]->GetS32BitValue() == INT32_MAX) { + *result = false; + return true; + } + if (constants[1] != nullptr && + constants[1]->GetS32BitValue() == INT32_MIN) { + *result = false; + return true; + } + break; + case SpvOp::SpvOpUGreaterThan: + if (constants[0] != nullptr && constants[0]->IsZero()) { + *result = false; + return true; + } + if (constants[1] != nullptr && + constants[1]->GetU32BitValue() == UINT32_MAX) { + *result = false; + return true; + } + break; + case SpvOp::SpvOpSGreaterThan: + if (constants[0] != nullptr && + constants[0]->GetS32BitValue() == INT32_MIN) { + *result = false; + return true; + } + if (constants[1] != nullptr && + constants[1]->GetS32BitValue() == INT32_MAX) { + *result = false; + return true; + } + break; + case SpvOp::SpvOpULessThanEqual: + if (constants[0] != nullptr && constants[0]->IsZero()) { + *result = true; + return true; + } + if (constants[1] != nullptr && + constants[1]->GetU32BitValue() == UINT32_MAX) { + *result = true; + return true; + } + break; + case SpvOp::SpvOpSLessThanEqual: + if (constants[0] != nullptr && + constants[0]->GetS32BitValue() == INT32_MIN) { + *result = true; + return true; + } + if (constants[1] != nullptr && + constants[1]->GetS32BitValue() == INT32_MAX) { + *result = true; + return true; + } + break; + case SpvOp::SpvOpUGreaterThanEqual: + if (constants[0] != nullptr && + constants[0]->GetU32BitValue() == UINT32_MAX) { + *result = true; + return true; + } + if (constants[1] != nullptr && constants[1]->GetU32BitValue() == 0) { + *result = true; + return true; + } + break; + case SpvOp::SpvOpSGreaterThanEqual: + if (constants[0] != nullptr && + constants[0]->GetS32BitValue() == INT32_MAX) { + *result = true; + return true; + } + if (constants[1] != nullptr && + constants[1]->GetS32BitValue() == INT32_MIN) { + *result = true; + return true; + } + break; + default: + break; + } + return false; +} + +// Returns true if |inst| is a binary operation on two boolean values, and folds +// to a constant boolean value when the ids have been replaced using |id_map|. +// If |inst| can be folded, the result value is returned in |*result|. +bool FoldBinaryBooleanOpToConstant(ir::Instruction* inst, + std::function id_map, + uint32_t* result) { + SpvOp opcode = inst->opcode(); + ir::IRContext* context = inst->context(); + analysis::ConstantManager* const_manger = context->get_constant_mgr(); + + uint32_t ids[2]; + const analysis::BoolConstant* constants[2]; + for (uint32_t i = 0; i < 2; i++) { + const ir::Operand* operand = &inst->GetInOperand(i); + if (operand->type != SPV_OPERAND_TYPE_ID) { + return false; + } + ids[i] = id_map(operand->words[0]); + const analysis::Constant* constant = + const_manger->FindDeclaredConstant(ids[i]); + constants[i] = (constant != nullptr ? constant->AsBoolConstant() : nullptr); + } + + switch (opcode) { + // Logical + case SpvOp::SpvOpLogicalOr: + for (uint32_t i = 0; i < 2; i++) { + if (constants[i] != nullptr) { + if (constants[i]->value()) { + *result = true; + return true; + } + } + } + break; + case SpvOp::SpvOpLogicalAnd: + for (uint32_t i = 0; i < 2; i++) { + if (constants[i] != nullptr) { + if (!constants[i]->value()) { + *result = false; + return true; + } + } + } + break; + + default: + break; + } + return false; +} + +// Returns true if |inst| can be folded to an constant when the ids have been +// substituted using id_map. If it can, the value is returned in |result|. If +// not, |result| is unchanged. It is assumed that not all operands are +// constant. Those cases are handled by |FoldScalar|. +bool FoldIntegerOpToConstant(ir::Instruction* inst, + std::function id_map, + uint32_t* result) { + assert(IsFoldableOpcode(inst->opcode()) && + "Unhandled instruction opcode in FoldScalars"); + switch (inst->NumInOperands()) { + case 2: + return FoldBinaryIntegerOpToConstant(inst, id_map, result) || + FoldBinaryBooleanOpToConstant(inst, id_map, result); + default: + return false; + } +} + +std::vector FoldVectors( + SpvOp opcode, uint32_t num_dims, + const std::vector& operands) { + assert(IsFoldableOpcode(opcode) && + "Unhandled instruction opcode in FoldVectors"); + std::vector result; + for (uint32_t d = 0; d < num_dims; d++) { + std::vector operand_values_for_one_dimension; + for (const auto& operand : operands) { + if (const analysis::VectorConstant* vector_operand = + operand->AsVectorConstant()) { + // Extract the raw value of the scalar component constants + // in 32-bit words here. The reason of not using FoldScalars() here + // is that we do not create temporary null constants as components + // when the vector operand is a NullConstant because Constant creation + // may need extra checks for the validity and that is not manageed in + // here. + if (const analysis::ScalarConstant* scalar_component = + vector_operand->GetComponents().at(d)->AsScalarConstant()) { + const auto& scalar_words = scalar_component->words(); + assert( + scalar_words.size() == 1 && + "Vector components with longer than 32-bit width are not allowed " + "in FoldVectors()"); + operand_values_for_one_dimension.push_back(scalar_words.front()); + } else if (operand->AsNullConstant()) { + operand_values_for_one_dimension.push_back(0u); + } else { + assert(false && + "VectorConst should only has ScalarConst or NullConst as " + "components"); + } + } else if (operand->AsNullConstant()) { + operand_values_for_one_dimension.push_back(0u); + } else { + assert(false && + "FoldVectors() only accepts VectorConst or NullConst type of " + "constant"); + } + } + result.push_back(OperateWords(opcode, operand_values_for_one_dimension)); + } + return result; +} + +bool IsFoldableOpcode(SpvOp opcode) { + // NOTE: Extend to more opcodes as new cases are handled in the folder + // functions. + switch (opcode) { + case SpvOp::SpvOpBitwiseAnd: + case SpvOp::SpvOpBitwiseOr: + case SpvOp::SpvOpBitwiseXor: + case SpvOp::SpvOpIAdd: + case SpvOp::SpvOpIEqual: + case SpvOp::SpvOpIMul: + case SpvOp::SpvOpINotEqual: + case SpvOp::SpvOpISub: + case SpvOp::SpvOpLogicalAnd: + case SpvOp::SpvOpLogicalEqual: + case SpvOp::SpvOpLogicalNot: + case SpvOp::SpvOpLogicalNotEqual: + case SpvOp::SpvOpLogicalOr: + case SpvOp::SpvOpNot: + case SpvOp::SpvOpSDiv: + case SpvOp::SpvOpSelect: + case SpvOp::SpvOpSGreaterThan: + case SpvOp::SpvOpSGreaterThanEqual: + case SpvOp::SpvOpShiftLeftLogical: + case SpvOp::SpvOpShiftRightArithmetic: + case SpvOp::SpvOpShiftRightLogical: + case SpvOp::SpvOpSLessThan: + case SpvOp::SpvOpSLessThanEqual: + case SpvOp::SpvOpSMod: + case SpvOp::SpvOpSNegate: + case SpvOp::SpvOpSRem: + case SpvOp::SpvOpUDiv: + case SpvOp::SpvOpUGreaterThan: + case SpvOp::SpvOpUGreaterThanEqual: + case SpvOp::SpvOpULessThan: + case SpvOp::SpvOpULessThanEqual: + case SpvOp::SpvOpUMod: + return true; + default: + return false; + } +} + +bool IsFoldableConstant(const analysis::Constant* cst) { + // Currently supported constants are 32-bit values or null constants. + if (const analysis::ScalarConstant* scalar = cst->AsScalarConstant()) + return scalar->words().size() == 1; + else + return cst->AsNullConstant() != nullptr; +} + +ir::Instruction* FoldInstructionToConstant( + ir::Instruction* inst, std::function id_map) { + ir::IRContext* context = inst->context(); + analysis::ConstantManager* const_mgr = context->get_constant_mgr(); + + if (!inst->IsFoldableByFoldScalar() && + !GetConstantFoldingRules().HasFoldingRule(inst->opcode())) { + return nullptr; + } + // Collect the values of the constant parameters. + std::vector constants; + bool missing_constants = false; + inst->ForEachInId([&constants, &missing_constants, const_mgr, + &id_map](uint32_t* op_id) { + uint32_t id = id_map(*op_id); + const analysis::Constant* const_op = const_mgr->FindDeclaredConstant(id); + if (!const_op) { + constants.push_back(nullptr); + missing_constants = true; + } else { + constants.push_back(const_op); + } + }); + + if (GetConstantFoldingRules().HasFoldingRule(inst->opcode())) { + const analysis::Constant* folded_const = nullptr; + for (auto rule : + GetConstantFoldingRules().GetRulesForOpcode(inst->opcode())) { + folded_const = rule(inst, constants); + if (folded_const != nullptr) { + ir::Instruction* const_inst = + const_mgr->GetDefiningInstruction(folded_const); + // May be a new instruction that needs to be analysed. + context->UpdateDefUse(const_inst); + return const_inst; + } + } + } + + uint32_t result_val = 0; + bool successful = false; + // If all parameters are constant, fold the instruction to a constant. + if (!missing_constants && inst->IsFoldableByFoldScalar()) { + result_val = FoldScalars(inst->opcode(), constants); + successful = true; + } + + if (!successful && inst->IsFoldableByFoldScalar()) { + successful = FoldIntegerOpToConstant(inst, id_map, &result_val); + } + + if (successful) { + const analysis::Constant* result_const = + const_mgr->GetConstant(const_mgr->GetType(inst), {result_val}); + return const_mgr->GetDefiningInstruction(result_const); + } + return nullptr; +} + +bool IsFoldableType(ir::Instruction* type_inst) { + // Support 32-bit integers. + if (type_inst->opcode() == SpvOpTypeInt) { + return type_inst->GetSingleWordInOperand(0) == 32; + } + // Support booleans. + if (type_inst->opcode() == SpvOpTypeBool) { + return true; + } + // Nothing else yet. + return false; +} + +bool FoldInstruction(ir::Instruction* inst) { + bool modified = false; + ir::Instruction* folded_inst(inst); + while (folded_inst->opcode() != SpvOpCopyObject && + FoldInstructionInternal(&*folded_inst)) { + modified = true; + } + return modified; +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/fold.h b/3rdparty/spirv-tools/source/opt/fold.h new file mode 100644 index 000000000..9c6028dfb --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/fold.h @@ -0,0 +1,98 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_UTIL_FOLD_H_ +#define LIBSPIRV_UTIL_FOLD_H_ + +#include +#include + +#include "const_folding_rules.h" +#include "constants.h" +#include "def_use_manager.h" + +namespace spvtools { +namespace opt { + +// Returns a reference to the ConstnatFoldingRules instance. +const ConstantFoldingRules& GetConstantFoldingRules(); + +// Returns the result of folding a scalar instruction with the given |opcode| +// and |operands|. Each entry in |operands| is a pointer to an +// analysis::Constant instance, which should've been created with the constant +// manager (See IRContext::get_constant_mgr). +// +// It is an error to call this function with an opcode that does not pass the +// IsFoldableOpcode test. If any error occurs during folding, the folder will +// faill with a call to assert. +uint32_t FoldScalars(SpvOp opcode, + const std::vector& operands); + +// Returns the result of performing an operation with the given |opcode| over +// constant vectors with |num_dims| dimensions. Each entry in |operands| is a +// pointer to an analysis::Constant instance, which should've been created with +// the constant manager (See IRContext::get_constant_mgr). +// +// This function iterates through the given vector type constant operands and +// calculates the result for each element of the result vector to return. +// Vectors with longer than 32-bit scalar components are not accepted in this +// function. +// +// It is an error to call this function with an opcode that does not pass the +// IsFoldableOpcode test. If any error occurs during folding, the folder will +// faill with a call to assert. +std::vector FoldVectors( + SpvOp opcode, uint32_t num_dims, + const std::vector& operands); + +// Returns true if |opcode| represents an operation handled by FoldScalars or +// FoldVectors. +bool IsFoldableOpcode(SpvOp opcode); + +// Returns true if |cst| is supported by FoldScalars and FoldVectors. +bool IsFoldableConstant(const analysis::Constant* cst); + +// Returns true if |FoldInstructionToConstant| could fold an instruction whose +// result type is |type_inst|. +bool IsFoldableType(ir::Instruction* type_inst); + +// Tries to fold |inst| to a single constant, when the input ids to |inst| have +// been substituted using |id_map|. Returns a pointer to the OpConstant* +// instruction if successful. If necessary, a new constant instruction is +// created and placed in the global values section. +// +// |id_map| is a function that takes one result id and returns another. It can +// be used for things like CCP where it is known that some ids contain a +// constant, but the instruction itself has not been updated yet. This can map +// those ids to the appropriate constants. +ir::Instruction* FoldInstructionToConstant( + ir::Instruction* inst, std::function id_map); + +// Returns true if |inst| can be folded into a simpler instruction. +// If |inst| can be simplified, |inst| is overwritten with the simplified +// instruction reusing the same result id. +// +// If |inst| is simplified, it is possible that the resulting code in invalid +// because the instruction is in a bad location. Callers of this function have +// to handle the following cases: +// +// 1) An OpPhi becomes and OpCopyObject - If there are OpPhi instruction after +// |inst| in a basic block then this is invalid. The caller must fix this +// up. +bool FoldInstruction(ir::Instruction* inst); + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_UTIL_FOLD_H_ diff --git a/3rdparty/spirv-tools/source/opt/fold_spec_constant_op_and_composite_pass.cpp b/3rdparty/spirv-tools/source/opt/fold_spec_constant_op_and_composite_pass.cpp new file mode 100644 index 000000000..79eaeade2 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/fold_spec_constant_op_and_composite_pass.cpp @@ -0,0 +1,397 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "fold_spec_constant_op_and_composite_pass.h" + +#include +#include +#include + +#include "constants.h" +#include "fold.h" +#include "ir_context.h" +#include "make_unique.h" + +namespace spvtools { +namespace opt { + +Pass::Status FoldSpecConstantOpAndCompositePass::Process( + ir::IRContext* irContext) { + Initialize(irContext); + return ProcessImpl(irContext); +} + +void FoldSpecConstantOpAndCompositePass::Initialize(ir::IRContext* irContext) { + InitializeProcessing(irContext); +} + +Pass::Status FoldSpecConstantOpAndCompositePass::ProcessImpl( + ir::IRContext* irContext) { + bool modified = false; + // Traverse through all the constant defining instructions. For Normal + // Constants whose values are determined and do not depend on OpUndef + // instructions, records their values in two internal maps: id_to_const_val_ + // and const_val_to_id_ so that we can use them to infer the value of Spec + // Constants later. + // For Spec Constants defined with OpSpecConstantComposite instructions, if + // all of their components are Normal Constants, they will be turned into + // Normal Constants too. For Spec Constants defined with OpSpecConstantOp + // instructions, we check if they only depends on Normal Constants and fold + // them when possible. The two maps for Normal Constants: id_to_const_val_ + // and const_val_to_id_ will be updated along the traversal so that the new + // Normal Constants generated from folding can be used to fold following Spec + // Constants. + // This algorithm depends on the SSA property of SPIR-V when + // defining constants. The dependent constants must be defined before the + // dependee constants. So a dependent Spec Constant must be defined and + // will be processed before its dependee Spec Constant. When we encounter + // the dependee Spec Constants, all its dependent constants must have been + // processed and all its dependent Spec Constants should have been folded if + // possible. + ir::Module::inst_iterator next_inst = irContext->types_values_begin(); + for (ir::Module::inst_iterator inst_iter = next_inst; + // Need to re-evaluate the end iterator since we may modify the list of + // instructions in this section of the module as the process goes. + inst_iter != irContext->types_values_end(); inst_iter = next_inst) { + ++next_inst; + ir::Instruction* inst = &*inst_iter; + // Collect constant values of normal constants and process the + // OpSpecConstantOp and OpSpecConstantComposite instructions if possible. + // The constant values will be stored in analysis::Constant instances. + // OpConstantSampler instruction is not collected here because it cannot be + // used in OpSpecConstant{Composite|Op} instructions. + // TODO(qining): If the constant or its type has decoration, we may need + // to skip it. + if (context()->get_constant_mgr()->GetType(inst) && + !context()->get_constant_mgr()->GetType(inst)->decoration_empty()) + continue; + switch (SpvOp opcode = inst->opcode()) { + // Records the values of Normal Constants. + case SpvOp::SpvOpConstantTrue: + case SpvOp::SpvOpConstantFalse: + case SpvOp::SpvOpConstant: + case SpvOp::SpvOpConstantNull: + case SpvOp::SpvOpConstantComposite: + case SpvOp::SpvOpSpecConstantComposite: { + // A Constant instance will be created if the given instruction is a + // Normal Constant whose value(s) are fixed. Note that for a composite + // Spec Constant defined with OpSpecConstantComposite instruction, if + // all of its components are Normal Constants already, the Spec + // Constant will be turned in to a Normal Constant. In that case, a + // Constant instance should also be created successfully and recorded + // in the id_to_const_val_ and const_val_to_id_ mapps. + if (auto const_value = + context()->get_constant_mgr()->GetConstantFromInst(inst)) { + // Need to replace the OpSpecConstantComposite instruction with a + // corresponding OpConstantComposite instruction. + if (opcode == SpvOp::SpvOpSpecConstantComposite) { + inst->SetOpcode(SpvOp::SpvOpConstantComposite); + modified = true; + } + context()->get_constant_mgr()->MapConstantToInst(const_value, inst); + } + break; + } + // For a Spec Constants defined with OpSpecConstantOp instruction, check + // if it only depends on Normal Constants. If so, the Spec Constant will + // be folded. The original Spec Constant defining instruction will be + // replaced by Normal Constant defining instructions, and the new Normal + // Constants will be added to id_to_const_val_ and const_val_to_id_ so + // that we can use the new Normal Constants when folding following Spec + // Constants. + case SpvOp::SpvOpSpecConstantOp: + modified |= ProcessOpSpecConstantOp(&inst_iter); + break; + default: + break; + } + } + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; +} + +bool FoldSpecConstantOpAndCompositePass::ProcessOpSpecConstantOp( + ir::Module::inst_iterator* pos) { + ir::Instruction* inst = &**pos; + ir::Instruction* folded_inst = nullptr; + assert(inst->GetInOperand(0).type == + SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER && + "The first in-operand of OpSpecContantOp instruction must be of " + "SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER type"); + + switch (static_cast(inst->GetSingleWordInOperand(0))) { + case SpvOp::SpvOpCompositeExtract: + folded_inst = DoCompositeExtract(pos); + break; + case SpvOp::SpvOpVectorShuffle: + folded_inst = DoVectorShuffle(pos); + break; + + case SpvOp::SpvOpCompositeInsert: + // Current Glslang does not generate code with OpSpecConstantOp + // CompositeInsert instruction, so this is not implmented so far. + // TODO(qining): Implement CompositeInsert case. + return false; + + default: + // Component-wise operations. + folded_inst = DoComponentWiseOperation(pos); + break; + } + if (!folded_inst) return false; + + // Replace the original constant with the new folded constant, kill the + // original constant. + uint32_t new_id = folded_inst->result_id(); + uint32_t old_id = inst->result_id(); + context()->ReplaceAllUsesWith(old_id, new_id); + context()->KillDef(old_id); + return true; +} + +uint32_t FoldSpecConstantOpAndCompositePass::GetTypeComponent( + uint32_t typeId, uint32_t element) const { + ir::Instruction* type = context()->get_def_use_mgr()->GetDef(typeId); + uint32_t subtype = type->GetTypeComponent(element); + assert(subtype != 0); + + return subtype; +} + +ir::Instruction* FoldSpecConstantOpAndCompositePass::DoCompositeExtract( + ir::Module::inst_iterator* pos) { + ir::Instruction* inst = &**pos; + assert(inst->NumInOperands() - 1 >= 2 && + "OpSpecConstantOp CompositeExtract requires at least two non-type " + "non-opcode operands."); + assert(inst->GetInOperand(1).type == SPV_OPERAND_TYPE_ID && + "The composite operand must have a SPV_OPERAND_TYPE_ID type"); + assert( + inst->GetInOperand(2).type == SPV_OPERAND_TYPE_LITERAL_INTEGER && + "The literal operand must have a SPV_OPERAND_TYPE_LITERAL_INTEGER type"); + + // Note that for OpSpecConstantOp, the second in-operand is the first id + // operand. The first in-operand is the spec opcode. + uint32_t source = inst->GetSingleWordInOperand(1); + uint32_t type = context()->get_def_use_mgr()->GetDef(source)->type_id(); + const analysis::Constant* first_operand_const = + context()->get_constant_mgr()->FindDeclaredConstant(source); + if (!first_operand_const) return nullptr; + + const analysis::Constant* current_const = first_operand_const; + for (uint32_t i = 2; i < inst->NumInOperands(); i++) { + uint32_t literal = inst->GetSingleWordInOperand(i); + type = GetTypeComponent(type, literal); + } + for (uint32_t i = 2; i < inst->NumInOperands(); i++) { + uint32_t literal = inst->GetSingleWordInOperand(i); + if (const analysis::CompositeConstant* composite_const = + current_const->AsCompositeConstant()) { + // Case 1: current constant is a non-null composite type constant. + assert(literal < composite_const->GetComponents().size() && + "Literal index out of bound of the composite constant"); + current_const = composite_const->GetComponents().at(literal); + } else if (current_const->AsNullConstant()) { + // Case 2: current constant is a constant created with OpConstantNull. + // Because components of a NullConstant are always NullConstants, we can + // return early with a NullConstant in the result type. + return context()->get_constant_mgr()->BuildInstructionAndAddToModule( + context()->get_constant_mgr()->GetConstant( + context()->get_constant_mgr()->GetType(inst), {}), + pos, type); + } else { + // Dereferencing a non-composite constant. Invalid case. + return nullptr; + } + } + return context()->get_constant_mgr()->BuildInstructionAndAddToModule( + current_const, pos); +} + +ir::Instruction* FoldSpecConstantOpAndCompositePass::DoVectorShuffle( + ir::Module::inst_iterator* pos) { + ir::Instruction* inst = &**pos; + analysis::Vector* result_vec_type = + context()->get_constant_mgr()->GetType(inst)->AsVector(); + assert(inst->NumInOperands() - 1 > 2 && + "OpSpecConstantOp DoVectorShuffle instruction requires more than 2 " + "operands (2 vector ids and at least one literal operand"); + assert(result_vec_type && + "The result of VectorShuffle must be of type vector"); + + // A temporary null constants that can be used as the components of the result + // vector. This is needed when any one of the vector operands are null + // constant. + const analysis::Constant* null_component_constants = nullptr; + + // Get a concatenated vector of scalar constants. The vector should be built + // with the components from the first and the second operand of VectorShuffle. + std::vector concatenated_components; + // Note that for OpSpecConstantOp, the second in-operand is the first id + // operand. The first in-operand is the spec opcode. + for (uint32_t i : {1, 2}) { + assert(inst->GetInOperand(i).type == SPV_OPERAND_TYPE_ID && + "The vector operand must have a SPV_OPERAND_TYPE_ID type"); + uint32_t operand_id = inst->GetSingleWordInOperand(i); + auto operand_const = + context()->get_constant_mgr()->FindDeclaredConstant(operand_id); + if (!operand_const) return nullptr; + const analysis::Type* operand_type = operand_const->type(); + assert(operand_type->AsVector() && + "The first two operand of VectorShuffle must be of vector type"); + if (auto vec_const = operand_const->AsVectorConstant()) { + // case 1: current operand is a non-null vector constant. + concatenated_components.insert(concatenated_components.end(), + vec_const->GetComponents().begin(), + vec_const->GetComponents().end()); + } else if (operand_const->AsNullConstant()) { + // case 2: current operand is a null vector constant. Create a temporary + // null scalar constant as the component. + if (!null_component_constants) { + const analysis::Type* component_type = + operand_type->AsVector()->element_type(); + null_component_constants = + context()->get_constant_mgr()->GetConstant(component_type, {}); + } + // Append the null scalar consts to the concatenated components + // vector. + concatenated_components.insert(concatenated_components.end(), + operand_type->AsVector()->element_count(), + null_component_constants); + } else { + // no other valid cases + return nullptr; + } + } + // Create null component constants if there are any. The component constants + // must be added to the module before the dependee composite constants to + // satisfy SSA def-use dominance. + if (null_component_constants) { + context()->get_constant_mgr()->BuildInstructionAndAddToModule( + null_component_constants, pos); + } + // Create the new vector constant with the selected components. + std::vector selected_components; + for (uint32_t i = 3; i < inst->NumInOperands(); i++) { + assert(inst->GetInOperand(i).type == SPV_OPERAND_TYPE_LITERAL_INTEGER && + "The literal operand must of type SPV_OPERAND_TYPE_LITERAL_INTEGER"); + uint32_t literal = inst->GetSingleWordInOperand(i); + assert(literal < concatenated_components.size() && + "Literal index out of bound of the concatenated vector"); + selected_components.push_back(concatenated_components[literal]); + } + auto new_vec_const = + new analysis::VectorConstant(result_vec_type, selected_components); + auto reg_vec_const = + context()->get_constant_mgr()->RegisterConstant(new_vec_const); + if (reg_vec_const != new_vec_const) delete new_vec_const; + return context()->get_constant_mgr()->BuildInstructionAndAddToModule( + reg_vec_const, pos); +} + +namespace { +// A helper function to check the type for component wise operations. Returns +// true if the type: +// 1) is bool type; +// 2) is 32-bit int type; +// 3) is vector of bool type; +// 4) is vector of 32-bit integer type. +// Otherwise returns false. +bool IsValidTypeForComponentWiseOperation(const analysis::Type* type) { + if (type->AsBool()) { + return true; + } else if (auto* it = type->AsInteger()) { + if (it->width() == 32) return true; + } else if (auto* vt = type->AsVector()) { + if (vt->element_type()->AsBool()) + return true; + else if (auto* vit = vt->element_type()->AsInteger()) { + if (vit->width() == 32) return true; + } + } + return false; +} +} // namespace + +ir::Instruction* FoldSpecConstantOpAndCompositePass::DoComponentWiseOperation( + ir::Module::inst_iterator* pos) { + const ir::Instruction* inst = &**pos; + const analysis::Type* result_type = + context()->get_constant_mgr()->GetType(inst); + SpvOp spec_opcode = static_cast(inst->GetSingleWordInOperand(0)); + // Check and collect operands. + std::vector operands; + + if (!std::all_of( + inst->cbegin(), inst->cend(), + [&operands, this](const ir::Operand& o) { + // skip the operands that is not an id. + if (o.type != spv_operand_type_t::SPV_OPERAND_TYPE_ID) return true; + uint32_t id = o.words.front(); + if (auto c = + context()->get_constant_mgr()->FindDeclaredConstant(id)) { + if (IsValidTypeForComponentWiseOperation(c->type())) { + operands.push_back(c); + return true; + } + } + return false; + })) + return nullptr; + + if (result_type->AsInteger() || result_type->AsBool()) { + // Scalar operation + uint32_t result_val = FoldScalars(spec_opcode, operands); + auto result_const = + context()->get_constant_mgr()->GetConstant(result_type, {result_val}); + return context()->get_constant_mgr()->BuildInstructionAndAddToModule( + result_const, pos); + } else if (result_type->AsVector()) { + // Vector operation + const analysis::Type* element_type = + result_type->AsVector()->element_type(); + uint32_t num_dims = result_type->AsVector()->element_count(); + std::vector result_vec = + FoldVectors(spec_opcode, num_dims, operands); + std::vector result_vector_components; + for (uint32_t r : result_vec) { + if (auto rc = + context()->get_constant_mgr()->GetConstant(element_type, {r})) { + result_vector_components.push_back(rc); + if (!context()->get_constant_mgr()->BuildInstructionAndAddToModule( + rc, pos)) { + assert(false && + "Failed to build and insert constant declaring instruction " + "for the given vector component constant"); + } + } else { + assert(false && "Failed to create constants with 32-bit word"); + } + } + auto new_vec_const = new analysis::VectorConstant(result_type->AsVector(), + result_vector_components); + auto reg_vec_const = + context()->get_constant_mgr()->RegisterConstant(new_vec_const); + if (reg_vec_const != new_vec_const) delete new_vec_const; + return context()->get_constant_mgr()->BuildInstructionAndAddToModule( + reg_vec_const, pos); + } else { + // Cannot process invalid component wise operation. The result of component + // wise operation must be of integer or bool scalar or vector of + // integer/bool type. + return nullptr; + } +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/fold_spec_constant_op_and_composite_pass.h b/3rdparty/spirv-tools/source/opt/fold_spec_constant_op_and_composite_pass.h new file mode 100644 index 000000000..5f901eeb1 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/fold_spec_constant_op_and_composite_pass.h @@ -0,0 +1,92 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_FOLD_SPEC_CONSTANT_OP_AND_COMPOSITE_PASS_H_ +#define LIBSPIRV_OPT_FOLD_SPEC_CONSTANT_OP_AND_COMPOSITE_PASS_H_ + +#include +#include +#include + +#include "constants.h" +#include "def_use_manager.h" +#include "ir_context.h" +#include "module.h" +#include "pass.h" +#include "type_manager.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class FoldSpecConstantOpAndCompositePass : public Pass { + public: + FoldSpecConstantOpAndCompositePass() = default; + + const char* name() const override { return "fold-spec-const-op-composite"; } + + Status Process(ir::IRContext* irContext) override; + + private: + // Initializes the type manager, def-use manager and get the maximal id used + // in the module. + void Initialize(ir::IRContext* irContext); + + // The real entry of processing. Iterates through the types-constants-globals + // section of the given module, finds the Spec Constants defined with + // OpSpecConstantOp and OpSpecConstantComposite instructions. If the result + // value of those spec constants can be folded, fold them to their + // corresponding normal constants. + Status ProcessImpl(ir::IRContext* irContext); + + // Processes the OpSpecConstantOp instruction pointed by the given + // instruction iterator, folds it to normal constants if possible. Returns + // true if the spec constant is folded to normal constants. New instructions + // will be inserted before the OpSpecConstantOp instruction pointed by the + // instruction iterator. The instruction iterator, which is passed by + // pointer, will still point to the original OpSpecConstantOp instruction. If + // folding is done successfully, the original OpSpecConstantOp instruction + // will be changed to Nop and new folded instruction will be inserted before + // it. + bool ProcessOpSpecConstantOp(ir::Module::inst_iterator* pos); + + // Try to fold the OpSpecConstantOp CompositeExtract instruction pointed by + // the given instruction iterator to a normal constant defining instruction. + // Returns the pointer to the new constant defining instruction if succeeded. + // Otherwise returns nullptr. + ir::Instruction* DoCompositeExtract(ir::Module::inst_iterator* inst_iter_ptr); + + // Try to fold the OpSpecConstantOp VectorShuffle instruction pointed by the + // given instruction iterator to a normal constant defining instruction. + // Returns the pointer to the new constant defining instruction if succeeded. + // Otherwise return nullptr. + ir::Instruction* DoVectorShuffle(ir::Module::inst_iterator* inst_iter_ptr); + + // Try to fold the OpSpecConstantOp instruction + // pointed by the given instruction iterator to a normal constant defining + // instruction. Returns the pointer to the new constant defining instruction + // if succeeded, otherwise return nullptr. + ir::Instruction* DoComponentWiseOperation( + ir::Module::inst_iterator* inst_iter_ptr); + + // Returns the |element|'th subtype of |type|. + // + // |type| must be a composite type. + uint32_t GetTypeComponent(uint32_t type, uint32_t element) const; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_FOLD_SPEC_CONSTANT_OP_AND_COMPOSITE_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/folding_rules.cpp b/3rdparty/spirv-tools/source/opt/folding_rules.cpp new file mode 100644 index 000000000..8e6fba115 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/folding_rules.cpp @@ -0,0 +1,1882 @@ +// Copyright (c) 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "folding_rules.h" +#include "latest_version_glsl_std_450_header.h" + +namespace spvtools { +namespace opt { + +namespace { +const uint32_t kExtractCompositeIdInIdx = 0; +const uint32_t kInsertObjectIdInIdx = 0; +const uint32_t kInsertCompositeIdInIdx = 1; +const uint32_t kExtInstSetIdInIdx = 0; +const uint32_t kExtInstInstructionInIdx = 1; +const uint32_t kFMixXIdInIdx = 2; +const uint32_t kFMixYIdInIdx = 3; + +// Returns the element width of |type|. +uint32_t ElementWidth(const analysis::Type* type) { + if (const analysis::Vector* vec_type = type->AsVector()) { + return ElementWidth(vec_type->element_type()); + } else if (const analysis::Float* float_type = type->AsFloat()) { + return float_type->width(); + } else { + assert(type->AsInteger()); + return type->AsInteger()->width(); + } +} + +// Returns true if |type| is Float or a vector of Float. +bool HasFloatingPoint(const analysis::Type* type) { + if (type->AsFloat()) { + return true; + } else if (const analysis::Vector* vec_type = type->AsVector()) { + return vec_type->element_type()->AsFloat() != nullptr; + } + + return false; +} + +// Returns false if |val| is NaN, infinite or subnormal. +template +bool IsValidResult(T val) { + int classified = std::fpclassify(val); + switch (classified) { + case FP_NAN: + case FP_INFINITE: + case FP_SUBNORMAL: + return false; + default: + return true; + } +} + +const analysis::Constant* ConstInput( + const std::vector& constants) { + return constants[0] ? constants[0] : constants[1]; +} + +ir::Instruction* NonConstInput(ir::IRContext* context, + const analysis::Constant* c, + ir::Instruction* inst) { + uint32_t in_op = c ? 1u : 0u; + return context->get_def_use_mgr()->GetDef( + inst->GetSingleWordInOperand(in_op)); +} + +// Returns the negation of |c|. |c| must be a 32 or 64 bit floating point +// constant. +uint32_t NegateFloatingPointConstant(analysis::ConstantManager* const_mgr, + const analysis::Constant* c) { + assert(c); + assert(c->type()->AsFloat()); + uint32_t width = c->type()->AsFloat()->width(); + assert(width == 32 || width == 64); + std::vector words; + if (width == 64) { + spvutils::FloatProxy result(c->GetDouble() * -1.0); + words = result.GetWords(); + } else { + spvutils::FloatProxy result(c->GetFloat() * -1.0f); + words = result.GetWords(); + } + + const analysis::Constant* negated_const = + const_mgr->GetConstant(c->type(), std::move(words)); + return const_mgr->GetDefiningInstruction(negated_const)->result_id(); +} + +std::vector ExtractInts(uint64_t val) { + std::vector words; + words.push_back(static_cast(val)); + words.push_back(static_cast(val >> 32)); + return words; +} + +// Negates the integer constant |c|. Returns the id of the defining instruction. +uint32_t NegateIntegerConstant(analysis::ConstantManager* const_mgr, + const analysis::Constant* c) { + assert(c); + assert(c->type()->AsInteger()); + uint32_t width = c->type()->AsInteger()->width(); + assert(width == 32 || width == 64); + std::vector words; + if (width == 64) { + uint64_t uval = static_cast(0 - c->GetU64()); + words = ExtractInts(uval); + } else { + words.push_back(static_cast(0 - c->GetU32())); + } + + const analysis::Constant* negated_const = + const_mgr->GetConstant(c->type(), std::move(words)); + return const_mgr->GetDefiningInstruction(negated_const)->result_id(); +} + +// Negates the vector constant |c|. Returns the id of the defining instruction. +uint32_t NegateVectorConstant(analysis::ConstantManager* const_mgr, + const analysis::Constant* c) { + assert(const_mgr && c); + assert(c->type()->AsVector()); + if (c->AsNullConstant()) { + // 0.0 vs -0.0 shouldn't matter. + return const_mgr->GetDefiningInstruction(c)->result_id(); + } else { + const analysis::Type* component_type = + c->AsVectorConstant()->component_type(); + std::vector words; + for (auto& comp : c->AsVectorConstant()->GetComponents()) { + if (component_type->AsFloat()) { + words.push_back(NegateFloatingPointConstant(const_mgr, comp)); + } else { + assert(component_type->AsInteger()); + words.push_back(NegateIntegerConstant(const_mgr, comp)); + } + } + + const analysis::Constant* negated_const = + const_mgr->GetConstant(c->type(), std::move(words)); + return const_mgr->GetDefiningInstruction(negated_const)->result_id(); + } +} + +// Negates |c|. Returns the id of the defining instruction. +uint32_t NegateConstant(analysis::ConstantManager* const_mgr, + const analysis::Constant* c) { + if (c->type()->AsVector()) { + return NegateVectorConstant(const_mgr, c); + } else if (c->type()->AsFloat()) { + return NegateFloatingPointConstant(const_mgr, c); + } else { + assert(c->type()->AsInteger()); + return NegateIntegerConstant(const_mgr, c); + } +} + +// Takes the reciprocal of |c|. |c|'s type must be Float or a vector of Float. +// Returns 0 if the reciprocal is NaN, infinite or subnormal. +uint32_t Reciprocal(analysis::ConstantManager* const_mgr, + const analysis::Constant* c) { + assert(const_mgr && c); + assert(c->type()->AsFloat()); + + uint32_t width = c->type()->AsFloat()->width(); + assert(width == 32 || width == 64); + std::vector words; + if (width == 64) { + spvutils::FloatProxy result(1.0 / c->GetDouble()); + if (!IsValidResult(result.getAsFloat())) return 0; + words = result.GetWords(); + } else { + spvutils::FloatProxy result(1.0f / c->GetFloat()); + if (!IsValidResult(result.getAsFloat())) return 0; + words = result.GetWords(); + } + + const analysis::Constant* negated_const = + const_mgr->GetConstant(c->type(), std::move(words)); + return const_mgr->GetDefiningInstruction(negated_const)->result_id(); +} + +// Replaces fdiv where second operand is constant with fmul. +FoldingRule ReciprocalFDiv() { + return [](ir::Instruction* inst, + const std::vector& constants) { + assert(inst->opcode() == SpvOpFDiv); + ir::IRContext* context = inst->context(); + analysis::ConstantManager* const_mgr = context->get_constant_mgr(); + const analysis::Type* type = + context->get_type_mgr()->GetType(inst->type_id()); + if (!inst->IsFloatingPointFoldingAllowed()) return false; + + uint32_t width = ElementWidth(type); + if (width != 32 && width != 64) return false; + + if (constants[1] != nullptr) { + uint32_t id = 0; + if (const analysis::VectorConstant* vector_const = + constants[1]->AsVectorConstant()) { + std::vector neg_ids; + for (auto& comp : vector_const->GetComponents()) { + id = Reciprocal(const_mgr, comp); + if (id == 0) return false; + neg_ids.push_back(id); + } + const analysis::Constant* negated_const = + const_mgr->GetConstant(constants[1]->type(), std::move(neg_ids)); + id = const_mgr->GetDefiningInstruction(negated_const)->result_id(); + } else if (constants[1]->AsFloatConstant()) { + id = Reciprocal(const_mgr, constants[1]); + if (id == 0) return false; + } else { + // Don't fold a null constant. + return false; + } + inst->SetOpcode(SpvOpFMul); + inst->SetInOperands( + {{SPV_OPERAND_TYPE_ID, {inst->GetSingleWordInOperand(0u)}}, + {SPV_OPERAND_TYPE_ID, {id}}}); + return true; + } + + return false; + }; +} + +// Elides consecutive negate instructions. +FoldingRule MergeNegateArithmetic() { + return [](ir::Instruction* inst, + const std::vector& constants) { + assert(inst->opcode() == SpvOpFNegate || inst->opcode() == SpvOpSNegate); + (void)constants; + ir::IRContext* context = inst->context(); + const analysis::Type* type = + context->get_type_mgr()->GetType(inst->type_id()); + if (HasFloatingPoint(type) && !inst->IsFloatingPointFoldingAllowed()) + return false; + + ir::Instruction* op_inst = + context->get_def_use_mgr()->GetDef(inst->GetSingleWordInOperand(0u)); + if (HasFloatingPoint(type) && !op_inst->IsFloatingPointFoldingAllowed()) + return false; + + if (op_inst->opcode() == inst->opcode()) { + // Elide negates. + inst->SetOpcode(SpvOpCopyObject); + inst->SetInOperands( + {{SPV_OPERAND_TYPE_ID, {op_inst->GetSingleWordInOperand(0u)}}}); + return true; + } + + return false; + }; +} + +// Merges negate into a mul or div operation if that operation contains a +// constant operand. +// Cases: +// -(x * 2) = x * -2 +// -(2 * x) = x * -2 +// -(x / 2) = x / -2 +// -(2 / x) = -2 / x +FoldingRule MergeNegateMulDivArithmetic() { + return [](ir::Instruction* inst, + const std::vector& constants) { + assert(inst->opcode() == SpvOpFNegate || inst->opcode() == SpvOpSNegate); + (void)constants; + ir::IRContext* context = inst->context(); + analysis::ConstantManager* const_mgr = context->get_constant_mgr(); + const analysis::Type* type = + context->get_type_mgr()->GetType(inst->type_id()); + if (HasFloatingPoint(type) && !inst->IsFloatingPointFoldingAllowed()) + return false; + + ir::Instruction* op_inst = + context->get_def_use_mgr()->GetDef(inst->GetSingleWordInOperand(0u)); + if (HasFloatingPoint(type) && !op_inst->IsFloatingPointFoldingAllowed()) + return false; + + uint32_t width = ElementWidth(type); + if (width != 32 && width != 64) return false; + + SpvOp opcode = op_inst->opcode(); + if (opcode == SpvOpFMul || opcode == SpvOpFDiv || opcode == SpvOpIMul || + opcode == SpvOpSDiv || opcode == SpvOpUDiv) { + std::vector op_constants = + const_mgr->GetOperandConstants(op_inst); + // Merge negate into mul or div if one operand is constant. + if (op_constants[0] || op_constants[1]) { + bool zero_is_variable = op_constants[0] == nullptr; + const analysis::Constant* c = ConstInput(op_constants); + uint32_t neg_id = NegateConstant(const_mgr, c); + uint32_t non_const_id = zero_is_variable + ? op_inst->GetSingleWordInOperand(0u) + : op_inst->GetSingleWordInOperand(1u); + // Change this instruction to a mul/div. + inst->SetOpcode(op_inst->opcode()); + if (opcode == SpvOpFDiv || opcode == SpvOpUDiv || opcode == SpvOpSDiv) { + uint32_t op0 = zero_is_variable ? non_const_id : neg_id; + uint32_t op1 = zero_is_variable ? neg_id : non_const_id; + inst->SetInOperands( + {{SPV_OPERAND_TYPE_ID, {op0}}, {SPV_OPERAND_TYPE_ID, {op1}}}); + } else { + inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {non_const_id}}, + {SPV_OPERAND_TYPE_ID, {neg_id}}}); + } + return true; + } + } + + return false; + }; +} + +// Merges negate into a add or sub operation if that operation contains a +// constant operand. +// Cases: +// -(x + 2) = -2 - x +// -(2 + x) = -2 - x +// -(x - 2) = 2 - x +// -(2 - x) = x - 2 +FoldingRule MergeNegateAddSubArithmetic() { + return [](ir::Instruction* inst, + const std::vector& constants) { + assert(inst->opcode() == SpvOpFNegate || inst->opcode() == SpvOpSNegate); + (void)constants; + ir::IRContext* context = inst->context(); + analysis::ConstantManager* const_mgr = context->get_constant_mgr(); + const analysis::Type* type = + context->get_type_mgr()->GetType(inst->type_id()); + if (HasFloatingPoint(type) && !inst->IsFloatingPointFoldingAllowed()) + return false; + + ir::Instruction* op_inst = + context->get_def_use_mgr()->GetDef(inst->GetSingleWordInOperand(0u)); + if (HasFloatingPoint(type) && !op_inst->IsFloatingPointFoldingAllowed()) + return false; + + uint32_t width = ElementWidth(type); + if (width != 32 && width != 64) return false; + + if (op_inst->opcode() == SpvOpFAdd || op_inst->opcode() == SpvOpFSub || + op_inst->opcode() == SpvOpIAdd || op_inst->opcode() == SpvOpISub) { + std::vector op_constants = + const_mgr->GetOperandConstants(op_inst); + if (op_constants[0] || op_constants[1]) { + bool zero_is_variable = op_constants[0] == nullptr; + bool is_add = (op_inst->opcode() == SpvOpFAdd) || + (op_inst->opcode() == SpvOpIAdd); + bool swap_operands = !is_add || zero_is_variable; + bool negate_const = is_add; + const analysis::Constant* c = ConstInput(op_constants); + uint32_t const_id = 0; + if (negate_const) { + const_id = NegateConstant(const_mgr, c); + } else { + const_id = zero_is_variable ? op_inst->GetSingleWordInOperand(1u) + : op_inst->GetSingleWordInOperand(0u); + } + + // Swap operands if necessary and make the instruction a subtraction. + uint32_t op0 = + zero_is_variable ? op_inst->GetSingleWordInOperand(0u) : const_id; + uint32_t op1 = + zero_is_variable ? const_id : op_inst->GetSingleWordInOperand(1u); + if (swap_operands) std::swap(op0, op1); + inst->SetOpcode(HasFloatingPoint(type) ? SpvOpFSub : SpvOpISub); + inst->SetInOperands( + {{SPV_OPERAND_TYPE_ID, {op0}}, {SPV_OPERAND_TYPE_ID, {op1}}}); + return true; + } + } + + return false; + }; +} + +// Returns true if |c| has a zero element. +bool HasZero(const analysis::Constant* c) { + if (c->AsNullConstant()) { + return true; + } + if (const analysis::VectorConstant* vec_const = c->AsVectorConstant()) { + for (auto& comp : vec_const->GetComponents()) + if (HasZero(comp)) return true; + } else { + assert(c->AsScalarConstant()); + return c->AsScalarConstant()->IsZero(); + } + + return false; +} + +// Performs |input1| |opcode| |input2| and returns the merged constant result +// id. Returns 0 if the result is not a valid value. The input types must be +// Float. +uint32_t PerformFloatingPointOperation(analysis::ConstantManager* const_mgr, + SpvOp opcode, + const analysis::Constant* input1, + const analysis::Constant* input2) { + const analysis::Type* type = input1->type(); + assert(type->AsFloat()); + uint32_t width = type->AsFloat()->width(); + assert(width == 32 || width == 64); + std::vector words; +#define FOLD_OP(op) \ + if (width == 64) { \ + spvutils::FloatProxy val = \ + input1->GetDouble() op input2->GetDouble(); \ + double dval = val.getAsFloat(); \ + if (!IsValidResult(dval)) return 0; \ + words = val.GetWords(); \ + } else { \ + spvutils::FloatProxy val = \ + input1->GetFloat() op input2->GetFloat(); \ + float fval = val.getAsFloat(); \ + if (!IsValidResult(fval)) return 0; \ + words = val.GetWords(); \ + } + switch (opcode) { + case SpvOpFMul: + FOLD_OP(*); + break; + case SpvOpFDiv: + if (HasZero(input2)) return 0; + FOLD_OP(/); + break; + case SpvOpFAdd: + FOLD_OP(+); + break; + case SpvOpFSub: + FOLD_OP(-); + break; + default: + assert(false && "Unexpected operation"); + break; + } +#undef FOLD_OP + const analysis::Constant* merged_const = const_mgr->GetConstant(type, words); + return const_mgr->GetDefiningInstruction(merged_const)->result_id(); +} + +// Performs |input1| |opcode| |input2| and returns the merged constant result +// id. Returns 0 if the result is not a valid value. The input types must be +// Integers. +uint32_t PerformIntegerOperation(analysis::ConstantManager* const_mgr, + SpvOp opcode, const analysis::Constant* input1, + const analysis::Constant* input2) { + assert(input1->type()->AsInteger()); + const analysis::Integer* type = input1->type()->AsInteger(); + uint32_t width = type->AsInteger()->width(); + assert(width == 32 || width == 64); + std::vector words; +#define FOLD_OP(op) \ + if (width == 64) { \ + if (type->IsSigned()) { \ + int64_t val = input1->GetS64() op input2->GetS64(); \ + words = ExtractInts(static_cast(val)); \ + } else { \ + uint64_t val = input1->GetU64() op input2->GetU64(); \ + words = ExtractInts(val); \ + } \ + } else { \ + if (type->IsSigned()) { \ + int32_t val = input1->GetS32() op input2->GetS32(); \ + words.push_back(static_cast(val)); \ + } else { \ + uint32_t val = input1->GetU32() op input2->GetU32(); \ + words.push_back(val); \ + } \ + } + switch (opcode) { + case SpvOpIMul: + FOLD_OP(*); + break; + case SpvOpSDiv: + case SpvOpUDiv: + assert(false && "Should not merge integer division"); + break; + case SpvOpIAdd: + FOLD_OP(+); + break; + case SpvOpISub: + FOLD_OP(-); + break; + default: + assert(false && "Unexpected operation"); + break; + } +#undef FOLD_OP + const analysis::Constant* merged_const = const_mgr->GetConstant(type, words); + return const_mgr->GetDefiningInstruction(merged_const)->result_id(); +} + +// Performs |input1| |opcode| |input2| and returns the merged constant result +// id. Returns 0 if the result is not a valid value. The input types must be +// Integers, Floats or Vectors of such. +uint32_t PerformOperation(analysis::ConstantManager* const_mgr, SpvOp opcode, + const analysis::Constant* input1, + const analysis::Constant* input2) { + assert(input1 && input2); + assert(input1->type() == input2->type()); + const analysis::Type* type = input1->type(); + std::vector words; + if (const analysis::Vector* vector_type = type->AsVector()) { + const analysis::Type* ele_type = vector_type->element_type(); + for (uint32_t i = 0; i != vector_type->element_count(); ++i) { + uint32_t id = 0; + + const analysis::Constant* input1_comp = nullptr; + if (const analysis::VectorConstant* input1_vector = + input1->AsVectorConstant()) { + input1_comp = input1_vector->GetComponents()[i]; + } else { + assert(input1->AsNullConstant()); + input1_comp = const_mgr->GetConstant(ele_type, {}); + } + + const analysis::Constant* input2_comp = nullptr; + if (const analysis::VectorConstant* input2_vector = + input2->AsVectorConstant()) { + input2_comp = input2_vector->GetComponents()[i]; + } else { + assert(input2->AsNullConstant()); + input2_comp = const_mgr->GetConstant(ele_type, {}); + } + + if (ele_type->AsFloat()) { + id = PerformFloatingPointOperation(const_mgr, opcode, input1_comp, + input2_comp); + } else { + assert(ele_type->AsInteger()); + id = PerformIntegerOperation(const_mgr, opcode, input1_comp, + input2_comp); + } + if (id == 0) return 0; + words.push_back(id); + } + const analysis::Constant* merged_const = + const_mgr->GetConstant(type, words); + return const_mgr->GetDefiningInstruction(merged_const)->result_id(); + } else if (type->AsFloat()) { + return PerformFloatingPointOperation(const_mgr, opcode, input1, input2); + } else { + assert(type->AsInteger()); + return PerformIntegerOperation(const_mgr, opcode, input1, input2); + } +} + +// Merges consecutive multiplies where each contains one constant operand. +// Cases: +// 2 * (x * 2) = x * 4 +// 2 * (2 * x) = x * 4 +// (x * 2) * 2 = x * 4 +// (2 * x) * 2 = x * 4 +FoldingRule MergeMulMulArithmetic() { + return [](ir::Instruction* inst, + const std::vector& constants) { + assert(inst->opcode() == SpvOpFMul || inst->opcode() == SpvOpIMul); + ir::IRContext* context = inst->context(); + analysis::ConstantManager* const_mgr = context->get_constant_mgr(); + const analysis::Type* type = + context->get_type_mgr()->GetType(inst->type_id()); + if (HasFloatingPoint(type) && !inst->IsFloatingPointFoldingAllowed()) + return false; + + uint32_t width = ElementWidth(type); + if (width != 32 && width != 64) return false; + + // Determine the constant input and the variable input in |inst|. + const analysis::Constant* const_input1 = ConstInput(constants); + if (!const_input1) return false; + ir::Instruction* other_inst = NonConstInput(context, constants[0], inst); + if (HasFloatingPoint(type) && !other_inst->IsFloatingPointFoldingAllowed()) + return false; + + if (other_inst->opcode() == inst->opcode()) { + std::vector other_constants = + const_mgr->GetOperandConstants(other_inst); + const analysis::Constant* const_input2 = ConstInput(other_constants); + if (!const_input2) return false; + + bool other_first_is_variable = other_constants[0] == nullptr; + uint32_t merged_id = PerformOperation(const_mgr, inst->opcode(), + const_input1, const_input2); + if (merged_id == 0) return false; + + uint32_t non_const_id = other_first_is_variable + ? other_inst->GetSingleWordInOperand(0u) + : other_inst->GetSingleWordInOperand(1u); + inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {non_const_id}}, + {SPV_OPERAND_TYPE_ID, {merged_id}}}); + return true; + } + + return false; + }; +} + +// Merges divides into subsequent multiplies if each instruction contains one +// constant operand. Does not support integer operations. +// Cases: +// 2 * (x / 2) = x * 1 +// 2 * (2 / x) = 4 / x +// (x / 2) * 2 = x * 1 +// (2 / x) * 2 = 4 / x +FoldingRule MergeMulDivArithmetic() { + return [](ir::Instruction* inst, + const std::vector& constants) { + assert(inst->opcode() == SpvOpFMul); + ir::IRContext* context = inst->context(); + analysis::ConstantManager* const_mgr = context->get_constant_mgr(); + const analysis::Type* type = + context->get_type_mgr()->GetType(inst->type_id()); + if (!inst->IsFloatingPointFoldingAllowed()) return false; + + uint32_t width = ElementWidth(type); + if (width != 32 && width != 64) return false; + + const analysis::Constant* const_input1 = ConstInput(constants); + if (!const_input1) return false; + ir::Instruction* other_inst = NonConstInput(context, constants[0], inst); + if (!other_inst->IsFloatingPointFoldingAllowed()) return false; + + if (other_inst->opcode() == SpvOpFDiv) { + std::vector other_constants = + const_mgr->GetOperandConstants(other_inst); + const analysis::Constant* const_input2 = ConstInput(other_constants); + if (!const_input2 || HasZero(const_input2)) return false; + + bool other_first_is_variable = other_constants[0] == nullptr; + // If the variable value is the second operand of the divide, multiply + // the constants together. Otherwise divide the constants. + uint32_t merged_id = PerformOperation( + const_mgr, + other_first_is_variable ? other_inst->opcode() : inst->opcode(), + const_input1, const_input2); + if (merged_id == 0) return false; + + uint32_t non_const_id = other_first_is_variable + ? other_inst->GetSingleWordInOperand(0u) + : other_inst->GetSingleWordInOperand(1u); + + // If the variable value is on the second operand of the div, then this + // operation is a div. Otherwise it should be a multiply. + inst->SetOpcode(other_first_is_variable ? inst->opcode() + : other_inst->opcode()); + if (other_first_is_variable) { + inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {non_const_id}}, + {SPV_OPERAND_TYPE_ID, {merged_id}}}); + } else { + inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {merged_id}}, + {SPV_OPERAND_TYPE_ID, {non_const_id}}}); + } + return true; + } + + return false; + }; +} + +// Merges multiply of constant and negation. +// Cases: +// (-x) * 2 = x * -2 +// 2 * (-x) = x * -2 +FoldingRule MergeMulNegateArithmetic() { + return [](ir::Instruction* inst, + const std::vector& constants) { + assert(inst->opcode() == SpvOpFMul || inst->opcode() == SpvOpIMul); + ir::IRContext* context = inst->context(); + analysis::ConstantManager* const_mgr = context->get_constant_mgr(); + const analysis::Type* type = + context->get_type_mgr()->GetType(inst->type_id()); + bool uses_float = HasFloatingPoint(type); + if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false; + + uint32_t width = ElementWidth(type); + if (width != 32 && width != 64) return false; + + const analysis::Constant* const_input1 = ConstInput(constants); + if (!const_input1) return false; + ir::Instruction* other_inst = NonConstInput(context, constants[0], inst); + if (uses_float && !other_inst->IsFloatingPointFoldingAllowed()) + return false; + + if (other_inst->opcode() == SpvOpFNegate || + other_inst->opcode() == SpvOpSNegate) { + uint32_t neg_id = NegateConstant(const_mgr, const_input1); + + inst->SetInOperands( + {{SPV_OPERAND_TYPE_ID, {other_inst->GetSingleWordInOperand(0u)}}, + {SPV_OPERAND_TYPE_ID, {neg_id}}}); + return true; + } + + return false; + }; +} + +// Merges consecutive divides if each instruction contains one constant operand. +// Does not support integer division. +// Cases: +// 2 / (x / 2) = 4 / x +// 4 / (2 / x) = 2 * x +// (4 / x) / 2 = 2 / x +// (x / 2) / 2 = x / 4 +FoldingRule MergeDivDivArithmetic() { + return [](ir::Instruction* inst, + const std::vector& constants) { + assert(inst->opcode() == SpvOpFDiv); + ir::IRContext* context = inst->context(); + analysis::ConstantManager* const_mgr = context->get_constant_mgr(); + const analysis::Type* type = + context->get_type_mgr()->GetType(inst->type_id()); + if (!inst->IsFloatingPointFoldingAllowed()) return false; + + uint32_t width = ElementWidth(type); + if (width != 32 && width != 64) return false; + + const analysis::Constant* const_input1 = ConstInput(constants); + if (!const_input1 || HasZero(const_input1)) return false; + ir::Instruction* other_inst = NonConstInput(context, constants[0], inst); + if (!other_inst->IsFloatingPointFoldingAllowed()) return false; + + bool first_is_variable = constants[0] == nullptr; + if (other_inst->opcode() == inst->opcode()) { + std::vector other_constants = + const_mgr->GetOperandConstants(other_inst); + const analysis::Constant* const_input2 = ConstInput(other_constants); + if (!const_input2 || HasZero(const_input2)) return false; + + bool other_first_is_variable = other_constants[0] == nullptr; + + SpvOp merge_op = inst->opcode(); + if (other_first_is_variable) { + // Constants magnify. + merge_op = SpvOpFMul; + } + + // This is an x / (*) case. Swap the inputs. Doesn't harm multiply + // because it is commutative. + if (first_is_variable) std::swap(const_input1, const_input2); + uint32_t merged_id = + PerformOperation(const_mgr, merge_op, const_input1, const_input2); + if (merged_id == 0) return false; + + uint32_t non_const_id = other_first_is_variable + ? other_inst->GetSingleWordInOperand(0u) + : other_inst->GetSingleWordInOperand(1u); + + SpvOp op = inst->opcode(); + if (!first_is_variable && !other_first_is_variable) { + // Effectively div of 1/x, so change to multiply. + op = SpvOpFMul; + } + + uint32_t op1 = merged_id; + uint32_t op2 = non_const_id; + if (first_is_variable && other_first_is_variable) std::swap(op1, op2); + inst->SetOpcode(op); + inst->SetInOperands( + {{SPV_OPERAND_TYPE_ID, {op1}}, {SPV_OPERAND_TYPE_ID, {op2}}}); + return true; + } + + return false; + }; +} + +// Fold multiplies succeeded by divides where each instruction contains a +// constant operand. Does not support integer divide. +// Cases: +// 4 / (x * 2) = 2 / x +// 4 / (2 * x) = 2 / x +// (x * 4) / 2 = x * 2 +// (4 * x) / 2 = x * 2 +FoldingRule MergeDivMulArithmetic() { + return [](ir::Instruction* inst, + const std::vector& constants) { + assert(inst->opcode() == SpvOpFDiv); + ir::IRContext* context = inst->context(); + analysis::ConstantManager* const_mgr = context->get_constant_mgr(); + const analysis::Type* type = + context->get_type_mgr()->GetType(inst->type_id()); + if (!inst->IsFloatingPointFoldingAllowed()) return false; + + uint32_t width = ElementWidth(type); + if (width != 32 && width != 64) return false; + + const analysis::Constant* const_input1 = ConstInput(constants); + if (!const_input1 || HasZero(const_input1)) return false; + ir::Instruction* other_inst = NonConstInput(context, constants[0], inst); + if (!other_inst->IsFloatingPointFoldingAllowed()) return false; + + bool first_is_variable = constants[0] == nullptr; + if (other_inst->opcode() == SpvOpFMul) { + std::vector other_constants = + const_mgr->GetOperandConstants(other_inst); + const analysis::Constant* const_input2 = ConstInput(other_constants); + if (!const_input2) return false; + + bool other_first_is_variable = other_constants[0] == nullptr; + + // This is an x / (*) case. Swap the inputs. + if (first_is_variable) std::swap(const_input1, const_input2); + uint32_t merged_id = PerformOperation(const_mgr, inst->opcode(), + const_input1, const_input2); + if (merged_id == 0) return false; + + uint32_t non_const_id = other_first_is_variable + ? other_inst->GetSingleWordInOperand(0u) + : other_inst->GetSingleWordInOperand(1u); + + uint32_t op1 = merged_id; + uint32_t op2 = non_const_id; + if (first_is_variable) std::swap(op1, op2); + + // Convert to multiply + if (first_is_variable) inst->SetOpcode(other_inst->opcode()); + inst->SetInOperands( + {{SPV_OPERAND_TYPE_ID, {op1}}, {SPV_OPERAND_TYPE_ID, {op2}}}); + return true; + } + + return false; + }; +} + +// Fold divides of a constant and a negation. +// Cases: +// (-x) / 2 = x / -2 +// 2 / (-x) = 2 / -x +FoldingRule MergeDivNegateArithmetic() { + return [](ir::Instruction* inst, + const std::vector& constants) { + assert(inst->opcode() == SpvOpFDiv || inst->opcode() == SpvOpSDiv || + inst->opcode() == SpvOpUDiv); + ir::IRContext* context = inst->context(); + analysis::ConstantManager* const_mgr = context->get_constant_mgr(); + const analysis::Type* type = + context->get_type_mgr()->GetType(inst->type_id()); + bool uses_float = HasFloatingPoint(type); + if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false; + + uint32_t width = ElementWidth(type); + if (width != 32 && width != 64) return false; + + const analysis::Constant* const_input1 = ConstInput(constants); + if (!const_input1) return false; + ir::Instruction* other_inst = NonConstInput(context, constants[0], inst); + if (uses_float && !other_inst->IsFloatingPointFoldingAllowed()) + return false; + + bool first_is_variable = constants[0] == nullptr; + if (other_inst->opcode() == SpvOpFNegate || + other_inst->opcode() == SpvOpSNegate) { + uint32_t neg_id = NegateConstant(const_mgr, const_input1); + + if (first_is_variable) { + inst->SetInOperands( + {{SPV_OPERAND_TYPE_ID, {other_inst->GetSingleWordInOperand(0u)}}, + {SPV_OPERAND_TYPE_ID, {neg_id}}}); + } else { + inst->SetInOperands( + {{SPV_OPERAND_TYPE_ID, {neg_id}}, + {SPV_OPERAND_TYPE_ID, {other_inst->GetSingleWordInOperand(0u)}}}); + } + return true; + } + + return false; + }; +} + +// Folds addition of a constant and a negation. +// Cases: +// (-x) + 2 = 2 - x +// 2 + (-x) = 2 - x +FoldingRule MergeAddNegateArithmetic() { + return [](ir::Instruction* inst, + const std::vector& constants) { + assert(inst->opcode() == SpvOpFAdd || inst->opcode() == SpvOpIAdd); + ir::IRContext* context = inst->context(); + const analysis::Type* type = + context->get_type_mgr()->GetType(inst->type_id()); + bool uses_float = HasFloatingPoint(type); + if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false; + + const analysis::Constant* const_input1 = ConstInput(constants); + if (!const_input1) return false; + ir::Instruction* other_inst = NonConstInput(context, constants[0], inst); + if (uses_float && !other_inst->IsFloatingPointFoldingAllowed()) + return false; + + if (other_inst->opcode() == SpvOpSNegate || + other_inst->opcode() == SpvOpFNegate) { + inst->SetOpcode(HasFloatingPoint(type) ? SpvOpFSub : SpvOpISub); + uint32_t const_id = constants[0] ? inst->GetSingleWordInOperand(0u) + : inst->GetSingleWordInOperand(1u); + inst->SetInOperands( + {{SPV_OPERAND_TYPE_ID, {const_id}}, + {SPV_OPERAND_TYPE_ID, {other_inst->GetSingleWordInOperand(0u)}}}); + return true; + } + return false; + }; +} + +// Folds subtraction of a constant and a negation. +// Cases: +// (-x) - 2 = -2 - x +// 2 - (-x) = x + 2 +FoldingRule MergeSubNegateArithmetic() { + return [](ir::Instruction* inst, + const std::vector& constants) { + assert(inst->opcode() == SpvOpFSub || inst->opcode() == SpvOpISub); + ir::IRContext* context = inst->context(); + analysis::ConstantManager* const_mgr = context->get_constant_mgr(); + const analysis::Type* type = + context->get_type_mgr()->GetType(inst->type_id()); + bool uses_float = HasFloatingPoint(type); + if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false; + + uint32_t width = ElementWidth(type); + if (width != 32 && width != 64) return false; + + const analysis::Constant* const_input1 = ConstInput(constants); + if (!const_input1) return false; + ir::Instruction* other_inst = NonConstInput(context, constants[0], inst); + if (uses_float && !other_inst->IsFloatingPointFoldingAllowed()) + return false; + + if (other_inst->opcode() == SpvOpSNegate || + other_inst->opcode() == SpvOpFNegate) { + uint32_t op1 = 0; + uint32_t op2 = 0; + SpvOp opcode = inst->opcode(); + if (constants[0] != nullptr) { + op1 = other_inst->GetSingleWordInOperand(0u); + op2 = inst->GetSingleWordInOperand(0u); + opcode = HasFloatingPoint(type) ? SpvOpFAdd : SpvOpIAdd; + } else { + op1 = NegateConstant(const_mgr, const_input1); + op2 = other_inst->GetSingleWordInOperand(0u); + } + + inst->SetOpcode(opcode); + inst->SetInOperands( + {{SPV_OPERAND_TYPE_ID, {op1}}, {SPV_OPERAND_TYPE_ID, {op2}}}); + return true; + } + return false; + }; +} + +// Folds addition of an addition where each operation has a constant operand. +// Cases: +// (x + 2) + 2 = x + 4 +// (2 + x) + 2 = x + 4 +// 2 + (x + 2) = x + 4 +// 2 + (2 + x) = x + 4 +FoldingRule MergeAddAddArithmetic() { + return [](ir::Instruction* inst, + const std::vector& constants) { + assert(inst->opcode() == SpvOpFAdd || inst->opcode() == SpvOpIAdd); + ir::IRContext* context = inst->context(); + const analysis::Type* type = + context->get_type_mgr()->GetType(inst->type_id()); + analysis::ConstantManager* const_mgr = context->get_constant_mgr(); + bool uses_float = HasFloatingPoint(type); + if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false; + + uint32_t width = ElementWidth(type); + if (width != 32 && width != 64) return false; + + const analysis::Constant* const_input1 = ConstInput(constants); + if (!const_input1) return false; + ir::Instruction* other_inst = NonConstInput(context, constants[0], inst); + if (uses_float && !other_inst->IsFloatingPointFoldingAllowed()) + return false; + + if (other_inst->opcode() == SpvOpFAdd || + other_inst->opcode() == SpvOpIAdd) { + std::vector other_constants = + const_mgr->GetOperandConstants(other_inst); + const analysis::Constant* const_input2 = ConstInput(other_constants); + if (!const_input2) return false; + + ir::Instruction* non_const_input = + NonConstInput(context, other_constants[0], other_inst); + uint32_t merged_id = PerformOperation(const_mgr, inst->opcode(), + const_input1, const_input2); + if (merged_id == 0) return false; + + inst->SetInOperands( + {{SPV_OPERAND_TYPE_ID, {non_const_input->result_id()}}, + {SPV_OPERAND_TYPE_ID, {merged_id}}}); + return true; + } + return false; + }; +} + +// Folds addition of a subtraction where each operation has a constant operand. +// Cases: +// (x - 2) + 2 = x + 0 +// (2 - x) + 2 = 4 - x +// 2 + (x - 2) = x + 0 +// 2 + (2 - x) = 4 - x +FoldingRule MergeAddSubArithmetic() { + return [](ir::Instruction* inst, + const std::vector& constants) { + assert(inst->opcode() == SpvOpFAdd || inst->opcode() == SpvOpIAdd); + ir::IRContext* context = inst->context(); + const analysis::Type* type = + context->get_type_mgr()->GetType(inst->type_id()); + analysis::ConstantManager* const_mgr = context->get_constant_mgr(); + bool uses_float = HasFloatingPoint(type); + if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false; + + uint32_t width = ElementWidth(type); + if (width != 32 && width != 64) return false; + + const analysis::Constant* const_input1 = ConstInput(constants); + if (!const_input1) return false; + ir::Instruction* other_inst = NonConstInput(context, constants[0], inst); + if (uses_float && !other_inst->IsFloatingPointFoldingAllowed()) + return false; + + if (other_inst->opcode() == SpvOpFSub || + other_inst->opcode() == SpvOpISub) { + std::vector other_constants = + const_mgr->GetOperandConstants(other_inst); + const analysis::Constant* const_input2 = ConstInput(other_constants); + if (!const_input2) return false; + + bool first_is_variable = other_constants[0] == nullptr; + SpvOp op = inst->opcode(); + uint32_t op1 = 0; + uint32_t op2 = 0; + if (first_is_variable) { + // Subtract constants. Non-constant operand is first. + op1 = other_inst->GetSingleWordInOperand(0u); + op2 = PerformOperation(const_mgr, other_inst->opcode(), const_input1, + const_input2); + } else { + // Add constants. Constant operand is first. Change the opcode. + op1 = PerformOperation(const_mgr, inst->opcode(), const_input1, + const_input2); + op2 = other_inst->GetSingleWordInOperand(1u); + op = other_inst->opcode(); + } + if (op1 == 0 || op2 == 0) return false; + + inst->SetOpcode(op); + inst->SetInOperands( + {{SPV_OPERAND_TYPE_ID, {op1}}, {SPV_OPERAND_TYPE_ID, {op2}}}); + return true; + } + return false; + }; +} + +// Folds subtraction of an addition where each operand has a constant operand. +// Cases: +// (x + 2) - 2 = x + 0 +// (2 + x) - 2 = x + 0 +// 2 - (x + 2) = 0 - x +// 2 - (2 + x) = 0 - x +FoldingRule MergeSubAddArithmetic() { + return [](ir::Instruction* inst, + const std::vector& constants) { + assert(inst->opcode() == SpvOpFSub || inst->opcode() == SpvOpISub); + ir::IRContext* context = inst->context(); + const analysis::Type* type = + context->get_type_mgr()->GetType(inst->type_id()); + analysis::ConstantManager* const_mgr = context->get_constant_mgr(); + bool uses_float = HasFloatingPoint(type); + if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false; + + uint32_t width = ElementWidth(type); + if (width != 32 && width != 64) return false; + + const analysis::Constant* const_input1 = ConstInput(constants); + if (!const_input1) return false; + ir::Instruction* other_inst = NonConstInput(context, constants[0], inst); + if (uses_float && !other_inst->IsFloatingPointFoldingAllowed()) + return false; + + if (other_inst->opcode() == SpvOpFAdd || + other_inst->opcode() == SpvOpIAdd) { + std::vector other_constants = + const_mgr->GetOperandConstants(other_inst); + const analysis::Constant* const_input2 = ConstInput(other_constants); + if (!const_input2) return false; + + ir::Instruction* non_const_input = + NonConstInput(context, other_constants[0], other_inst); + + // If the first operand of the sub is not a constant, swap the constants + // so the subtraction has the correct operands. + if (constants[0] == nullptr) std::swap(const_input1, const_input2); + // Subtract the constants. + uint32_t merged_id = PerformOperation(const_mgr, inst->opcode(), + const_input1, const_input2); + SpvOp op = inst->opcode(); + uint32_t op1 = 0; + uint32_t op2 = 0; + if (constants[0] == nullptr) { + // Non-constant operand is first. Change the opcode. + op1 = non_const_input->result_id(); + op2 = merged_id; + op = other_inst->opcode(); + } else { + // Constant operand is first. + op1 = merged_id; + op2 = non_const_input->result_id(); + } + if (op1 == 0 || op2 == 0) return false; + + inst->SetOpcode(op); + inst->SetInOperands( + {{SPV_OPERAND_TYPE_ID, {op1}}, {SPV_OPERAND_TYPE_ID, {op2}}}); + return true; + } + return false; + }; +} + +// Folds subtraction of a subtraction where each operand has a constant operand. +// Cases: +// (x - 2) - 2 = x - 4 +// (2 - x) - 2 = 0 - x +// 2 - (x - 2) = 4 - x +// 2 - (2 - x) = x + 0 +FoldingRule MergeSubSubArithmetic() { + return [](ir::Instruction* inst, + const std::vector& constants) { + assert(inst->opcode() == SpvOpFSub || inst->opcode() == SpvOpISub); + ir::IRContext* context = inst->context(); + const analysis::Type* type = + context->get_type_mgr()->GetType(inst->type_id()); + analysis::ConstantManager* const_mgr = context->get_constant_mgr(); + bool uses_float = HasFloatingPoint(type); + if (uses_float && !inst->IsFloatingPointFoldingAllowed()) return false; + + uint32_t width = ElementWidth(type); + if (width != 32 && width != 64) return false; + + const analysis::Constant* const_input1 = ConstInput(constants); + if (!const_input1) return false; + ir::Instruction* other_inst = NonConstInput(context, constants[0], inst); + if (uses_float && !other_inst->IsFloatingPointFoldingAllowed()) + return false; + + if (other_inst->opcode() == SpvOpFSub || + other_inst->opcode() == SpvOpISub) { + std::vector other_constants = + const_mgr->GetOperandConstants(other_inst); + const analysis::Constant* const_input2 = ConstInput(other_constants); + if (!const_input2) return false; + + ir::Instruction* non_const_input = + NonConstInput(context, other_constants[0], other_inst); + + // Merge the constants. + uint32_t merged_id = 0; + SpvOp merge_op = inst->opcode(); + if (other_constants[0] == nullptr) { + merge_op = uses_float ? SpvOpFAdd : SpvOpIAdd; + } else if (constants[0] == nullptr) { + std::swap(const_input1, const_input2); + } + merged_id = + PerformOperation(const_mgr, merge_op, const_input1, const_input2); + if (merged_id == 0) return false; + + SpvOp op = inst->opcode(); + if (constants[0] != nullptr && other_constants[0] != nullptr) { + // Change the operation. + op = uses_float ? SpvOpFAdd : SpvOpIAdd; + } + + uint32_t op1 = 0; + uint32_t op2 = 0; + if ((constants[0] == nullptr) ^ (other_constants[0] == nullptr)) { + op1 = merged_id; + op2 = non_const_input->result_id(); + } else { + op1 = non_const_input->result_id(); + op2 = merged_id; + } + + inst->SetOpcode(op); + inst->SetInOperands( + {{SPV_OPERAND_TYPE_ID, {op1}}, {SPV_OPERAND_TYPE_ID, {op2}}}); + return true; + } + return false; + }; +} + +FoldingRule IntMultipleBy1() { + return [](ir::Instruction* inst, + const std::vector& constants) { + assert(inst->opcode() == SpvOpIMul && "Wrong opcode. Should be OpIMul."); + for (uint32_t i = 0; i < 2; i++) { + if (constants[i] == nullptr) { + continue; + } + const analysis::IntConstant* int_constant = constants[i]->AsIntConstant(); + if (int_constant) { + uint32_t width = ElementWidth(int_constant->type()); + if (width != 32 && width != 64) return false; + bool is_one = (width == 32) ? int_constant->GetU32BitValue() == 1u + : int_constant->GetU64BitValue() == 1ull; + if (is_one) { + inst->SetOpcode(SpvOpCopyObject); + inst->SetInOperands( + {{SPV_OPERAND_TYPE_ID, {inst->GetSingleWordInOperand(1 - i)}}}); + return true; + } + } + } + return false; + }; +} + +FoldingRule CompositeConstructFeedingExtract() { + return [](ir::Instruction* inst, + const std::vector&) { + // If the input to an OpCompositeExtract is an OpCompositeConstruct, + // then we can simply use the appropriate element in the construction. + assert(inst->opcode() == SpvOpCompositeExtract && + "Wrong opcode. Should be OpCompositeExtract."); + analysis::DefUseManager* def_use_mgr = inst->context()->get_def_use_mgr(); + analysis::TypeManager* type_mgr = inst->context()->get_type_mgr(); + uint32_t cid = inst->GetSingleWordInOperand(kExtractCompositeIdInIdx); + ir::Instruction* cinst = def_use_mgr->GetDef(cid); + + if (cinst->opcode() != SpvOpCompositeConstruct) { + return false; + } + + std::vector operands; + analysis::Type* composite_type = type_mgr->GetType(cinst->type_id()); + if (composite_type->AsVector() == nullptr) { + // Get the element being extracted from the OpCompositeConstruct + // Since it is not a vector, it is simple to extract the single element. + uint32_t element_index = inst->GetSingleWordInOperand(1); + uint32_t element_id = cinst->GetSingleWordInOperand(element_index); + operands.push_back({SPV_OPERAND_TYPE_ID, {element_id}}); + + // Add the remaining indices for extraction. + for (uint32_t i = 2; i < inst->NumInOperands(); ++i) { + operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, + {inst->GetSingleWordInOperand(i)}}); + } + + } else { + // With vectors we have to handle the case where it is concatenating + // vectors. + assert(inst->NumInOperands() == 2 && + "Expecting a vector of scalar values."); + + uint32_t element_index = inst->GetSingleWordInOperand(1); + for (uint32_t construct_index = 0; + construct_index < cinst->NumInOperands(); ++construct_index) { + uint32_t element_id = cinst->GetSingleWordInOperand(construct_index); + ir::Instruction* element_def = def_use_mgr->GetDef(element_id); + analysis::Vector* element_type = + type_mgr->GetType(element_def->type_id())->AsVector(); + if (element_type) { + uint32_t vector_size = element_type->element_count(); + if (vector_size < element_index) { + // The element we want comes after this vector. + element_index -= vector_size; + } else { + // We want an element of this vector. + operands.push_back({SPV_OPERAND_TYPE_ID, {element_id}}); + operands.push_back( + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {element_index}}); + break; + } + } else { + if (element_index == 0) { + // This is a scalar, and we this is the element we are extracting. + operands.push_back({SPV_OPERAND_TYPE_ID, {element_id}}); + break; + } else { + // Skip over this scalar value. + --element_index; + } + } + } + } + + // If there were no extra indices, then we have the final object. No need + // to extract even more. + if (operands.size() == 1) { + inst->SetOpcode(SpvOpCopyObject); + } + + inst->SetInOperands(std::move(operands)); + return true; + }; +} + +FoldingRule CompositeExtractFeedingConstruct() { + // If the OpCompositeConstruct is simply putting back together elements that + // where extracted from the same souce, we can simlpy reuse the source. + // + // This is a common code pattern because of the way that scalar replacement + // works. + return [](ir::Instruction* inst, + const std::vector&) { + assert(inst->opcode() == SpvOpCompositeConstruct && + "Wrong opcode. Should be OpCompositeConstruct."); + analysis::DefUseManager* def_use_mgr = inst->context()->get_def_use_mgr(); + uint32_t original_id = 0; + + // Check each element to make sure they are: + // - extractions + // - extracting the same position they are inserting + // - all extract from the same id. + for (uint32_t i = 0; i < inst->NumInOperands(); ++i) { + uint32_t element_id = inst->GetSingleWordInOperand(i); + ir::Instruction* element_inst = def_use_mgr->GetDef(element_id); + + if (element_inst->opcode() != SpvOpCompositeExtract) { + return false; + } + + if (element_inst->NumInOperands() != 2) { + return false; + } + + if (element_inst->GetSingleWordInOperand(1) != i) { + return false; + } + + if (i == 0) { + original_id = + element_inst->GetSingleWordInOperand(kExtractCompositeIdInIdx); + } else if (original_id != element_inst->GetSingleWordInOperand( + kExtractCompositeIdInIdx)) { + return false; + } + } + + // The last check it to see that the object being extracted from is the + // correct type. + ir::Instruction* original_inst = def_use_mgr->GetDef(original_id); + if (original_inst->type_id() != inst->type_id()) { + return false; + } + + // Simplify by using the original object. + inst->SetOpcode(SpvOpCopyObject); + inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {original_id}}}); + return true; + }; +} + +FoldingRule InsertFeedingExtract() { + return [](ir::Instruction* inst, + const std::vector&) { + assert(inst->opcode() == SpvOpCompositeExtract && + "Wrong opcode. Should be OpCompositeExtract."); + analysis::DefUseManager* def_use_mgr = inst->context()->get_def_use_mgr(); + uint32_t cid = inst->GetSingleWordInOperand(kExtractCompositeIdInIdx); + ir::Instruction* cinst = def_use_mgr->GetDef(cid); + + if (cinst->opcode() != SpvOpCompositeInsert) { + return false; + } + + // Find the first position where the list of insert and extract indicies + // differ, if at all. + uint32_t i; + for (i = 1; i < inst->NumInOperands(); ++i) { + if (i + 1 >= cinst->NumInOperands()) { + break; + } + + if (inst->GetSingleWordInOperand(i) != + cinst->GetSingleWordInOperand(i + 1)) { + break; + } + } + + // We are extracting the element that was inserted. + if (i == inst->NumInOperands() && i + 1 == cinst->NumInOperands()) { + inst->SetOpcode(SpvOpCopyObject); + inst->SetInOperands( + {{SPV_OPERAND_TYPE_ID, + {cinst->GetSingleWordInOperand(kInsertObjectIdInIdx)}}}); + return true; + } + + // Extracting the value that was inserted along with values for the base + // composite. Cannot do anything. + if (i == inst->NumInOperands()) { + return false; + } + + // Extracting an element of the value that was inserted. Extract from + // that value directly. + if (i + 1 == cinst->NumInOperands()) { + std::vector operands; + operands.push_back( + {SPV_OPERAND_TYPE_ID, + {cinst->GetSingleWordInOperand(kInsertObjectIdInIdx)}}); + for (; i < inst->NumInOperands(); ++i) { + operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, + {inst->GetSingleWordInOperand(i)}}); + } + inst->SetInOperands(std::move(operands)); + return true; + } + + // Extracting a value that is disjoint from the element being inserted. + // Rewrite the extract to use the composite input to the insert. + std::vector operands; + operands.push_back( + {SPV_OPERAND_TYPE_ID, + {cinst->GetSingleWordInOperand(kInsertCompositeIdInIdx)}}); + for (i = 1; i < inst->NumInOperands(); ++i) { + operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, + {inst->GetSingleWordInOperand(i)}}); + } + inst->SetInOperands(std::move(operands)); + return true; + }; +} + +// When a VectorShuffle is feeding an Extract, we can extract from one of the +// operands of the VectorShuffle. We just need to adjust the index in the +// extract instruction. +FoldingRule VectorShuffleFeedingExtract() { + return [](ir::Instruction* inst, + const std::vector&) { + assert(inst->opcode() == SpvOpCompositeExtract && + "Wrong opcode. Should be OpCompositeExtract."); + analysis::DefUseManager* def_use_mgr = inst->context()->get_def_use_mgr(); + analysis::TypeManager* type_mgr = inst->context()->get_type_mgr(); + uint32_t cid = inst->GetSingleWordInOperand(kExtractCompositeIdInIdx); + ir::Instruction* cinst = def_use_mgr->GetDef(cid); + + if (cinst->opcode() != SpvOpVectorShuffle) { + return false; + } + + // Find the size of the first vector operand of the VectorShuffle + ir::Instruction* first_input = + def_use_mgr->GetDef(cinst->GetSingleWordInOperand(0)); + analysis::Type* first_input_type = + type_mgr->GetType(first_input->type_id()); + assert(first_input_type->AsVector() && + "Input to vector shuffle should be vectors."); + uint32_t first_input_size = first_input_type->AsVector()->element_count(); + + // Get index of the element the vector shuffle is placing in the position + // being extracted. + uint32_t new_index = + cinst->GetSingleWordInOperand(2 + inst->GetSingleWordInOperand(1)); + + // Get the id of the of the vector the elemtent comes from, and update the + // index if needed. + uint32_t new_vector = 0; + if (new_index < first_input_size) { + new_vector = cinst->GetSingleWordInOperand(0); + } else { + new_vector = cinst->GetSingleWordInOperand(1); + new_index -= first_input_size; + } + + // Update the extract instruction. + inst->SetInOperand(kExtractCompositeIdInIdx, {new_vector}); + inst->SetInOperand(1, {new_index}); + return true; + }; +} + +FoldingRule RedundantPhi() { + // An OpPhi instruction where all values are the same or the result of the phi + // itself, can be replaced by the value itself. + return + [](ir::Instruction* inst, const std::vector&) { + assert(inst->opcode() == SpvOpPhi && "Wrong opcode. Should be OpPhi."); + + ir::IRContext* context = inst->context(); + analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); + + uint32_t incoming_value = 0; + + for (uint32_t i = 0; i < inst->NumInOperands(); i += 2) { + uint32_t op_id = inst->GetSingleWordInOperand(i); + if (op_id == inst->result_id()) { + continue; + } + + ir::Instruction* op_inst = def_use_mgr->GetDef(op_id); + if (op_inst->opcode() == SpvOpUndef) { + // TODO: We should be able to still use op_id if we know that + // the definition of op_id dominates |inst|. + return false; + } + + if (incoming_value == 0) { + incoming_value = op_id; + } else if (op_id != incoming_value) { + // Found two possible value. Can't simplify. + return false; + } + } + + if (incoming_value == 0) { + // Code looks invalid. Don't do anything. + return false; + } + + // We have a single incoming value. Simplify using that value. + inst->SetOpcode(SpvOpCopyObject); + inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {incoming_value}}}); + return true; + }; +} + +FoldingRule RedundantSelect() { + // An OpSelect instruction where both values are the same or the condition is + // constant can be replaced by one of the values + return [](ir::Instruction* inst, + const std::vector& constants) { + assert(inst->opcode() == SpvOpSelect && + "Wrong opcode. Should be OpSelect."); + assert(inst->NumInOperands() == 3); + assert(constants.size() == 3); + + uint32_t true_id = inst->GetSingleWordInOperand(1); + uint32_t false_id = inst->GetSingleWordInOperand(2); + + if (true_id == false_id) { + // Both results are the same, condition doesn't matter + inst->SetOpcode(SpvOpCopyObject); + inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {true_id}}}); + return true; + } else if (constants[0]) { + const analysis::Type* type = constants[0]->type(); + if (type->AsBool()) { + // Scalar constant value, select the corresponding value. + inst->SetOpcode(SpvOpCopyObject); + if (constants[0]->AsNullConstant() || + !constants[0]->AsBoolConstant()->value()) { + inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {false_id}}}); + } else { + inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {true_id}}}); + } + return true; + } else { + assert(type->AsVector()); + if (constants[0]->AsNullConstant()) { + // All values come from false id. + inst->SetOpcode(SpvOpCopyObject); + inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {false_id}}}); + return true; + } else { + // Convert to a vector shuffle. + std::vector ops; + ops.push_back({SPV_OPERAND_TYPE_ID, {true_id}}); + ops.push_back({SPV_OPERAND_TYPE_ID, {false_id}}); + const analysis::VectorConstant* vector_const = + constants[0]->AsVectorConstant(); + uint32_t size = + static_cast(vector_const->GetComponents().size()); + for (uint32_t i = 0; i != size; ++i) { + const analysis::Constant* component = + vector_const->GetComponents()[i]; + if (component->AsNullConstant() || + !component->AsBoolConstant()->value()) { + // Selecting from the false vector which is the second input + // vector to the shuffle. Offset the index by |size|. + ops.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {i + size}}); + } else { + // Selecting from true vector which is the first input vector to + // the shuffle. + ops.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {i}}); + } + } + + inst->SetOpcode(SpvOpVectorShuffle); + inst->SetInOperands(std::move(ops)); + return true; + } + } + } + + return false; + }; +} + +enum class FloatConstantKind { Unknown, Zero, One }; + +FloatConstantKind getFloatConstantKind(const analysis::Constant* constant) { + if (constant == nullptr) { + return FloatConstantKind::Unknown; + } + + assert(HasFloatingPoint(constant->type()) && "Unexpected constant type"); + + if (constant->AsNullConstant()) { + return FloatConstantKind::Zero; + } else if (const analysis::VectorConstant* vc = + constant->AsVectorConstant()) { + const std::vector& components = + vc->GetComponents(); + assert(!components.empty()); + + FloatConstantKind kind = getFloatConstantKind(components[0]); + + for (size_t i = 1; i < components.size(); ++i) { + if (getFloatConstantKind(components[i]) != kind) { + return FloatConstantKind::Unknown; + } + } + + return kind; + } else if (const analysis::FloatConstant* fc = constant->AsFloatConstant()) { + if (fc->IsZero()) return FloatConstantKind::Zero; + + uint32_t width = fc->type()->AsFloat()->width(); + if (width != 32 && width != 64) return FloatConstantKind::Unknown; + + double value = (width == 64) ? fc->GetDoubleValue() : fc->GetFloatValue(); + + if (value == 0.0) { + return FloatConstantKind::Zero; + } else if (value == 1.0) { + return FloatConstantKind::One; + } else { + return FloatConstantKind::Unknown; + } + } else { + return FloatConstantKind::Unknown; + } +} + +FoldingRule RedundantFAdd() { + return [](ir::Instruction* inst, + const std::vector& constants) { + assert(inst->opcode() == SpvOpFAdd && "Wrong opcode. Should be OpFAdd."); + assert(constants.size() == 2); + + if (!inst->IsFloatingPointFoldingAllowed()) { + return false; + } + + FloatConstantKind kind0 = getFloatConstantKind(constants[0]); + FloatConstantKind kind1 = getFloatConstantKind(constants[1]); + + if (kind0 == FloatConstantKind::Zero || kind1 == FloatConstantKind::Zero) { + inst->SetOpcode(SpvOpCopyObject); + inst->SetInOperands({{SPV_OPERAND_TYPE_ID, + {inst->GetSingleWordInOperand( + kind0 == FloatConstantKind::Zero ? 1 : 0)}}}); + return true; + } + + return false; + }; +} + +FoldingRule RedundantFSub() { + return [](ir::Instruction* inst, + const std::vector& constants) { + assert(inst->opcode() == SpvOpFSub && "Wrong opcode. Should be OpFSub."); + assert(constants.size() == 2); + + if (!inst->IsFloatingPointFoldingAllowed()) { + return false; + } + + FloatConstantKind kind0 = getFloatConstantKind(constants[0]); + FloatConstantKind kind1 = getFloatConstantKind(constants[1]); + + if (kind0 == FloatConstantKind::Zero) { + inst->SetOpcode(SpvOpFNegate); + inst->SetInOperands( + {{SPV_OPERAND_TYPE_ID, {inst->GetSingleWordInOperand(1)}}}); + return true; + } + + if (kind1 == FloatConstantKind::Zero) { + inst->SetOpcode(SpvOpCopyObject); + inst->SetInOperands( + {{SPV_OPERAND_TYPE_ID, {inst->GetSingleWordInOperand(0)}}}); + return true; + } + + return false; + }; +} + +FoldingRule RedundantFMul() { + return [](ir::Instruction* inst, + const std::vector& constants) { + assert(inst->opcode() == SpvOpFMul && "Wrong opcode. Should be OpFMul."); + assert(constants.size() == 2); + + if (!inst->IsFloatingPointFoldingAllowed()) { + return false; + } + + FloatConstantKind kind0 = getFloatConstantKind(constants[0]); + FloatConstantKind kind1 = getFloatConstantKind(constants[1]); + + if (kind0 == FloatConstantKind::Zero || kind1 == FloatConstantKind::Zero) { + inst->SetOpcode(SpvOpCopyObject); + inst->SetInOperands({{SPV_OPERAND_TYPE_ID, + {inst->GetSingleWordInOperand( + kind0 == FloatConstantKind::Zero ? 0 : 1)}}}); + return true; + } + + if (kind0 == FloatConstantKind::One || kind1 == FloatConstantKind::One) { + inst->SetOpcode(SpvOpCopyObject); + inst->SetInOperands({{SPV_OPERAND_TYPE_ID, + {inst->GetSingleWordInOperand( + kind0 == FloatConstantKind::One ? 1 : 0)}}}); + return true; + } + + return false; + }; +} + +FoldingRule RedundantFDiv() { + return [](ir::Instruction* inst, + const std::vector& constants) { + assert(inst->opcode() == SpvOpFDiv && "Wrong opcode. Should be OpFDiv."); + assert(constants.size() == 2); + + if (!inst->IsFloatingPointFoldingAllowed()) { + return false; + } + + FloatConstantKind kind0 = getFloatConstantKind(constants[0]); + FloatConstantKind kind1 = getFloatConstantKind(constants[1]); + + if (kind0 == FloatConstantKind::Zero) { + inst->SetOpcode(SpvOpCopyObject); + inst->SetInOperands( + {{SPV_OPERAND_TYPE_ID, {inst->GetSingleWordInOperand(0)}}}); + return true; + } + + if (kind1 == FloatConstantKind::One) { + inst->SetOpcode(SpvOpCopyObject); + inst->SetInOperands( + {{SPV_OPERAND_TYPE_ID, {inst->GetSingleWordInOperand(0)}}}); + return true; + } + + return false; + }; +} + +FoldingRule RedundantFMix() { + return [](ir::Instruction* inst, + const std::vector& constants) { + assert(inst->opcode() == SpvOpExtInst && + "Wrong opcode. Should be OpExtInst."); + + if (!inst->IsFloatingPointFoldingAllowed()) { + return false; + } + + uint32_t instSetId = + inst->context()->get_feature_mgr()->GetExtInstImportId_GLSLstd450(); + + if (inst->GetSingleWordInOperand(kExtInstSetIdInIdx) == instSetId && + inst->GetSingleWordInOperand(kExtInstInstructionInIdx) == + GLSLstd450FMix) { + assert(constants.size() == 5); + + FloatConstantKind kind4 = getFloatConstantKind(constants[4]); + + if (kind4 == FloatConstantKind::Zero || kind4 == FloatConstantKind::One) { + inst->SetOpcode(SpvOpCopyObject); + inst->SetInOperands( + {{SPV_OPERAND_TYPE_ID, + {inst->GetSingleWordInOperand(kind4 == FloatConstantKind::Zero + ? kFMixXIdInIdx + : kFMixYIdInIdx)}}}); + return true; + } + } + + return false; + }; +} + +} // namespace + +spvtools::opt::FoldingRules::FoldingRules() { + // Add all folding rules to the list for the opcodes to which they apply. + // Note that the order in which rules are added to the list matters. If a rule + // applies to the instruction, the rest of the rules will not be attempted. + // Take that into consideration. + + rules_[SpvOpCompositeConstruct].push_back(CompositeExtractFeedingConstruct()); + + rules_[SpvOpCompositeExtract].push_back(InsertFeedingExtract()); + rules_[SpvOpCompositeExtract].push_back(CompositeConstructFeedingExtract()); + rules_[SpvOpCompositeExtract].push_back(VectorShuffleFeedingExtract()); + + rules_[SpvOpExtInst].push_back(RedundantFMix()); + + rules_[SpvOpFAdd].push_back(RedundantFAdd()); + rules_[SpvOpFAdd].push_back(MergeAddNegateArithmetic()); + rules_[SpvOpFAdd].push_back(MergeAddAddArithmetic()); + rules_[SpvOpFAdd].push_back(MergeAddSubArithmetic()); + + rules_[SpvOpFDiv].push_back(RedundantFDiv()); + rules_[SpvOpFDiv].push_back(ReciprocalFDiv()); + rules_[SpvOpFDiv].push_back(MergeDivDivArithmetic()); + rules_[SpvOpFDiv].push_back(MergeDivMulArithmetic()); + rules_[SpvOpFDiv].push_back(MergeDivNegateArithmetic()); + + rules_[SpvOpFMul].push_back(RedundantFMul()); + rules_[SpvOpFMul].push_back(MergeMulMulArithmetic()); + rules_[SpvOpFMul].push_back(MergeMulDivArithmetic()); + rules_[SpvOpFMul].push_back(MergeMulNegateArithmetic()); + + rules_[SpvOpFNegate].push_back(MergeNegateArithmetic()); + rules_[SpvOpFNegate].push_back(MergeNegateAddSubArithmetic()); + rules_[SpvOpFNegate].push_back(MergeNegateMulDivArithmetic()); + + rules_[SpvOpFSub].push_back(RedundantFSub()); + rules_[SpvOpFSub].push_back(MergeSubNegateArithmetic()); + rules_[SpvOpFSub].push_back(MergeSubAddArithmetic()); + rules_[SpvOpFSub].push_back(MergeSubSubArithmetic()); + + rules_[SpvOpIAdd].push_back(MergeAddNegateArithmetic()); + rules_[SpvOpIAdd].push_back(MergeAddAddArithmetic()); + rules_[SpvOpIAdd].push_back(MergeAddSubArithmetic()); + + rules_[SpvOpIMul].push_back(IntMultipleBy1()); + rules_[SpvOpIMul].push_back(MergeMulMulArithmetic()); + rules_[SpvOpIMul].push_back(MergeMulNegateArithmetic()); + + rules_[SpvOpISub].push_back(MergeSubNegateArithmetic()); + rules_[SpvOpISub].push_back(MergeSubAddArithmetic()); + rules_[SpvOpISub].push_back(MergeSubSubArithmetic()); + + rules_[SpvOpPhi].push_back(RedundantPhi()); + + rules_[SpvOpSDiv].push_back(MergeDivNegateArithmetic()); + + rules_[SpvOpSNegate].push_back(MergeNegateArithmetic()); + rules_[SpvOpSNegate].push_back(MergeNegateMulDivArithmetic()); + rules_[SpvOpSNegate].push_back(MergeNegateAddSubArithmetic()); + + rules_[SpvOpSelect].push_back(RedundantSelect()); + + rules_[SpvOpUDiv].push_back(MergeDivNegateArithmetic()); +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/folding_rules.h b/3rdparty/spirv-tools/source/opt/folding_rules.h new file mode 100644 index 000000000..78277e82c --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/folding_rules.h @@ -0,0 +1,81 @@ +// Copyright (c) 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_UTIL_FOLDING_RULES_H_ +#define LIBSPIRV_UTIL_FOLDING_RULES_H_ + +#include +#include + +#include "constants.h" +#include "def_use_manager.h" +#include "ir_builder.h" +#include "ir_context.h" + +namespace spvtools { +namespace opt { + +// Folding Rules: +// +// The folding mechanism is built around the concept of a |FoldingRule|. A +// folding rule is a function that implements a method of simplifying an +// instruction. +// +// The inputs to a folding rule are: +// |inst| - the instruction to be simplified. +// |constants| - if an in-operands is an id of a constant, then the +// corresponding value in |constants| contains that +// constant value. Otherwise, the corresponding entry in +// |constants| is |nullptr|. +// +// A folding rule returns true if |inst| can be simplified using this rule. If +// the instruction can be simplified, then |inst| is changed to the simplified +// instruction. Otherwise, |inst| remains the same. +// +// See folding_rules.cpp for examples on how to write a folding rule. It is +// important to note that if |inst| can be folded to the result of an +// instruction that feed it, then |inst| should be changed to an OpCopyObject +// that copies that id. +// +// Be sure to add new folding rules to the table of folding rules in the +// constructor for FoldingRules. The new rule should be added to the list for +// every opcode that it applies to. Note that earlier rules in the list are +// given priority. That is, if an earlier rule is able to fold an instruction, +// the later rules will not be attempted. + +using FoldingRule = std::function& constants)>; + +class FoldingRules { + public: + FoldingRules(); + + const std::vector& GetRulesForOpcode(SpvOp opcode) { + auto it = rules_.find(opcode); + if (it != rules_.end()) { + return it->second; + } + return empty_vector_; + } + + private: + std::unordered_map> rules_; + std::vector empty_vector_; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_UTIL_FOLDING_RULES_H_ diff --git a/3rdparty/spirv-tools/source/opt/freeze_spec_constant_value_pass.cpp b/3rdparty/spirv-tools/source/opt/freeze_spec_constant_value_pass.cpp new file mode 100644 index 000000000..ef589b03f --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/freeze_spec_constant_value_pass.cpp @@ -0,0 +1,53 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "freeze_spec_constant_value_pass.h" +#include "ir_context.h" + +namespace spvtools { +namespace opt { + +Pass::Status FreezeSpecConstantValuePass::Process(ir::IRContext* irContext) { + bool modified = false; + irContext->module()->ForEachInst( + [&modified, irContext](ir::Instruction* inst) { + switch (inst->opcode()) { + case SpvOp::SpvOpSpecConstant: + inst->SetOpcode(SpvOp::SpvOpConstant); + modified = true; + break; + case SpvOp::SpvOpSpecConstantTrue: + inst->SetOpcode(SpvOp::SpvOpConstantTrue); + modified = true; + break; + case SpvOp::SpvOpSpecConstantFalse: + inst->SetOpcode(SpvOp::SpvOpConstantFalse); + modified = true; + break; + case SpvOp::SpvOpDecorate: + if (inst->GetSingleWordInOperand(1) == + SpvDecoration::SpvDecorationSpecId) { + irContext->KillInst(inst); + modified = true; + } + break; + default: + break; + } + }); + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/freeze_spec_constant_value_pass.h b/3rdparty/spirv-tools/source/opt/freeze_spec_constant_value_pass.h new file mode 100644 index 000000000..fc7f44e70 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/freeze_spec_constant_value_pass.h @@ -0,0 +1,35 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_FREEZE_SPEC_CONSTANT_VALUE_PASS_H_ +#define LIBSPIRV_OPT_FREEZE_SPEC_CONSTANT_VALUE_PASS_H_ + +#include "ir_context.h" +#include "module.h" +#include "pass.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class FreezeSpecConstantValuePass : public Pass { + public: + const char* name() const override { return "freeze-spec-const"; } + Status Process(ir::IRContext*) override; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_FREEZE_SPEC_CONSTANT_VALUE_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/function.cpp b/3rdparty/spirv-tools/source/opt/function.cpp new file mode 100644 index 000000000..5a648eb11 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/function.cpp @@ -0,0 +1,109 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "function.h" + +#include +#include + +namespace spvtools { +namespace ir { + +Function* Function::Clone(IRContext* ctx) const { + Function* clone = + new Function(std::unique_ptr(DefInst().Clone(ctx))); + clone->params_.reserve(params_.size()); + ForEachParam( + [clone, ctx](const Instruction* inst) { + clone->AddParameter(std::unique_ptr(inst->Clone(ctx))); + }, + true); + + clone->blocks_.reserve(blocks_.size()); + for (const auto& b : blocks_) { + std::unique_ptr bb(b->Clone(ctx)); + bb->SetParent(clone); + clone->AddBasicBlock(std::move(bb)); + } + + clone->SetFunctionEnd(std::unique_ptr(EndInst()->Clone(ctx))); + return clone; +} + +void Function::ForEachInst(const std::function& f, + bool run_on_debug_line_insts) { + if (def_inst_) def_inst_->ForEachInst(f, run_on_debug_line_insts); + for (auto& param : params_) param->ForEachInst(f, run_on_debug_line_insts); + for (auto& bb : blocks_) bb->ForEachInst(f, run_on_debug_line_insts); + if (end_inst_) end_inst_->ForEachInst(f, run_on_debug_line_insts); +} + +void Function::ForEachInst(const std::function& f, + bool run_on_debug_line_insts) const { + if (def_inst_) + static_cast(def_inst_.get()) + ->ForEachInst(f, run_on_debug_line_insts); + + for (const auto& param : params_) + static_cast(param.get()) + ->ForEachInst(f, run_on_debug_line_insts); + + for (const auto& bb : blocks_) + static_cast(bb.get())->ForEachInst( + f, run_on_debug_line_insts); + + if (end_inst_) + static_cast(end_inst_.get()) + ->ForEachInst(f, run_on_debug_line_insts); +} + +void Function::ForEachParam(const std::function& f, + bool run_on_debug_line_insts) const { + for (const auto& param : params_) + static_cast(param.get()) + ->ForEachInst(f, run_on_debug_line_insts); +} + +BasicBlock* Function::InsertBasicBlockAfter( + std::unique_ptr&& new_block, BasicBlock* position) { + for (auto bb_iter = begin(); bb_iter != end(); ++bb_iter) { + if (&*bb_iter == position) { + new_block->SetParent(this); + ++bb_iter; + bb_iter = bb_iter.InsertBefore(std::move(new_block)); + return &*bb_iter; + } + } + assert(false && "Could not find insertion point."); + return nullptr; +} + +std::ostream& operator<<(std::ostream& str, const Function& func) { + str << func.PrettyPrint(); + return str; +} + +std::string Function::PrettyPrint(uint32_t options) const { + std::ostringstream str; + ForEachInst([&str, options](const ir::Instruction* inst) { + str << inst->PrettyPrint(options); + if (inst->opcode() != SpvOpFunctionEnd) { + str << std::endl; + } + }); + return str.str(); +} + +} // namespace ir +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/function.h b/3rdparty/spirv-tools/source/opt/function.h new file mode 100644 index 000000000..c4d4c613a --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/function.h @@ -0,0 +1,172 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_CONSTRUCTS_H_ +#define LIBSPIRV_OPT_CONSTRUCTS_H_ + +#include +#include +#include +#include +#include + +#include "basic_block.h" +#include "instruction.h" +#include "iterator.h" + +namespace spvtools { +namespace ir { + +class CFG; +class IRContext; +class Module; + +// A SPIR-V function. +class Function { + public: + using iterator = UptrVectorIterator; + using const_iterator = UptrVectorIterator; + + // Creates a function instance declared by the given OpFunction instruction + // |def_inst|. + inline explicit Function(std::unique_ptr def_inst); + + explicit Function(const Function& f) = delete; + + // Creates a clone of the instruction in the given |context| + // + // The parent module will default to null and needs to be explicitly set by + // the user. + Function* Clone(IRContext*) const; + // The OpFunction instruction that begins the definition of this function. + Instruction& DefInst() { return *def_inst_; } + const Instruction& DefInst() const { return *def_inst_; } + + // Sets the enclosing module for this function. + void SetParent(Module* module) { module_ = module; } + // Gets the enclosing module for this function + Module* GetParent() const { return module_; } + // Appends a parameter to this function. + inline void AddParameter(std::unique_ptr p); + // Appends a basic block to this function. + inline void AddBasicBlock(std::unique_ptr b); + // Appends a basic block to this function at the position |ip|. + inline void AddBasicBlock(std::unique_ptr b, iterator ip); + template + inline void AddBasicBlocks(T begin, T end, iterator ip); + + // Saves the given function end instruction. + inline void SetFunctionEnd(std::unique_ptr end_inst); + + // Returns the given function end instruction. + inline Instruction* EndInst() { return end_inst_.get(); } + inline const Instruction* EndInst() const { return end_inst_.get(); } + + // Returns function's id + inline uint32_t result_id() const { return def_inst_->result_id(); } + + // Returns function's return type id + inline uint32_t type_id() const { return def_inst_->type_id(); } + + // Returns the entry basic block for this function. + const std::unique_ptr& entry() const { return blocks_.front(); } + + iterator begin() { return iterator(&blocks_, blocks_.begin()); } + iterator end() { return iterator(&blocks_, blocks_.end()); } + const_iterator begin() const { return cbegin(); } + const_iterator end() const { return cend(); } + const_iterator cbegin() const { + return const_iterator(&blocks_, blocks_.cbegin()); + } + const_iterator cend() const { + return const_iterator(&blocks_, blocks_.cend()); + } + + // Returns an iterator to the basic block |id|. + iterator FindBlock(uint32_t bb_id) { + return std::find_if(begin(), end(), [bb_id](const ir::BasicBlock& it_bb) { + return bb_id == it_bb.id(); + }); + } + + // Runs the given function |f| on each instruction in this function, and + // optionally on debug line instructions that might precede them. + void ForEachInst(const std::function& f, + bool run_on_debug_line_insts = false); + void ForEachInst(const std::function& f, + bool run_on_debug_line_insts = false) const; + + // Runs the given function |f| on each parameter instruction in this function, + // and optionally on debug line instructions that might precede them. + void ForEachParam(const std::function& f, + bool run_on_debug_line_insts = false) const; + + // Returns the context of the current function. + IRContext* context() const { return def_inst_->context(); } + + BasicBlock* InsertBasicBlockAfter(std::unique_ptr&& new_block, + BasicBlock* position); + + // Pretty-prints all the basic blocks in this function into a std::string. + // + // |options| are the disassembly options. SPV_BINARY_TO_TEXT_OPTION_NO_HEADER + // is always added to |options|. + std::string PrettyPrint(uint32_t options = 0u) const; + + private: + // The enclosing module. + Module* module_; + // The OpFunction instruction that begins the definition of this function. + std::unique_ptr def_inst_; + // All parameters to this function. + std::vector> params_; + // All basic blocks inside this function in specification order + std::vector> blocks_; + // The OpFunctionEnd instruction. + std::unique_ptr end_inst_; +}; + +// Pretty-prints |func| to |str|. Returns |str|. +std::ostream& operator<<(std::ostream& str, const Function& func); + +inline Function::Function(std::unique_ptr def_inst) + : module_(nullptr), def_inst_(std::move(def_inst)), end_inst_() {} + +inline void Function::AddParameter(std::unique_ptr p) { + params_.emplace_back(std::move(p)); +} + +inline void Function::AddBasicBlock(std::unique_ptr b) { + AddBasicBlock(std::move(b), end()); +} + +inline void Function::AddBasicBlock(std::unique_ptr b, + iterator ip) { + ip.InsertBefore(std::move(b)); +} + +template +inline void Function::AddBasicBlocks(T src_begin, T src_end, iterator ip) { + blocks_.insert(ip.Get(), std::make_move_iterator(src_begin), + std::make_move_iterator(src_end)); +} + +inline void Function::SetFunctionEnd(std::unique_ptr end_inst) { + end_inst_ = std::move(end_inst); +} + +} // namespace ir +} // namespace spvtools + +#endif // LIBSPIRV_OPT_CONSTRUCTS_H_ diff --git a/3rdparty/spirv-tools/source/opt/if_conversion.cpp b/3rdparty/spirv-tools/source/opt/if_conversion.cpp new file mode 100644 index 000000000..951b7ea45 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/if_conversion.cpp @@ -0,0 +1,188 @@ +// Copyright (c) 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "if_conversion.h" + +namespace spvtools { +namespace opt { + +Pass::Status IfConversion::Process(ir::IRContext* c) { + InitializeProcessing(c); + + bool modified = false; + std::vector to_kill; + for (auto& func : *get_module()) { + DominatorAnalysis* dominators = + context()->GetDominatorAnalysis(&func, *cfg()); + for (auto& block : func) { + // Check if it is possible for |block| to have phis that can be + // transformed. + ir::BasicBlock* common = nullptr; + if (!CheckBlock(&block, dominators, &common)) continue; + + // Get an insertion point. + auto iter = block.begin(); + while (iter != block.end() && iter->opcode() == SpvOpPhi) { + ++iter; + } + + InstructionBuilder builder( + context(), &*iter, + ir::IRContext::kAnalysisDefUse | + ir::IRContext::kAnalysisInstrToBlockMapping); + block.ForEachPhiInst([this, &builder, &modified, &common, &to_kill, + dominators, &block](ir::Instruction* phi) { + // This phi is not compatible, but subsequent phis might be. + if (!CheckType(phi->type_id())) return; + + // We cannot transform cases where the phi is used by another phi in the + // same block due to instruction ordering restrictions. + // TODO(alan-baker): If all inappropriate uses could also be + // transformed, we could still remove this phi. + if (!CheckPhiUsers(phi, &block)) return; + + // Identify the incoming values associated with the true and false + // branches. If |then_block| dominates |inc0| or if the true edge + // branches straight to this block and |common| is |inc0|, then |inc0| + // is on the true branch. Otherwise the |inc1| is on the true branch. + ir::BasicBlock* inc0 = GetIncomingBlock(phi, 0u); + ir::Instruction* branch = common->terminator(); + uint32_t condition = branch->GetSingleWordInOperand(0u); + ir::BasicBlock* then_block = + GetBlock(branch->GetSingleWordInOperand(1u)); + ir::Instruction* true_value = nullptr; + ir::Instruction* false_value = nullptr; + if ((then_block == &block && inc0 == common) || + dominators->Dominates(then_block, inc0)) { + true_value = GetIncomingValue(phi, 0u); + false_value = GetIncomingValue(phi, 1u); + } else { + true_value = GetIncomingValue(phi, 1u); + false_value = GetIncomingValue(phi, 0u); + } + + // If either incoming value is defined in a block that does not dominate + // this phi, then we cannot eliminate the phi with a select. + // TODO(alan-baker): Perform code motion where it makes sense to enable + // the transform in this case. + ir::BasicBlock* true_def_block = context()->get_instr_block(true_value); + if (true_def_block && !dominators->Dominates(true_def_block, &block)) + return; + + ir::BasicBlock* false_def_block = + context()->get_instr_block(false_value); + if (false_def_block && !dominators->Dominates(false_def_block, &block)) + return; + + analysis::Type* data_ty = + context()->get_type_mgr()->GetType(true_value->type_id()); + if (analysis::Vector* vec_data_ty = data_ty->AsVector()) { + condition = SplatCondition(vec_data_ty, condition, &builder); + } + + ir::Instruction* select = builder.AddSelect(phi->type_id(), condition, + true_value->result_id(), + false_value->result_id()); + context()->ReplaceAllUsesWith(phi->result_id(), select->result_id()); + to_kill.push_back(phi); + modified = true; + + return; + }); + } + } + + for (auto inst : to_kill) { + context()->KillInst(inst); + } + + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; +} + +bool IfConversion::CheckBlock(ir::BasicBlock* block, + DominatorAnalysis* dominators, + ir::BasicBlock** common) { + const std::vector& preds = cfg()->preds(block->id()); + + // TODO(alan-baker): Extend to more than two predecessors + if (preds.size() != 2) return false; + + ir::BasicBlock* inc0 = context()->get_instr_block(preds[0]); + if (dominators->Dominates(block, inc0)) return false; + + ir::BasicBlock* inc1 = context()->get_instr_block(preds[1]); + if (dominators->Dominates(block, inc1)) return false; + + // All phis will have the same common dominator, so cache the result + // for this block. If there is no common dominator, then we cannot transform + // any phi in this basic block. + *common = dominators->CommonDominator(inc0, inc1); + if (!*common || cfg()->IsPseudoEntryBlock(*common)) return false; + ir::Instruction* branch = (*common)->terminator(); + if (branch->opcode() != SpvOpBranchConditional) return false; + + return true; +} + +bool IfConversion::CheckPhiUsers(ir::Instruction* phi, ir::BasicBlock* block) { + return get_def_use_mgr()->WhileEachUser(phi, [block, + this](ir::Instruction* user) { + if (user->opcode() == SpvOpPhi && context()->get_instr_block(user) == block) + return false; + return true; + }); +} + +uint32_t IfConversion::SplatCondition(analysis::Vector* vec_data_ty, + uint32_t cond, + InstructionBuilder* builder) { + // If the data inputs to OpSelect are vectors, the condition for + // OpSelect must be a boolean vector with the same number of + // components. So splat the condition for the branch into a vector + // type. + analysis::Bool bool_ty; + analysis::Vector bool_vec_ty(&bool_ty, vec_data_ty->element_count()); + uint32_t bool_vec_id = + context()->get_type_mgr()->GetTypeInstruction(&bool_vec_ty); + std::vector ids(vec_data_ty->element_count(), cond); + return builder->AddCompositeConstruct(bool_vec_id, ids)->result_id(); +} + +bool IfConversion::CheckType(uint32_t id) { + ir::Instruction* type = get_def_use_mgr()->GetDef(id); + SpvOp op = type->opcode(); + if (spvOpcodeIsScalarType(op) || op == SpvOpTypePointer || + op == SpvOpTypeVector) + return true; + return false; +} + +ir::BasicBlock* IfConversion::GetBlock(uint32_t id) { + return context()->get_instr_block(get_def_use_mgr()->GetDef(id)); +} + +ir::BasicBlock* IfConversion::GetIncomingBlock(ir::Instruction* phi, + uint32_t predecessor) { + uint32_t in_index = 2 * predecessor + 1; + return GetBlock(phi->GetSingleWordInOperand(in_index)); +} + +ir::Instruction* IfConversion::GetIncomingValue(ir::Instruction* phi, + uint32_t predecessor) { + uint32_t in_index = 2 * predecessor; + return get_def_use_mgr()->GetDef(phi->GetSingleWordInOperand(in_index)); +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/if_conversion.h b/3rdparty/spirv-tools/source/opt/if_conversion.h new file mode 100644 index 000000000..eb97406d4 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/if_conversion.h @@ -0,0 +1,76 @@ +// Copyright (c) 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_IF_CONVERSION_H_ +#define LIBSPIRV_OPT_IF_CONVERSION_H_ + +#include "basic_block.h" +#include "ir_builder.h" +#include "pass.h" +#include "types.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class IfConversion : public Pass { + public: + const char* name() const override { return "if-conversion"; } + Status Process(ir::IRContext* context) override; + + ir::IRContext::Analysis GetPreservedAnalyses() override { + return ir::IRContext::kAnalysisDefUse | + ir::IRContext::kAnalysisDominatorAnalysis | + ir::IRContext::kAnalysisInstrToBlockMapping | + ir::IRContext::kAnalysisCFG | ir::IRContext::kAnalysisNameMap; + } + + private: + // Returns true if |id| is a valid type for use with OpSelect. OpSelect only + // allows scalars, vectors and pointers as valid inputs. + bool CheckType(uint32_t id); + + // Returns the basic block containing |id|. + ir::BasicBlock* GetBlock(uint32_t id); + + // Returns the basic block for the |predecessor|'th index predecessor of + // |phi|. + ir::BasicBlock* GetIncomingBlock(ir::Instruction* phi, uint32_t predecessor); + + // Returns the instruction defining the |predecessor|'th index of |phi|. + ir::Instruction* GetIncomingValue(ir::Instruction* phi, uint32_t predecessor); + + // Returns the id of a OpCompositeConstruct boolean vector. The composite has + // the same number of elements as |vec_data_ty| and each member is |cond|. + // |where| indicates the location in |block| to insert the composite + // construct. If necessary, this function will also construct the necessary + // type instructions for the boolean vector. + uint32_t SplatCondition(analysis::Vector* vec_data_ty, uint32_t cond, + InstructionBuilder* builder); + + // Returns true if none of |phi|'s users are in |block|. + bool CheckPhiUsers(ir::Instruction* phi, ir::BasicBlock* block); + + // Returns |false| if |block| is not appropriate to transform. Only + // transforms blocks with two predecessors. Neither incoming block can be + // dominated by |block|. Both predecessors must share a common dominator that + // is terminated by a conditional branch. + bool CheckBlock(ir::BasicBlock* block, DominatorAnalysis* dominators, + ir::BasicBlock** common); +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_IF_CONVERSION_H_ diff --git a/3rdparty/spirv-tools/source/opt/inline_exhaustive_pass.cpp b/3rdparty/spirv-tools/source/opt/inline_exhaustive_pass.cpp new file mode 100644 index 000000000..a5bc9f358 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/inline_exhaustive_pass.cpp @@ -0,0 +1,81 @@ +// Copyright (c) 2017 The Khronos Group Inc. +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "inline_exhaustive_pass.h" + +namespace spvtools { +namespace opt { + +bool InlineExhaustivePass::InlineExhaustive(ir::Function* func) { + bool modified = false; + // Using block iterators here because of block erasures and insertions. + for (auto bi = func->begin(); bi != func->end(); ++bi) { + for (auto ii = bi->begin(); ii != bi->end();) { + if (IsInlinableFunctionCall(&*ii)) { + // Inline call. + std::vector> newBlocks; + std::vector> newVars; + GenInlineCode(&newBlocks, &newVars, ii, bi); + // If call block is replaced with more than one block, point + // succeeding phis at new last block. + if (newBlocks.size() > 1) UpdateSucceedingPhis(newBlocks); + // Replace old calling block with new block(s). + + // We need to kill the name and decorations for the call, which + // will be deleted. Other instructions in the block will be moved to + // newBlocks. We don't need to do anything with those. + context()->KillNamesAndDecorates(&*ii); + + bi = bi.Erase(); + + for (auto& bb : newBlocks) { + bb->SetParent(func); + } + bi = bi.InsertBefore(&newBlocks); + // Insert new function variables. + if (newVars.size() > 0) + func->begin()->begin().InsertBefore(std::move(newVars)); + // Restart inlining at beginning of calling block. + ii = bi->begin(); + modified = true; + } else { + ++ii; + } + } + } + return modified; +} + +void InlineExhaustivePass::Initialize(ir::IRContext* c) { InitializeInline(c); } + +Pass::Status InlineExhaustivePass::ProcessImpl() { + // Attempt exhaustive inlining on each entry point function in module + ProcessFunction pfn = [this](ir::Function* fp) { + return InlineExhaustive(fp); + }; + bool modified = ProcessEntryPointCallTree(pfn, get_module()); + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; +} + +InlineExhaustivePass::InlineExhaustivePass() {} + +Pass::Status InlineExhaustivePass::Process(ir::IRContext* c) { + Initialize(c); + return ProcessImpl(); +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/inline_exhaustive_pass.h b/3rdparty/spirv-tools/source/opt/inline_exhaustive_pass.h new file mode 100644 index 000000000..08b4387b5 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/inline_exhaustive_pass.h @@ -0,0 +1,53 @@ +// Copyright (c) 2017 The Khronos Group Inc. +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_INLINE_EXHAUSTIVE_PASS_H_ +#define LIBSPIRV_OPT_INLINE_EXHAUSTIVE_PASS_H_ + +#include +#include +#include +#include +#include + +#include "def_use_manager.h" +#include "inline_pass.h" +#include "module.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class InlineExhaustivePass : public InlinePass { + public: + InlineExhaustivePass(); + Status Process(ir::IRContext* c) override; + + const char* name() const override { return "inline-entry-points-exhaustive"; } + + private: + // Exhaustively inline all function calls in func as well as in + // all code that is inlined into func. Return true if func is modified. + bool InlineExhaustive(ir::Function* func); + + void Initialize(ir::IRContext* c); + Pass::Status ProcessImpl(); +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_INLINE_EXHAUSTIVE_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/inline_opaque_pass.cpp b/3rdparty/spirv-tools/source/opt/inline_opaque_pass.cpp new file mode 100644 index 000000000..13880502e --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/inline_opaque_pass.cpp @@ -0,0 +1,112 @@ +// Copyright (c) 2017 The Khronos Group Inc. +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "inline_opaque_pass.h" + +namespace spvtools { +namespace opt { + +namespace { + +const uint32_t kTypePointerTypeIdInIdx = 1; + +} // anonymous namespace + +bool InlineOpaquePass::IsOpaqueType(uint32_t typeId) { + const ir::Instruction* typeInst = get_def_use_mgr()->GetDef(typeId); + switch (typeInst->opcode()) { + case SpvOpTypeSampler: + case SpvOpTypeImage: + case SpvOpTypeSampledImage: + return true; + case SpvOpTypePointer: + return IsOpaqueType( + typeInst->GetSingleWordInOperand(kTypePointerTypeIdInIdx)); + default: + break; + } + // TODO(greg-lunarg): Handle arrays containing opaque type + if (typeInst->opcode() != SpvOpTypeStruct) return false; + // Return true if any member is opaque + return !typeInst->WhileEachInId([this](const uint32_t* tid) { + if (IsOpaqueType(*tid)) return false; + return true; + }); +} + +bool InlineOpaquePass::HasOpaqueArgsOrReturn(const ir::Instruction* callInst) { + // Check return type + if (IsOpaqueType(callInst->type_id())) return true; + // Check args + int icnt = 0; + return !callInst->WhileEachInId([&icnt, this](const uint32_t* iid) { + if (icnt > 0) { + const ir::Instruction* argInst = get_def_use_mgr()->GetDef(*iid); + if (IsOpaqueType(argInst->type_id())) return false; + } + ++icnt; + return true; + }); +} + +bool InlineOpaquePass::InlineOpaque(ir::Function* func) { + bool modified = false; + // Using block iterators here because of block erasures and insertions. + for (auto bi = func->begin(); bi != func->end(); ++bi) { + for (auto ii = bi->begin(); ii != bi->end();) { + if (IsInlinableFunctionCall(&*ii) && HasOpaqueArgsOrReturn(&*ii)) { + // Inline call. + std::vector> newBlocks; + std::vector> newVars; + GenInlineCode(&newBlocks, &newVars, ii, bi); + // If call block is replaced with more than one block, point + // succeeding phis at new last block. + if (newBlocks.size() > 1) UpdateSucceedingPhis(newBlocks); + // Replace old calling block with new block(s). + bi = bi.Erase(); + bi = bi.InsertBefore(&newBlocks); + // Insert new function variables. + if (newVars.size() > 0) + func->begin()->begin().InsertBefore(std::move(newVars)); + // Restart inlining at beginning of calling block. + ii = bi->begin(); + modified = true; + } else { + ++ii; + } + } + } + return modified; +} + +void InlineOpaquePass::Initialize(ir::IRContext* c) { InitializeInline(c); } + +Pass::Status InlineOpaquePass::ProcessImpl() { + // Do opaque inlining on each function in entry point call tree + ProcessFunction pfn = [this](ir::Function* fp) { return InlineOpaque(fp); }; + bool modified = ProcessEntryPointCallTree(pfn, get_module()); + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; +} + +InlineOpaquePass::InlineOpaquePass() {} + +Pass::Status InlineOpaquePass::Process(ir::IRContext* c) { + Initialize(c); + return ProcessImpl(); +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/inline_opaque_pass.h b/3rdparty/spirv-tools/source/opt/inline_opaque_pass.h new file mode 100644 index 000000000..0a6cf0ecb --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/inline_opaque_pass.h @@ -0,0 +1,60 @@ +// Copyright (c) 2017 The Khronos Group Inc. +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_INLINE_OPAQUE_PASS_H_ +#define LIBSPIRV_OPT_INLINE_OPAQUE_PASS_H_ + +#include +#include +#include +#include +#include + +#include "def_use_manager.h" +#include "inline_pass.h" +#include "module.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class InlineOpaquePass : public InlinePass { + public: + InlineOpaquePass(); + Status Process(ir::IRContext* c) override; + + const char* name() const override { return "inline-entry-points-opaque"; } + + private: + // Return true if |typeId| is or contains opaque type + bool IsOpaqueType(uint32_t typeId); + + // Return true if function call |callInst| has opaque argument or return type + bool HasOpaqueArgsOrReturn(const ir::Instruction* callInst); + + // Inline all function calls in |func| that have opaque params or return + // type. Inline similarly all code that is inlined into func. Return true + // if func is modified. + bool InlineOpaque(ir::Function* func); + + void Initialize(ir::IRContext* c); + Pass::Status ProcessImpl(); +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_INLINE_OPAQUE_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/inline_pass.cpp b/3rdparty/spirv-tools/source/opt/inline_pass.cpp new file mode 100644 index 000000000..61c734329 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/inline_pass.cpp @@ -0,0 +1,676 @@ +// Copyright (c) 2017 The Khronos Group Inc. +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "inline_pass.h" + +#include "cfa.h" + +// Indices of operands in SPIR-V instructions + +static const int kSpvFunctionCallFunctionId = 2; +static const int kSpvFunctionCallArgumentId = 3; +static const int kSpvReturnValueId = 0; +static const int kSpvLoopMergeMergeBlockId = 0; +static const int kSpvLoopMergeContinueTargetIdInIdx = 1; + +namespace spvtools { +namespace opt { + +uint32_t InlinePass::AddPointerToType(uint32_t type_id, + SpvStorageClass storage_class) { + uint32_t resultId = TakeNextId(); + std::unique_ptr type_inst(new ir::Instruction( + context(), SpvOpTypePointer, 0, resultId, + {{spv_operand_type_t::SPV_OPERAND_TYPE_STORAGE_CLASS, + {uint32_t(storage_class)}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {type_id}}})); + context()->AddType(std::move(type_inst)); + analysis::Type* pointeeTy; + std::unique_ptr pointerTy; + std::tie(pointeeTy, pointerTy) = + context()->get_type_mgr()->GetTypeAndPointerType(type_id, + SpvStorageClassFunction); + context()->get_type_mgr()->RegisterType(resultId, *pointerTy); + return resultId; +} + +void InlinePass::AddBranch(uint32_t label_id, + std::unique_ptr* block_ptr) { + std::unique_ptr newBranch(new ir::Instruction( + context(), SpvOpBranch, 0, 0, + {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {label_id}}})); + (*block_ptr)->AddInstruction(std::move(newBranch)); +} + +void InlinePass::AddBranchCond(uint32_t cond_id, uint32_t true_id, + uint32_t false_id, + std::unique_ptr* block_ptr) { + std::unique_ptr newBranch(new ir::Instruction( + context(), SpvOpBranchConditional, 0, 0, + {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {cond_id}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {true_id}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {false_id}}})); + (*block_ptr)->AddInstruction(std::move(newBranch)); +} + +void InlinePass::AddLoopMerge(uint32_t merge_id, uint32_t continue_id, + std::unique_ptr* block_ptr) { + std::unique_ptr newLoopMerge(new ir::Instruction( + context(), SpvOpLoopMerge, 0, 0, + {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {merge_id}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {continue_id}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_LOOP_CONTROL, {0}}})); + (*block_ptr)->AddInstruction(std::move(newLoopMerge)); +} + +void InlinePass::AddStore(uint32_t ptr_id, uint32_t val_id, + std::unique_ptr* block_ptr) { + std::unique_ptr newStore(new ir::Instruction( + context(), SpvOpStore, 0, 0, + {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {ptr_id}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {val_id}}})); + (*block_ptr)->AddInstruction(std::move(newStore)); +} + +void InlinePass::AddLoad(uint32_t type_id, uint32_t resultId, uint32_t ptr_id, + std::unique_ptr* block_ptr) { + std::unique_ptr newLoad(new ir::Instruction( + context(), SpvOpLoad, type_id, resultId, + {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {ptr_id}}})); + (*block_ptr)->AddInstruction(std::move(newLoad)); +} + +std::unique_ptr InlinePass::NewLabel(uint32_t label_id) { + std::unique_ptr newLabel( + new ir::Instruction(context(), SpvOpLabel, 0, label_id, {})); + return newLabel; +} + +uint32_t InlinePass::GetFalseId() { + if (false_id_ != 0) return false_id_; + false_id_ = get_module()->GetGlobalValue(SpvOpConstantFalse); + if (false_id_ != 0) return false_id_; + uint32_t boolId = get_module()->GetGlobalValue(SpvOpTypeBool); + if (boolId == 0) { + boolId = TakeNextId(); + get_module()->AddGlobalValue(SpvOpTypeBool, boolId, 0); + } + false_id_ = TakeNextId(); + get_module()->AddGlobalValue(SpvOpConstantFalse, false_id_, boolId); + return false_id_; +} + +void InlinePass::MapParams( + ir::Function* calleeFn, ir::BasicBlock::iterator call_inst_itr, + std::unordered_map* callee2caller) { + int param_idx = 0; + calleeFn->ForEachParam( + [&call_inst_itr, ¶m_idx, &callee2caller](const ir::Instruction* cpi) { + const uint32_t pid = cpi->result_id(); + (*callee2caller)[pid] = call_inst_itr->GetSingleWordOperand( + kSpvFunctionCallArgumentId + param_idx); + ++param_idx; + }); +} + +void InlinePass::CloneAndMapLocals( + ir::Function* calleeFn, + std::vector>* new_vars, + std::unordered_map* callee2caller) { + auto callee_block_itr = calleeFn->begin(); + auto callee_var_itr = callee_block_itr->begin(); + while (callee_var_itr->opcode() == SpvOp::SpvOpVariable) { + std::unique_ptr var_inst( + callee_var_itr->Clone(callee_var_itr->context())); + uint32_t newId = TakeNextId(); + get_decoration_mgr()->CloneDecorations(callee_var_itr->result_id(), newId); + var_inst->SetResultId(newId); + (*callee2caller)[callee_var_itr->result_id()] = newId; + new_vars->push_back(std::move(var_inst)); + ++callee_var_itr; + } +} + +uint32_t InlinePass::CreateReturnVar( + ir::Function* calleeFn, + std::vector>* new_vars) { + uint32_t returnVarId = 0; + const uint32_t calleeTypeId = calleeFn->type_id(); + analysis::Type* calleeType = context()->get_type_mgr()->GetType(calleeTypeId); + if (calleeType->AsVoid() == nullptr) { + // Find or create ptr to callee return type. + uint32_t returnVarTypeId = context()->get_type_mgr()->FindPointerToType( + calleeTypeId, SpvStorageClassFunction); + if (returnVarTypeId == 0) + returnVarTypeId = AddPointerToType(calleeTypeId, SpvStorageClassFunction); + // Add return var to new function scope variables. + returnVarId = TakeNextId(); + std::unique_ptr var_inst(new ir::Instruction( + context(), SpvOpVariable, returnVarTypeId, returnVarId, + {{spv_operand_type_t::SPV_OPERAND_TYPE_STORAGE_CLASS, + {SpvStorageClassFunction}}})); + new_vars->push_back(std::move(var_inst)); + } + get_decoration_mgr()->CloneDecorations(calleeFn->result_id(), returnVarId); + return returnVarId; +} + +bool InlinePass::IsSameBlockOp(const ir::Instruction* inst) const { + return inst->opcode() == SpvOpSampledImage || inst->opcode() == SpvOpImage; +} + +void InlinePass::CloneSameBlockOps( + std::unique_ptr* inst, + std::unordered_map* postCallSB, + std::unordered_map* preCallSB, + std::unique_ptr* block_ptr) { + (*inst)->ForEachInId( + [&postCallSB, &preCallSB, &block_ptr, this](uint32_t* iid) { + const auto mapItr = (*postCallSB).find(*iid); + if (mapItr == (*postCallSB).end()) { + const auto mapItr2 = (*preCallSB).find(*iid); + if (mapItr2 != (*preCallSB).end()) { + // Clone pre-call same-block ops, map result id. + const ir::Instruction* inInst = mapItr2->second; + std::unique_ptr sb_inst( + inInst->Clone(inInst->context())); + CloneSameBlockOps(&sb_inst, postCallSB, preCallSB, block_ptr); + const uint32_t rid = sb_inst->result_id(); + const uint32_t nid = this->TakeNextId(); + get_decoration_mgr()->CloneDecorations(rid, nid); + sb_inst->SetResultId(nid); + (*postCallSB)[rid] = nid; + *iid = nid; + (*block_ptr)->AddInstruction(std::move(sb_inst)); + } + } else { + // Reset same-block op operand. + *iid = mapItr->second; + } + }); +} + +void InlinePass::GenInlineCode( + std::vector>* new_blocks, + std::vector>* new_vars, + ir::BasicBlock::iterator call_inst_itr, + ir::UptrVectorIterator call_block_itr) { + // Map from all ids in the callee to their equivalent id in the caller + // as callee instructions are copied into caller. + std::unordered_map callee2caller; + // Pre-call same-block insts + std::unordered_map preCallSB; + // Post-call same-block op ids + std::unordered_map postCallSB; + + // Invalidate the def-use chains. They are not kept up to date while + // inlining. However, certain calls try to keep them up-to-date if they are + // valid. These operations can fail. + context()->InvalidateAnalyses(ir::IRContext::kAnalysisDefUse); + + ir::Function* calleeFn = id2function_[call_inst_itr->GetSingleWordOperand( + kSpvFunctionCallFunctionId)]; + + // Check for multiple returns in the callee. + auto fi = multi_return_funcs_.find(calleeFn->result_id()); + const bool multiReturn = fi != multi_return_funcs_.end(); + + // Map parameters to actual arguments. + MapParams(calleeFn, call_inst_itr, &callee2caller); + + // Define caller local variables for all callee variables and create map to + // them. + CloneAndMapLocals(calleeFn, new_vars, &callee2caller); + + // Create return var if needed. + uint32_t returnVarId = CreateReturnVar(calleeFn, new_vars); + + // Create set of callee result ids. Used to detect forward references + std::unordered_set callee_result_ids; + calleeFn->ForEachInst([&callee_result_ids](const ir::Instruction* cpi) { + const uint32_t rid = cpi->result_id(); + if (rid != 0) callee_result_ids.insert(rid); + }); + + // If the caller is in a single-block loop, and the callee has multiple + // blocks, then the normal inlining logic will place the OpLoopMerge in + // the last of several blocks in the loop. Instead, it should be placed + // at the end of the first block. First determine if the caller is in a + // single block loop. We'll wait to move the OpLoopMerge until the end + // of the regular inlining logic, and only if necessary. + bool caller_is_single_block_loop = false; + bool caller_is_loop_header = false; + if (auto* loop_merge = call_block_itr->GetLoopMergeInst()) { + caller_is_loop_header = true; + caller_is_single_block_loop = + call_block_itr->id() == + loop_merge->GetSingleWordInOperand(kSpvLoopMergeContinueTargetIdInIdx); + } + + bool callee_begins_with_structured_header = + (*(calleeFn->begin())).GetMergeInst() != nullptr; + + // Clone and map callee code. Copy caller block code to beginning of + // first block and end of last block. + bool prevInstWasReturn = false; + uint32_t singleTripLoopHeaderId = 0; + uint32_t singleTripLoopContinueId = 0; + uint32_t returnLabelId = 0; + bool multiBlocks = false; + const uint32_t calleeTypeId = calleeFn->type_id(); + // new_blk_ptr is a new basic block in the caller. New instructions are + // written to it. It is created when we encounter the OpLabel + // of the first callee block. It is appended to new_blocks only when + // it is complete. + std::unique_ptr new_blk_ptr; + calleeFn->ForEachInst([&new_blocks, &callee2caller, &call_block_itr, + &call_inst_itr, &new_blk_ptr, &prevInstWasReturn, + &returnLabelId, &returnVarId, caller_is_loop_header, + callee_begins_with_structured_header, &calleeTypeId, + &multiBlocks, &postCallSB, &preCallSB, multiReturn, + &singleTripLoopHeaderId, &singleTripLoopContinueId, + &callee_result_ids, this](const ir::Instruction* cpi) { + switch (cpi->opcode()) { + case SpvOpFunction: + case SpvOpFunctionParameter: + case SpvOpVariable: + // Already processed + break; + case SpvOpLabel: { + // If previous instruction was early return, insert branch + // instruction to return block. + if (prevInstWasReturn) { + if (returnLabelId == 0) returnLabelId = this->TakeNextId(); + AddBranch(returnLabelId, &new_blk_ptr); + prevInstWasReturn = false; + } + // Finish current block (if it exists) and get label for next block. + uint32_t labelId; + bool firstBlock = false; + if (new_blk_ptr != nullptr) { + new_blocks->push_back(std::move(new_blk_ptr)); + // If result id is already mapped, use it, otherwise get a new + // one. + const uint32_t rid = cpi->result_id(); + const auto mapItr = callee2caller.find(rid); + labelId = (mapItr != callee2caller.end()) ? mapItr->second + : this->TakeNextId(); + } else { + // First block needs to use label of original block + // but map callee label in case of phi reference. + labelId = call_block_itr->id(); + callee2caller[cpi->result_id()] = labelId; + firstBlock = true; + } + // Create first/next block. + new_blk_ptr.reset(new ir::BasicBlock(NewLabel(labelId))); + if (firstBlock) { + // Copy contents of original caller block up to call instruction. + for (auto cii = call_block_itr->begin(); cii != call_inst_itr; + cii = call_block_itr->begin()) { + ir::Instruction* inst = &*cii; + inst->RemoveFromList(); + std::unique_ptr cp_inst(inst); + // Remember same-block ops for possible regeneration. + if (IsSameBlockOp(&*cp_inst)) { + auto* sb_inst_ptr = cp_inst.get(); + preCallSB[cp_inst->result_id()] = sb_inst_ptr; + } + new_blk_ptr->AddInstruction(std::move(cp_inst)); + } + if (caller_is_loop_header && callee_begins_with_structured_header) { + // We can't place both the caller's merge instruction and another + // merge instruction in the same block. So split the calling block. + // Insert an unconditional branch to a new guard block. Later, + // once we know the ID of the last block, we will move the caller's + // OpLoopMerge from the last generated block into the first block. + // We also wait to avoid invalidating various iterators. + const auto guard_block_id = this->TakeNextId(); + AddBranch(guard_block_id, &new_blk_ptr); + new_blocks->push_back(std::move(new_blk_ptr)); + // Start the next block. + new_blk_ptr.reset(new ir::BasicBlock(NewLabel(guard_block_id))); + // Reset the mapping of the callee's entry block to point to + // the guard block. Do this so we can fix up phis later on to + // satisfy dominance. + callee2caller[cpi->result_id()] = guard_block_id; + } + // If callee has multiple returns, insert a header block for + // single-trip loop that will encompass callee code. Start postheader + // block. + // + // Note: Consider the following combination: + // - the caller is a single block loop + // - the callee does not begin with a structure header + // - the callee has multiple returns. + // We still need to split the caller block and insert a guard block. + // But we only need to do it once. We haven't done it yet, but the + // single-trip loop header will serve the same purpose. + if (multiReturn) { + singleTripLoopHeaderId = this->TakeNextId(); + AddBranch(singleTripLoopHeaderId, &new_blk_ptr); + new_blocks->push_back(std::move(new_blk_ptr)); + new_blk_ptr.reset( + new ir::BasicBlock(NewLabel(singleTripLoopHeaderId))); + returnLabelId = this->TakeNextId(); + singleTripLoopContinueId = this->TakeNextId(); + AddLoopMerge(returnLabelId, singleTripLoopContinueId, &new_blk_ptr); + uint32_t postHeaderId = this->TakeNextId(); + AddBranch(postHeaderId, &new_blk_ptr); + new_blocks->push_back(std::move(new_blk_ptr)); + new_blk_ptr.reset(new ir::BasicBlock(NewLabel(postHeaderId))); + multiBlocks = true; + // Reset the mapping of the callee's entry block to point to + // the post-header block. Do this so we can fix up phis later + // on to satisfy dominance. + callee2caller[cpi->result_id()] = postHeaderId; + } + } else { + multiBlocks = true; + } + } break; + case SpvOpReturnValue: { + // Store return value to return variable. + assert(returnVarId != 0); + uint32_t valId = cpi->GetInOperand(kSpvReturnValueId).words[0]; + const auto mapItr = callee2caller.find(valId); + if (mapItr != callee2caller.end()) { + valId = mapItr->second; + } + AddStore(returnVarId, valId, &new_blk_ptr); + + // Remember we saw a return; if followed by a label, will need to + // insert branch. + prevInstWasReturn = true; + } break; + case SpvOpReturn: { + // Remember we saw a return; if followed by a label, will need to + // insert branch. + prevInstWasReturn = true; + } break; + case SpvOpFunctionEnd: { + // If there was an early return, we generated a return label id + // for it. Now we have to generate the return block with that Id. + if (returnLabelId != 0) { + // If previous instruction was return, insert branch instruction + // to return block. + if (prevInstWasReturn) AddBranch(returnLabelId, &new_blk_ptr); + if (multiReturn) { + // If we generated a loop header to for the single-trip loop + // to accommodate multiple returns, insert the continue + // target block now, with a false branch back to the loop header. + new_blocks->push_back(std::move(new_blk_ptr)); + new_blk_ptr.reset( + new ir::BasicBlock(NewLabel(singleTripLoopContinueId))); + AddBranchCond(GetFalseId(), singleTripLoopHeaderId, returnLabelId, + &new_blk_ptr); + } + // Generate the return block. + new_blocks->push_back(std::move(new_blk_ptr)); + new_blk_ptr.reset(new ir::BasicBlock(NewLabel(returnLabelId))); + multiBlocks = true; + } + // Load return value into result id of call, if it exists. + if (returnVarId != 0) { + const uint32_t resId = call_inst_itr->result_id(); + assert(resId != 0); + AddLoad(calleeTypeId, resId, returnVarId, &new_blk_ptr); + } + // Copy remaining instructions from caller block. + for (ir::Instruction* inst = call_inst_itr->NextNode(); inst; + inst = call_inst_itr->NextNode()) { + inst->RemoveFromList(); + std::unique_ptr cp_inst(inst); + // If multiple blocks generated, regenerate any same-block + // instruction that has not been seen in this last block. + if (multiBlocks) { + CloneSameBlockOps(&cp_inst, &postCallSB, &preCallSB, &new_blk_ptr); + // Remember same-block ops in this block. + if (IsSameBlockOp(&*cp_inst)) { + const uint32_t rid = cp_inst->result_id(); + postCallSB[rid] = rid; + } + } + new_blk_ptr->AddInstruction(std::move(cp_inst)); + } + // Finalize inline code. + new_blocks->push_back(std::move(new_blk_ptr)); + } break; + default: { + // Copy callee instruction and remap all input Ids. + std::unique_ptr cp_inst(cpi->Clone(context())); + cp_inst->ForEachInId([&callee2caller, &callee_result_ids, + this](uint32_t* iid) { + const auto mapItr = callee2caller.find(*iid); + if (mapItr != callee2caller.end()) { + *iid = mapItr->second; + } else if (callee_result_ids.find(*iid) != callee_result_ids.end()) { + // Forward reference. Allocate a new id, map it, + // use it and check for it when remapping result ids + const uint32_t nid = this->TakeNextId(); + callee2caller[*iid] = nid; + *iid = nid; + } + }); + // If result id is non-zero, remap it. If already mapped, use mapped + // value, else use next id. + const uint32_t rid = cp_inst->result_id(); + if (rid != 0) { + const auto mapItr = callee2caller.find(rid); + uint32_t nid; + if (mapItr != callee2caller.end()) { + nid = mapItr->second; + } else { + nid = this->TakeNextId(); + callee2caller[rid] = nid; + } + cp_inst->SetResultId(nid); + get_decoration_mgr()->CloneDecorations(rid, nid); + } + new_blk_ptr->AddInstruction(std::move(cp_inst)); + } break; + } + }); + + if (caller_is_loop_header && (new_blocks->size() > 1)) { + // Move the OpLoopMerge from the last block back to the first, where + // it belongs. + auto& first = new_blocks->front(); + auto& last = new_blocks->back(); + assert(first != last); + + // Insert a modified copy of the loop merge into the first block. + auto loop_merge_itr = last->tail(); + --loop_merge_itr; + assert(loop_merge_itr->opcode() == SpvOpLoopMerge); + std::unique_ptr cp_inst(loop_merge_itr->Clone(context())); + if (caller_is_single_block_loop) { + // Also, update its continue target to point to the last block. + cp_inst->SetInOperand(kSpvLoopMergeContinueTargetIdInIdx, {last->id()}); + } + first->tail().InsertBefore(std::move(cp_inst)); + + // Remove the loop merge from the last block. + loop_merge_itr->RemoveFromList(); + delete &*loop_merge_itr; + } + + // Update block map given replacement blocks. + for (auto& blk : *new_blocks) { + id2block_[blk->id()] = &*blk; + } +} + +bool InlinePass::IsInlinableFunctionCall(const ir::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(); +} + +void InlinePass::UpdateSucceedingPhis( + std::vector>& new_blocks) { + const auto firstBlk = new_blocks.begin(); + const auto lastBlk = new_blocks.end() - 1; + const uint32_t firstId = (*firstBlk)->id(); + const uint32_t lastId = (*lastBlk)->id(); + const ir::BasicBlock& const_last_block = *lastBlk->get(); + const_last_block.ForEachSuccessorLabel( + [&firstId, &lastId, this](const uint32_t succ) { + ir::BasicBlock* sbp = this->id2block_[succ]; + sbp->ForEachPhiInst([&firstId, &lastId](ir::Instruction* phi) { + phi->ForEachInId([&firstId, &lastId](uint32_t* id) { + if (*id == firstId) *id = lastId; + }); + }); + }); +} + +bool InlinePass::HasMultipleReturns(ir::Function* func) { + bool seenReturn = false; + bool multipleReturns = false; + for (auto& blk : *func) { + auto terminal_ii = blk.cend(); + --terminal_ii; + if (terminal_ii->opcode() == SpvOpReturn || + terminal_ii->opcode() == SpvOpReturnValue) { + if (seenReturn) { + multipleReturns = true; + break; + } + seenReturn = true; + } + } + return multipleReturns; +} + +void InlinePass::ComputeStructuredSuccessors(ir::Function* func) { + // If header, make merge block first successor. + for (auto& blk : *func) { + uint32_t mbid = blk.MergeBlockIdIfAny(); + if (mbid != 0) { + block2structured_succs_[&blk].push_back(id2block_[mbid]); + } + + // Add true successors. + const auto& const_blk = blk; + const_blk.ForEachSuccessorLabel([&blk, this](const uint32_t sbid) { + block2structured_succs_[&blk].push_back(id2block_[sbid]); + }); + } +} + +InlinePass::GetBlocksFunction InlinePass::StructuredSuccessorsFunction() { + return [this](const ir::BasicBlock* block) { + return &(block2structured_succs_[block]); + }; +} + +bool InlinePass::HasNoReturnInLoop(ir::Function* func) { + // If control not structured, do not do loop/return analysis + // TODO: Analyze returns in non-structured control flow + if (!context()->get_feature_mgr()->HasCapability(SpvCapabilityShader)) + return false; + // Compute structured block order. This order has the property + // that dominators are before all blocks they dominate and merge blocks + // are after all blocks that are in the control constructs of their header. + ComputeStructuredSuccessors(func); + auto ignore_block = [](cbb_ptr) {}; + auto ignore_edge = [](cbb_ptr, cbb_ptr) {}; + std::list structuredOrder; + spvtools::CFA::DepthFirstTraversal( + &*func->begin(), StructuredSuccessorsFunction(), ignore_block, + [&](cbb_ptr b) { structuredOrder.push_front(b); }, ignore_edge); + // Search for returns in loops. Only need to track outermost loop + bool return_in_loop = false; + uint32_t outerLoopMergeId = 0; + for (auto& blk : structuredOrder) { + // Exiting current outer loop + if (blk->id() == outerLoopMergeId) outerLoopMergeId = 0; + // Return block + auto terminal_ii = blk->cend(); + --terminal_ii; + if (terminal_ii->opcode() == SpvOpReturn || + terminal_ii->opcode() == SpvOpReturnValue) { + if (outerLoopMergeId != 0) { + return_in_loop = true; + break; + } + } else if (terminal_ii != blk->cbegin()) { + auto merge_ii = terminal_ii; + --merge_ii; + // Entering outermost loop + if (merge_ii->opcode() == SpvOpLoopMerge && outerLoopMergeId == 0) + outerLoopMergeId = + merge_ii->GetSingleWordOperand(kSpvLoopMergeMergeBlockId); + } + } + return !return_in_loop; +} + +void InlinePass::AnalyzeReturns(ir::Function* func) { + // Look for multiple returns + if (!HasMultipleReturns(func)) { + no_return_in_loop_.insert(func->result_id()); + return; + } + multi_return_funcs_.insert(func->result_id()); + // If multiple returns, see if any are in a loop + if (HasNoReturnInLoop(func)) no_return_in_loop_.insert(func->result_id()); +} + +bool InlinePass::IsInlinableFunction(ir::Function* func) { + // We can only inline a function if it has blocks. + if (func->cbegin() == func->cend()) return false; + // Do not inline functions with returns in loops. Currently early return + // functions are inlined by wrapping them in a one trip loop and implementing + // 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. + AnalyzeReturns(func); + return no_return_in_loop_.find(func->result_id()) != + no_return_in_loop_.cend(); +} + +void InlinePass::InitializeInline(ir::IRContext* c) { + InitializeProcessing(c); + + false_id_ = 0; + + // clear collections + id2function_.clear(); + id2block_.clear(); + block2structured_succs_.clear(); + inlinable_.clear(); + no_return_in_loop_.clear(); + multi_return_funcs_.clear(); + + for (auto& fn : *get_module()) { + // Initialize function and block maps. + id2function_[fn.result_id()] = &fn; + for (auto& blk : fn) { + id2block_[blk.id()] = &blk; + } + // Compute inlinability + if (IsInlinableFunction(&fn)) inlinable_.insert(fn.result_id()); + } +} + +InlinePass::InlinePass() {} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/inline_pass.h b/3rdparty/spirv-tools/source/opt/inline_pass.h new file mode 100644 index 000000000..dd2d0c7d0 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/inline_pass.h @@ -0,0 +1,191 @@ +// Copyright (c) 2017 The Khronos Group Inc. +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_INLINE_PASS_H_ +#define LIBSPIRV_OPT_INLINE_PASS_H_ + +#include +#include +#include +#include +#include + +#include "decoration_manager.h" +#include "module.h" +#include "pass.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class InlinePass : public Pass { + using cbb_ptr = const ir::BasicBlock*; + + public: + using GetBlocksFunction = + std::function*(const ir::BasicBlock*)>; + + InlinePass(); + virtual ~InlinePass() = default; + + protected: + + // Add pointer to type to module and return resultId. + uint32_t AddPointerToType(uint32_t type_id, SpvStorageClass storage_class); + + // Add unconditional branch to labelId to end of block block_ptr. + void AddBranch(uint32_t labelId, std::unique_ptr* block_ptr); + + // Add conditional branch to end of block |block_ptr|. + void AddBranchCond(uint32_t cond_id, uint32_t true_id, uint32_t false_id, + std::unique_ptr* block_ptr); + + // Add unconditional branch to labelId to end of block block_ptr. + void AddLoopMerge(uint32_t merge_id, uint32_t continue_id, + std::unique_ptr* block_ptr); + + // Add store of valId to ptrId to end of block block_ptr. + void AddStore(uint32_t ptrId, uint32_t valId, + std::unique_ptr* block_ptr); + + // Add load of ptrId into resultId to end of block block_ptr. + void AddLoad(uint32_t typeId, uint32_t resultId, uint32_t ptrId, + std::unique_ptr* block_ptr); + + // Return new label. + std::unique_ptr NewLabel(uint32_t label_id); + + // Returns the id for the boolean false value. Looks in the module first + // and creates it if not found. Remembers it for future calls. + uint32_t GetFalseId(); + + // Map callee params to caller args + void MapParams(ir::Function* calleeFn, ir::BasicBlock::iterator call_inst_itr, + std::unordered_map* callee2caller); + + // Clone and map callee locals + void CloneAndMapLocals( + ir::Function* calleeFn, + std::vector>* new_vars, + std::unordered_map* callee2caller); + + // Create return variable for callee clone code if needed. Return id + // if created, otherwise 0. + uint32_t CreateReturnVar( + ir::Function* calleeFn, + std::vector>* new_vars); + + // Return true if instruction must be in the same block that its result + // is used. + bool IsSameBlockOp(const ir::Instruction* inst) const; + + // Clone operands which must be in same block as consumer instructions. + // Look in preCallSB for instructions that need cloning. Look in + // postCallSB for instructions already cloned. Add cloned instruction + // to postCallSB. + void CloneSameBlockOps( + std::unique_ptr* inst, + std::unordered_map* postCallSB, + std::unordered_map* preCallSB, + std::unique_ptr* block_ptr); + + // Return in new_blocks the result of inlining the call at call_inst_itr + // within its block at call_block_itr. The block at call_block_itr can + // just be replaced with the blocks in new_blocks. Any additional branches + // are avoided. Debug instructions are cloned along with their callee + // instructions. Early returns are replaced by a store to a local return + // variable and a branch to a (created) exit block where the local variable + // is returned. Formal parameters are trivially mapped to their actual + // parameters. Note that the first block in new_blocks retains the label + // of the original calling block. Also note that if an exit block is + // created, it is the last block of new_blocks. + // + // Also return in new_vars additional OpVariable instructions required by + // and to be inserted into the caller function after the block at + // call_block_itr is replaced with new_blocks. + void GenInlineCode(std::vector>* new_blocks, + std::vector>* new_vars, + ir::BasicBlock::iterator call_inst_itr, + ir::UptrVectorIterator call_block_itr); + + // Return true if |inst| is a function call that can be inlined. + bool IsInlinableFunctionCall(const ir::Instruction* inst); + + // Compute structured successors for function |func|. + // A block's structured successors are the blocks it branches to + // together with its declared merge block if it has one. + // When order matters, the merge block always appears first. + // This assures correct depth first search in the presence of early + // returns and kills. If the successor vector contain duplicates + // if the merge block, they are safely ignored by DFS. + void ComputeStructuredSuccessors(ir::Function* func); + + // Return function to return ordered structure successors for a given block + // Assumes ComputeStructuredSuccessors() has been called. + GetBlocksFunction StructuredSuccessorsFunction(); + + // Return true if |func| has multiple returns + bool HasMultipleReturns(ir::Function* func); + + // Return true if |func| has no return in a loop. The current analysis + // requires structured control flow, so return false if control flow not + // structured ie. module is not a shader. + bool HasNoReturnInLoop(ir::Function* func); + + // Find all functions with multiple returns and no returns in loops + void AnalyzeReturns(ir::Function* func); + + // Return true if |func| is a function that can be inlined. + bool IsInlinableFunction(ir::Function* func); + + // Update phis in succeeding blocks to point to new last block + void UpdateSucceedingPhis( + std::vector>& new_blocks); + + // Initialize state for optimization of |module| + void InitializeInline(ir::IRContext* c); + + // Map from function's result id to function. + std::unordered_map id2function_; + + // Map from block's label id to block. TODO(dnovillo): This is superfluous wrt + // opt::CFG. It has functionality not present in opt::CFG. Consolidate. + std::unordered_map id2block_; + + // Set of ids of functions with multiple returns. + std::set multi_return_funcs_; + + // Set of ids of functions with no returns in loop + std::set no_return_in_loop_; + + // Set of ids of inlinable functions + std::set inlinable_; + + // result id for OpConstantFalse + uint32_t false_id_; + + // Map from block to its structured successor blocks. See + // ComputeStructuredSuccessors() for definition. TODO(dnovillo): This is + // superfluous wrt opt::CFG, but it seems to be computed in a slightly + // different way in the inliner. Can these be consolidated? + std::unordered_map> + block2structured_succs_; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_INLINE_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/insert_extract_elim.cpp b/3rdparty/spirv-tools/source/opt/insert_extract_elim.cpp new file mode 100644 index 000000000..2d0d8a24b --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/insert_extract_elim.cpp @@ -0,0 +1,221 @@ +// Copyright (c) 2017 The Khronos Group Inc. +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "insert_extract_elim.h" + +#include "composite.h" +#include "ir_context.h" +#include "iterator.h" +#include "latest_version_glsl_std_450_header.h" + +#include + +namespace spvtools { +namespace opt { + +namespace { + +const uint32_t kConstantValueInIdx = 0; +const uint32_t kExtractCompositeIdInIdx = 0; +const uint32_t kInsertObjectIdInIdx = 0; +const uint32_t kInsertCompositeIdInIdx = 1; +const uint32_t kVectorShuffleVec1IdInIdx = 0; +const uint32_t kVectorShuffleVec2IdInIdx = 1; +const uint32_t kVectorShuffleCompsInIdx = 2; +const uint32_t kTypeVectorCompTypeIdInIdx = 0; +const uint32_t kTypeVectorLengthInIdx = 1; +const uint32_t kTypeFloatWidthInIdx = 0; +const uint32_t kExtInstSetIdInIdx = 0; +const uint32_t kExtInstInstructionInIdx = 1; +const uint32_t kFMixXIdInIdx = 2; +const uint32_t kFMixYIdInIdx = 3; +const uint32_t kFMixAIdInIdx = 4; + +} // anonymous namespace + +uint32_t InsertExtractElimPass::DoExtract(ir::Instruction* compInst, + std::vector* pExtIndices, + uint32_t extOffset) { + ir::Instruction* cinst = compInst; + uint32_t cid = 0; + uint32_t replId = 0; + while (true) { + if (cinst->opcode() == SpvOpCompositeInsert) { + if (ExtInsMatch(*pExtIndices, cinst, extOffset)) { + // Match! Use inserted value as replacement + replId = cinst->GetSingleWordInOperand(kInsertObjectIdInIdx); + break; + } else if (ExtInsConflict(*pExtIndices, cinst, extOffset)) { + // If extract has fewer indices than the insert, stop searching. + // Otherwise increment offset of extract indices considered and + // continue searching through the inserted value + if (pExtIndices->size() - extOffset < cinst->NumInOperands() - 2) { + break; + } else { + extOffset += cinst->NumInOperands() - 2; + cid = cinst->GetSingleWordInOperand(kInsertObjectIdInIdx); + } + } else { + // Consider next composite in insert chain + cid = cinst->GetSingleWordInOperand(kInsertCompositeIdInIdx); + } + } else if (cinst->opcode() == SpvOpVectorShuffle) { + // Get length of vector1 + uint32_t v1_id = cinst->GetSingleWordInOperand(kVectorShuffleVec1IdInIdx); + ir::Instruction* v1_inst = get_def_use_mgr()->GetDef(v1_id); + uint32_t v1_type_id = v1_inst->type_id(); + ir::Instruction* v1_type_inst = get_def_use_mgr()->GetDef(v1_type_id); + uint32_t v1_len = + v1_type_inst->GetSingleWordInOperand(kTypeVectorLengthInIdx); + // Get shuffle idx + uint32_t comp_idx = (*pExtIndices)[extOffset]; + uint32_t shuffle_idx = + cinst->GetSingleWordInOperand(kVectorShuffleCompsInIdx + comp_idx); + // If undefined, give up + // TODO(greg-lunarg): Return OpUndef + if (shuffle_idx == 0xFFFFFFFF) break; + if (shuffle_idx < v1_len) { + cid = v1_id; + (*pExtIndices)[extOffset] = shuffle_idx; + } else { + cid = cinst->GetSingleWordInOperand(kVectorShuffleVec2IdInIdx); + (*pExtIndices)[extOffset] = shuffle_idx - v1_len; + } + } else if (cinst->opcode() == SpvOpExtInst && + cinst->GetSingleWordInOperand(kExtInstSetIdInIdx) == + get_feature_mgr()->GetExtInstImportId_GLSLstd450() && + cinst->GetSingleWordInOperand(kExtInstInstructionInIdx) == + GLSLstd450FMix) { + // If mixing value component is 0 or 1 we just match with x or y. + // Otherwise give up. + uint32_t comp_idx = (*pExtIndices)[extOffset]; + std::vector aIndices = {comp_idx}; + uint32_t a_id = cinst->GetSingleWordInOperand(kFMixAIdInIdx); + ir::Instruction* a_inst = get_def_use_mgr()->GetDef(a_id); + uint32_t a_comp_id = DoExtract(a_inst, &aIndices, 0); + if (a_comp_id == 0) break; + ir::Instruction* a_comp_inst = get_def_use_mgr()->GetDef(a_comp_id); + if (a_comp_inst->opcode() != SpvOpConstant) break; + // If a value is not 32-bit, give up + uint32_t a_comp_type_id = a_comp_inst->type_id(); + ir::Instruction* a_comp_type = get_def_use_mgr()->GetDef(a_comp_type_id); + if (a_comp_type->GetSingleWordInOperand(kTypeFloatWidthInIdx) != 32) + break; + uint32_t u = a_comp_inst->GetSingleWordInOperand(kConstantValueInIdx); + float* fp = reinterpret_cast(&u); + if (*fp == 0.0) + cid = cinst->GetSingleWordInOperand(kFMixXIdInIdx); + else if (*fp == 1.0) + cid = cinst->GetSingleWordInOperand(kFMixYIdInIdx); + else + break; + } else { + break; + } + cinst = get_def_use_mgr()->GetDef(cid); + } + // If search ended with CompositeConstruct or ConstantComposite + // and the extract has one index, return the appropriate component. + // TODO(greg-lunarg): Handle multiple-indices, ConstantNull, special + // vector composition, and additional CompositeInsert. + if (replId == 0 && + (cinst->opcode() == SpvOpCompositeConstruct || + cinst->opcode() == SpvOpConstantComposite) && + (*pExtIndices).size() - extOffset == 1) { + uint32_t compIdx = (*pExtIndices)[extOffset]; + // If a vector CompositeConstruct we make sure all preceding + // components are of component type (not vector composition). + uint32_t ctype_id = cinst->type_id(); + ir::Instruction* ctype_inst = get_def_use_mgr()->GetDef(ctype_id); + if (ctype_inst->opcode() == SpvOpTypeVector && + cinst->opcode() == SpvOpConstantComposite) { + uint32_t vec_comp_type_id = + ctype_inst->GetSingleWordInOperand(kTypeVectorCompTypeIdInIdx); + if (compIdx < cinst->NumInOperands()) { + uint32_t i = 0; + for (; i <= compIdx; i++) { + uint32_t compId = cinst->GetSingleWordInOperand(i); + ir::Instruction* componentInst = get_def_use_mgr()->GetDef(compId); + if (componentInst->type_id() != vec_comp_type_id) break; + } + if (i > compIdx) replId = cinst->GetSingleWordInOperand(compIdx); + } + } else { + replId = cinst->GetSingleWordInOperand(compIdx); + } + } + return replId; +} + +bool InsertExtractElimPass::EliminateInsertExtract(ir::Function* func) { + bool modified = false; + for (auto bi = func->begin(); bi != func->end(); ++bi) { + ir::Instruction* inst = &*bi->begin(); + while (inst) { + switch (inst->opcode()) { + case SpvOpCompositeExtract: { + uint32_t cid = inst->GetSingleWordInOperand(kExtractCompositeIdInIdx); + ir::Instruction* cinst = get_def_use_mgr()->GetDef(cid); + // Capture extract indices + std::vector extIndices; + uint32_t icnt = 0; + inst->ForEachInOperand([&icnt, &extIndices](const uint32_t* idp) { + if (icnt > 0) extIndices.push_back(*idp); + ++icnt; + }); + // Offset of extract indices being compared to insert indices. + // Offset increases as indices are matched. + uint32_t replId = DoExtract(cinst, &extIndices, 0); + if (replId != 0) { + const uint32_t extId = inst->result_id(); + (void)context()->ReplaceAllUsesWith(extId, replId); + inst = context()->KillInst(inst); + modified = true; + } else { + inst = inst->NextNode(); + } + } break; + default: + inst = inst->NextNode(); + break; + } + } + } + return modified; +} + +void InsertExtractElimPass::Initialize(ir::IRContext* c) { + InitializeProcessing(c); +} + +Pass::Status InsertExtractElimPass::ProcessImpl() { + // Process all entry point functions. + ProcessFunction pfn = [this](ir::Function* fp) { + return EliminateInsertExtract(fp); + }; + bool modified = ProcessEntryPointCallTree(pfn, get_module()); + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; +} + +InsertExtractElimPass::InsertExtractElimPass() {} + +Pass::Status InsertExtractElimPass::Process(ir::IRContext* c) { + Initialize(c); + return ProcessImpl(); +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/insert_extract_elim.h b/3rdparty/spirv-tools/source/opt/insert_extract_elim.h new file mode 100644 index 000000000..3f1ba00bb --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/insert_extract_elim.h @@ -0,0 +1,61 @@ +// Copyright (c) 2017 The Khronos Group Inc. +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_INSERT_EXTRACT_ELIM_PASS_H_ +#define LIBSPIRV_OPT_INSERT_EXTRACT_ELIM_PASS_H_ + +#include +#include +#include +#include +#include + +#include "basic_block.h" +#include "def_use_manager.h" +#include "ir_context.h" +#include "mem_pass.h" +#include "module.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class InsertExtractElimPass : public MemPass { + public: + InsertExtractElimPass(); + const char* name() const override { return "eliminate-insert-extract"; } + Status Process(ir::IRContext*) override; + + private: + // Return id of component of |cinst| specified by |extIndices| starting with + // index at |extOffset|. Return 0 if indices cannot be matched exactly. + uint32_t DoExtract(ir::Instruction* cinst, std::vector* extIndices, + uint32_t extOffset); + + // Look for OpExtract on sequence of OpInserts in |func|. If there is a + // reaching insert which corresponds to the indices of the extract, replace + // the extract with the value that is inserted. Also resolve extracts from + // CompositeConstruct or ConstantComposite. + bool EliminateInsertExtract(ir::Function* func); + + void Initialize(ir::IRContext* c); + Pass::Status ProcessImpl(); +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_INSERT_EXTRACT_ELIM_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/instruction.cpp b/3rdparty/spirv-tools/source/opt/instruction.cpp new file mode 100644 index 000000000..ba8413e35 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/instruction.cpp @@ -0,0 +1,600 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "instruction.h" + +#include + +#include "disassemble.h" +#include "fold.h" +#include "ir_context.h" +#include "reflect.h" + +namespace spvtools { +namespace ir { + +namespace { +// Indices used to get particular operands out of instructions using InOperand. +const uint32_t kTypeImageDimIndex = 1; +const uint32_t kLoadBaseIndex = 0; +const uint32_t kVariableStorageClassIndex = 0; +const uint32_t kTypeImageSampledIndex = 5; +} // namespace + +Instruction::Instruction(IRContext* c) + : utils::IntrusiveNodeBase(), + context_(c), + opcode_(SpvOpNop), + type_id_(0), + result_id_(0), + unique_id_(c->TakeNextUniqueId()) {} + +Instruction::Instruction(IRContext* c, SpvOp op) + : utils::IntrusiveNodeBase(), + context_(c), + opcode_(op), + type_id_(0), + result_id_(0), + unique_id_(c->TakeNextUniqueId()) {} + +Instruction::Instruction(IRContext* c, const spv_parsed_instruction_t& inst, + std::vector&& dbg_line) + : context_(c), + opcode_(static_cast(inst.opcode)), + type_id_(inst.type_id), + result_id_(inst.result_id), + unique_id_(c->TakeNextUniqueId()), + dbg_line_insts_(std::move(dbg_line)) { + assert((!IsDebugLineInst(opcode_) || dbg_line.empty()) && + "Op(No)Line attaching to Op(No)Line found"); + for (uint32_t i = 0; i < inst.num_operands; ++i) { + const auto& current_payload = inst.operands[i]; + std::vector words( + inst.words + current_payload.offset, + inst.words + current_payload.offset + current_payload.num_words); + operands_.emplace_back(current_payload.type, std::move(words)); + } +} + +Instruction::Instruction(IRContext* c, SpvOp op, uint32_t ty_id, + uint32_t res_id, + const std::vector& in_operands) + : utils::IntrusiveNodeBase(), + context_(c), + opcode_(op), + type_id_(ty_id), + result_id_(res_id), + unique_id_(c->TakeNextUniqueId()), + operands_() { + if (type_id_ != 0) { + operands_.emplace_back(spv_operand_type_t::SPV_OPERAND_TYPE_TYPE_ID, + std::initializer_list{type_id_}); + } + if (result_id_ != 0) { + operands_.emplace_back(spv_operand_type_t::SPV_OPERAND_TYPE_RESULT_ID, + std::initializer_list{result_id_}); + } + operands_.insert(operands_.end(), in_operands.begin(), in_operands.end()); +} + +Instruction::Instruction(Instruction&& that) + : utils::IntrusiveNodeBase(), + opcode_(that.opcode_), + type_id_(that.type_id_), + result_id_(that.result_id_), + unique_id_(that.unique_id_), + operands_(std::move(that.operands_)), + dbg_line_insts_(std::move(that.dbg_line_insts_)) {} + +Instruction& Instruction::operator=(Instruction&& that) { + opcode_ = that.opcode_; + type_id_ = that.type_id_; + result_id_ = that.result_id_; + unique_id_ = that.unique_id_; + operands_ = std::move(that.operands_); + dbg_line_insts_ = std::move(that.dbg_line_insts_); + return *this; +} + +Instruction* Instruction::Clone(IRContext* c) const { + Instruction* clone = new Instruction(c); + clone->opcode_ = opcode_; + clone->type_id_ = type_id_; + clone->result_id_ = result_id_; + clone->unique_id_ = c->TakeNextUniqueId(); + clone->operands_ = operands_; + clone->dbg_line_insts_ = dbg_line_insts_; + return clone; +} + +uint32_t Instruction::GetSingleWordOperand(uint32_t index) const { + const auto& words = GetOperand(index).words; + assert(words.size() == 1 && "expected the operand only taking one word"); + return words.front(); +} + +uint32_t Instruction::NumInOperandWords() const { + uint32_t size = 0; + for (uint32_t i = TypeResultIdCount(); i < operands_.size(); ++i) + size += static_cast(operands_[i].words.size()); + return size; +} + +void Instruction::ToBinaryWithoutAttachedDebugInsts( + std::vector* binary) const { + const uint32_t num_words = 1 + NumOperandWords(); + binary->push_back((num_words << 16) | static_cast(opcode_)); + for (const auto& operand : operands_) + binary->insert(binary->end(), operand.words.begin(), operand.words.end()); +} + +void Instruction::ReplaceOperands(const std::vector& new_operands) { + operands_.clear(); + operands_.insert(operands_.begin(), new_operands.begin(), new_operands.end()); + operands_.shrink_to_fit(); +} + +bool Instruction::IsReadOnlyLoad() const { + if (IsLoad()) { + ir::Instruction* address_def = GetBaseAddress(); + if (!address_def || address_def->opcode() != SpvOpVariable) { + return false; + } + return address_def->IsReadOnlyVariable(); + } + return false; +} + +Instruction* Instruction::GetBaseAddress() const { + assert((IsLoad() || opcode() == SpvOpStore || opcode() == SpvOpAccessChain || + opcode() == SpvOpInBoundsAccessChain || opcode() == SpvOpCopyObject || + opcode() == SpvOpImageTexelPointer) && + "GetBaseAddress should only be called on instructions that take a " + "pointer or image."); + uint32_t base = GetSingleWordInOperand(kLoadBaseIndex); + ir::Instruction* base_inst = context()->get_def_use_mgr()->GetDef(base); + bool done = false; + while (!done) { + switch (base_inst->opcode()) { + case SpvOpAccessChain: + case SpvOpInBoundsAccessChain: + case SpvOpPtrAccessChain: + case SpvOpInBoundsPtrAccessChain: + case SpvOpImageTexelPointer: + case SpvOpCopyObject: + // All of these instructions have the base pointer use a base pointer + // in in-operand 0. + base = base_inst->GetSingleWordInOperand(0); + base_inst = context()->get_def_use_mgr()->GetDef(base); + break; + default: + done = true; + break; + } + } + + switch (opcode()) { + case SpvOpLoad: + case SpvOpStore: + case SpvOpAccessChain: + case SpvOpInBoundsAccessChain: + case SpvOpCopyObject: + // A load or store through a pointer. + assert(base_inst->IsValidBasePointer() && + "We cannot have a base pointer come from this load"); + break; + default: + // A load or store of an image. + assert(base_inst->IsValidBaseImage() && "We are expecting an image."); + break; + } + return base_inst; +} + +bool Instruction::IsReadOnlyVariable() const { + if (context()->get_feature_mgr()->HasCapability(SpvCapabilityShader)) + return IsReadOnlyVariableShaders(); + else + return IsReadOnlyVariableKernel(); +} + +bool Instruction::IsVulkanStorageImage() const { + if (opcode() != SpvOpTypePointer) { + return false; + } + + uint32_t storage_class = GetSingleWordInOperand(kVariableStorageClassIndex); + if (storage_class != SpvStorageClassUniformConstant) { + return false; + } + + ir::Instruction* base_type = + context()->get_def_use_mgr()->GetDef(GetSingleWordInOperand(1)); + if (base_type->opcode() != SpvOpTypeImage) { + return false; + } + + if (base_type->GetSingleWordInOperand(kTypeImageDimIndex) == SpvDimBuffer) { + return false; + } + + // Check if the image is sampled. If we do not know for sure that it is, + // then assume it is a storage image. + auto s = base_type->GetSingleWordInOperand(kTypeImageSampledIndex); + return s != 1; +} + +bool Instruction::IsVulkanSampledImage() const { + if (opcode() != SpvOpTypePointer) { + return false; + } + + uint32_t storage_class = GetSingleWordInOperand(kVariableStorageClassIndex); + if (storage_class != SpvStorageClassUniformConstant) { + return false; + } + + ir::Instruction* base_type = + context()->get_def_use_mgr()->GetDef(GetSingleWordInOperand(1)); + if (base_type->opcode() != SpvOpTypeImage) { + return false; + } + + if (base_type->GetSingleWordInOperand(kTypeImageDimIndex) == SpvDimBuffer) { + return false; + } + + // Check if the image is sampled. If we know for sure that it is, + // then return true. + auto s = base_type->GetSingleWordInOperand(kTypeImageSampledIndex); + return s == 1; +} + +bool Instruction::IsVulkanStorageTexelBuffer() const { + if (opcode() != SpvOpTypePointer) { + return false; + } + + uint32_t storage_class = GetSingleWordInOperand(kVariableStorageClassIndex); + if (storage_class != SpvStorageClassUniformConstant) { + return false; + } + + ir::Instruction* base_type = + context()->get_def_use_mgr()->GetDef(GetSingleWordInOperand(1)); + if (base_type->opcode() != SpvOpTypeImage) { + return false; + } + + if (base_type->GetSingleWordInOperand(kTypeImageDimIndex) != SpvDimBuffer) { + return false; + } + + // Check if the image is sampled. If we do not know for sure that it is, + // then assume it is a storage texel buffer. + return base_type->GetSingleWordInOperand(kTypeImageSampledIndex) != 1; +} + +bool Instruction::IsVulkanStorageBuffer() const { + // Is there a difference between a "Storage buffer" and a "dynamic storage + // buffer" in SPIR-V and do we care about the difference? + if (opcode() != SpvOpTypePointer) { + return false; + } + + ir::Instruction* base_type = + context()->get_def_use_mgr()->GetDef(GetSingleWordInOperand(1)); + + if (base_type->opcode() != SpvOpTypeStruct) { + return false; + } + + uint32_t storage_class = GetSingleWordInOperand(kVariableStorageClassIndex); + if (storage_class == SpvStorageClassUniform) { + bool is_buffer_block = false; + context()->get_decoration_mgr()->ForEachDecoration( + base_type->result_id(), SpvDecorationBufferBlock, + [&is_buffer_block](const ir::Instruction&) { is_buffer_block = true; }); + return is_buffer_block; + } else if (storage_class == SpvStorageClassStorageBuffer) { + bool is_block = false; + context()->get_decoration_mgr()->ForEachDecoration( + base_type->result_id(), SpvDecorationBlock, + [&is_block](const ir::Instruction&) { is_block = true; }); + return is_block; + } + return false; +} + +bool Instruction::IsVulkanUniformBuffer() const { + if (opcode() != SpvOpTypePointer) { + return false; + } + + uint32_t storage_class = GetSingleWordInOperand(kVariableStorageClassIndex); + if (storage_class != SpvStorageClassUniform) { + return false; + } + + ir::Instruction* base_type = + context()->get_def_use_mgr()->GetDef(GetSingleWordInOperand(1)); + if (base_type->opcode() != SpvOpTypeStruct) { + return false; + } + + bool is_block = false; + context()->get_decoration_mgr()->ForEachDecoration( + base_type->result_id(), SpvDecorationBlock, + [&is_block](const ir::Instruction&) { is_block = true; }); + return is_block; +} + +bool Instruction::IsReadOnlyVariableShaders() const { + uint32_t storage_class = GetSingleWordInOperand(kVariableStorageClassIndex); + Instruction* type_def = context()->get_def_use_mgr()->GetDef(type_id()); + + switch (storage_class) { + case SpvStorageClassUniformConstant: + if (!type_def->IsVulkanStorageImage() && + !type_def->IsVulkanStorageTexelBuffer()) { + return true; + } + break; + case SpvStorageClassUniform: + if (!type_def->IsVulkanStorageBuffer()) { + return true; + } + break; + case SpvStorageClassPushConstant: + case SpvStorageClassInput: + return true; + default: + break; + } + + bool is_nonwritable = false; + context()->get_decoration_mgr()->ForEachDecoration( + result_id(), SpvDecorationNonWritable, + [&is_nonwritable](const Instruction&) { is_nonwritable = true; }); + return is_nonwritable; +} + +bool Instruction::IsReadOnlyVariableKernel() const { + uint32_t storage_class = GetSingleWordInOperand(kVariableStorageClassIndex); + return storage_class == SpvStorageClassUniformConstant; +} + +uint32_t Instruction::GetTypeComponent(uint32_t element) const { + uint32_t subtype = 0; + switch (opcode()) { + case SpvOpTypeStruct: + subtype = GetSingleWordInOperand(element); + break; + case SpvOpTypeArray: + case SpvOpTypeRuntimeArray: + case SpvOpTypeVector: + case SpvOpTypeMatrix: + // These types all have uniform subtypes. + subtype = GetSingleWordInOperand(0u); + break; + default: + break; + } + + return subtype; +} + +Instruction* Instruction::InsertBefore( + std::vector>&& list) { + Instruction* first_node = list.front().get(); + for (auto& i : list) { + i.release()->InsertBefore(this); + } + list.clear(); + return first_node; +} + +Instruction* Instruction::InsertBefore(std::unique_ptr&& i) { + i.get()->InsertBefore(this); + return i.release(); +} + +bool Instruction::IsValidBasePointer() const { + uint32_t tid = type_id(); + if (tid == 0) { + return false; + } + + ir::Instruction* type = context()->get_def_use_mgr()->GetDef(tid); + if (type->opcode() != SpvOpTypePointer) { + return false; + } + + if (context()->get_feature_mgr()->HasCapability(SpvCapabilityAddresses)) { + // TODO: The rules here could be more restrictive. + return true; + } + + if (opcode() == SpvOpVariable || opcode() == SpvOpFunctionParameter) { + return true; + } + + uint32_t pointee_type_id = type->GetSingleWordInOperand(1); + ir::Instruction* pointee_type_inst = + context()->get_def_use_mgr()->GetDef(pointee_type_id); + + if (pointee_type_inst->IsOpaqueType()) { + return true; + } + return false; +} + +bool Instruction::IsValidBaseImage() const { + uint32_t tid = type_id(); + if (tid == 0) { + return false; + } + + ir::Instruction* type = context()->get_def_use_mgr()->GetDef(tid); + return (type->opcode() == SpvOpTypeImage || + type->opcode() == SpvOpTypeSampledImage); +} + +bool Instruction::IsOpaqueType() const { + if (opcode() == SpvOpTypeStruct) { + bool is_opaque = false; + ForEachInOperand([&is_opaque, this](const uint32_t* op_id) { + ir::Instruction* type_inst = context()->get_def_use_mgr()->GetDef(*op_id); + is_opaque |= type_inst->IsOpaqueType(); + }); + return is_opaque; + } else if (opcode() == SpvOpTypeArray) { + uint32_t sub_type_id = GetSingleWordInOperand(0); + ir::Instruction* sub_type_inst = + context()->get_def_use_mgr()->GetDef(sub_type_id); + return sub_type_inst->IsOpaqueType(); + } else { + return opcode() == SpvOpTypeRuntimeArray || + spvOpcodeIsBaseOpaqueType(opcode()); + } +} + +bool Instruction::IsFoldable() const { + return IsFoldableByFoldScalar() || + opt::GetConstantFoldingRules().HasFoldingRule(opcode()); +} + +bool Instruction::IsFoldableByFoldScalar() const { + if (!opt::IsFoldableOpcode(opcode())) { + return false; + } + Instruction* type = context()->get_def_use_mgr()->GetDef(type_id()); + return opt::IsFoldableType(type); +} + +bool Instruction::IsFloatingPointFoldingAllowed() const { + // TODO: Add the rules for kernels. For now it will be pessimistic. + if (!context_->get_feature_mgr()->HasCapability(SpvCapabilityShader)) { + return false; + } + + bool is_nocontract = false; + context_->get_decoration_mgr()->WhileEachDecoration( + opcode_, SpvDecorationNoContraction, + [&is_nocontract](const ir::Instruction&) { + is_nocontract = true; + return false; + }); + return !is_nocontract; +} + +std::string Instruction::PrettyPrint(uint32_t options) const { + // Convert the module to binary. + std::vector module_binary; + context()->module()->ToBinary(&module_binary, /* skip_nop = */ false); + + // Convert the instruction to binary. This is used to identify the correct + // stream of words to output from the module. + std::vector inst_binary; + ToBinaryWithoutAttachedDebugInsts(&inst_binary); + + // Do not generate a header. + return spvInstructionBinaryToText( + context()->grammar().target_env(), inst_binary.data(), inst_binary.size(), + module_binary.data(), module_binary.size(), + options | SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); +} + +std::ostream& operator<<(std::ostream& str, const ir::Instruction& inst) { + str << inst.PrettyPrint(); + return str; +} + +bool Instruction::IsOpcodeCodeMotionSafe() const { + switch (opcode_) { + case SpvOpVectorExtractDynamic: + case SpvOpVectorInsertDynamic: + case SpvOpVectorShuffle: + case SpvOpConvertFToU: + case SpvOpConvertFToS: + case SpvOpConvertSToF: + case SpvOpConvertUToF: + case SpvOpUConvert: + case SpvOpSConvert: + case SpvOpFConvert: + case SpvOpQuantizeToF16: + case SpvOpBitcast: + case SpvOpSNegate: + case SpvOpFNegate: + case SpvOpIAdd: + case SpvOpFAdd: + case SpvOpISub: + case SpvOpFSub: + case SpvOpIMul: + case SpvOpFMul: + case SpvOpUDiv: + case SpvOpSDiv: + case SpvOpFDiv: + case SpvOpUMod: + case SpvOpSRem: + case SpvOpSMod: + case SpvOpFRem: + case SpvOpFMod: + case SpvOpVectorTimesScalar: + case SpvOpMatrixTimesScalar: + case SpvOpVectorTimesMatrix: + case SpvOpMatrixTimesVector: + case SpvOpMatrixTimesMatrix: + case SpvOpLogicalEqual: + case SpvOpLogicalNotEqual: + case SpvOpLogicalOr: + case SpvOpLogicalAnd: + case SpvOpLogicalNot: + case SpvOpIEqual: + case SpvOpINotEqual: + case SpvOpUGreaterThan: + case SpvOpSGreaterThan: + case SpvOpUGreaterThanEqual: + case SpvOpSGreaterThanEqual: + case SpvOpULessThan: + case SpvOpSLessThan: + case SpvOpULessThanEqual: + case SpvOpSLessThanEqual: + case SpvOpFOrdEqual: + case SpvOpFUnordEqual: + case SpvOpFOrdNotEqual: + case SpvOpFUnordNotEqual: + case SpvOpFOrdLessThan: + case SpvOpFUnordLessThan: + case SpvOpFOrdGreaterThan: + case SpvOpFUnordGreaterThan: + case SpvOpFOrdLessThanEqual: + case SpvOpFUnordLessThanEqual: + case SpvOpFOrdGreaterThanEqual: + case SpvOpFUnordGreaterThanEqual: + case SpvOpShiftRightLogical: + case SpvOpShiftRightArithmetic: + case SpvOpShiftLeftLogical: + case SpvOpBitwiseOr: + case SpvOpBitwiseXor: + case SpvOpBitwiseAnd: + case SpvOpNot: + return true; + default: + return false; + } +} + +} // namespace ir +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/instruction.h b/3rdparty/spirv-tools/source/opt/instruction.h new file mode 100644 index 000000000..e74631057 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/instruction.h @@ -0,0 +1,705 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_INSTRUCTION_H_ +#define LIBSPIRV_OPT_INSTRUCTION_H_ + +#include +#include +#include +#include + +#include "opcode.h" +#include "operand.h" +#include "util/ilist_node.h" + +#include "latest_version_spirv_header.h" +#include "reflect.h" +#include "spirv-tools/libspirv.h" + +namespace spvtools { +namespace ir { + +class Function; +class IRContext; +class Module; +class InstructionList; + +// Relaxed logcial addressing: +// +// In the logical addressing model, pointers cannot be stored or loaded. This +// is a useful assumption because it simplifies the aliasing significantly. +// However, for the purpose of legalizing code generated from HLSL, we will have +// to allow storing and loading of pointers to opaque objects and runtime +// arrays. This relaxation of the rule still implies that function and private +// scope variables do not have any aliasing, so we can treat them as before. +// This will be call the relaxed logical addressing model. +// +// This relaxation of the rule will be allowed by |GetBaseAddress|, but it will +// enforce that no other pointers are stored or loaded. + +// About operand: +// +// In the SPIR-V specification, the term "operand" is used to mean any single +// SPIR-V word following the leading wordcount-opcode word. Here, the term +// "operand" is used to mean a *logical* operand. A logical operand may consist +// of multiple SPIR-V words, which together make up the same component. For +// example, a logical operand of a 64-bit integer needs two words to express. +// +// Further, we categorize logical operands into *in* and *out* operands. +// In operands are operands actually serve as input to operations, while out +// operands are operands that represent ids generated from operations (result +// type id or result id). For example, for "OpIAdd %rtype %rid %inop1 %inop2", +// "%inop1" and "%inop2" are in operands, while "%rtype" and "%rid" are out +// operands. + +// A *logical* operand to a SPIR-V instruction. It can be the type id, result +// id, or other additional operands carried in an instruction. +struct Operand { + Operand(spv_operand_type_t t, std::vector&& w) + : type(t), words(std::move(w)) {} + + Operand(spv_operand_type_t t, const std::vector& w) + : type(t), words(w) {} + + spv_operand_type_t type; // Type of this logical operand. + std::vector words; // Binary segments of this logical operand. + + friend bool operator==(const Operand& o1, const Operand& o2) { + return o1.type == o2.type && o1.words == o2.words; + } + + // TODO(antiagainst): create fields for literal number kind, width, etc. +}; + +inline bool operator!=(const Operand& o1, const Operand& o2) { + return !(o1 == o2); +} + +// A SPIR-V instruction. It contains the opcode and any additional logical +// operand, including the result id (if any) and result type id (if any). It +// may also contain line-related debug instruction (OpLine, OpNoLine) directly +// appearing before this instruction. Note that the result id of an instruction +// should never change after the instruction being built. If the result id +// needs to change, the user should create a new instruction instead. +class Instruction : public utils::IntrusiveNodeBase { + public: + using iterator = std::vector::iterator; + using const_iterator = std::vector::const_iterator; + + // Creates a default OpNop instruction. + // This exists solely for containers that can't do without. Should be removed. + Instruction() + : utils::IntrusiveNodeBase(), + context_(nullptr), + opcode_(SpvOpNop), + type_id_(0), + result_id_(0), + unique_id_(0) {} + + // Creates a default OpNop instruction. + Instruction(IRContext*); + // Creates an instruction with the given opcode |op| and no additional logical + // operands. + Instruction(IRContext*, SpvOp); + // Creates an instruction using the given spv_parsed_instruction_t |inst|. All + // the data inside |inst| will be copied and owned in this instance. And keep + // record of line-related debug instructions |dbg_line| ahead of this + // instruction, if any. + Instruction(IRContext* c, const spv_parsed_instruction_t& inst, + std::vector&& dbg_line = {}); + + // Creates an instruction with the given opcode |op|, type id: |ty_id|, + // result id: |res_id| and input operands: |in_operands|. + Instruction(IRContext* c, SpvOp op, uint32_t ty_id, uint32_t res_id, + const std::vector& in_operands); + + // TODO: I will want to remove these, but will first have to remove the use of + // std::vector. + Instruction(const Instruction&) = default; + Instruction& operator=(const Instruction&) = default; + + Instruction(Instruction&&); + Instruction& operator=(Instruction&&); + + virtual ~Instruction() = default; + + // Returns a newly allocated instruction that has the same operands, result, + // and type as |this|. The new instruction is not linked into any list. + // It is the responsibility of the caller to make sure that the storage is + // removed. It is the caller's responsibility to make sure that there is only + // one instruction for each result id. + Instruction* Clone(IRContext* c) const; + + IRContext* context() const { return context_; } + + SpvOp opcode() const { return opcode_; } + // Sets the opcode of this instruction to a specific opcode. Note this may + // invalidate the instruction. + // TODO(qining): Remove this function when instruction building and insertion + // is well implemented. + void SetOpcode(SpvOp op) { opcode_ = op; } + uint32_t type_id() const { return type_id_; } + uint32_t result_id() const { return result_id_; } + uint32_t unique_id() const { + assert(unique_id_ != 0); + return unique_id_; + } + // Returns the vector of line-related debug instructions attached to this + // instruction and the caller can directly modify them. + std::vector& dbg_line_insts() { return dbg_line_insts_; } + const std::vector& dbg_line_insts() const { + return dbg_line_insts_; + } + + // Same semantics as in the base class except the list the InstructionList + // containing |pos| will now assume ownership of |this|. + // inline void MoveBefore(Instruction* pos); + // inline void InsertAfter(Instruction* pos); + + // Begin and end iterators for operands. + iterator begin() { return operands_.begin(); } + iterator end() { return operands_.end(); } + const_iterator begin() const { return operands_.cbegin(); } + const_iterator end() const { return operands_.cend(); } + // Const begin and end iterators for operands. + const_iterator cbegin() const { return operands_.cbegin(); } + const_iterator cend() const { return operands_.cend(); } + + // Gets the number of logical operands. + uint32_t NumOperands() const { + return static_cast(operands_.size()); + } + // Gets the number of SPIR-V words occupied by all logical operands. + uint32_t NumOperandWords() const { + return NumInOperandWords() + TypeResultIdCount(); + } + // Gets the |index|-th logical operand. + inline Operand& GetOperand(uint32_t index); + inline const Operand& GetOperand(uint32_t index) const; + // Adds |operand| to the list of operands of this instruction. + // It is the responsibility of the caller to make sure + // that the instruction remains valid. + inline void AddOperand(Operand&& operand); + // Gets the |index|-th logical operand as a single SPIR-V word. This method is + // not expected to be used with logical operands consisting of multiple SPIR-V + // words. + uint32_t GetSingleWordOperand(uint32_t index) const; + // Sets the |index|-th in-operand's data to the given |data|. + inline void SetInOperand(uint32_t index, std::vector&& data); + // Sets the |index|-th operand's data to the given |data|. + // This is for in-operands modification only, but with |index| expressed in + // terms of operand index rather than in-operand index. + inline void SetOperand(uint32_t index, std::vector&& data); + // Replace all of the in operands with those in |new_operands|. + inline void SetInOperands(std::vector&& new_operands); + // Sets the result type id. + inline void SetResultType(uint32_t ty_id); + // Sets the result id + inline void SetResultId(uint32_t res_id); + inline bool HasResultId() const { return result_id_ != 0; } + // Remove the |index|-th operand + void RemoveOperand(uint32_t index) { + operands_.erase(operands_.begin() + index); + } + + // The following methods are similar to the above, but are for in operands. + uint32_t NumInOperands() const { + return static_cast(operands_.size() - TypeResultIdCount()); + } + uint32_t NumInOperandWords() const; + Operand& GetInOperand(uint32_t index) { + return GetOperand(index + TypeResultIdCount()); + } + const Operand& GetInOperand(uint32_t index) const { + return GetOperand(index + TypeResultIdCount()); + } + uint32_t GetSingleWordInOperand(uint32_t index) const { + return GetSingleWordOperand(index + TypeResultIdCount()); + } + void RemoveInOperand(uint32_t index) { + operands_.erase(operands_.begin() + index + TypeResultIdCount()); + } + + // Returns true if this instruction is OpNop. + inline bool IsNop() const; + // Turns this instruction to OpNop. This does not clear out all preceding + // line-related debug instructions. + inline void ToNop(); + + // Runs the given function |f| on this instruction and optionally on the + // preceding debug line instructions. The function will always be run + // if this is itself a debug line instruction. + inline void ForEachInst(const std::function& f, + bool run_on_debug_line_insts = false); + inline void ForEachInst(const std::function& f, + bool run_on_debug_line_insts = false) const; + + // Runs the given function |f| on this instruction and optionally on the + // preceding debug line instructions. The function will always be run + // if this is itself a debug line instruction. If |f| returns false, + // iteration is terminated and this function returns false. + inline bool WhileEachInst(const std::function& f, + bool run_on_debug_line_insts = false); + inline bool WhileEachInst(const std::function& f, + bool run_on_debug_line_insts = false) const; + + // Runs the given function |f| on all operand ids. + // + // |f| should not transform an ID into 0, as 0 is an invalid ID. + inline void ForEachId(const std::function& f); + inline void ForEachId(const std::function& f) const; + + // Runs the given function |f| on all "in" operand ids. + inline void ForEachInId(const std::function& f); + inline void ForEachInId(const std::function& f) const; + + // Runs the given function |f| on all "in" operand ids. If |f| returns false, + // iteration is terminated and this function returns false. + inline bool WhileEachInId(const std::function& f); + inline bool WhileEachInId( + const std::function& f) const; + + // Runs the given function |f| on all "in" operands. + inline void ForEachInOperand(const std::function& f); + inline void ForEachInOperand( + const std::function& f) const; + + // Runs the given function |f| on all "in" operands. If |f| returns false, + // iteration is terminated and this function return false. + inline bool WhileEachInOperand(const std::function& f); + inline bool WhileEachInOperand( + const std::function& f) const; + + // Returns true if any operands can be labels + inline bool HasLabels() const; + + // Pushes the binary segments for this instruction into the back of *|binary|. + void ToBinaryWithoutAttachedDebugInsts(std::vector* binary) const; + + // Replaces the operands to the instruction with |new_operands|. The caller + // is responsible for building a complete and valid list of operands for + // this instruction. + void ReplaceOperands(const std::vector& new_operands); + + // Returns true if the instruction annotates an id with a decoration. + inline bool IsDecoration() const; + + // Returns true if the instruction is known to be a load from read-only + // memory. + bool IsReadOnlyLoad() const; + + // Returns the instruction that gives the base address of an address + // calculation. The instruction must be a load, as defined by |IsLoad|, + // store, copy, or access chain instruction. In logical addressing mode, will + // return an OpVariable or OpFunctionParameter instruction. For relaxed + // logical addressing, it would also return a load of a pointer to an opaque + // object. For physical addressing mode, could return other types of + // instructions. + Instruction* GetBaseAddress() const; + + // Returns true if the instruction loads from memory or samples an image, and + // stores the result into an id. It considers only core instructions. + // Memory-to-memory instructions are not considered loads. + inline bool IsLoad() const; + + // Returns true if the instruction declares a variable that is read-only. + bool IsReadOnlyVariable() const; + + // The following functions check for the various descriptor types defined in + // the Vulkan specification section 13.1. + + // Returns true if the instruction defines a pointer type that points to a + // storage image. + bool IsVulkanStorageImage() const; + + // Returns true if the instruction defines a pointer type that points to a + // sampled image. + bool IsVulkanSampledImage() const; + + // Returns true if the instruction defines a pointer type that points to a + // storage texel buffer. + bool IsVulkanStorageTexelBuffer() const; + + // Returns true if the instruction defines a pointer type that points to a + // storage buffer. + bool IsVulkanStorageBuffer() const; + + // Returns true if the instruction defines a pointer type that points to a + // uniform buffer. + bool IsVulkanUniformBuffer() const; + + // Returns true if the instruction is an atom operation. + inline bool IsAtomicOp() const; + + // Returns true if this instruction is a branch or switch instruction (either + // conditional or not). + bool IsBranch() const { return spvOpcodeIsBranch(opcode()); } + + // Returns true if this instruction causes the function to finish execution + // and return to its caller + bool IsReturn() const { return spvOpcodeIsReturn(opcode()); } + + // Returns true if this instruction exits this function or aborts execution. + bool IsReturnOrAbort() const { return spvOpcodeIsReturnOrAbort(opcode()); } + + // Returns the id for the |element|'th subtype. If the |this| is not a + // composite type, this function returns 0. + uint32_t GetTypeComponent(uint32_t element) const; + + // Returns true if this instruction is a basic block terminator. + bool IsBlockTerminator() const { + return spvOpcodeIsBlockTerminator(opcode()); + } + + // Returns true if |this| is an instruction that define an opaque type. Since + // runtime array have similar characteristics they are included as opaque + // types. + bool IsOpaqueType() const; + + // Returns true if |this| is an instruction which could be folded into a + // constant value. + bool IsFoldable() const; + + // Returns true if |this| is an instruction which could be folded into a + // constant value by |FoldScalar|. + bool IsFoldableByFoldScalar() const; + + // Returns true if we are allowed to fold or otherwise manipulate the + // instruction that defines |id| in the given context. This includes not + // handling NaN values. + bool IsFloatingPointFoldingAllowed() const; + + inline bool operator==(const Instruction&) const; + inline bool operator!=(const Instruction&) const; + inline bool operator<(const Instruction&) const; + + Instruction* InsertBefore(std::vector>&& list); + Instruction* InsertBefore(std::unique_ptr&& i); + using utils::IntrusiveNodeBase::InsertBefore; + + // Returns true if |this| is an instruction defining a constant, but not a + // Spec constant. + inline bool IsConstant() const; + + // Returns true if |this| is an instruction with an opcode safe to move + bool IsOpcodeCodeMotionSafe() const; + + // Pretty-prints |inst|. + // + // Provides the disassembly of a specific instruction. Utilizes |inst|'s + // context to provide the correct interpretation of types, constants, etc. + // + // |options| are the disassembly options. SPV_BINARY_TO_TEXT_OPTION_NO_HEADER + // is always added to |options|. + std::string PrettyPrint(uint32_t options = 0u) const; + + private: + // Returns the total count of result type id and result id. + uint32_t TypeResultIdCount() const { + return (type_id_ != 0) + (result_id_ != 0); + } + + // Returns true if the instruction declares a variable that is read-only. The + // first version assumes the module is a shader module. The second assumes a + // kernel. + bool IsReadOnlyVariableShaders() const; + bool IsReadOnlyVariableKernel() const; + + // Returns true if it is valid to use the result of |inst| as the base + // pointer for a load or store. In this case, valid is defined by the relaxed + // logical addressing rules when using logical addressing. Normal validation + // rules for physical addressing. + bool IsValidBasePointer() const; + + // Returns true if the result of |inst| can be used as the base image for an + // instruction that samples a image, reads an image, or writes to an image. + bool IsValidBaseImage() const; + + IRContext* context_; // IR Context + SpvOp opcode_; // Opcode + uint32_t type_id_; // Result type id. A value of 0 means no result type id. + uint32_t result_id_; // Result id. A value of 0 means no result id. + uint32_t unique_id_; // Unique instruction id + // All logical operands, including result type id and result id. + std::vector operands_; + // Opline and OpNoLine instructions preceding this instruction. Note that for + // Instructions representing OpLine or OpNonLine itself, this field should be + // empty. + std::vector dbg_line_insts_; + + friend InstructionList; +}; + +// Pretty-prints |inst| to |str| and returns |str|. +// +// Provides the disassembly of a specific instruction. Utilizes |inst|'s context +// to provide the correct interpretation of types, constants, etc. +// +// Disassembly uses raw ids (not pretty printed names). +std::ostream& operator<<(std::ostream& str, const ir::Instruction& inst); + +inline bool Instruction::operator==(const Instruction& other) const { + return unique_id() == other.unique_id(); +} + +inline bool Instruction::operator!=(const Instruction& other) const { + return !(*this == other); +} + +inline bool Instruction::operator<(const Instruction& other) const { + return unique_id() < other.unique_id(); +} + +inline Operand& Instruction::GetOperand(uint32_t index) { + assert(index < operands_.size() && "operand index out of bound"); + return operands_[index]; +} + +inline const Operand& Instruction::GetOperand(uint32_t index) const { + assert(index < operands_.size() && "operand index out of bound"); + return operands_[index]; +} + +inline void Instruction::AddOperand(Operand&& operand) { + operands_.push_back(std::move(operand)); +} + +inline void Instruction::SetInOperand(uint32_t index, + std::vector&& data) { + SetOperand(index + TypeResultIdCount(), std::move(data)); +} + +inline void Instruction::SetOperand(uint32_t index, + std::vector&& data) { + assert(index < operands_.size() && "operand index out of bound"); + assert(index >= TypeResultIdCount() && "operand is not a in-operand"); + operands_[index].words = std::move(data); +} + +inline void Instruction::SetInOperands(std::vector&& new_operands) { + // Remove the old in operands. + operands_.erase(operands_.begin() + TypeResultIdCount(), operands_.end()); + // Add the new in operands. + operands_.insert(operands_.end(), new_operands.begin(), new_operands.end()); +} + +inline void Instruction::SetResultId(uint32_t res_id) { + result_id_ = res_id; + auto ridx = (type_id_ != 0) ? 1 : 0; + assert(operands_[ridx].type == SPV_OPERAND_TYPE_RESULT_ID); + operands_[ridx].words = {res_id}; +} + +inline void Instruction::SetResultType(uint32_t ty_id) { + if (type_id_ != 0) { + type_id_ = ty_id; + assert(operands_.front().type == SPV_OPERAND_TYPE_TYPE_ID); + operands_.front().words = {ty_id}; + } +} + +inline bool Instruction::IsNop() const { + return opcode_ == SpvOpNop && type_id_ == 0 && result_id_ == 0 && + operands_.empty(); +} + +inline void Instruction::ToNop() { + opcode_ = SpvOpNop; + type_id_ = result_id_ = 0; + operands_.clear(); +} + +inline bool Instruction::WhileEachInst( + const std::function& f, bool run_on_debug_line_insts) { + if (run_on_debug_line_insts) { + for (auto& dbg_line : dbg_line_insts_) { + if (!f(&dbg_line)) return false; + } + } + return f(this); +} + +inline bool Instruction::WhileEachInst( + const std::function& f, + bool run_on_debug_line_insts) const { + if (run_on_debug_line_insts) { + for (auto& dbg_line : dbg_line_insts_) { + if (!f(&dbg_line)) return false; + } + } + return f(this); +} + +inline void Instruction::ForEachInst(const std::function& f, + bool run_on_debug_line_insts) { + WhileEachInst( + [&f](Instruction* inst) { + f(inst); + return true; + }, + run_on_debug_line_insts); +} + +inline void Instruction::ForEachInst( + const std::function& f, + bool run_on_debug_line_insts) const { + WhileEachInst( + [&f](const Instruction* inst) { + f(inst); + return true; + }, + run_on_debug_line_insts); +} + +inline void Instruction::ForEachId(const std::function& f) { + for (auto& opnd : operands_) + if (spvIsIdType(opnd.type)) f(&opnd.words[0]); + if (type_id_ != 0u) type_id_ = GetSingleWordOperand(0u); + if (result_id_ != 0u) + result_id_ = GetSingleWordOperand(type_id_ == 0u ? 0u : 1u); +} + +inline void Instruction::ForEachId( + const std::function& f) const { + for (const auto& opnd : operands_) + if (spvIsIdType(opnd.type)) f(&opnd.words[0]); +} + +inline bool Instruction::WhileEachInId( + const std::function& f) { + for (auto& opnd : operands_) { + switch (opnd.type) { + case SPV_OPERAND_TYPE_RESULT_ID: + case SPV_OPERAND_TYPE_TYPE_ID: + break; + default: + if (spvIsIdType(opnd.type)) { + if (!f(&opnd.words[0])) return false; + } + break; + } + } + return true; +} + +inline bool Instruction::WhileEachInId( + const std::function& f) const { + for (const auto& opnd : operands_) { + switch (opnd.type) { + case SPV_OPERAND_TYPE_RESULT_ID: + case SPV_OPERAND_TYPE_TYPE_ID: + break; + default: + if (spvIsIdType(opnd.type)) { + if (!f(&opnd.words[0])) return false; + } + break; + } + } + return true; +} + +inline void Instruction::ForEachInId(const std::function& f) { + WhileEachInId([&f](uint32_t* id) { + f(id); + return true; + }); +} + +inline void Instruction::ForEachInId( + const std::function& f) const { + WhileEachInId([&f](const uint32_t* id) { + f(id); + return true; + }); +} + +inline bool Instruction::WhileEachInOperand( + const std::function& f) { + for (auto& opnd : operands_) { + switch (opnd.type) { + case SPV_OPERAND_TYPE_RESULT_ID: + case SPV_OPERAND_TYPE_TYPE_ID: + break; + default: + if (!f(&opnd.words[0])) return false; + break; + } + } + return true; +} + +inline bool Instruction::WhileEachInOperand( + const std::function& f) const { + for (const auto& opnd : operands_) { + switch (opnd.type) { + case SPV_OPERAND_TYPE_RESULT_ID: + case SPV_OPERAND_TYPE_TYPE_ID: + break; + default: + if (!f(&opnd.words[0])) return false; + break; + } + } + return true; +} + +inline void Instruction::ForEachInOperand( + const std::function& f) { + WhileEachInOperand([&f](uint32_t* op) { + f(op); + return true; + }); +} + +inline void Instruction::ForEachInOperand( + const std::function& f) const { + WhileEachInOperand([&f](const uint32_t* op) { + f(op); + return true; + }); +} + +inline bool Instruction::HasLabels() const { + switch (opcode_) { + case SpvOpSelectionMerge: + case SpvOpBranch: + case SpvOpLoopMerge: + case SpvOpBranchConditional: + case SpvOpSwitch: + case SpvOpPhi: + return true; + break; + default: + break; + } + return false; +} + +bool Instruction::IsDecoration() const { + return spvOpcodeIsDecoration(opcode()); +} + +bool Instruction::IsLoad() const { return spvOpcodeIsLoad(opcode()); } + +bool Instruction::IsAtomicOp() const { return spvOpcodeIsAtomicOp(opcode()); } + +bool Instruction::IsConstant() const { + return IsCompileTimeConstantInst(opcode()); +} +} // namespace ir +} // namespace spvtools + +#endif // LIBSPIRV_OPT_INSTRUCTION_H_ diff --git a/3rdparty/spirv-tools/source/opt/instruction_list.cpp b/3rdparty/spirv-tools/source/opt/instruction_list.cpp new file mode 100644 index 000000000..d8ddb84e5 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/instruction_list.cpp @@ -0,0 +1,36 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "instruction_list.h" + +namespace spvtools { +namespace ir { + +InstructionList::iterator InstructionList::iterator::InsertBefore( + std::vector>&& list) { + Instruction* first_node = list.front().get(); + for (auto& i : list) { + i.release()->InsertBefore(node_); + } + list.clear(); + return iterator(first_node); +} + +InstructionList::iterator InstructionList::iterator::InsertBefore( + std::unique_ptr&& i) { + i.get()->InsertBefore(node_); + return iterator(i.release()); +} +} // namespace ir +} // namespace spvtools \ No newline at end of file diff --git a/3rdparty/spirv-tools/source/opt/instruction_list.h b/3rdparty/spirv-tools/source/opt/instruction_list.h new file mode 100644 index 000000000..182317fb4 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/instruction_list.h @@ -0,0 +1,130 @@ + +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_INSTRUCTION_LIST_H_ +#define LIBSPIRV_OPT_INSTRUCTION_LIST_H_ + +#include +#include +#include +#include + +#include "instruction.h" +#include "operand.h" +#include "util/ilist.h" + +#include "latest_version_spirv_header.h" +#include "spirv-tools/libspirv.h" + +namespace spvtools { +namespace ir { + +// This class is intended to be the container for Instructions. This container +// owns the instructions that are in it. When removing an Instruction from the +// list, the caller is assuming responsibility for deleting the storage. +// +// TODO: Because there are a number of other data structures that will want +// pointers to instruction, ownership should probably be moved to the module. +// Because of that I have not made the ownership passing in this class fully +// explicit. For example, RemoveFromList takes ownership from the list, but +// does not return an std::unique_ptr to signal that. When we fully decide on +// ownership, this will have to be fixed up one way or the other. +class InstructionList : public utils::IntrusiveList { + public: + InstructionList() = default; + InstructionList(InstructionList&& that) + : utils::IntrusiveList(std::move(that)) {} + InstructionList& operator=(InstructionList&& that) { + auto p = static_cast*>(this); + *p = std::move(that); + return *this; + } + + // Destroy this list and any instructions in the list. + inline virtual ~InstructionList(); + + class iterator : public utils::IntrusiveList::iterator { + public: + iterator(const utils::IntrusiveList::iterator& i) + : utils::IntrusiveList::iterator(i) {} + iterator(Instruction* i) : utils::IntrusiveList::iterator(i) {} + + // DEPRECATED: Please use MoveBefore with an InstructionList instead. + // + // Moves the nodes in |list| to the list that |this| points to. The + // positions of the nodes will be immediately before the element pointed to + // by the iterator. The return value will be an iterator pointing to the + // first of the newly inserted elements. Ownership of the elements in + // |list| is now passed on to |*this|. + iterator InsertBefore(std::vector>&& list); + + // The node |i| will be inserted immediately before |this|. The return value + // will be an iterator pointing to the newly inserted node. The owner of + // |*i| becomes |*this| + iterator InsertBefore(std::unique_ptr&& i); + + // Removes the node from the list, and deletes the storage. Returns a valid + // iterator to the next node. + iterator Erase() { + iterator_template next_node = *this; + ++next_node; + node_->RemoveFromList(); + delete node_; + return next_node; + } + }; + + iterator begin() { return utils::IntrusiveList::begin(); } + iterator end() { return utils::IntrusiveList::end(); } + const_iterator begin() const { + return utils::IntrusiveList::begin(); + } + const_iterator end() const { + return utils::IntrusiveList::end(); + } + + void push_back(std::unique_ptr&& inst) { + utils::IntrusiveList::push_back(inst.release()); + } + + // Same as in the base class, except it will delete the data as well. + inline void clear(); + + // Runs the given function |f| on the instructions in the list and optionally + // on the preceding debug line instructions. + inline void ForEachInst(const std::function& f, + bool run_on_debug_line_insts) { + auto next = begin(); + for (auto i = next; i != end(); i = next) { + ++next; + i->ForEachInst(f, run_on_debug_line_insts); + } + } +}; + +InstructionList::~InstructionList() { clear(); } + +void InstructionList::clear() { + while (!empty()) { + Instruction* inst = &front(); + inst->RemoveFromList(); + delete inst; + } +} + +} // namespace ir +} // namespace spvtools + +#endif // LIBSPIRV_OPT_INSTRUCTION_LIST_H_ diff --git a/3rdparty/spirv-tools/source/opt/ir_builder.h b/3rdparty/spirv-tools/source/opt/ir_builder.h new file mode 100644 index 000000000..aba6ef360 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/ir_builder.h @@ -0,0 +1,411 @@ +// Copyright (c) 2018 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_IR_BUILDER_H_ +#define LIBSPIRV_OPT_IR_BUILDER_H_ + +#include "opt/basic_block.h" +#include "opt/constants.h" +#include "opt/instruction.h" +#include "opt/ir_context.h" +namespace spvtools { +namespace opt { + +// In SPIR-V, ids are encoded as uint16_t, this id is guaranteed to be always +// invalid. +const uint32_t kInvalidId = std::numeric_limits::max(); + +// Helper class to abstract instruction construction and insertion. +// The instruction builder can preserve the following analyses (specified via +// the constructors): +// - Def-use analysis +// - Instruction to block analysis +class InstructionBuilder { + public: + using InsertionPointTy = spvtools::ir::BasicBlock::iterator; + + // Creates an InstructionBuilder, all new instructions will be inserted before + // the instruction |insert_before|. + InstructionBuilder( + ir::IRContext* context, ir::Instruction* insert_before, + ir::IRContext::Analysis preserved_analyses = ir::IRContext::kAnalysisNone) + : InstructionBuilder(context, context->get_instr_block(insert_before), + InsertionPointTy(insert_before), + preserved_analyses) {} + + // Creates an InstructionBuilder, all new instructions will be inserted at the + // end of the basic block |parent_block|. + InstructionBuilder( + ir::IRContext* context, ir::BasicBlock* parent_block, + ir::IRContext::Analysis preserved_analyses = ir::IRContext::kAnalysisNone) + : InstructionBuilder(context, parent_block, parent_block->end(), + preserved_analyses) {} + + // Creates a new selection merge instruction. + // The id |merge_id| is the merge basic block id. + ir::Instruction* AddSelectionMerge( + uint32_t merge_id, + uint32_t selection_control = SpvSelectionControlMaskNone) { + std::unique_ptr new_branch_merge(new ir::Instruction( + GetContext(), SpvOpSelectionMerge, 0, 0, + {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {merge_id}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_SELECTION_CONTROL, + {selection_control}}})); + return AddInstruction(std::move(new_branch_merge)); + } + + // Creates a new branch instruction to |label_id|. + // Note that the user must make sure the final basic block is + // well formed. + ir::Instruction* AddBranch(uint32_t label_id) { + std::unique_ptr new_branch(new ir::Instruction( + GetContext(), SpvOpBranch, 0, 0, + {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {label_id}}})); + return AddInstruction(std::move(new_branch)); + } + + // Creates a new conditional instruction and the associated selection merge + // instruction if requested. + // The id |cond_id| is the id of the condition instruction, must be of + // type bool. + // The id |true_id| is the id of the basic block to branch to if the condition + // is true. + // The id |false_id| is the id of the basic block to branch to if the + // condition is false. + // The id |merge_id| is the id of the merge basic block for the selection + // merge instruction. If |merge_id| equals kInvalidId then no selection merge + // instruction will be created. + // The value |selection_control| is the selection control flag for the + // selection merge instruction. + // Note that the user must make sure the final basic block is + // well formed. + ir::Instruction* AddConditionalBranch( + uint32_t cond_id, uint32_t true_id, uint32_t false_id, + uint32_t merge_id = kInvalidId, + uint32_t selection_control = SpvSelectionControlMaskNone) { + if (merge_id != kInvalidId) { + AddSelectionMerge(merge_id, selection_control); + } + std::unique_ptr new_branch(new ir::Instruction( + GetContext(), SpvOpBranchConditional, 0, 0, + {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {cond_id}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {true_id}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {false_id}}})); + return AddInstruction(std::move(new_branch)); + } + + // Creates a new switch instruction and the associated selection merge + // instruction if requested. + // The id |selector_id| is the id of the selector instruction, must be of + // type int. + // The id |default_id| is the id of the default basic block to branch to. + // The vector |targets| is the pair of literal/branch id. + // The id |merge_id| is the id of the merge basic block for the selection + // merge instruction. If |merge_id| equals kInvalidId then no selection merge + // instruction will be created. + // The value |selection_control| is the selection control flag for the + // selection merge instruction. + // Note that the user must make sure the final basic block is + // well formed. + ir::Instruction* AddSwitch( + uint32_t selector_id, uint32_t default_id, + const std::vector, uint32_t>>& targets, + uint32_t merge_id = kInvalidId, + uint32_t selection_control = SpvSelectionControlMaskNone) { + if (merge_id != kInvalidId) { + AddSelectionMerge(merge_id, selection_control); + } + std::vector operands; + operands.emplace_back( + ir::Operand{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {selector_id}}); + operands.emplace_back( + ir::Operand{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {default_id}}); + for (auto& target : targets) { + operands.emplace_back( + ir::Operand{spv_operand_type_t::SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER, + target.first}); + operands.emplace_back(ir::Operand{spv_operand_type_t::SPV_OPERAND_TYPE_ID, + {target.second}}); + } + std::unique_ptr new_switch( + new ir::Instruction(GetContext(), SpvOpSwitch, 0, 0, operands)); + return AddInstruction(std::move(new_switch)); + } + + // Creates a phi instruction. + // The id |type| must be the id of the phi instruction's type. + // The vector |incomings| must be a sequence of pairs of . + ir::Instruction* AddPhi(uint32_t type, + const std::vector& incomings) { + assert(incomings.size() % 2 == 0 && "A sequence of pairs is expected"); + std::vector phi_ops; + for (size_t i = 0; i < incomings.size(); i++) { + phi_ops.push_back({SPV_OPERAND_TYPE_ID, {incomings[i]}}); + } + std::unique_ptr phi_inst(new ir::Instruction( + GetContext(), SpvOpPhi, type, GetContext()->TakeNextId(), phi_ops)); + return AddInstruction(std::move(phi_inst)); + } + + // Creates an addition instruction. + // The id |type| must be the id of the instruction's type, must be the same as + // |op1| and |op2| types. + // The id |op1| is the left hand side of the operation. + // The id |op2| is the right hand side of the operation. + ir::Instruction* AddIAdd(uint32_t type, uint32_t op1, uint32_t op2) { + std::unique_ptr inst(new ir::Instruction( + GetContext(), SpvOpIAdd, type, GetContext()->TakeNextId(), + {{SPV_OPERAND_TYPE_ID, {op1}}, {SPV_OPERAND_TYPE_ID, {op2}}})); + return AddInstruction(std::move(inst)); + } + + // Creates a less than instruction for unsigned integer. + // The id |op1| is the left hand side of the operation. + // The id |op2| is the right hand side of the operation. + // It is assumed that |op1| and |op2| have the same underlying type. + ir::Instruction* AddULessThan(uint32_t op1, uint32_t op2) { + analysis::Bool bool_type; + uint32_t type = GetContext()->get_type_mgr()->GetId(&bool_type); + std::unique_ptr inst(new ir::Instruction( + GetContext(), SpvOpULessThan, type, GetContext()->TakeNextId(), + {{SPV_OPERAND_TYPE_ID, {op1}}, {SPV_OPERAND_TYPE_ID, {op2}}})); + return AddInstruction(std::move(inst)); + } + + // Creates a less than instruction for signed integer. + // The id |op1| is the left hand side of the operation. + // The id |op2| is the right hand side of the operation. + // It is assumed that |op1| and |op2| have the same underlying type. + ir::Instruction* AddSLessThan(uint32_t op1, uint32_t op2) { + analysis::Bool bool_type; + uint32_t type = GetContext()->get_type_mgr()->GetId(&bool_type); + std::unique_ptr inst(new ir::Instruction( + GetContext(), SpvOpSLessThan, type, GetContext()->TakeNextId(), + {{SPV_OPERAND_TYPE_ID, {op1}}, {SPV_OPERAND_TYPE_ID, {op2}}})); + return AddInstruction(std::move(inst)); + } + + // Creates an OpILessThan or OpULessThen instruction depending on the sign of + // |op1|. The id |op1| is the left hand side of the operation. The id |op2| is + // the right hand side of the operation. It is assumed that |op1| and |op2| + // have the same underlying type. + ir::Instruction* AddLessThan(uint32_t op1, uint32_t op2) { + ir::Instruction* op1_insn = context_->get_def_use_mgr()->GetDef(op1); + analysis::Type* type = + GetContext()->get_type_mgr()->GetType(op1_insn->type_id()); + analysis::Integer* int_type = type->AsInteger(); + assert(int_type && "Operand is not of int type"); + + if (int_type->IsSigned()) + return AddSLessThan(op1, op2); + else + return AddULessThan(op1, op2); + } + + // Creates a select instruction. + // |type| must match the types of |true_value| and |false_value|. It is up to + // the caller to ensure that |cond| is a correct type (bool or vector of + // bool) for |type|. + ir::Instruction* AddSelect(uint32_t type, uint32_t cond, uint32_t true_value, + uint32_t false_value) { + std::unique_ptr select(new ir::Instruction( + GetContext(), SpvOpSelect, type, GetContext()->TakeNextId(), + std::initializer_list{ + {SPV_OPERAND_TYPE_ID, {cond}}, + {SPV_OPERAND_TYPE_ID, {true_value}}, + {SPV_OPERAND_TYPE_ID, {false_value}}})); + return AddInstruction(std::move(select)); + } + + // Adds a signed int32 constant to the binary. + // The |value| parameter is the constant value to be added. + ir::Instruction* Add32BitSignedIntegerConstant(int32_t value) { + return Add32BitConstantInteger(value, true); + } + + // Create a composite construct. + // |type| should be a composite type and the number of elements it has should + // match the size od |ids|. + ir::Instruction* AddCompositeConstruct(uint32_t type, + const std::vector& ids) { + std::vector ops; + for (auto id : ids) { + ops.emplace_back(SPV_OPERAND_TYPE_ID, + std::initializer_list{id}); + } + std::unique_ptr construct( + new ir::Instruction(GetContext(), SpvOpCompositeConstruct, type, + GetContext()->TakeNextId(), ops)); + return AddInstruction(std::move(construct)); + } + // Adds an unsigned int32 constant to the binary. + // The |value| parameter is the constant value to be added. + ir::Instruction* Add32BitUnsignedIntegerConstant(uint32_t value) { + return Add32BitConstantInteger(value, false); + } + + // Adds either a signed or unsigned 32 bit integer constant to the binary + // depedning on the |sign|. If |sign| is true then the value is added as a + // signed constant otherwise as an unsigned constant. If |sign| is false the + // value must not be a negative number. + template + ir::Instruction* Add32BitConstantInteger(T value, bool sign) { + // Assert that we are not trying to store a negative number in an unsigned + // type. + if (!sign) + assert(value >= 0 && + "Trying to add a signed integer with an unsigned type!"); + + analysis::Integer int_type{32, sign}; + + // Get or create the integer type. This rebuilds the type and manages the + // memory for the rebuilt type. + uint32_t type_id = + GetContext()->get_type_mgr()->GetTypeInstruction(&int_type); + + // Get the memory managed type so that it is safe to be stored by + // GetConstant. + analysis::Type* rebuilt_type = + GetContext()->get_type_mgr()->GetType(type_id); + + // Even if the value is negative we need to pass the bit pattern as a + // uint32_t to GetConstant. + uint32_t word = value; + + // Create the constant value. + const opt::analysis::Constant* constant = + GetContext()->get_constant_mgr()->GetConstant(rebuilt_type, {word}); + + // Create the OpConstant instruction using the type and the value. + return GetContext()->get_constant_mgr()->GetDefiningInstruction(constant); + } + + ir::Instruction* AddCompositeExtract( + uint32_t type, uint32_t id_of_composite, + const std::vector& index_list) { + std::vector operands; + operands.push_back({SPV_OPERAND_TYPE_ID, {id_of_composite}}); + + for (uint32_t index : index_list) { + operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {index}}); + } + + std::unique_ptr new_inst( + new ir::Instruction(GetContext(), SpvOpCompositeExtract, type, + GetContext()->TakeNextId(), operands)); + return AddInstruction(std::move(new_inst)); + } + + // Creates an unreachable instruction. + ir::Instruction* AddUnreachable() { + std::unique_ptr select( + new ir::Instruction(GetContext(), SpvOpUnreachable, 0, 0, + std::initializer_list{})); + return AddInstruction(std::move(select)); + } + + ir::Instruction* AddAccessChain(uint32_t type_id, uint32_t base_ptr_id, + std::vector ids) { + std::vector operands; + operands.push_back({SPV_OPERAND_TYPE_ID, {base_ptr_id}}); + + for (uint32_t index_id : ids) { + operands.push_back({SPV_OPERAND_TYPE_ID, {index_id}}); + } + + std::unique_ptr new_inst( + new ir::Instruction(GetContext(), SpvOpAccessChain, type_id, + GetContext()->TakeNextId(), operands)); + return AddInstruction(std::move(new_inst)); + } + + // Inserts the new instruction before the insertion point. + ir::Instruction* AddInstruction(std::unique_ptr&& insn) { + ir::Instruction* insn_ptr = &*insert_before_.InsertBefore(std::move(insn)); + UpdateInstrToBlockMapping(insn_ptr); + UpdateDefUseMgr(insn_ptr); + return insn_ptr; + } + + // Returns the insertion point iterator. + InsertionPointTy GetInsertPoint() { return insert_before_; } + + // Change the insertion point to insert before the instruction + // |insert_before|. + void SetInsertPoint(ir::Instruction* insert_before) { + parent_ = context_->get_instr_block(insert_before); + insert_before_ = InsertionPointTy(insert_before); + } + + // Change the insertion point to insert at the end of the basic block + // |parent_block|. + void SetInsertPoint(ir::BasicBlock* parent_block) { + parent_ = parent_block; + insert_before_ = parent_block->end(); + } + + // Returns the context which instructions are constructed for. + ir::IRContext* GetContext() const { return context_; } + + // Returns the set of preserved analyses. + inline ir::IRContext::Analysis GetPreservedAnalysis() const { + return preserved_analyses_; + } + + private: + InstructionBuilder(ir::IRContext* context, ir::BasicBlock* parent, + InsertionPointTy insert_before, + ir::IRContext::Analysis preserved_analyses) + : context_(context), + parent_(parent), + insert_before_(insert_before), + preserved_analyses_(preserved_analyses) { + assert(!(preserved_analyses_ & + ~(ir::IRContext::kAnalysisDefUse | + ir::IRContext::kAnalysisInstrToBlockMapping))); + } + + // Returns true if the users requested to update |analysis|. + inline bool IsAnalysisUpdateRequested( + ir::IRContext::Analysis analysis) const { + return preserved_analyses_ & analysis; + } + + // Updates the def/use manager if the user requested it. If he did not request + // an update, this function does nothing. + inline void UpdateDefUseMgr(ir::Instruction* insn) { + if (IsAnalysisUpdateRequested(ir::IRContext::kAnalysisDefUse)) + GetContext()->get_def_use_mgr()->AnalyzeInstDefUse(insn); + } + + // Updates the instruction to block analysis if the user requested it. If he + // did not request an update, this function does nothing. + inline void UpdateInstrToBlockMapping(ir::Instruction* insn) { + if (IsAnalysisUpdateRequested( + ir::IRContext::kAnalysisInstrToBlockMapping) && + parent_) + GetContext()->set_instr_block(insn, parent_); + } + + ir::IRContext* context_; + ir::BasicBlock* parent_; + InsertionPointTy insert_before_; + const ir::IRContext::Analysis preserved_analyses_; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_IR_BUILDER_H_ diff --git a/3rdparty/spirv-tools/source/opt/ir_context.cpp b/3rdparty/spirv-tools/source/opt/ir_context.cpp new file mode 100644 index 000000000..856e40367 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/ir_context.cpp @@ -0,0 +1,643 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ir_context.h" +#include "latest_version_glsl_std_450_header.h" +#include "log.h" +#include "mem_pass.h" +#include "reflect.h" + +#include + +namespace spvtools { +namespace ir { + +void IRContext::BuildInvalidAnalyses(IRContext::Analysis set) { + if (set & kAnalysisDefUse) { + BuildDefUseManager(); + } + if (set & kAnalysisInstrToBlockMapping) { + BuildInstrToBlockMapping(); + } + if (set & kAnalysisDecorations) { + BuildDecorationManager(); + } + if (set & kAnalysisCFG) { + BuildCFG(); + } + if (set & kAnalysisDominatorAnalysis) { + ResetDominatorAnalysis(); + } + if (set & kAnalysisLoopAnalysis) { + ResetLoopAnalysis(); + } + if (set & kAnalysisNameMap) { + BuildIdToNameMap(); + } + if (set & kAnalysisScalarEvolution) { + BuildScalarEvolutionAnalysis(); + } +} + +void IRContext::InvalidateAnalysesExceptFor( + IRContext::Analysis preserved_analyses) { + uint32_t analyses_to_invalidate = valid_analyses_ & (~preserved_analyses); + InvalidateAnalyses(static_cast(analyses_to_invalidate)); +} + +void IRContext::InvalidateAnalyses(IRContext::Analysis analyses_to_invalidate) { + if (analyses_to_invalidate & kAnalysisDefUse) { + def_use_mgr_.reset(nullptr); + } + if (analyses_to_invalidate & kAnalysisInstrToBlockMapping) { + instr_to_block_.clear(); + } + if (analyses_to_invalidate & kAnalysisDecorations) { + decoration_mgr_.reset(nullptr); + } + if (analyses_to_invalidate & kAnalysisCombinators) { + combinator_ops_.clear(); + } + if (analyses_to_invalidate & kAnalysisCFG) { + cfg_.reset(nullptr); + } + if (analyses_to_invalidate & kAnalysisDominatorAnalysis) { + dominator_trees_.clear(); + post_dominator_trees_.clear(); + } + if (analyses_to_invalidate & kAnalysisNameMap) { + id_to_name_.reset(nullptr); + } + + valid_analyses_ = Analysis(valid_analyses_ & ~analyses_to_invalidate); +} + +Instruction* IRContext::KillInst(ir::Instruction* inst) { + if (!inst) { + return nullptr; + } + + KillNamesAndDecorates(inst); + + if (AreAnalysesValid(kAnalysisDefUse)) { + get_def_use_mgr()->ClearInst(inst); + } + if (AreAnalysesValid(kAnalysisInstrToBlockMapping)) { + instr_to_block_.erase(inst); + } + if (AreAnalysesValid(kAnalysisDecorations)) { + if (inst->result_id() != 0) { + decoration_mgr_->RemoveDecorationsFrom(inst->result_id()); + } + if (inst->IsDecoration()) { + decoration_mgr_->RemoveDecoration(inst); + } + } + + if (type_mgr_ && ir::IsTypeInst(inst->opcode())) { + type_mgr_->RemoveId(inst->result_id()); + } + + if (constant_mgr_ && ir::IsConstantInst(inst->opcode())) { + constant_mgr_->RemoveId(inst->result_id()); + } + + RemoveFromIdToName(inst); + + Instruction* next_instruction = nullptr; + if (inst->IsInAList()) { + next_instruction = inst->NextNode(); + inst->RemoveFromList(); + delete inst; + } else { + // Needed for instructions that are not part of a list like OpLabels, + // OpFunction, OpFunctionEnd, etc.. + inst->ToNop(); + } + return next_instruction; +} + +bool IRContext::KillDef(uint32_t id) { + ir::Instruction* def = get_def_use_mgr()->GetDef(id); + if (def != nullptr) { + KillInst(def); + return true; + } + return false; +} + +bool IRContext::ReplaceAllUsesWith(uint32_t before, uint32_t after) { + if (before == after) return false; + + // Ensure that |after| has been registered as def. + assert(get_def_use_mgr()->GetDef(after) && + "'after' is not a registered def."); + + std::vector> uses_to_update; + get_def_use_mgr()->ForEachUse( + before, [&uses_to_update](ir::Instruction* user, uint32_t index) { + uses_to_update.emplace_back(user, index); + }); + + ir::Instruction* prev = nullptr; + for (auto p : uses_to_update) { + ir::Instruction* user = p.first; + uint32_t index = p.second; + if (prev == nullptr || prev != user) { + ForgetUses(user); + prev = user; + } + const uint32_t type_result_id_count = + (user->result_id() != 0) + (user->type_id() != 0); + + if (index < type_result_id_count) { + // Update the type_id. Note that result id is immutable so it should + // never be updated. + if (user->type_id() != 0 && index == 0) { + user->SetResultType(after); + } else if (user->type_id() == 0) { + SPIRV_ASSERT(consumer_, false, + "Result type id considered as use while the instruction " + "doesn't have a result type id."); + (void)consumer_; // Makes the compiler happy for release build. + } else { + SPIRV_ASSERT(consumer_, false, + "Trying setting the immutable result id."); + } + } else { + // Update an in-operand. + uint32_t in_operand_pos = index - type_result_id_count; + // Make the modification in the instruction. + user->SetInOperand(in_operand_pos, {after}); + } + AnalyzeUses(user); + }; + + return true; +} + +bool IRContext::IsConsistent() { +#ifndef SPIRV_CHECK_CONTEXT + return true; +#endif + + if (AreAnalysesValid(kAnalysisDefUse)) { + opt::analysis::DefUseManager new_def_use(module()); + if (*get_def_use_mgr() != new_def_use) { + return false; + } + } + + if (AreAnalysesValid(kAnalysisInstrToBlockMapping)) { + for (auto& func : *module()) { + for (auto& block : func) { + if (!block.WhileEachInst([this, &block](ir::Instruction* inst) { + if (get_instr_block(inst) != &block) { + return false; + } + return true; + })) + return false; + } + } + } + + if (!CheckCFG()) { + return false; + } + + return true; +} + +void spvtools::ir::IRContext::ForgetUses(Instruction* inst) { + if (AreAnalysesValid(kAnalysisDefUse)) { + get_def_use_mgr()->EraseUseRecordsOfOperandIds(inst); + } + if (AreAnalysesValid(kAnalysisDecorations)) { + if (inst->IsDecoration()) { + get_decoration_mgr()->RemoveDecoration(inst); + } + } + RemoveFromIdToName(inst); +} + +void IRContext::AnalyzeUses(Instruction* inst) { + if (AreAnalysesValid(kAnalysisDefUse)) { + get_def_use_mgr()->AnalyzeInstUse(inst); + } + if (AreAnalysesValid(kAnalysisDecorations)) { + if (inst->IsDecoration()) { + get_decoration_mgr()->AddDecoration(inst); + } + } + if (id_to_name_ && + (inst->opcode() == SpvOpName || inst->opcode() == SpvOpMemberName)) { + id_to_name_->insert({inst->GetSingleWordInOperand(0), inst}); + } +} + +void IRContext::KillNamesAndDecorates(uint32_t id) { + std::vector decorations = + get_decoration_mgr()->GetDecorationsFor(id, true); + + for (Instruction* inst : decorations) { + KillInst(inst); + } + + std::vector name_to_kill; + for (auto name : GetNames(id)) { + name_to_kill.push_back(name.second); + } + for (ir::Instruction* name_inst : name_to_kill) { + KillInst(name_inst); + } +} + +void IRContext::KillNamesAndDecorates(Instruction* inst) { + const uint32_t rId = inst->result_id(); + if (rId == 0) return; + KillNamesAndDecorates(rId); +} + +void IRContext::AddCombinatorsForCapability(uint32_t capability) { + if (capability == SpvCapabilityShader) { + combinator_ops_[0].insert({ + SpvOpNop, + SpvOpUndef, + SpvOpConstant, + SpvOpConstantTrue, + SpvOpConstantFalse, + SpvOpConstantComposite, + SpvOpConstantSampler, + SpvOpConstantNull, + SpvOpTypeVoid, + SpvOpTypeBool, + SpvOpTypeInt, + SpvOpTypeFloat, + SpvOpTypeVector, + SpvOpTypeMatrix, + SpvOpTypeImage, + SpvOpTypeSampler, + SpvOpTypeSampledImage, + SpvOpTypeArray, + SpvOpTypeRuntimeArray, + SpvOpTypeStruct, + SpvOpTypeOpaque, + SpvOpTypePointer, + SpvOpTypeFunction, + SpvOpTypeEvent, + SpvOpTypeDeviceEvent, + SpvOpTypeReserveId, + SpvOpTypeQueue, + SpvOpTypePipe, + SpvOpTypeForwardPointer, + SpvOpVariable, + SpvOpImageTexelPointer, + SpvOpLoad, + SpvOpAccessChain, + SpvOpInBoundsAccessChain, + SpvOpArrayLength, + SpvOpVectorExtractDynamic, + SpvOpVectorInsertDynamic, + SpvOpVectorShuffle, + SpvOpCompositeConstruct, + SpvOpCompositeExtract, + SpvOpCompositeInsert, + SpvOpCopyObject, + SpvOpTranspose, + SpvOpSampledImage, + SpvOpImageSampleImplicitLod, + SpvOpImageSampleExplicitLod, + SpvOpImageSampleDrefImplicitLod, + SpvOpImageSampleDrefExplicitLod, + SpvOpImageSampleProjImplicitLod, + SpvOpImageSampleProjExplicitLod, + SpvOpImageSampleProjDrefImplicitLod, + SpvOpImageSampleProjDrefExplicitLod, + SpvOpImageFetch, + SpvOpImageGather, + SpvOpImageDrefGather, + SpvOpImageRead, + SpvOpImage, + SpvOpConvertFToU, + SpvOpConvertFToS, + SpvOpConvertSToF, + SpvOpConvertUToF, + SpvOpUConvert, + SpvOpSConvert, + SpvOpFConvert, + SpvOpQuantizeToF16, + SpvOpBitcast, + SpvOpSNegate, + SpvOpFNegate, + SpvOpIAdd, + SpvOpFAdd, + SpvOpISub, + SpvOpFSub, + SpvOpIMul, + SpvOpFMul, + SpvOpUDiv, + SpvOpSDiv, + SpvOpFDiv, + SpvOpUMod, + SpvOpSRem, + SpvOpSMod, + SpvOpFRem, + SpvOpFMod, + SpvOpVectorTimesScalar, + SpvOpMatrixTimesScalar, + SpvOpVectorTimesMatrix, + SpvOpMatrixTimesVector, + SpvOpMatrixTimesMatrix, + SpvOpOuterProduct, + SpvOpDot, + SpvOpIAddCarry, + SpvOpISubBorrow, + SpvOpUMulExtended, + SpvOpSMulExtended, + SpvOpAny, + SpvOpAll, + SpvOpIsNan, + SpvOpIsInf, + SpvOpLogicalEqual, + SpvOpLogicalNotEqual, + SpvOpLogicalOr, + SpvOpLogicalAnd, + SpvOpLogicalNot, + SpvOpSelect, + SpvOpIEqual, + SpvOpINotEqual, + SpvOpUGreaterThan, + SpvOpSGreaterThan, + SpvOpUGreaterThanEqual, + SpvOpSGreaterThanEqual, + SpvOpULessThan, + SpvOpSLessThan, + SpvOpULessThanEqual, + SpvOpSLessThanEqual, + SpvOpFOrdEqual, + SpvOpFUnordEqual, + SpvOpFOrdNotEqual, + SpvOpFUnordNotEqual, + SpvOpFOrdLessThan, + SpvOpFUnordLessThan, + SpvOpFOrdGreaterThan, + SpvOpFUnordGreaterThan, + SpvOpFOrdLessThanEqual, + SpvOpFUnordLessThanEqual, + SpvOpFOrdGreaterThanEqual, + SpvOpFUnordGreaterThanEqual, + SpvOpShiftRightLogical, + SpvOpShiftRightArithmetic, + SpvOpShiftLeftLogical, + SpvOpBitwiseOr, + SpvOpBitwiseXor, + SpvOpBitwiseAnd, + SpvOpNot, + SpvOpBitFieldInsert, + SpvOpBitFieldSExtract, + SpvOpBitFieldUExtract, + SpvOpBitReverse, + SpvOpBitCount, + SpvOpPhi, + SpvOpImageSparseSampleImplicitLod, + SpvOpImageSparseSampleExplicitLod, + SpvOpImageSparseSampleDrefImplicitLod, + SpvOpImageSparseSampleDrefExplicitLod, + SpvOpImageSparseSampleProjImplicitLod, + SpvOpImageSparseSampleProjExplicitLod, + SpvOpImageSparseSampleProjDrefImplicitLod, + SpvOpImageSparseSampleProjDrefExplicitLod, + SpvOpImageSparseFetch, + SpvOpImageSparseGather, + SpvOpImageSparseDrefGather, + SpvOpImageSparseTexelsResident, + SpvOpImageSparseRead, + SpvOpSizeOf + // TODO(dneto): Add instructions enabled by ImageQuery + }); + } +} + +void IRContext::AddCombinatorsForExtension(ir::Instruction* extension) { + assert(extension->opcode() == SpvOpExtInstImport && + "Expecting an import of an extension's instruction set."); + const char* extension_name = + reinterpret_cast(&extension->GetInOperand(0).words[0]); + if (!strcmp(extension_name, "GLSL.std.450")) { + combinator_ops_[extension->result_id()] = {GLSLstd450Round, + GLSLstd450RoundEven, + GLSLstd450Trunc, + GLSLstd450FAbs, + GLSLstd450SAbs, + GLSLstd450FSign, + GLSLstd450SSign, + GLSLstd450Floor, + GLSLstd450Ceil, + GLSLstd450Fract, + GLSLstd450Radians, + GLSLstd450Degrees, + GLSLstd450Sin, + GLSLstd450Cos, + GLSLstd450Tan, + GLSLstd450Asin, + GLSLstd450Acos, + GLSLstd450Atan, + GLSLstd450Sinh, + GLSLstd450Cosh, + GLSLstd450Tanh, + GLSLstd450Asinh, + GLSLstd450Acosh, + GLSLstd450Atanh, + GLSLstd450Atan2, + GLSLstd450Pow, + GLSLstd450Exp, + GLSLstd450Log, + GLSLstd450Exp2, + GLSLstd450Log2, + GLSLstd450Sqrt, + GLSLstd450InverseSqrt, + GLSLstd450Determinant, + GLSLstd450MatrixInverse, + GLSLstd450ModfStruct, + GLSLstd450FMin, + GLSLstd450UMin, + GLSLstd450SMin, + GLSLstd450FMax, + GLSLstd450UMax, + GLSLstd450SMax, + GLSLstd450FClamp, + GLSLstd450UClamp, + GLSLstd450SClamp, + GLSLstd450FMix, + GLSLstd450IMix, + GLSLstd450Step, + GLSLstd450SmoothStep, + GLSLstd450Fma, + GLSLstd450FrexpStruct, + GLSLstd450Ldexp, + GLSLstd450PackSnorm4x8, + GLSLstd450PackUnorm4x8, + GLSLstd450PackSnorm2x16, + GLSLstd450PackUnorm2x16, + GLSLstd450PackHalf2x16, + GLSLstd450PackDouble2x32, + GLSLstd450UnpackSnorm2x16, + GLSLstd450UnpackUnorm2x16, + GLSLstd450UnpackHalf2x16, + GLSLstd450UnpackSnorm4x8, + GLSLstd450UnpackUnorm4x8, + GLSLstd450UnpackDouble2x32, + GLSLstd450Length, + GLSLstd450Distance, + GLSLstd450Cross, + GLSLstd450Normalize, + GLSLstd450FaceForward, + GLSLstd450Reflect, + GLSLstd450Refract, + GLSLstd450FindILsb, + GLSLstd450FindSMsb, + GLSLstd450FindUMsb, + GLSLstd450InterpolateAtCentroid, + GLSLstd450InterpolateAtSample, + GLSLstd450InterpolateAtOffset, + GLSLstd450NMin, + GLSLstd450NMax, + GLSLstd450NClamp}; + } else { + // Map the result id to the empty set. + combinator_ops_[extension->result_id()]; + } +} + +void IRContext::InitializeCombinators() { + get_feature_mgr()->GetCapabilities()->ForEach( + [this](SpvCapability cap) { AddCombinatorsForCapability(cap); }); + + for (auto& extension : module()->ext_inst_imports()) { + AddCombinatorsForExtension(&extension); + } + + valid_analyses_ |= kAnalysisCombinators; +} + +void IRContext::RemoveFromIdToName(const Instruction* inst) { + if (id_to_name_ && + (inst->opcode() == SpvOpName || inst->opcode() == SpvOpMemberName)) { + auto range = id_to_name_->equal_range(inst->GetSingleWordInOperand(0)); + for (auto it = range.first; it != range.second; ++it) { + if (it->second == inst) { + id_to_name_->erase(it); + break; + } + } + } +} + +ir::LoopDescriptor* IRContext::GetLoopDescriptor(const ir::Function* f) { + if (!AreAnalysesValid(kAnalysisLoopAnalysis)) { + ResetLoopAnalysis(); + } + + std::unordered_map::iterator it = + loop_descriptors_.find(f); + if (it == loop_descriptors_.end()) { + return &loop_descriptors_.emplace(std::make_pair(f, ir::LoopDescriptor(f))) + .first->second; + } + + return &it->second; +} + +// Gets the dominator analysis for function |f|. +opt::DominatorAnalysis* IRContext::GetDominatorAnalysis(const ir::Function* f, + const ir::CFG& in_cfg) { + if (!AreAnalysesValid(kAnalysisDominatorAnalysis)) { + ResetDominatorAnalysis(); + } + + if (dominator_trees_.find(f) == dominator_trees_.end()) { + dominator_trees_[f].InitializeTree(f, in_cfg); + } + + return &dominator_trees_[f]; +} + +// Gets the postdominator analysis for function |f|. +opt::PostDominatorAnalysis* IRContext::GetPostDominatorAnalysis( + const ir::Function* f, const ir::CFG& in_cfg) { + if (!AreAnalysesValid(kAnalysisDominatorAnalysis)) { + ResetDominatorAnalysis(); + } + + if (post_dominator_trees_.find(f) == post_dominator_trees_.end()) { + post_dominator_trees_[f].InitializeTree(f, in_cfg); + } + + return &post_dominator_trees_[f]; +} + +bool ir::IRContext::CheckCFG() { + std::unordered_map> real_preds; + if (!AreAnalysesValid(kAnalysisCFG)) { + return true; + } + + for (ir::Function& function : *module()) { + for (const auto& bb : function) { + bb.ForEachSuccessorLabel([&bb, &real_preds](const uint32_t lab_id) { + real_preds[lab_id].push_back(bb.id()); + }); + } + + for (auto& bb : function) { + std::vector preds = cfg()->preds(bb.id()); + std::vector real = real_preds[bb.id()]; + std::sort(preds.begin(), preds.end()); + std::sort(real.begin(), real.end()); + + bool same = true; + if (preds.size() != real.size()) { + same = false; + } + + for (size_t i = 0; i < real.size() && same; i++) { + if (preds[i] != real[i]) { + same = false; + } + } + + if (!same) { + std::cerr << "Predecessors for " << bb.id() << " are different:\n"; + + std::cerr << "Real:"; + for (uint32_t i : real) { + std::cerr << ' ' << i; + } + std::cerr << std::endl; + + std::cerr << "Recorded:"; + for (uint32_t i : preds) { + std::cerr << ' ' << i; + } + std::cerr << std::endl; + } + if (!same) return false; + } + } + + return true; +} +} // namespace ir +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/ir_context.h b/3rdparty/spirv-tools/source/opt/ir_context.h new file mode 100644 index 000000000..bb44b4997 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/ir_context.h @@ -0,0 +1,809 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SPIRV_TOOLS_IR_CONTEXT_H +#define SPIRV_TOOLS_IR_CONTEXT_H + +#include "assembly_grammar.h" +#include "cfg.h" +#include "constants.h" +#include "decoration_manager.h" +#include "def_use_manager.h" +#include "dominator_analysis.h" +#include "feature_manager.h" +#include "loop_descriptor.h" +#include "module.h" +#include "scalar_analysis.h" +#include "type_manager.h" + +#include +#include +#include +#include + +namespace spvtools { +namespace ir { + +class IRContext { + public: + // Available analyses. + // + // When adding a new analysis: + // + // 1. Enum values should be powers of 2. These are cast into uint32_t + // bitmasks, so we can have at most 31 analyses represented. + // + // 2. Make sure it gets invalidated or preserved by IRContext methods that add + // or remove IR elements (e.g., KillDef, KillInst, ReplaceAllUsesWith). + // + // 3. Add handling code in BuildInvalidAnalyses and InvalidateAnalyses + enum Analysis { + kAnalysisNone = 0 << 0, + kAnalysisBegin = 1 << 0, + kAnalysisDefUse = kAnalysisBegin, + kAnalysisInstrToBlockMapping = 1 << 1, + kAnalysisDecorations = 1 << 2, + kAnalysisCombinators = 1 << 3, + kAnalysisCFG = 1 << 4, + kAnalysisDominatorAnalysis = 1 << 5, + kAnalysisLoopAnalysis = 1 << 6, + kAnalysisNameMap = 1 << 7, + kAnalysisScalarEvolution = 1 << 8, + kAnalysisEnd = 1 << 9 + }; + + friend inline Analysis operator|(Analysis lhs, Analysis rhs); + friend inline Analysis& operator|=(Analysis& lhs, Analysis rhs); + friend inline Analysis operator<<(Analysis a, int shift); + friend inline Analysis& operator<<=(Analysis& a, int shift); + + // Creates an |IRContext| that contains an owned |Module| + IRContext(spv_target_env env, spvtools::MessageConsumer c) + : syntax_context_(spvContextCreate(env)), + grammar_(syntax_context_), + unique_id_(0), + module_(new Module()), + consumer_(std::move(c)), + def_use_mgr_(nullptr), + valid_analyses_(kAnalysisNone), + constant_mgr_(nullptr), + type_mgr_(nullptr), + id_to_name_(nullptr) { + libspirv::SetContextMessageConsumer(syntax_context_, consumer_); + module_->SetContext(this); + } + + IRContext(spv_target_env env, std::unique_ptr&& m, + spvtools::MessageConsumer c) + : syntax_context_(spvContextCreate(env)), + grammar_(syntax_context_), + unique_id_(0), + module_(std::move(m)), + consumer_(std::move(c)), + def_use_mgr_(nullptr), + valid_analyses_(kAnalysisNone), + type_mgr_(nullptr), + id_to_name_(nullptr) { + libspirv::SetContextMessageConsumer(syntax_context_, consumer_); + module_->SetContext(this); + InitializeCombinators(); + } + + ~IRContext() { spvContextDestroy(syntax_context_); } + + Module* module() const { return module_.get(); } + + // Returns a vector of pointers to constant-creation instructions in this + // context. + inline std::vector GetConstants(); + inline std::vector GetConstants() const; + + // Iterators for annotation instructions contained in this context. + inline Module::inst_iterator annotation_begin(); + inline Module::inst_iterator annotation_end(); + inline IteratorRange annotations(); + inline IteratorRange annotations() const; + + // Iterators for capabilities instructions contained in this module. + inline Module::inst_iterator capability_begin(); + inline Module::inst_iterator capability_end(); + inline IteratorRange capabilities(); + inline IteratorRange capabilities() const; + + // Iterators for types, constants and global variables instructions. + inline ir::Module::inst_iterator types_values_begin(); + inline ir::Module::inst_iterator types_values_end(); + inline IteratorRange types_values(); + inline IteratorRange types_values() const; + + // Iterators for extension instructions contained in this module. + inline Module::inst_iterator ext_inst_import_begin(); + inline Module::inst_iterator ext_inst_import_end(); + inline IteratorRange ext_inst_imports(); + inline IteratorRange ext_inst_imports() const; + + // There are several kinds of debug instructions, according to where they can + // appear in the logical layout of a module: + // - Section 7a: OpString, OpSourceExtension, OpSource, OpSourceContinued + // - Section 7b: OpName, OpMemberName + // - Section 7c: OpModuleProcessed + // - Mostly anywhere: OpLine and OpNoLine + // + + // Iterators for debug 1 instructions (excluding OpLine & OpNoLine) contained + // in this module. These are for layout section 7a. + inline Module::inst_iterator debug1_begin(); + inline Module::inst_iterator debug1_end(); + inline IteratorRange debugs1(); + inline IteratorRange debugs1() const; + + // Iterators for debug 2 instructions (excluding OpLine & OpNoLine) contained + // in this module. These are for layout section 7b. + inline Module::inst_iterator debug2_begin(); + inline Module::inst_iterator debug2_end(); + inline IteratorRange debugs2(); + inline IteratorRange debugs2() const; + + // Iterators for debug 3 instructions (excluding OpLine & OpNoLine) contained + // in this module. These are for layout section 7c. + inline Module::inst_iterator debug3_begin(); + inline Module::inst_iterator debug3_end(); + inline IteratorRange debugs3(); + inline IteratorRange debugs3() const; + + // Clears all debug instructions (excluding OpLine & OpNoLine). + inline void debug_clear(); + + // Appends a capability instruction to this module. + inline void AddCapability(std::unique_ptr&& c); + // Appends an extension instruction to this module. + inline void AddExtension(std::unique_ptr&& e); + // Appends an extended instruction set instruction to this module. + inline void AddExtInstImport(std::unique_ptr&& e); + // Set the memory model for this module. + inline void SetMemoryModel(std::unique_ptr&& m); + // Appends an entry point instruction to this module. + inline void AddEntryPoint(std::unique_ptr&& e); + // Appends an execution mode instruction to this module. + inline void AddExecutionMode(std::unique_ptr&& e); + // Appends a debug 1 instruction (excluding OpLine & OpNoLine) to this module. + // "debug 1" instructions are the ones in layout section 7.a), see section + // 2.4 Logical Layout of a Module from the SPIR-V specification. + inline void AddDebug1Inst(std::unique_ptr&& d); + // Appends a debug 2 instruction (excluding OpLine & OpNoLine) to this module. + // "debug 2" instructions are the ones in layout section 7.b), see section + // 2.4 Logical Layout of a Module from the SPIR-V specification. + inline void AddDebug2Inst(std::unique_ptr&& d); + // Appends a debug 3 instruction (OpModuleProcessed) to this module. + // This is due to decision by the SPIR Working Group, pending publication. + inline void AddDebug3Inst(std::unique_ptr&& d); + // Appends an annotation instruction to this module. + inline void AddAnnotationInst(std::unique_ptr&& a); + // Appends a type-declaration instruction to this module. + inline void AddType(std::unique_ptr&& t); + // Appends a constant, global variable, or OpUndef instruction to this module. + inline void AddGlobalValue(std::unique_ptr&& v); + // Appends a function to this module. + inline void AddFunction(std::unique_ptr&& f); + + // Returns a pointer to a def-use manager. If the def-use manager is + // invalid, it is rebuilt first. + opt::analysis::DefUseManager* get_def_use_mgr() { + if (!AreAnalysesValid(kAnalysisDefUse)) { + BuildDefUseManager(); + } + return def_use_mgr_.get(); + } + + // Returns the basic block for instruction |instr|. Re-builds the instruction + // block map, if needed. + ir::BasicBlock* get_instr_block(ir::Instruction* instr) { + if (!AreAnalysesValid(kAnalysisInstrToBlockMapping)) { + BuildInstrToBlockMapping(); + } + auto entry = instr_to_block_.find(instr); + return (entry != instr_to_block_.end()) ? entry->second : nullptr; + } + + // Returns the basic block for |id|. Re-builds the instruction block map, if + // needed. + // + // |id| must be a registered definition. + ir::BasicBlock* get_instr_block(uint32_t id) { + ir::Instruction* def = get_def_use_mgr()->GetDef(id); + return get_instr_block(def); + } + + // Sets the basic block for |inst|. Re-builds the mapping if it has become + // invalid. + void set_instr_block(ir::Instruction* inst, ir::BasicBlock* block) { + if (AreAnalysesValid(kAnalysisInstrToBlockMapping)) { + instr_to_block_[inst] = block; + } + } + + // Returns a pointer the decoration manager. If the decoration manger is + // invalid, it is rebuilt first. + opt::analysis::DecorationManager* get_decoration_mgr() { + if (!AreAnalysesValid(kAnalysisDecorations)) { + BuildDecorationManager(); + } + return decoration_mgr_.get(); + }; + + // Returns a pointer to the constant manager. If no constant manager has been + // created yet, it creates one. NOTE: Once created, the constant manager + // remains active and it is never re-built. + opt::analysis::ConstantManager* get_constant_mgr() { + if (!constant_mgr_) + constant_mgr_.reset(new opt::analysis::ConstantManager(this)); + return constant_mgr_.get(); + } + + // Returns a pointer to the type manager. If no type manager has been created + // yet, it creates one. NOTE: Once created, the type manager remains active it + // is never re-built. + opt::analysis::TypeManager* get_type_mgr() { + if (!type_mgr_) + type_mgr_.reset(new opt::analysis::TypeManager(consumer(), this)); + return type_mgr_.get(); + } + + // Returns a pointer to the scalar evolution analysis. If it is invalid it + // will be rebuilt first. + opt::ScalarEvolutionAnalysis* GetScalarEvolutionAnalysis() { + if (!AreAnalysesValid(kAnalysisScalarEvolution)) { + BuildScalarEvolutionAnalysis(); + } + return scalar_evolution_analysis_.get(); + } + + // Build the map from the ids to the OpName and OpMemberName instruction + // associated with it. + inline void BuildIdToNameMap(); + + // Returns a range of instrucions that contain all of the OpName and + // OpMemberNames associated with the given id. + inline IteratorRange::iterator> + GetNames(uint32_t id); + + // Sets the message consumer to the given |consumer|. |consumer| which will be + // invoked every time there is a message to be communicated to the outside. + void SetMessageConsumer(spvtools::MessageConsumer c) { + consumer_ = std::move(c); + } + + // Returns the reference to the message consumer for this pass. + const spvtools::MessageConsumer& consumer() const { return consumer_; } + + // Rebuilds the analyses in |set| that are invalid. + void BuildInvalidAnalyses(Analysis set); + + // Invalidates all of the analyses except for those in |preserved_analyses|. + void InvalidateAnalysesExceptFor(Analysis preserved_analyses); + + // Invalidates the analyses marked in |analyses_to_invalidate|. + void InvalidateAnalyses(Analysis analyses_to_invalidate); + + // Deletes the instruction defining the given |id|. Returns true on + // success, false if the given |id| is not defined at all. This method also + // erases the name, decorations, and defintion of |id|. + // + // Pointers and iterators pointing to the deleted instructions become invalid. + // However other pointers and iterators are still valid. + bool KillDef(uint32_t id); + + // Deletes the given instruction |inst|. This method erases the + // information of the given instruction's uses of its operands. If |inst| + // defines a result id, its name and decorations will also be deleted. + // + // Pointer and iterator pointing to the deleted instructions become invalid. + // However other pointers and iterators are still valid. + // + // Note that if an instruction is not in an instruction list, the memory may + // not be safe to delete, so the instruction is turned into a OpNop instead. + // This can happen with OpLabel. + // + // Returns a pointer to the instruction after |inst| or |nullptr| if no such + // instruction exists. + Instruction* KillInst(ir::Instruction* inst); + + // Returns true if all of the given analyses are valid. + bool AreAnalysesValid(Analysis set) { return (set & valid_analyses_) == set; } + + // Replaces all uses of |before| id with |after| id. Returns true if any + // replacement happens. This method does not kill the definition of the + // |before| id. If |after| is the same as |before|, does nothing and returns + // false. + // + // |before| and |after| must be registered definitions in the DefUseManager. + bool ReplaceAllUsesWith(uint32_t before, uint32_t after); + + // Returns true if all of the analyses that are suppose to be valid are + // actually valid. + bool IsConsistent(); + + // The IRContext will look at the def and uses of |inst| and update any valid + // analyses will be updated accordingly. + inline void AnalyzeDefUse(Instruction* inst); + + // Informs the IRContext that the uses of |inst| are going to change, and that + // is should forget everything it know about the current uses. Any valid + // analyses will be updated accordingly. + void ForgetUses(Instruction* inst); + + // The IRContext will look at the uses of |inst| and update any valid analyses + // will be updated accordingly. + void AnalyzeUses(Instruction* inst); + + // Kill all name and decorate ops targeting |id|. + void KillNamesAndDecorates(uint32_t id); + + // Kill all name and decorate ops targeting the result id of |inst|. + void KillNamesAndDecorates(ir::Instruction* inst); + + // Returns the next unique id for use by an instruction. + inline uint32_t TakeNextUniqueId() { + assert(unique_id_ != std::numeric_limits::max()); + + // Skip zero. + return ++unique_id_; + } + + // Returns true if |inst| is a combinator in the current context. + // |combinator_ops_| is built if it has not been already. + inline bool IsCombinatorInstruction(ir::Instruction* inst) { + if (!AreAnalysesValid(kAnalysisCombinators)) { + InitializeCombinators(); + } + const uint32_t kExtInstSetIdInIndx = 0; + const uint32_t kExtInstInstructionInIndx = 1; + + if (inst->opcode() != SpvOpExtInst) { + return combinator_ops_[0].count(inst->opcode()) != 0; + } else { + uint32_t set = inst->GetSingleWordInOperand(kExtInstSetIdInIndx); + uint32_t op = inst->GetSingleWordInOperand(kExtInstInstructionInIndx); + return combinator_ops_[set].count(op) != 0; + } + } + + // Returns a pointer to the CFG for all the functions in |module_|. + ir::CFG* cfg() { + if (!AreAnalysesValid(kAnalysisCFG)) { + BuildCFG(); + } + return cfg_.get(); + } + + // Gets the loop descriptor for function |f|. + ir::LoopDescriptor* GetLoopDescriptor(const ir::Function* f); + + // Gets the dominator analysis for function |f|. + opt::DominatorAnalysis* GetDominatorAnalysis(const ir::Function* f, + const ir::CFG&); + + // Gets the postdominator analysis for function |f|. + opt::PostDominatorAnalysis* GetPostDominatorAnalysis(const ir::Function* f, + const ir::CFG&); + + // Remove the dominator tree of |f| from the cache. + inline void RemoveDominatorAnalysis(const ir::Function* f) { + dominator_trees_.erase(f); + } + + // Remove the postdominator tree of |f| from the cache. + inline void RemovePostDominatorAnalysis(const ir::Function* f) { + post_dominator_trees_.erase(f); + } + + // Return the next available SSA id and increment it. + inline uint32_t TakeNextId() { return module()->TakeNextIdBound(); } + + opt::FeatureManager* get_feature_mgr() { + if (!feature_mgr_.get()) { + AnalyzeFeatures(); + } + return feature_mgr_.get(); + } + + // Returns the grammar for this context. + const libspirv::AssemblyGrammar& grammar() const { return grammar_; } + + // If |inst| has not yet been analysed by the def-use manager, then analyse + // its definitions and uses. + inline void UpdateDefUse(Instruction* inst); + + private: + // Builds the def-use manager from scratch, even if it was already valid. + void BuildDefUseManager() { + def_use_mgr_.reset(new opt::analysis::DefUseManager(module())); + valid_analyses_ = valid_analyses_ | kAnalysisDefUse; + } + + // Builds the instruction-block map for the whole module. + void BuildInstrToBlockMapping() { + instr_to_block_.clear(); + for (auto& fn : *module_) { + for (auto& block : fn) { + block.ForEachInst([this, &block](ir::Instruction* inst) { + instr_to_block_[inst] = █ + }); + } + } + valid_analyses_ = valid_analyses_ | kAnalysisInstrToBlockMapping; + } + + void BuildDecorationManager() { + decoration_mgr_.reset(new opt::analysis::DecorationManager(module())); + valid_analyses_ = valid_analyses_ | kAnalysisDecorations; + } + + void BuildCFG() { + cfg_.reset(new ir::CFG(module())); + valid_analyses_ = valid_analyses_ | kAnalysisCFG; + } + + void BuildScalarEvolutionAnalysis() { + scalar_evolution_analysis_.reset(new opt::ScalarEvolutionAnalysis(this)); + valid_analyses_ = valid_analyses_ | kAnalysisScalarEvolution; + } + + // Removes all computed dominator and post-dominator trees. This will force + // the context to rebuild the trees on demand. + void ResetDominatorAnalysis() { + // Clear the cache. + dominator_trees_.clear(); + post_dominator_trees_.clear(); + valid_analyses_ = valid_analyses_ | kAnalysisDominatorAnalysis; + } + + // Removes all computed loop descriptors. + void ResetLoopAnalysis() { + // Clear the cache. + loop_descriptors_.clear(); + valid_analyses_ = valid_analyses_ | kAnalysisLoopAnalysis; + } + + // Analyzes the features in the owned module. Builds the manager if required. + void AnalyzeFeatures() { + feature_mgr_.reset(new opt::FeatureManager(grammar_)); + feature_mgr_->Analyze(module()); + } + + // Scans a module looking for it capabilities, and initializes combinator_ops_ + // accordingly. + void InitializeCombinators(); + + // Add the combinator opcode for the given capability to combinator_ops_. + void AddCombinatorsForCapability(uint32_t capability); + + // Add the combinator opcode for the given extension to combinator_ops_. + void AddCombinatorsForExtension(ir::Instruction* extension); + + // Remove |inst| from |id_to_name_| if it is in map. + void RemoveFromIdToName(const Instruction* inst); + + // Returns true if it is suppose to be valid but it is incorrect. Returns + // true if the cfg is invalidated. + bool CheckCFG(); + + // The SPIR-V syntax context containing grammar tables for opcodes and + // operands. + spv_context syntax_context_; + + // Auxiliary object for querying SPIR-V grammar facts. + libspirv::AssemblyGrammar grammar_; + + // An unique identifier for instructions in |module_|. Can be used to order + // instructions in a container. + // + // This member is initialized to 0, but always issues this value plus one. + // Therefore, 0 is not a valid unique id for an instruction. + uint32_t unique_id_; + + // The module being processed within this IR context. + std::unique_ptr module_; + + // A message consumer for diagnostics. + spvtools::MessageConsumer consumer_; + + // The def-use manager for |module_|. + std::unique_ptr def_use_mgr_; + + // The instruction decoration manager for |module_|. + std::unique_ptr decoration_mgr_; + std::unique_ptr feature_mgr_; + + // A map from instructions the the basic block they belong to. This mapping is + // built on-demand when get_instr_block() is called. + // + // NOTE: Do not traverse this map. Ever. Use the function and basic block + // iterators to traverse instructions. + std::unordered_map instr_to_block_; + + // A bitset indicating which analyes are currently valid. + Analysis valid_analyses_; + + // Opcodes of shader capability core executable instructions + // without side-effect. + std::unordered_map> combinator_ops_; + + // The CFG for all the functions in |module_|. + std::unique_ptr cfg_; + + // Each function in the module will create its own dominator tree. We cache + // the result so it doesn't need to be rebuilt each time. + std::map dominator_trees_; + std::map + post_dominator_trees_; + + // Cache of loop descriptors for each function. + std::unordered_map loop_descriptors_; + + // Constant manager for |module_|. + std::unique_ptr constant_mgr_; + + // Type manager for |module_|. + std::unique_ptr type_mgr_; + + // A map from an id to its corresponding OpName and OpMemberName instructions. + std::unique_ptr> id_to_name_; + + // The cache scalar evolution analysis node. + std::unique_ptr scalar_evolution_analysis_; +}; + +inline ir::IRContext::Analysis operator|(ir::IRContext::Analysis lhs, + ir::IRContext::Analysis rhs) { + return static_cast(static_cast(lhs) | + static_cast(rhs)); +} + +inline ir::IRContext::Analysis& operator|=(ir::IRContext::Analysis& lhs, + ir::IRContext::Analysis rhs) { + lhs = static_cast(static_cast(lhs) | + static_cast(rhs)); + return lhs; +} + +inline ir::IRContext::Analysis operator<<(ir::IRContext::Analysis a, + int shift) { + return static_cast(static_cast(a) << shift); +} + +inline ir::IRContext::Analysis& operator<<=(ir::IRContext::Analysis& a, + int shift) { + a = static_cast(static_cast(a) << shift); + return a; +} + +std::vector spvtools::ir::IRContext::GetConstants() { + return module()->GetConstants(); +} + +std::vector IRContext::GetConstants() const { + return ((const Module*)module())->GetConstants(); +} + +Module::inst_iterator IRContext::annotation_begin() { + return module()->annotation_begin(); +} + +Module::inst_iterator IRContext::annotation_end() { + return module()->annotation_end(); +} + +IteratorRange IRContext::annotations() { + return module_->annotations(); +} + +IteratorRange IRContext::annotations() const { + return ((const Module*)module_.get())->annotations(); +} + +Module::inst_iterator IRContext::capability_begin() { + return module()->capability_begin(); +} + +Module::inst_iterator IRContext::capability_end() { + return module()->capability_end(); +} + +IteratorRange IRContext::capabilities() { + return module()->capabilities(); +} + +IteratorRange IRContext::capabilities() const { + return ((const Module*)module())->capabilities(); +} + +ir::Module::inst_iterator IRContext::types_values_begin() { + return module()->types_values_begin(); +} + +ir::Module::inst_iterator IRContext::types_values_end() { + return module()->types_values_end(); +} + +IteratorRange IRContext::types_values() { + return module()->types_values(); +} + +IteratorRange IRContext::types_values() const { + return ((const Module*)module_.get())->types_values(); +} + +Module::inst_iterator IRContext::ext_inst_import_begin() { + return module()->ext_inst_import_begin(); +} + +Module::inst_iterator IRContext::ext_inst_import_end() { + return module()->ext_inst_import_end(); +} + +IteratorRange IRContext::ext_inst_imports() { + return module()->ext_inst_imports(); +} + +IteratorRange IRContext::ext_inst_imports() const { + return ((const Module*)module_.get())->ext_inst_imports(); +} + +Module::inst_iterator IRContext::debug1_begin() { + return module()->debug1_begin(); +} + +Module::inst_iterator IRContext::debug1_end() { return module()->debug1_end(); } + +IteratorRange IRContext::debugs1() { + return module()->debugs1(); +} + +IteratorRange IRContext::debugs1() const { + return ((const Module*)module_.get())->debugs1(); +} + +Module::inst_iterator IRContext::debug2_begin() { + return module()->debug2_begin(); +} +Module::inst_iterator IRContext::debug2_end() { return module()->debug2_end(); } + +IteratorRange IRContext::debugs2() { + return module()->debugs2(); +} + +IteratorRange IRContext::debugs2() const { + return ((const Module*)module_.get())->debugs2(); +} + +Module::inst_iterator IRContext::debug3_begin() { + return module()->debug3_begin(); +} + +Module::inst_iterator IRContext::debug3_end() { return module()->debug3_end(); } + +IteratorRange IRContext::debugs3() { + return module()->debugs3(); +} + +IteratorRange IRContext::debugs3() const { + return ((const Module*)module_.get())->debugs3(); +} + +void IRContext::debug_clear() { module_->debug_clear(); } + +void IRContext::AddCapability(std::unique_ptr&& c) { + AddCombinatorsForCapability(c->GetSingleWordInOperand(0)); + module()->AddCapability(std::move(c)); +} + +void IRContext::AddExtension(std::unique_ptr&& e) { + module()->AddExtension(std::move(e)); +} + +void IRContext::AddExtInstImport(std::unique_ptr&& e) { + AddCombinatorsForExtension(e.get()); + module()->AddExtInstImport(std::move(e)); +} + +void IRContext::SetMemoryModel(std::unique_ptr&& m) { + module()->SetMemoryModel(std::move(m)); +} + +void IRContext::AddEntryPoint(std::unique_ptr&& e) { + module()->AddEntryPoint(std::move(e)); +} + +void IRContext::AddExecutionMode(std::unique_ptr&& e) { + module()->AddExecutionMode(std::move(e)); +} + +void IRContext::AddDebug1Inst(std::unique_ptr&& d) { + module()->AddDebug1Inst(std::move(d)); +} + +void IRContext::AddDebug2Inst(std::unique_ptr&& d) { + if (AreAnalysesValid(kAnalysisNameMap)) { + if (d->opcode() == SpvOpName || d->opcode() == SpvOpMemberName) { + id_to_name_->insert({d->result_id(), d.get()}); + } + } + module()->AddDebug2Inst(std::move(d)); +} + +void IRContext::AddDebug3Inst(std::unique_ptr&& d) { + module()->AddDebug3Inst(std::move(d)); +} + +void IRContext::AddAnnotationInst(std::unique_ptr&& a) { + if (AreAnalysesValid(kAnalysisDecorations)) { + get_decoration_mgr()->AddDecoration(a.get()); + } + module()->AddAnnotationInst(std::move(a)); +} + +void IRContext::AddType(std::unique_ptr&& t) { + module()->AddType(std::move(t)); + if (AreAnalysesValid(kAnalysisDefUse)) { + get_def_use_mgr()->AnalyzeInstDef(&*(--types_values_end())); + } +} + +void IRContext::AddGlobalValue(std::unique_ptr&& v) { + module()->AddGlobalValue(std::move(v)); + if (AreAnalysesValid(kAnalysisDefUse)) { + get_def_use_mgr()->AnalyzeInstDef(&*(--types_values_end())); + } +} + +void IRContext::AddFunction(std::unique_ptr&& f) { + module()->AddFunction(std::move(f)); +} + +void IRContext::AnalyzeDefUse(Instruction* inst) { + if (AreAnalysesValid(kAnalysisDefUse)) { + get_def_use_mgr()->AnalyzeInstDefUse(inst); + } +} + +void IRContext::UpdateDefUse(Instruction* inst) { + if (AreAnalysesValid(kAnalysisDefUse)) { + get_def_use_mgr()->UpdateDefUse(inst); + } +} + +void IRContext::BuildIdToNameMap() { + id_to_name_.reset(new std::multimap()); + for (Instruction& debug_inst : debugs2()) { + if (debug_inst.opcode() == SpvOpMemberName || + debug_inst.opcode() == SpvOpName) { + id_to_name_->insert({debug_inst.GetSingleWordInOperand(0), &debug_inst}); + } + } + valid_analyses_ = valid_analyses_ | kAnalysisNameMap; +} + +IteratorRange::iterator> +IRContext::GetNames(uint32_t id) { + if (!AreAnalysesValid(kAnalysisNameMap)) { + BuildIdToNameMap(); + } + auto result = id_to_name_->equal_range(id); + return make_range(std::move(result.first), std::move(result.second)); +} + +} // namespace ir +} // namespace spvtools +#endif // SPIRV_TOOLS_IR_CONTEXT_H diff --git a/3rdparty/spirv-tools/source/opt/ir_loader.cpp b/3rdparty/spirv-tools/source/opt/ir_loader.cpp new file mode 100644 index 000000000..a526d6f9e --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/ir_loader.cpp @@ -0,0 +1,161 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ir_loader.h" + +#include "log.h" +#include "reflect.h" + +namespace spvtools { +namespace ir { + +IrLoader::IrLoader(const MessageConsumer& consumer, Module* m) + : consumer_(consumer), + module_(m), + source_(""), + inst_index_(0) {} + +bool IrLoader::AddInstruction(const spv_parsed_instruction_t* inst) { + ++inst_index_; + const auto opcode = static_cast(inst->opcode); + if (IsDebugLineInst(opcode)) { + dbg_line_info_.push_back(Instruction(module()->context(), *inst)); + return true; + } + + std::unique_ptr spv_inst( + new Instruction(module()->context(), *inst, std::move(dbg_line_info_))); + dbg_line_info_.clear(); + + const char* src = source_.c_str(); + spv_position_t loc = {inst_index_, 0, 0}; + + // Handle function and basic block boundaries first, then normal + // instructions. + if (opcode == SpvOpFunction) { + if (function_ != nullptr) { + Error(consumer_, src, loc, "function inside function"); + return false; + } + function_.reset(new Function(std::move(spv_inst))); + } else if (opcode == SpvOpFunctionEnd) { + if (function_ == nullptr) { + Error(consumer_, src, loc, + "OpFunctionEnd without corresponding OpFunction"); + return false; + } + if (block_ != nullptr) { + Error(consumer_, src, loc, "OpFunctionEnd inside basic block"); + return false; + } + function_->SetFunctionEnd(std::move(spv_inst)); + module_->AddFunction(std::move(function_)); + function_ = nullptr; + } else if (opcode == SpvOpLabel) { + if (function_ == nullptr) { + Error(consumer_, src, loc, "OpLabel outside function"); + return false; + } + if (block_ != nullptr) { + Error(consumer_, src, loc, "OpLabel inside basic block"); + return false; + } + block_.reset(new BasicBlock(std::move(spv_inst))); + } else if (IsTerminatorInst(opcode)) { + if (function_ == nullptr) { + Error(consumer_, src, loc, "terminator instruction outside function"); + return false; + } + if (block_ == nullptr) { + Error(consumer_, src, loc, "terminator instruction outside basic block"); + return false; + } + block_->AddInstruction(std::move(spv_inst)); + function_->AddBasicBlock(std::move(block_)); + block_ = nullptr; + } else { + if (function_ == nullptr) { // Outside function definition + SPIRV_ASSERT(consumer_, block_ == nullptr); + if (opcode == SpvOpCapability) { + module_->AddCapability(std::move(spv_inst)); + } else if (opcode == SpvOpExtension) { + module_->AddExtension(std::move(spv_inst)); + } else if (opcode == SpvOpExtInstImport) { + module_->AddExtInstImport(std::move(spv_inst)); + } else if (opcode == SpvOpMemoryModel) { + module_->SetMemoryModel(std::move(spv_inst)); + } else if (opcode == SpvOpEntryPoint) { + module_->AddEntryPoint(std::move(spv_inst)); + } else if (opcode == SpvOpExecutionMode) { + module_->AddExecutionMode(std::move(spv_inst)); + } else if (IsDebug1Inst(opcode)) { + module_->AddDebug1Inst(std::move(spv_inst)); + } else if (IsDebug2Inst(opcode)) { + module_->AddDebug2Inst(std::move(spv_inst)); + } else if (IsDebug3Inst(opcode)) { + module_->AddDebug3Inst(std::move(spv_inst)); + } else if (IsAnnotationInst(opcode)) { + module_->AddAnnotationInst(std::move(spv_inst)); + } else if (IsTypeInst(opcode)) { + module_->AddType(std::move(spv_inst)); + } else if (IsConstantInst(opcode) || opcode == SpvOpVariable || + opcode == SpvOpUndef) { + module_->AddGlobalValue(std::move(spv_inst)); + } else { + SPIRV_UNIMPLEMENTED(consumer_, + "unhandled inst type outside function definition"); + } + } else { + if (block_ == nullptr) { // Inside function but outside blocks + if (opcode != SpvOpFunctionParameter) { + Errorf(consumer_, src, loc, + "Non-OpFunctionParameter (opcode: %d) found inside " + "function but outside basic block", + opcode); + return false; + } + function_->AddParameter(std::move(spv_inst)); + } else { + block_->AddInstruction(std::move(spv_inst)); + } + } + } + return true; +} + +// Resolves internal references among the module, functions, basic blocks, etc. +// This function should be called after adding all instructions. +void IrLoader::EndModule() { + if (block_ && function_) { + // We're in the middle of a basic block, but the terminator is missing. + // Register the block anyway. This lets us write tests with less + // boilerplate. + function_->AddBasicBlock(std::move(block_)); + block_ = nullptr; + } + if (function_) { + // We're in the middle of a function, but the OpFunctionEnd is missing. + // Register the function anyway. This lets us write tests with less + // boilerplate. + module_->AddFunction(std::move(function_)); + function_ = nullptr; + } + for (auto& function : *module_) { + for (auto& bb : function) bb.SetParent(&function); + function.SetParent(module_); + } +} + +} // namespace ir +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/ir_loader.h b/3rdparty/spirv-tools/source/opt/ir_loader.h new file mode 100644 index 000000000..2f0ca8b0b --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/ir_loader.h @@ -0,0 +1,84 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_IR_LOADER_H_ +#define LIBSPIRV_OPT_IR_LOADER_H_ + +#include + +#include "basic_block.h" +#include "instruction.h" +#include "module.h" +#include "spirv-tools/libspirv.hpp" + +namespace spvtools { +namespace ir { + +// Loader class for constructing SPIR-V in-memory IR representation. Methods in +// this class are designed to work with the interface for spvBinaryParse() in +// libspirv.h so that we can leverage the syntax checks implemented behind it. +// +// The user is expected to call SetModuleHeader() to fill in the module's +// header, and then AddInstruction() for each decoded instruction, and finally +// EndModule() to finalize the module. The instructions processed in sequence +// by AddInstruction() should comprise a valid SPIR-V module. +class IrLoader { + public: + // Instantiates a builder to construct the given |module| gradually. + // All internal messages will be communicated to the outside via the given + // message |consumer|. This instance only keeps a reference to the |consumer|, + // so the |consumer| should outlive this instance. + IrLoader(const MessageConsumer& consumer, Module* m); + + // Sets the source name of the module. + void SetSource(const std::string& src) { source_ = src; } + + Module* module() const { return module_; } + + // Sets the fields in the module's header to the given parameters. + void SetModuleHeader(uint32_t magic, uint32_t version, uint32_t generator, + uint32_t bound, uint32_t reserved) { + module_->SetHeader({magic, version, generator, bound, reserved}); + } + // Adds an instruction to the module. Returns true if no error occurs. This + // method will properly capture and store the data provided in |inst| so that + // |inst| is no longer needed after returning. + bool AddInstruction(const spv_parsed_instruction_t* inst); + // Finalizes the module construction. This must be called after the module + // header has been set and all instructions have been added. This is + // forgiving in the case of a missing terminator instruction on a basic block, + // or a missing OpFunctionEnd. Resolves internal bookkeeping. + void EndModule(); + + private: + // Consumer for communicating messages to outside. + const MessageConsumer& consumer_; + // The module to be built. + Module* module_; + // The source name of the module. + std::string source_; + // The last used instruction index. + uint32_t inst_index_; + // The current Function under construction. + std::unique_ptr function_; + // The current BasicBlock under construction. + std::unique_ptr block_; + // Line related debug instructions accumulated thus far. + std::vector dbg_line_info_; +}; + +} // namespace ir +} // namespace spvtools + +#endif // LIBSPIRV_OPT_IR_LOADER_H_ diff --git a/3rdparty/spirv-tools/source/opt/iterator.h b/3rdparty/spirv-tools/source/opt/iterator.h new file mode 100644 index 000000000..d43dfbef7 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/iterator.h @@ -0,0 +1,258 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_ITERATOR_H_ +#define LIBSPIRV_OPT_ITERATOR_H_ + +#include // for ptrdiff_t +#include +#include +#include +#include + +namespace spvtools { +namespace ir { + +// An ad hoc iterator class for std::vector>. The +// purpose of this iterator class is to provide transparent access to those +// std::unique_ptr managed elements in the vector, behaving like we are using +// std::vector<|ValueType|>. +template +class UptrVectorIterator + : public std::iterator::type> { + public: + using super = std::iterator< + std::random_access_iterator_tag, + typename std::conditional::type>; + + using pointer = typename super::pointer; + using reference = typename super::reference; + using difference_type = typename super::difference_type; + + // Type aliases. We need to apply constness properly if |IsConst| is true. + using Uptr = std::unique_ptr; + using UptrVector = typename std::conditional, + std::vector>::type; + using UnderlyingIterator = + typename std::conditional::type; + + // Creates a new iterator from the given |container| and its raw iterator + // |it|. + UptrVectorIterator(UptrVector* container, const UnderlyingIterator& it) + : container_(container), iterator_(it) {} + UptrVectorIterator(const UptrVectorIterator&) = default; + UptrVectorIterator& operator=(const UptrVectorIterator&) = default; + + inline UptrVectorIterator& operator++(); + inline UptrVectorIterator operator++(int); + inline UptrVectorIterator& operator--(); + inline UptrVectorIterator operator--(int); + + reference operator*() const { return **iterator_; } + pointer operator->() { return (*iterator_).get(); } + reference operator[](ptrdiff_t index) { return **(iterator_ + index); } + + inline bool operator==(const UptrVectorIterator& that) const; + inline bool operator!=(const UptrVectorIterator& that) const; + + inline ptrdiff_t operator-(const UptrVectorIterator& that) const; + inline bool operator<(const UptrVectorIterator& that) const; + + // Inserts the given |value| to the position pointed to by this iterator + // and returns an iterator to the newly iserted |value|. + // If the underlying vector changes capacity, all previous iterators will be + // invalidated. Otherwise, those previous iterators pointing to after the + // insertion point will be invalidated. + template + inline typename std::enable_if::type + InsertBefore(Uptr value); + + // Inserts the given |valueVector| to the position pointed to by this iterator + // and returns an iterator to the first newly inserted value. + // If the underlying vector changes capacity, all previous iterators will be + // invalidated. Otherwise, those previous iterators pointing to after the + // insertion point will be invalidated. + template + inline typename std::enable_if::type + InsertBefore(UptrVector* valueVector); + + // Erases the value at the position pointed to by this iterator + // and returns an iterator to the following value. + // If the underlying vector changes capacity, all previous iterators will be + // invalidated. Otherwise, those previous iterators pointing to after the + // erasure point will be invalidated. + template + inline typename std::enable_if::type + Erase(); + + // Returns the underlying iterator. + UnderlyingIterator Get() const { return iterator_; } + + // Returns a valid end iterator for the underlying container. + UptrVectorIterator End() const { + return UptrVectorIterator(container_, container_->end()); + } + + private: + UptrVector* container_; // The container we are manipulating. + UnderlyingIterator iterator_; // The raw iterator from the container. +}; + +// Handy class for a (begin, end) iterator pair. +template +class IteratorRange { + public: + IteratorRange(const IteratorType& b, const IteratorType& e) + : begin_(b), end_(e) {} + IteratorRange(IteratorType&& b, IteratorType&& e) + : begin_(std::move(b)), end_(std::move(e)) {} + + IteratorType begin() const { return begin_; } + IteratorType end() const { return end_; } + + bool empty() const { return begin_ == end_; } + size_t size() const { return end_ - begin_; } + + private: + IteratorType begin_; + IteratorType end_; +}; + +// Returns a (begin, end) iterator pair for the given iterators. +// The iterators must belong to the same container. +template +inline IteratorRange make_range(const IteratorType& begin, + const IteratorType& end) { + return {begin, end}; +} + +// Returns a (begin, end) iterator pair for the given iterators. +// The iterators must belong to the same container. +template +inline IteratorRange make_range(IteratorType&& begin, + IteratorType&& end) { + return {std::move(begin), std::move(end)}; +} + +// Returns a (begin, end) iterator pair for the given container. +template > +inline IteratorRange make_range( + std::vector>& container) { + return {IteratorType(&container, container.begin()), + IteratorType(&container, container.end())}; +} + +// Returns a const (begin, end) iterator pair for the given container. +template > +inline IteratorRange make_const_range( + const std::vector>& container) { + return {IteratorType(&container, container.cbegin()), + IteratorType(&container, container.cend())}; +} + +template +inline UptrVectorIterator& UptrVectorIterator::operator++() { + ++iterator_; + return *this; +} + +template +inline UptrVectorIterator UptrVectorIterator::operator++(int) { + auto it = *this; + ++(*this); + return it; +} + +template +inline UptrVectorIterator& UptrVectorIterator::operator--() { + --iterator_; + return *this; +} + +template +inline UptrVectorIterator UptrVectorIterator::operator--(int) { + auto it = *this; + --(*this); + return it; +} + +template +inline bool UptrVectorIterator::operator==( + const UptrVectorIterator& that) const { + return container_ == that.container_ && iterator_ == that.iterator_; +} + +template +inline bool UptrVectorIterator::operator!=( + const UptrVectorIterator& that) const { + return !(*this == that); +} + +template +inline ptrdiff_t UptrVectorIterator::operator-( + const UptrVectorIterator& that) const { + assert(container_ == that.container_); + return iterator_ - that.iterator_; +} + +template +inline bool UptrVectorIterator::operator<( + const UptrVectorIterator& that) const { + assert(container_ == that.container_); + return iterator_ < that.iterator_; +} + +template +template +inline + typename std::enable_if>::type + UptrVectorIterator::InsertBefore(Uptr value) { + auto index = iterator_ - container_->begin(); + container_->insert(iterator_, std::move(value)); + return UptrVectorIterator(container_, container_->begin() + index); +} + +template +template +inline + typename std::enable_if>::type + UptrVectorIterator::InsertBefore(UptrVector* values) { + const auto pos = iterator_ - container_->begin(); + const auto origsz = container_->size(); + container_->resize(origsz + values->size()); + std::move_backward(container_->begin() + pos, container_->begin() + origsz, + container_->end()); + std::move(values->begin(), values->end(), container_->begin() + pos); + return UptrVectorIterator(container_, container_->begin() + pos); +} + +template +template +inline + typename std::enable_if>::type + UptrVectorIterator::Erase() { + auto index = iterator_ - container_->begin(); + (void)container_->erase(iterator_); + return UptrVectorIterator(container_, container_->begin() + index); +} + +} // namespace ir +} // namespace spvtools + +#endif // LIBSPIRV_OPT_ITERATOR_H_ diff --git a/3rdparty/spirv-tools/source/opt/licm_pass.cpp b/3rdparty/spirv-tools/source/opt/licm_pass.cpp new file mode 100644 index 000000000..7faa21d82 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/licm_pass.cpp @@ -0,0 +1,125 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "opt/licm_pass.h" +#include "opt/module.h" +#include "opt/pass.h" + +#include +#include + +namespace spvtools { +namespace opt { + +Pass::Status LICMPass::Process(ir::IRContext* c) { + InitializeProcessing(c); + bool modified = false; + + if (c != nullptr) { + modified = ProcessIRContext(); + } + + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; +} + +bool LICMPass::ProcessIRContext() { + bool modified = false; + ir::Module* module = get_module(); + + // Process each function in the module + for (ir::Function& f : *module) { + modified |= ProcessFunction(&f); + } + return modified; +} + +bool LICMPass::ProcessFunction(ir::Function* f) { + bool modified = false; + ir::LoopDescriptor* loop_descriptor = context()->GetLoopDescriptor(f); + + // Process each loop in the function + for (ir::Loop& loop : *loop_descriptor) { + // Ignore nested loops, as we will process them in order in ProcessLoop + if (loop.IsNested()) { + continue; + } + modified |= ProcessLoop(&loop, f); + } + return modified; +} + +bool LICMPass::ProcessLoop(ir::Loop* loop, ir::Function* f) { + bool modified = false; + + // Process all nested loops first + for (ir::Loop* nested_loop : *loop) { + modified |= ProcessLoop(nested_loop, f); + } + + std::vector loop_bbs{}; + modified |= AnalyseAndHoistFromBB(loop, f, loop->GetHeaderBlock(), &loop_bbs); + + for (size_t i = 0; i < loop_bbs.size(); ++i) { + ir::BasicBlock* bb = loop_bbs[i]; + // do not delete the element + modified |= AnalyseAndHoistFromBB(loop, f, bb, &loop_bbs); + } + + return modified; +} + +bool LICMPass::AnalyseAndHoistFromBB(ir::Loop* loop, ir::Function* f, + ir::BasicBlock* bb, + std::vector* loop_bbs) { + bool modified = false; + std::function hoist_inst = + [this, &loop, &modified](ir::Instruction* inst) { + if (loop->ShouldHoistInstruction(this->context(), inst)) { + HoistInstruction(loop, inst); + modified = true; + } + }; + + if (IsImmediatelyContainedInLoop(loop, f, bb)) { + bb->ForEachInst(hoist_inst, false); + } + + opt::DominatorAnalysis* dom_analysis = + context()->GetDominatorAnalysis(f, *cfg()); + opt::DominatorTree& dom_tree = dom_analysis->GetDomTree(); + + for (opt::DominatorTreeNode* child_dom_tree_node : + *dom_tree.GetTreeNode(bb)) { + if (loop->IsInsideLoop(child_dom_tree_node->bb_)) { + loop_bbs->push_back(child_dom_tree_node->bb_); + } + } + + return modified; +} + +bool LICMPass::IsImmediatelyContainedInLoop(ir::Loop* loop, ir::Function* f, + ir::BasicBlock* bb) { + ir::LoopDescriptor* loop_descriptor = context()->GetLoopDescriptor(f); + return loop == (*loop_descriptor)[bb->id()]; +} + +void LICMPass::HoistInstruction(ir::Loop* loop, ir::Instruction* inst) { + ir::BasicBlock* pre_header_bb = loop->GetOrCreatePreHeaderBlock(); + inst->InsertBefore(std::move(&(*pre_header_bb->tail()))); + context()->set_instr_block(inst, pre_header_bb); +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/licm_pass.h b/3rdparty/spirv-tools/source/opt/licm_pass.h new file mode 100644 index 000000000..1d8ae2039 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/licm_pass.h @@ -0,0 +1,68 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_OPT_LICM_PASS_H_ +#define SOURCE_OPT_LICM_PASS_H_ + +#include "opt/basic_block.h" +#include "opt/instruction.h" +#include "opt/loop_descriptor.h" +#include "opt/pass.h" + +#include + +namespace spvtools { +namespace opt { + +class LICMPass : public Pass { + public: + LICMPass() {} + + const char* name() const override { return "loop-invariant-code-motion"; } + Status Process(ir::IRContext*) override; + + private: + // Searches the IRContext for functions and processes each, moving invariants + // outside loops within the function where possible + // Returns true if a change was made to a function within the IRContext + bool ProcessIRContext(); + + // Checks the function for loops, calling ProcessLoop on each one found. + // Returns true if a change was made to the function, false otherwise. + bool ProcessFunction(ir::Function* f); + + // Checks for invariants in the loop and attempts to move them to the loops + // preheader. Works from inner loop to outer when nested loops are found. + // Returns true if a change was made to the loop, false otherwise. + bool ProcessLoop(ir::Loop* loop, ir::Function* f); + + // Analyses each instruction in |bb|, hoisting invariants to |pre_header_bb|. + // Each child of |bb| wrt to |dom_tree| is pushed to |loop_bbs| + bool AnalyseAndHoistFromBB(ir::Loop* loop, ir::Function* f, + ir::BasicBlock* bb, + std::vector* loop_bbs); + + // Returns true if |bb| is immediately contained in |loop| + bool IsImmediatelyContainedInLoop(ir::Loop* loop, ir::Function* f, + ir::BasicBlock* bb); + + // Move the instruction to the given BasicBlock + // This method will update the instruction to block mapping for the context + void HoistInstruction(ir::Loop* loop, ir::Instruction* inst); +}; + +} // namespace opt +} // namespace spvtools + +#endif // SOURCE_OPT_LICM_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/local_access_chain_convert_pass.cpp b/3rdparty/spirv-tools/source/opt/local_access_chain_convert_pass.cpp new file mode 100644 index 000000000..f87478ab5 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/local_access_chain_convert_pass.cpp @@ -0,0 +1,341 @@ +// Copyright (c) 2017 The Khronos Group Inc. +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "local_access_chain_convert_pass.h" + +#include "ir_context.h" +#include "iterator.h" + +namespace spvtools { +namespace opt { + +namespace { + +const uint32_t kStoreValIdInIdx = 1; +const uint32_t kAccessChainPtrIdInIdx = 0; +const uint32_t kConstantValueInIdx = 0; +const uint32_t kTypeIntWidthInIdx = 0; + +} // anonymous namespace + +void LocalAccessChainConvertPass::BuildAndAppendInst( + SpvOp opcode, uint32_t typeId, uint32_t resultId, + const std::vector& in_opnds, + std::vector>* newInsts) { + std::unique_ptr newInst( + new ir::Instruction(context(), opcode, typeId, resultId, in_opnds)); + get_def_use_mgr()->AnalyzeInstDefUse(&*newInst); + newInsts->emplace_back(std::move(newInst)); +} + +uint32_t LocalAccessChainConvertPass::BuildAndAppendVarLoad( + const ir::Instruction* ptrInst, uint32_t* varId, uint32_t* varPteTypeId, + std::vector>* newInsts) { + const uint32_t ldResultId = TakeNextId(); + *varId = ptrInst->GetSingleWordInOperand(kAccessChainPtrIdInIdx); + const ir::Instruction* varInst = get_def_use_mgr()->GetDef(*varId); + assert(varInst->opcode() == SpvOpVariable); + *varPteTypeId = GetPointeeTypeId(varInst); + BuildAndAppendInst(SpvOpLoad, *varPteTypeId, ldResultId, + {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {*varId}}}, + newInsts); + return ldResultId; +} + +void LocalAccessChainConvertPass::AppendConstantOperands( + const ir::Instruction* ptrInst, std::vector* in_opnds) { + uint32_t iidIdx = 0; + ptrInst->ForEachInId([&iidIdx, &in_opnds, this](const uint32_t* iid) { + if (iidIdx > 0) { + const ir::Instruction* cInst = get_def_use_mgr()->GetDef(*iid); + uint32_t val = cInst->GetSingleWordInOperand(kConstantValueInIdx); + in_opnds->push_back( + {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {val}}); + } + ++iidIdx; + }); +} + +uint32_t LocalAccessChainConvertPass::GenAccessChainLoadReplacement( + const ir::Instruction* ptrInst, + std::vector>* newInsts) { + // Build and append load of variable in ptrInst + uint32_t varId; + uint32_t varPteTypeId; + const uint32_t ldResultId = + BuildAndAppendVarLoad(ptrInst, &varId, &varPteTypeId, newInsts); + + // Build and append Extract + const uint32_t extResultId = TakeNextId(); + const uint32_t ptrPteTypeId = GetPointeeTypeId(ptrInst); + std::vector ext_in_opnds = { + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {ldResultId}}}; + AppendConstantOperands(ptrInst, &ext_in_opnds); + BuildAndAppendInst(SpvOpCompositeExtract, ptrPteTypeId, extResultId, + ext_in_opnds, newInsts); + return extResultId; +} + +void LocalAccessChainConvertPass::GenAccessChainStoreReplacement( + const ir::Instruction* ptrInst, uint32_t valId, + std::vector>* newInsts) { + // Build and append load of variable in ptrInst + uint32_t varId; + uint32_t varPteTypeId; + const uint32_t ldResultId = + BuildAndAppendVarLoad(ptrInst, &varId, &varPteTypeId, newInsts); + + // Build and append Insert + const uint32_t insResultId = TakeNextId(); + std::vector ins_in_opnds = { + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {valId}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {ldResultId}}}; + AppendConstantOperands(ptrInst, &ins_in_opnds); + BuildAndAppendInst(SpvOpCompositeInsert, varPteTypeId, insResultId, + ins_in_opnds, newInsts); + + // Build and append Store + BuildAndAppendInst(SpvOpStore, 0, 0, + {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {varId}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {insResultId}}}, + newInsts); +} + +bool LocalAccessChainConvertPass::IsConstantIndexAccessChain( + const ir::Instruction* acp) const { + uint32_t inIdx = 0; + return acp->WhileEachInId([&inIdx, this](const uint32_t* tid) { + if (inIdx > 0) { + ir::Instruction* opInst = get_def_use_mgr()->GetDef(*tid); + if (opInst->opcode() != SpvOpConstant) return false; + } + ++inIdx; + return true; + }); +} + +bool LocalAccessChainConvertPass::HasOnlySupportedRefs(uint32_t ptrId) { + if (supported_ref_ptrs_.find(ptrId) != supported_ref_ptrs_.end()) return true; + if (get_def_use_mgr()->WhileEachUser(ptrId, [this](ir::Instruction* user) { + SpvOp op = user->opcode(); + if (IsNonPtrAccessChain(op) || op == SpvOpCopyObject) { + if (!HasOnlySupportedRefs(user->result_id())) { + return false; + } + } else if (op != SpvOpStore && op != SpvOpLoad && op != SpvOpName && + !IsNonTypeDecorate(op)) { + return false; + } + return true; + })) { + supported_ref_ptrs_.insert(ptrId); + return true; + } + return false; +} + +void LocalAccessChainConvertPass::FindTargetVars(ir::Function* func) { + for (auto bi = func->begin(); bi != func->end(); ++bi) { + for (auto ii = bi->begin(); ii != bi->end(); ++ii) { + switch (ii->opcode()) { + case SpvOpStore: + case SpvOpLoad: { + uint32_t varId; + ir::Instruction* ptrInst = GetPtr(&*ii, &varId); + if (!IsTargetVar(varId)) break; + const SpvOp op = ptrInst->opcode(); + // Rule out variables with non-supported refs eg function calls + if (!HasOnlySupportedRefs(varId)) { + seen_non_target_vars_.insert(varId); + seen_target_vars_.erase(varId); + break; + } + // Rule out variables with nested access chains + // TODO(): Convert nested access chains + if (IsNonPtrAccessChain(op) && ptrInst->GetSingleWordInOperand( + kAccessChainPtrIdInIdx) != varId) { + seen_non_target_vars_.insert(varId); + seen_target_vars_.erase(varId); + break; + } + // Rule out variables accessed with non-constant indices + if (!IsConstantIndexAccessChain(ptrInst)) { + seen_non_target_vars_.insert(varId); + seen_target_vars_.erase(varId); + break; + } + } break; + default: + break; + } + } + } +} + +bool LocalAccessChainConvertPass::ConvertLocalAccessChains(ir::Function* func) { + FindTargetVars(func); + // Replace access chains of all targeted variables with equivalent + // extract and insert sequences + bool modified = false; + for (auto bi = func->begin(); bi != func->end(); ++bi) { + std::vector dead_instructions; + for (auto ii = bi->begin(); ii != bi->end(); ++ii) { + switch (ii->opcode()) { + case SpvOpLoad: { + uint32_t varId; + ir::Instruction* ptrInst = GetPtr(&*ii, &varId); + if (!IsNonPtrAccessChain(ptrInst->opcode())) break; + if (!IsTargetVar(varId)) break; + std::vector> newInsts; + uint32_t replId = GenAccessChainLoadReplacement(ptrInst, &newInsts); + context()->KillNamesAndDecorates(&*ii); + context()->ReplaceAllUsesWith(ii->result_id(), replId); + dead_instructions.push_back(&*ii); + ++ii; + ii = ii.InsertBefore(std::move(newInsts)); + ++ii; + modified = true; + } break; + case SpvOpStore: { + uint32_t varId; + ir::Instruction* ptrInst = GetPtr(&*ii, &varId); + if (!IsNonPtrAccessChain(ptrInst->opcode())) break; + if (!IsTargetVar(varId)) break; + std::vector> newInsts; + uint32_t valId = ii->GetSingleWordInOperand(kStoreValIdInIdx); + GenAccessChainStoreReplacement(ptrInst, valId, &newInsts); + dead_instructions.push_back(&*ii); + ++ii; + ii = ii.InsertBefore(std::move(newInsts)); + ++ii; + ++ii; + modified = true; + } break; + default: + break; + } + } + + while (!dead_instructions.empty()) { + ir::Instruction* inst = dead_instructions.back(); + dead_instructions.pop_back(); + DCEInst(inst, [&dead_instructions](ir::Instruction* other_inst) { + auto i = std::find(dead_instructions.begin(), dead_instructions.end(), + other_inst); + if (i != dead_instructions.end()) { + dead_instructions.erase(i); + } + }); + } + } + return modified; +} + +void LocalAccessChainConvertPass::Initialize(ir::IRContext* c) { + InitializeProcessing(c); + + // Initialize Target Variable Caches + seen_target_vars_.clear(); + seen_non_target_vars_.clear(); + + // Initialize collections + supported_ref_ptrs_.clear(); + + // Initialize extension whitelist + InitExtensions(); +} + +bool LocalAccessChainConvertPass::AllExtensionsSupported() const { + // If any extension not in whitelist, return false + for (auto& ei : get_module()->extensions()) { + const char* extName = + reinterpret_cast(&ei.GetInOperand(0).words[0]); + if (extensions_whitelist_.find(extName) == extensions_whitelist_.end()) + return false; + } + return true; +} + +Pass::Status LocalAccessChainConvertPass::ProcessImpl() { + // If non-32-bit integer type in module, terminate processing + // TODO(): Handle non-32-bit integer constants in access chains + for (const ir::Instruction& inst : get_module()->types_values()) + if (inst.opcode() == SpvOpTypeInt && + inst.GetSingleWordInOperand(kTypeIntWidthInIdx) != 32) + return Status::SuccessWithoutChange; + // Do not process if module contains OpGroupDecorate. Additional + // support required in KillNamesAndDecorates(). + // TODO(greg-lunarg): Add support for OpGroupDecorate + for (auto& ai : get_module()->annotations()) + if (ai.opcode() == SpvOpGroupDecorate) return Status::SuccessWithoutChange; + // Do not process if any disallowed extensions are enabled + if (!AllExtensionsSupported()) return Status::SuccessWithoutChange; + // Process all entry point functions. + ProcessFunction pfn = [this](ir::Function* fp) { + return ConvertLocalAccessChains(fp); + }; + bool modified = ProcessEntryPointCallTree(pfn, get_module()); + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; +} + +LocalAccessChainConvertPass::LocalAccessChainConvertPass() {} + +Pass::Status LocalAccessChainConvertPass::Process(ir::IRContext* c) { + Initialize(c); + return ProcessImpl(); +} + +void LocalAccessChainConvertPass::InitExtensions() { + extensions_whitelist_.clear(); + extensions_whitelist_.insert({ + "SPV_AMD_shader_explicit_vertex_parameter", + "SPV_AMD_shader_trinary_minmax", + "SPV_AMD_gcn_shader", + "SPV_KHR_shader_ballot", + "SPV_AMD_shader_ballot", + "SPV_AMD_gpu_shader_half_float", + "SPV_KHR_shader_draw_parameters", + "SPV_KHR_subgroup_vote", + "SPV_KHR_16bit_storage", + "SPV_KHR_device_group", + "SPV_KHR_multiview", + "SPV_NVX_multiview_per_view_attributes", + "SPV_NV_viewport_array2", + "SPV_NV_stereo_view_rendering", + "SPV_NV_sample_mask_override_coverage", + "SPV_NV_geometry_shader_passthrough", + "SPV_AMD_texture_gather_bias_lod", + "SPV_KHR_storage_buffer_storage_class", + // SPV_KHR_variable_pointers + // Currently do not support extended pointer expressions + "SPV_AMD_gpu_shader_int16", + "SPV_KHR_post_depth_coverage", + "SPV_KHR_shader_atomic_counter_ops", + "SPV_EXT_shader_stencil_export", + "SPV_EXT_shader_viewport_index_layer", + "SPV_AMD_shader_image_load_store_lod", + "SPV_AMD_shader_fragment_mask", + "SPV_EXT_fragment_fully_covered", + "SPV_AMD_gpu_shader_half_float_fetch", + "SPV_GOOGLE_decorate_string", + "SPV_GOOGLE_hlsl_functionality1", + "SPV_NV_shader_subgroup_partitioned", + "SPV_EXT_descriptor_indexing", + }); +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/local_access_chain_convert_pass.h b/3rdparty/spirv-tools/source/opt/local_access_chain_convert_pass.h new file mode 100644 index 000000000..98f009a89 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/local_access_chain_convert_pass.h @@ -0,0 +1,126 @@ +// Copyright (c) 2017 The Khronos Group Inc. +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_LOCAL_ACCESS_CHAIN_CONVERT_PASS_H_ +#define LIBSPIRV_OPT_LOCAL_ACCESS_CHAIN_CONVERT_PASS_H_ + +#include +#include +#include +#include +#include +#include + +#include "basic_block.h" +#include "def_use_manager.h" +#include "mem_pass.h" +#include "module.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class LocalAccessChainConvertPass : public MemPass { + public: + LocalAccessChainConvertPass(); + const char* name() const override { return "convert-local-access-chains"; } + Status Process(ir::IRContext* c) override; + + ir::IRContext::Analysis GetPreservedAnalyses() override { + return ir::IRContext::kAnalysisDefUse; + } + + using ProcessFunction = std::function; + + private: + // Return true if all refs through |ptrId| are only loads or stores and + // cache ptrId in supported_ref_ptrs_. TODO(dnovillo): This function is + // replicated in other passes and it's slightly different in every pass. Is it + // possible to make one common implementation? + bool HasOnlySupportedRefs(uint32_t ptrId); + + // Search |func| and cache function scope variables of target type that are + // not accessed with non-constant-index access chains. Also cache non-target + // variables. + void FindTargetVars(ir::Function* func); + + // Build instruction from |opcode|, |typeId|, |resultId|, and |in_opnds|. + // Append to |newInsts|. + void BuildAndAppendInst( + SpvOp opcode, uint32_t typeId, uint32_t resultId, + const std::vector& in_opnds, + std::vector>* newInsts); + + // Build load of variable in |ptrInst| and append to |newInsts|. + // Return var in |varId| and its pointee type in |varPteTypeId|. + uint32_t BuildAndAppendVarLoad( + const ir::Instruction* ptrInst, uint32_t* varId, uint32_t* varPteTypeId, + std::vector>* newInsts); + + // Append literal integer operands to |in_opnds| corresponding to constant + // integer operands from access chain |ptrInst|. Assumes all indices in + // access chains are OpConstant. + void AppendConstantOperands(const ir::Instruction* ptrInst, + std::vector* in_opnds); + + // Create a load/insert/store equivalent to a store of + // |valId| through (constant index) access chaing |ptrInst|. + // Append to |newInsts|. + void GenAccessChainStoreReplacement( + const ir::Instruction* ptrInst, uint32_t valId, + std::vector>* newInsts); + + // For the (constant index) access chain |ptrInst|, create an + // equivalent load and extract. Append to |newInsts|. + uint32_t GenAccessChainLoadReplacement( + const ir::Instruction* ptrInst, + std::vector>* newInsts); + + // Return true if all indices of access chain |acp| are OpConstant integers + bool IsConstantIndexAccessChain(const ir::Instruction* acp) const; + + // Identify all function scope variables of target type which are + // accessed only with loads, stores and access chains with constant + // indices. Convert all loads and stores of such variables into equivalent + // loads, stores, extracts and inserts. This unifies access to these + // variables to a single mode and simplifies analysis and optimization. + // See IsTargetType() for targeted types. + // + // Nested access chains and pointer access chains are not currently + // converted. + bool ConvertLocalAccessChains(ir::Function* func); + + // Initialize extensions whitelist + void InitExtensions(); + + // Return true if all extensions in this module are allowed by this pass. + bool AllExtensionsSupported() const; + + void Initialize(ir::IRContext* c); + Pass::Status ProcessImpl(); + + // Variables with only supported references, ie. loads and stores using + // variable directly or through non-ptr access chains. + std::unordered_set supported_ref_ptrs_; + + // Extensions supported by this pass. + std::unordered_set extensions_whitelist_; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_LOCAL_ACCESS_CHAIN_CONVERT_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/local_redundancy_elimination.cpp b/3rdparty/spirv-tools/source/opt/local_redundancy_elimination.cpp new file mode 100644 index 000000000..d6fb48caf --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/local_redundancy_elimination.cpp @@ -0,0 +1,69 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "local_redundancy_elimination.h" + +#include "value_number_table.h" + +namespace spvtools { +namespace opt { + +Pass::Status LocalRedundancyEliminationPass::Process(ir::IRContext* c) { + InitializeProcessing(c); + + bool modified = false; + ValueNumberTable vnTable(context()); + + for (auto& func : *get_module()) { + for (auto& bb : func) { + // Keeps track of all ids that contain a given value number. We keep + // track of multiple values because they could have the same value, but + // different decorations. + std::map value_to_ids; + if (EliminateRedundanciesInBB(&bb, vnTable, &value_to_ids)) + modified = true; + } + } + return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange); +} + +bool LocalRedundancyEliminationPass::EliminateRedundanciesInBB( + ir::BasicBlock* block, const ValueNumberTable& vnTable, + std::map* value_to_ids) { + bool modified = false; + + auto func = [this, &vnTable, &modified, value_to_ids](ir::Instruction* inst) { + if (inst->result_id() == 0) { + return; + } + + uint32_t value = vnTable.GetValueNumber(inst); + + if (value == 0) { + return; + } + + auto candidate = value_to_ids->insert({value, inst->result_id()}); + if (!candidate.second) { + context()->KillNamesAndDecorates(inst); + context()->ReplaceAllUsesWith(inst->result_id(), candidate.first->second); + context()->KillInst(inst); + modified = true; + } + }; + block->ForEachInst(func); + return modified; +} +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/local_redundancy_elimination.h b/3rdparty/spirv-tools/source/opt/local_redundancy_elimination.h new file mode 100644 index 000000000..cc83b6061 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/local_redundancy_elimination.h @@ -0,0 +1,65 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_LOCAL_REDUNDANCY_ELIMINATION_H_ +#define LIBSPIRV_OPT_LOCAL_REDUNDANCY_ELIMINATION_H_ + +#include "ir_context.h" +#include "pass.h" +#include "value_number_table.h" + +namespace spvtools { +namespace opt { + +// This pass implements local redundancy elimination. Its goal is to reduce the +// number of times the same value is computed. It works on each basic block +// independently, ie local. For each instruction in a basic block, it gets the +// value number for the result id, |id|, of the instruction. If that value +// number has already been computed in the basic block, it tries to replace the +// uses of |id| by the id that already contains the same value. Then the +// current instruction is deleted. +class LocalRedundancyEliminationPass : public Pass { + public: + const char* name() const override { return "local-redundancy-elimination"; } + Status Process(ir::IRContext*) override; + virtual ir::IRContext::Analysis GetPreservedAnalyses() override { + return ir::IRContext::kAnalysisDefUse | + ir::IRContext::kAnalysisInstrToBlockMapping | + ir::IRContext::kAnalysisDecorations | + ir::IRContext::kAnalysisCombinators | ir::IRContext::kAnalysisCFG | + ir::IRContext::kAnalysisDominatorAnalysis | + ir::IRContext::kAnalysisNameMap; + } + + protected: + // Deletes instructions in |block| whose value is in |value_to_ids| or is + // computed earlier in |block|. + // + // |vnTable| must have computed a value number for every result id defined + // in |bb|. + // + // |value_to_ids| is a map from value number to ids. If {vn, id} is in + // |value_to_ids| then vn is the value number of id, and the definition of id + // dominates |bb|. + // + // Returns true if the module is changed. + bool EliminateRedundanciesInBB(ir::BasicBlock* block, + const ValueNumberTable& vnTable, + std::map* value_to_ids); +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_LOCAL_REDUNDANCY_ELIMINATION_H_ diff --git a/3rdparty/spirv-tools/source/opt/local_single_block_elim_pass.cpp b/3rdparty/spirv-tools/source/opt/local_single_block_elim_pass.cpp new file mode 100644 index 000000000..ae4c4977c --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/local_single_block_elim_pass.cpp @@ -0,0 +1,221 @@ +// Copyright (c) 2017 The Khronos Group Inc. +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "local_single_block_elim_pass.h" + +#include "iterator.h" + +namespace spvtools { +namespace opt { + +namespace { + +const uint32_t kStoreValIdInIdx = 1; + +} // anonymous namespace + +bool LocalSingleBlockLoadStoreElimPass::HasOnlySupportedRefs(uint32_t ptrId) { + if (supported_ref_ptrs_.find(ptrId) != supported_ref_ptrs_.end()) return true; + if (get_def_use_mgr()->WhileEachUser(ptrId, [this](ir::Instruction* user) { + SpvOp op = user->opcode(); + if (IsNonPtrAccessChain(op) || op == SpvOpCopyObject) { + if (!HasOnlySupportedRefs(user->result_id())) { + return false; + } + } else if (op != SpvOpStore && op != SpvOpLoad && op != SpvOpName && + !IsNonTypeDecorate(op)) { + return false; + } + return true; + })) { + supported_ref_ptrs_.insert(ptrId); + return true; + } + return false; +} + +bool LocalSingleBlockLoadStoreElimPass::LocalSingleBlockLoadStoreElim( + ir::Function* func) { + // Perform local store/load and load/load elimination on each block + bool modified = false; + for (auto bi = func->begin(); bi != func->end(); ++bi) { + var2store_.clear(); + var2load_.clear(); + pinned_vars_.clear(); + auto next = bi->begin(); + for (auto ii = next; ii != bi->end(); ii = next) { + ++next; + switch (ii->opcode()) { + case SpvOpStore: { + // Verify store variable is target type + uint32_t varId; + ir::Instruction* ptrInst = GetPtr(&*ii, &varId); + if (!IsTargetVar(varId)) continue; + if (!HasOnlySupportedRefs(varId)) continue; + // Register the store + if (ptrInst->opcode() == SpvOpVariable) { + // if not pinned, look for WAW + if (pinned_vars_.find(varId) == pinned_vars_.end()) { + auto si = var2store_.find(varId); + if (si != var2store_.end()) { + } + } + var2store_[varId] = &*ii; + } else { + assert(IsNonPtrAccessChain(ptrInst->opcode())); + var2store_.erase(varId); + } + pinned_vars_.erase(varId); + var2load_.erase(varId); + } break; + case SpvOpLoad: { + // Verify store variable is target type + uint32_t varId; + ir::Instruction* ptrInst = GetPtr(&*ii, &varId); + if (!IsTargetVar(varId)) continue; + if (!HasOnlySupportedRefs(varId)) continue; + // Look for previous store or load + uint32_t replId = 0; + if (ptrInst->opcode() == SpvOpVariable) { + auto si = var2store_.find(varId); + if (si != var2store_.end()) { + replId = si->second->GetSingleWordInOperand(kStoreValIdInIdx); + } else { + auto li = var2load_.find(varId); + if (li != var2load_.end()) { + replId = li->second->result_id(); + } + } + } + if (replId != 0) { + // replace load's result id and delete load + context()->KillNamesAndDecorates(&*ii); + context()->ReplaceAllUsesWith(ii->result_id(), replId); + modified = true; + } else { + if (ptrInst->opcode() == SpvOpVariable) + var2load_[varId] = &*ii; // register load + pinned_vars_.insert(varId); + } + } break; + case SpvOpFunctionCall: { + // Conservatively assume all locals are redefined for now. + // TODO(): Handle more optimally + var2store_.clear(); + var2load_.clear(); + pinned_vars_.clear(); + } break; + default: + break; + } + } + } + return modified; +} + +void LocalSingleBlockLoadStoreElimPass::Initialize(ir::IRContext* c) { + InitializeProcessing(c); + + // Initialize Target Type Caches + seen_target_vars_.clear(); + seen_non_target_vars_.clear(); + + // Clear collections + supported_ref_ptrs_.clear(); + + // Initialize extensions whitelist + InitExtensions(); +} + +bool LocalSingleBlockLoadStoreElimPass::AllExtensionsSupported() const { + // If any extension not in whitelist, return false + for (auto& ei : get_module()->extensions()) { + const char* extName = + reinterpret_cast(&ei.GetInOperand(0).words[0]); + if (extensions_whitelist_.find(extName) == extensions_whitelist_.end()) + return false; + } + return true; +} + +Pass::Status LocalSingleBlockLoadStoreElimPass::ProcessImpl() { + // Assumes relaxed logical addressing only (see instruction.h). + if (context()->get_feature_mgr()->HasCapability(SpvCapabilityAddresses)) + return Status::SuccessWithoutChange; + // Do not process if module contains OpGroupDecorate. Additional + // support required in KillNamesAndDecorates(). + // TODO(greg-lunarg): Add support for OpGroupDecorate + for (auto& ai : get_module()->annotations()) + if (ai.opcode() == SpvOpGroupDecorate) return Status::SuccessWithoutChange; + // If any extensions in the module are not explicitly supported, + // return unmodified. + if (!AllExtensionsSupported()) return Status::SuccessWithoutChange; + // Process all entry point functions + ProcessFunction pfn = [this](ir::Function* fp) { + return LocalSingleBlockLoadStoreElim(fp); + }; + bool modified = ProcessEntryPointCallTree(pfn, get_module()); + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; +} + +LocalSingleBlockLoadStoreElimPass::LocalSingleBlockLoadStoreElimPass() {} + +Pass::Status LocalSingleBlockLoadStoreElimPass::Process(ir::IRContext* c) { + Initialize(c); + return ProcessImpl(); +} + +void LocalSingleBlockLoadStoreElimPass::InitExtensions() { + extensions_whitelist_.clear(); + extensions_whitelist_.insert({ + "SPV_AMD_shader_explicit_vertex_parameter", + "SPV_AMD_shader_trinary_minmax", + "SPV_AMD_gcn_shader", + "SPV_KHR_shader_ballot", + "SPV_AMD_shader_ballot", + "SPV_AMD_gpu_shader_half_float", + "SPV_KHR_shader_draw_parameters", + "SPV_KHR_subgroup_vote", + "SPV_KHR_16bit_storage", + "SPV_KHR_device_group", + "SPV_KHR_multiview", + "SPV_NVX_multiview_per_view_attributes", + "SPV_NV_viewport_array2", + "SPV_NV_stereo_view_rendering", + "SPV_NV_sample_mask_override_coverage", + "SPV_NV_geometry_shader_passthrough", + "SPV_AMD_texture_gather_bias_lod", + "SPV_KHR_storage_buffer_storage_class", + // SPV_KHR_variable_pointers + // Currently do not support extended pointer expressions + "SPV_AMD_gpu_shader_int16", + "SPV_KHR_post_depth_coverage", + "SPV_KHR_shader_atomic_counter_ops", + "SPV_EXT_shader_stencil_export", + "SPV_EXT_shader_viewport_index_layer", + "SPV_AMD_shader_image_load_store_lod", + "SPV_AMD_shader_fragment_mask", + "SPV_EXT_fragment_fully_covered", + "SPV_AMD_gpu_shader_half_float_fetch", + "SPV_GOOGLE_decorate_string", + "SPV_GOOGLE_hlsl_functionality1", + "SPV_NV_shader_subgroup_partitioned", + "SPV_EXT_descriptor_indexing", + }); +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/local_single_block_elim_pass.h b/3rdparty/spirv-tools/source/opt/local_single_block_elim_pass.h new file mode 100644 index 000000000..fa68788c1 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/local_single_block_elim_pass.h @@ -0,0 +1,103 @@ +// Copyright (c) 2017 The Khronos Group Inc. +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_LOCAL_SINGLE_BLOCK_ELIM_PASS_H_ +#define LIBSPIRV_OPT_LOCAL_SINGLE_BLOCK_ELIM_PASS_H_ + +#include +#include +#include +#include +#include +#include + +#include "basic_block.h" +#include "def_use_manager.h" +#include "mem_pass.h" +#include "module.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class LocalSingleBlockLoadStoreElimPass : public MemPass { + public: + LocalSingleBlockLoadStoreElimPass(); + const char* name() const override { return "eliminate-local-single-block"; } + Status Process(ir::IRContext* c) override; + + ir::IRContext::Analysis GetPreservedAnalyses() override { + return ir::IRContext::kAnalysisDefUse; + } + + private: + // Return true if all uses of |varId| are only through supported reference + // operations ie. loads and store. Also cache in supported_ref_ptrs_. + // TODO(dnovillo): This function is replicated in other passes and it's + // slightly different in every pass. Is it possible to make one common + // implementation? + bool HasOnlySupportedRefs(uint32_t varId); + + // On all entry point functions, within each basic block, eliminate + // loads and stores to function variables where possible. For + // loads, if previous load or store to same variable, replace + // load id with previous id and delete load. Finally, check if + // remaining stores are useless, and delete store and variable + // where possible. Assumes logical addressing. + bool LocalSingleBlockLoadStoreElim(ir::Function* func); + + // Initialize extensions whitelist + void InitExtensions(); + + // Return true if all extensions in this module are supported by this pass. + bool AllExtensionsSupported() const; + + void Initialize(ir::IRContext* c); + Pass::Status ProcessImpl(); + + // Map from function scope variable to a store of that variable in the + // current block whose value is currently valid. This map is cleared + // at the start of each block and incrementally updated as the block + // is scanned. The stores are candidates for elimination. The map is + // conservatively cleared when a function call is encountered. + std::unordered_map var2store_; + + // Map from function scope variable to a load of that variable in the + // current block whose value is currently valid. This map is cleared + // at the start of each block and incrementally updated as the block + // is scanned. The stores are candidates for elimination. The map is + // conservatively cleared when a function call is encountered. + std::unordered_map var2load_; + + // Set of variables whose most recent store in the current block cannot be + // deleted, for example, if there is a load of the variable which is + // dependent on the store and is not replaced and deleted by this pass, + // for example, a load through an access chain. A variable is removed + // from this set each time a new store of that variable is encountered. + std::unordered_set pinned_vars_; + + // Extensions supported by this pass. + std::unordered_set extensions_whitelist_; + + // Variables that are only referenced by supported operations for this + // pass ie. loads and stores. + std::unordered_set supported_ref_ptrs_; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_LOCAL_SINGLE_BLOCK_ELIM_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/local_single_store_elim_pass.cpp b/3rdparty/spirv-tools/source/opt/local_single_store_elim_pass.cpp new file mode 100644 index 000000000..405fba3c0 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/local_single_store_elim_pass.cpp @@ -0,0 +1,324 @@ +// Copyright (c) 2017 The Khronos Group Inc. +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "local_single_store_elim_pass.h" + +#include "cfa.h" +#include "iterator.h" +#include "latest_version_glsl_std_450_header.h" + +namespace spvtools { +namespace opt { + +namespace { + +const uint32_t kStoreValIdInIdx = 1; +const uint32_t kVariableInitIdInIdx = 1; + +} // anonymous namespace + +bool LocalSingleStoreElimPass::HasOnlySupportedRefs(uint32_t ptrId) { + if (supported_ref_ptrs_.find(ptrId) != supported_ref_ptrs_.end()) return true; + if (get_def_use_mgr()->WhileEachUser(ptrId, [this](ir::Instruction* user) { + SpvOp op = user->opcode(); + if (IsNonPtrAccessChain(op) || op == SpvOpCopyObject) { + if (!HasOnlySupportedRefs(user->result_id())) { + return false; + } + } else if (op != SpvOpStore && op != SpvOpLoad && op != SpvOpName && + !IsNonTypeDecorate(op)) { + return false; + } + return true; + })) { + supported_ref_ptrs_.insert(ptrId); + return true; + } + return false; +} + +void LocalSingleStoreElimPass::SingleStoreAnalyze(ir::Function* func) { + ssa_var2store_.clear(); + non_ssa_vars_.clear(); + store2idx_.clear(); + store2blk_.clear(); + for (auto bi = func->begin(); bi != func->end(); ++bi) { + uint32_t instIdx = 0; + for (auto ii = bi->begin(); ii != bi->end(); ++ii, ++instIdx) { + uint32_t varId = 0; + ir::Instruction* ptrInst = nullptr; + switch (ii->opcode()) { + case SpvOpStore: { + ptrInst = GetPtr(&*ii, &varId); + } break; + case SpvOpVariable: { + // If initializer, treat like store + if (ii->NumInOperands() > 1) { + varId = ii->result_id(); + ptrInst = &*ii; + } + } break; + default: + break; + } // switch + if (varId == 0) continue; + // Verify variable is target type + if (non_ssa_vars_.find(varId) != non_ssa_vars_.end()) continue; + if (ptrInst->opcode() != SpvOpVariable) { + non_ssa_vars_.insert(varId); + ssa_var2store_.erase(varId); + continue; + } + // Verify target type and function storage class + if (!IsTargetVar(varId)) { + non_ssa_vars_.insert(varId); + continue; + } + if (!HasOnlySupportedRefs(varId)) { + non_ssa_vars_.insert(varId); + continue; + } + // Ignore variables with multiple stores + if (ssa_var2store_.find(varId) != ssa_var2store_.end()) { + non_ssa_vars_.insert(varId); + ssa_var2store_.erase(varId); + continue; + } + // Remember pointer to variable's store and it's + // ordinal position in block + ssa_var2store_[varId] = &*ii; + store2idx_[&*ii] = instIdx; + store2blk_[&*ii] = &*bi; + } + } +} + +LocalSingleStoreElimPass::GetBlocksFunction +LocalSingleStoreElimPass::AugmentedCFGSuccessorsFunction() const { + return [this](const ir::BasicBlock* block) { + auto asmi = augmented_successors_map_.find(block); + if (asmi != augmented_successors_map_.end()) return &(*asmi).second; + auto smi = successors_map_.find(block); + return &(*smi).second; + }; +} + +LocalSingleStoreElimPass::GetBlocksFunction +LocalSingleStoreElimPass::AugmentedCFGPredecessorsFunction() const { + return [this](const ir::BasicBlock* block) { + auto apmi = augmented_predecessors_map_.find(block); + if (apmi != augmented_predecessors_map_.end()) return &(*apmi).second; + auto pmi = predecessors_map_.find(block); + return &(*pmi).second; + }; +} + +void LocalSingleStoreElimPass::CalculateImmediateDominators( + ir::Function* func) { + // Compute CFG + vector ordered_blocks; + predecessors_map_.clear(); + successors_map_.clear(); + for (auto& blk : *func) { + ordered_blocks.push_back(&blk); + const auto& const_blk = blk; + const_blk.ForEachSuccessorLabel([&blk, this](const uint32_t sbid) { + successors_map_[&blk].push_back(label2block_[sbid]); + predecessors_map_[label2block_[sbid]].push_back(&blk); + }); + } + // Compute Augmented CFG + augmented_successors_map_.clear(); + augmented_predecessors_map_.clear(); + successors_map_[cfg()->pseudo_exit_block()] = {}; + predecessors_map_[cfg()->pseudo_entry_block()] = {}; + auto succ_func = [this](const ir::BasicBlock* b) { + return &successors_map_[b]; + }; + auto pred_func = [this](const ir::BasicBlock* b) { + return &predecessors_map_[b]; + }; + CFA::ComputeAugmentedCFG( + ordered_blocks, cfg()->pseudo_entry_block(), cfg()->pseudo_exit_block(), + &augmented_successors_map_, &augmented_predecessors_map_, succ_func, + pred_func); + // Compute Dominators + vector postorder; + auto ignore_block = [](cbb_ptr) {}; + auto ignore_edge = [](cbb_ptr, cbb_ptr) {}; + spvtools::CFA::DepthFirstTraversal( + ordered_blocks[0], AugmentedCFGSuccessorsFunction(), ignore_block, + [&](cbb_ptr b) { postorder.push_back(b); }, ignore_edge); + auto edges = spvtools::CFA::CalculateDominators( + postorder, AugmentedCFGPredecessorsFunction()); + idom_.clear(); + for (auto edge : edges) idom_[edge.first] = edge.second; +} + +bool LocalSingleStoreElimPass::Dominates(ir::BasicBlock* blk0, uint32_t idx0, + ir::BasicBlock* blk1, uint32_t idx1) { + if (blk0 == blk1) return idx0 <= idx1; + ir::BasicBlock* b = blk1; + while (idom_[b] != b) { + b = idom_[b]; + if (b == blk0) return true; + } + return false; +} + +bool LocalSingleStoreElimPass::SingleStoreProcess(ir::Function* func) { + CalculateImmediateDominators(func); + bool modified = false; + for (auto bi = func->begin(); bi != func->end(); ++bi) { + uint32_t instIdx = 0; + for (auto ii = bi->begin(); ii != bi->end(); ++ii, ++instIdx) { + if (ii->opcode() != SpvOpLoad) continue; + uint32_t varId; + ir::Instruction* ptrInst = GetPtr(&*ii, &varId); + // Skip access chain loads + if (ptrInst->opcode() != SpvOpVariable) continue; + const auto vsi = ssa_var2store_.find(varId); + if (vsi == ssa_var2store_.end()) continue; + if (non_ssa_vars_.find(varId) != non_ssa_vars_.end()) continue; + // store must dominate load + if (!Dominates(store2blk_[vsi->second], store2idx_[vsi->second], &*bi, + instIdx)) + continue; + // Determine replacement id depending on OpStore or OpVariable + uint32_t replId; + if (vsi->second->opcode() == SpvOpStore) + replId = vsi->second->GetSingleWordInOperand(kStoreValIdInIdx); + else + replId = vsi->second->GetSingleWordInOperand(kVariableInitIdInIdx); + // Replace all instances of the load's id with the SSA value's id + // and add load to removal list + context()->KillNamesAndDecorates(&*ii); + context()->ReplaceAllUsesWith(ii->result_id(), replId); + modified = true; + } + } + return modified; +} + +bool LocalSingleStoreElimPass::LocalSingleStoreElim(ir::Function* func) { + bool modified = false; + SingleStoreAnalyze(func); + if (ssa_var2store_.empty()) return false; + modified |= SingleStoreProcess(func); + return modified; +} + +void LocalSingleStoreElimPass::Initialize(ir::IRContext* irContext) { + InitializeProcessing(irContext); + + // Initialize function and block maps + label2block_.clear(); + for (auto& fn : *get_module()) { + for (auto& blk : fn) { + uint32_t bid = blk.id(); + label2block_[bid] = &blk; + } + } + + // Initialize Target Type Caches + seen_target_vars_.clear(); + seen_non_target_vars_.clear(); + + // Initialize Supported Ref Pointer Cache + supported_ref_ptrs_.clear(); + + // Initialize extension whitelist + InitExtensions(); +} + +bool LocalSingleStoreElimPass::AllExtensionsSupported() const { + // If any extension not in whitelist, return false + for (auto& ei : get_module()->extensions()) { + const char* extName = + reinterpret_cast(&ei.GetInOperand(0).words[0]); + if (extensions_whitelist_.find(extName) == extensions_whitelist_.end()) + return false; + } + return true; +} + +Pass::Status LocalSingleStoreElimPass::ProcessImpl() { + // Assumes relaxed logical addressing only (see instruction.h) + if (context()->get_feature_mgr()->HasCapability(SpvCapabilityAddresses)) + return Status::SuccessWithoutChange; + // Do not process if module contains OpGroupDecorate. Additional + // support required in KillNamesAndDecorates(). + // TODO(greg-lunarg): Add support for OpGroupDecorate + for (auto& ai : get_module()->annotations()) + if (ai.opcode() == SpvOpGroupDecorate) return Status::SuccessWithoutChange; + // Do not process if any disallowed extensions are enabled + if (!AllExtensionsSupported()) return Status::SuccessWithoutChange; + // Process all entry point functions + ProcessFunction pfn = [this](ir::Function* fp) { + return LocalSingleStoreElim(fp); + }; + bool modified = ProcessEntryPointCallTree(pfn, get_module()); + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; +} + +LocalSingleStoreElimPass::LocalSingleStoreElimPass() {} + +Pass::Status LocalSingleStoreElimPass::Process(ir::IRContext* irContext) { + Initialize(irContext); + return ProcessImpl(); +} + +void LocalSingleStoreElimPass::InitExtensions() { + extensions_whitelist_.clear(); + extensions_whitelist_.insert({ + "SPV_AMD_shader_explicit_vertex_parameter", + "SPV_AMD_shader_trinary_minmax", + "SPV_AMD_gcn_shader", + "SPV_KHR_shader_ballot", + "SPV_AMD_shader_ballot", + "SPV_AMD_gpu_shader_half_float", + "SPV_KHR_shader_draw_parameters", + "SPV_KHR_subgroup_vote", + "SPV_KHR_16bit_storage", + "SPV_KHR_device_group", + "SPV_KHR_multiview", + "SPV_NVX_multiview_per_view_attributes", + "SPV_NV_viewport_array2", + "SPV_NV_stereo_view_rendering", + "SPV_NV_sample_mask_override_coverage", + "SPV_NV_geometry_shader_passthrough", + "SPV_AMD_texture_gather_bias_lod", + "SPV_KHR_storage_buffer_storage_class", + // SPV_KHR_variable_pointers + // Currently do not support extended pointer expressions + "SPV_AMD_gpu_shader_int16", + "SPV_KHR_post_depth_coverage", + "SPV_KHR_shader_atomic_counter_ops", + "SPV_EXT_shader_stencil_export", + "SPV_EXT_shader_viewport_index_layer", + "SPV_AMD_shader_image_load_store_lod", + "SPV_AMD_shader_fragment_mask", + "SPV_EXT_fragment_fully_covered", + "SPV_AMD_gpu_shader_half_float_fetch", + "SPV_GOOGLE_decorate_string", + "SPV_GOOGLE_hlsl_functionality1", + "SPV_NV_shader_subgroup_partitioned", + "SPV_EXT_descriptor_indexing", + }); +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/local_single_store_elim_pass.h b/3rdparty/spirv-tools/source/opt/local_single_store_elim_pass.h new file mode 100644 index 000000000..47b80049d --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/local_single_store_elim_pass.h @@ -0,0 +1,149 @@ +// Copyright (c) 2017 The Khronos Group Inc. +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_LOCAL_SINGLE_STORE_ELIM_PASS_H_ +#define LIBSPIRV_OPT_LOCAL_SINGLE_STORE_ELIM_PASS_H_ + +#include +#include +#include +#include +#include +#include + +#include "basic_block.h" +#include "def_use_manager.h" +#include "mem_pass.h" +#include "module.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class LocalSingleStoreElimPass : public MemPass { + using cbb_ptr = const ir::BasicBlock*; + + public: + LocalSingleStoreElimPass(); + const char* name() const override { return "eliminate-local-single-store"; } + Status Process(ir::IRContext* irContext) override; + + ir::IRContext::Analysis GetPreservedAnalyses() override { + return ir::IRContext::kAnalysisDefUse; + } + + private: + // Return true if all refs through |ptrId| are only loads or stores and + // cache ptrId in supported_ref_ptrs_. TODO(dnovillo): This function is + // replicated in other passes and it's slightly different in every pass. Is it + // possible to make one common implementation? + bool HasOnlySupportedRefs(uint32_t ptrId); + + // Find all function scope variables in |func| that are stored to + // only once (SSA) and map to their stored value id. Only analyze + // variables of scalar, vector, matrix types and struct and array + // types comprising only these types. Currently this analysis is + // is not done in the presence of function calls. TODO(): Allow + // analysis in the presence of function calls. + void SingleStoreAnalyze(ir::Function* func); + + using GetBlocksFunction = + std::function*(const ir::BasicBlock*)>; + + /// Returns the block successors function for the augmented CFG. + GetBlocksFunction AugmentedCFGSuccessorsFunction() const; + + /// Returns the block predecessors function for the augmented CFG. + GetBlocksFunction AugmentedCFGPredecessorsFunction() const; + + // Calculate immediate dominators for |func|'s CFG. Leaves result + // in idom_. Entries for augmented CFG (pseudo blocks) are not created. + // TODO(dnovillo): Move to new CFG class. + void CalculateImmediateDominators(ir::Function* func); + + // Return true if instruction in |blk0| at ordinal position |idx0| + // dominates instruction in |blk1| at position |idx1|. + bool Dominates(ir::BasicBlock* blk0, uint32_t idx0, ir::BasicBlock* blk1, + uint32_t idx1); + + // For each load of an SSA variable in |func|, replace all uses of + // the load with the value stored if the store dominates the load. + // Assumes that SingleStoreAnalyze() has just been run. Return true + // if any instructions are modified. + bool SingleStoreProcess(ir::Function* func); + + // Do "single-store" optimization of function variables defined only + // with a single non-access-chain store in |func|. Replace all their + // non-access-chain loads with the value that is stored and eliminate + // any resulting dead code. + bool LocalSingleStoreElim(ir::Function* func); + + // Initialize extensions whitelist + void InitExtensions(); + + // Return true if all extensions in this module are allowed by this pass. + bool AllExtensionsSupported() const; + + void Initialize(ir::IRContext* irContext); + Pass::Status ProcessImpl(); + + // Map from block's label id to block + std::unordered_map label2block_; + + // Map from SSA Variable to its single store + std::unordered_map ssa_var2store_; + + // Map from store to its ordinal position in its block. + std::unordered_map store2idx_; + + // Map from store to its block. + std::unordered_map store2blk_; + + // Set of non-SSA Variables + std::unordered_set non_ssa_vars_; + + // Variables with only supported references, ie. loads and stores using + // variable directly or through non-ptr access chains. + std::unordered_set supported_ref_ptrs_; + + // CFG Predecessors + std::unordered_map> + predecessors_map_; + + // CFG Successors + std::unordered_map> + successors_map_; + + // CFG Augmented Predecessors + std::unordered_map> + augmented_predecessors_map_; + + // CFG Augmented Successors + std::unordered_map> + augmented_successors_map_; + + // Immediate Dominator Map + // If block has no idom it points to itself. + std::unordered_map idom_; + + // Extensions supported by this pass. + std::unordered_set extensions_whitelist_; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_LOCAL_SINGLE_STORE_ELIM_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/local_ssa_elim_pass.cpp b/3rdparty/spirv-tools/source/opt/local_ssa_elim_pass.cpp new file mode 100644 index 000000000..14c14bd3b --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/local_ssa_elim_pass.cpp @@ -0,0 +1,111 @@ +// Copyright (c) 2017 The Khronos Group Inc. +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "local_ssa_elim_pass.h" + +#include "cfa.h" +#include "iterator.h" +#include "ssa_rewrite_pass.h" + +namespace spvtools { +namespace opt { + +void LocalMultiStoreElimPass::Initialize(ir::IRContext* c) { + InitializeProcessing(c); + + // Initialize extension whitelist + InitExtensions(); +} + +bool LocalMultiStoreElimPass::AllExtensionsSupported() const { + // If any extension not in whitelist, return false + for (auto& ei : get_module()->extensions()) { + const char* extName = + reinterpret_cast(&ei.GetInOperand(0).words[0]); + if (extensions_whitelist_.find(extName) == extensions_whitelist_.end()) + return false; + } + return true; +} + +Pass::Status LocalMultiStoreElimPass::ProcessImpl() { + // Assumes relaxed logical addressing only (see instruction.h) + // TODO(greg-lunarg): Add support for physical addressing + if (context()->get_feature_mgr()->HasCapability(SpvCapabilityAddresses)) + return Status::SuccessWithoutChange; + // Do not process if module contains OpGroupDecorate. Additional + // support required in KillNamesAndDecorates(). + // TODO(greg-lunarg): Add support for OpGroupDecorate + for (auto& ai : get_module()->annotations()) + if (ai.opcode() == SpvOpGroupDecorate) return Status::SuccessWithoutChange; + // Do not process if any disallowed extensions are enabled + if (!AllExtensionsSupported()) return Status::SuccessWithoutChange; + // Process functions + ProcessFunction pfn = [this](ir::Function* fp) { + return SSARewriter(this).RewriteFunctionIntoSSA(fp); + }; + bool modified = ProcessEntryPointCallTree(pfn, get_module()); + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; +} + +LocalMultiStoreElimPass::LocalMultiStoreElimPass() {} + +Pass::Status LocalMultiStoreElimPass::Process(ir::IRContext* c) { + Initialize(c); + return ProcessImpl(); +} + +void LocalMultiStoreElimPass::InitExtensions() { + extensions_whitelist_.clear(); + extensions_whitelist_.insert({ + "SPV_AMD_shader_explicit_vertex_parameter", + "SPV_AMD_shader_trinary_minmax", + "SPV_AMD_gcn_shader", + "SPV_KHR_shader_ballot", + "SPV_AMD_shader_ballot", + "SPV_AMD_gpu_shader_half_float", + "SPV_KHR_shader_draw_parameters", + "SPV_KHR_subgroup_vote", + "SPV_KHR_16bit_storage", + "SPV_KHR_device_group", + "SPV_KHR_multiview", + "SPV_NVX_multiview_per_view_attributes", + "SPV_NV_viewport_array2", + "SPV_NV_stereo_view_rendering", + "SPV_NV_sample_mask_override_coverage", + "SPV_NV_geometry_shader_passthrough", + "SPV_AMD_texture_gather_bias_lod", + "SPV_KHR_storage_buffer_storage_class", + // SPV_KHR_variable_pointers + // Currently do not support extended pointer expressions + "SPV_AMD_gpu_shader_int16", + "SPV_KHR_post_depth_coverage", + "SPV_KHR_shader_atomic_counter_ops", + "SPV_EXT_shader_stencil_export", + "SPV_EXT_shader_viewport_index_layer", + "SPV_AMD_shader_image_load_store_lod", + "SPV_AMD_shader_fragment_mask", + "SPV_EXT_fragment_fully_covered", + "SPV_AMD_gpu_shader_half_float_fetch", + "SPV_GOOGLE_decorate_string", + "SPV_GOOGLE_hlsl_functionality1", + "SPV_NV_shader_subgroup_partitioned", + "SPV_EXT_descriptor_indexing", + }); +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/local_ssa_elim_pass.h b/3rdparty/spirv-tools/source/opt/local_ssa_elim_pass.h new file mode 100644 index 000000000..c3f70f62c --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/local_ssa_elim_pass.h @@ -0,0 +1,69 @@ +// Copyright (c) 2017 The Khronos Group Inc. +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_LOCAL_SSA_ELIM_PASS_H_ +#define LIBSPIRV_OPT_LOCAL_SSA_ELIM_PASS_H_ + +#include +#include +#include +#include +#include +#include + +#include "basic_block.h" +#include "def_use_manager.h" +#include "mem_pass.h" +#include "module.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class LocalMultiStoreElimPass : public MemPass { + using cbb_ptr = const ir::BasicBlock*; + + public: + using GetBlocksFunction = + std::function*(const ir::BasicBlock*)>; + + LocalMultiStoreElimPass(); + const char* name() const override { return "eliminate-local-multi-store"; } + Status Process(ir::IRContext* c) override; + + ir::IRContext::Analysis GetPreservedAnalyses() override { + return ir::IRContext::kAnalysisDefUse | + ir::IRContext::kAnalysisInstrToBlockMapping; + } + + private: + // Initialize extensions whitelist + void InitExtensions(); + + // Return true if all extensions in this module are allowed by this pass. + bool AllExtensionsSupported() const; + + void Initialize(ir::IRContext* c); + Pass::Status ProcessImpl(); + + // Extensions supported by this pass. + std::unordered_set extensions_whitelist_; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_LOCAL_SSA_ELIM_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/log.h b/3rdparty/spirv-tools/source/opt/log.h new file mode 100644 index 000000000..70ae223c0 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/log.h @@ -0,0 +1,231 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SPIRV_TOOLS_LOG_H_ +#define SPIRV_TOOLS_LOG_H_ + +#include +#include +#include +#include + +#include "spirv-tools/libspirv.hpp" + +// Asserts the given condition is true. Otherwise, sends a message to the +// consumer and exits the problem with failure code. Accepts the following +// formats: +// +// SPIRV_ASSERT(, ); +// SPIRV_ASSERT(, , ); +// SPIRV_ASSERT(, , +// , ); +// +// In the third format, the number of cannot exceed (5 - +// 2). If more arguments are wanted, grow PP_ARG_N and PP_NARGS in the below. +#if !defined(NDEBUG) +#define SPIRV_ASSERT(consumer, ...) SPIRV_ASSERT_IMPL(consumer, __VA_ARGS__) +#else +#define SPIRV_ASSERT(consumer, ...) +#endif + +// Logs a debug message to the consumer. Accepts the following formats: +// +// SPIRV_DEBUG(, ); +// SPIRV_DEBUG(, , ); +// +// In the second format, the number of cannot exceed (5 - +// 1). If more arguments are wanted, grow PP_ARG_N and PP_NARGS in the below. +#if !defined(NDEBUG) && defined(SPIRV_LOG_DEBUG) +#define SPIRV_DEBUG(consumer, ...) SPIRV_DEBUG_IMPL(consumer, __VA_ARGS__) +#else +#define SPIRV_DEBUG(consumer, ...) +#endif + +// Logs an error message to the consumer saying the given feature is +// unimplemented. +#define SPIRV_UNIMPLEMENTED(consumer, feature) \ + do { \ + spvtools::Log(consumer, SPV_MSG_INTERNAL_ERROR, __FILE__, \ + {__LINE__, 0, 0}, "unimplemented: " feature); \ + } while (0) + +// Logs an error message to the consumer saying the code location +// should be unreachable. +#define SPIRV_UNREACHABLE(consumer) \ + do { \ + spvtools::Log(consumer, SPV_MSG_INTERNAL_ERROR, __FILE__, \ + {__LINE__, 0, 0}, "unreachable"); \ + } while (0) + +// Helper macros for concatenating arguments. +#define SPIRV_CONCATENATE(a, b) SPIRV_CONCATENATE_(a, b) +#define SPIRV_CONCATENATE_(a, b) a##b + +// Helper macro to force expanding __VA_ARGS__ to satisfy MSVC compiler. +#define PP_EXPAND(x) x + +namespace spvtools { + +// Calls the given |consumer| by supplying the |message|. The |message| is from +// the given |source| and |location| and of the given severity |level|. +inline void Log(const MessageConsumer& consumer, spv_message_level_t level, + const char* source, const spv_position_t& position, + const char* message) { + if (consumer != nullptr) consumer(level, source, position, message); +} + +// Calls the given |consumer| by supplying the message composed according to the +// given |format|. The |message| is from the given |source| and |location| and +// of the given severity |level|. +template +void Logf(const MessageConsumer& consumer, spv_message_level_t level, + const char* source, const spv_position_t& position, + const char* format, Args&&... args) { +#if defined(_MSC_VER) && _MSC_VER < 1900 +// Sadly, snprintf() is not supported until Visual Studio 2015! +#define snprintf _snprintf +#endif + + enum { kInitBufferSize = 256 }; + + char message[kInitBufferSize]; + const int size = + snprintf(message, kInitBufferSize, format, std::forward(args)...); + + if (size >= 0 && size < kInitBufferSize) { + Log(consumer, level, source, position, message); + return; + } + + if (size >= 0) { + // The initial buffer is insufficient. Allocate a buffer of a larger size, + // and write to it instead. Force the size to be unsigned to avoid a + // warning in GCC 7.1. + std::vector longer_message(size + 1u); + snprintf(longer_message.data(), longer_message.size(), format, + std::forward(args)...); + Log(consumer, level, source, position, longer_message.data()); + return; + } + + Log(consumer, level, source, position, "cannot compose log message"); + +#if defined(_MSC_VER) && _MSC_VER < 1900 +#undef snprintf +#endif +} + +// Calls the given |consumer| by supplying the given error |message|. The +// |message| is from the given |source| and |location|. +inline void Error(const MessageConsumer& consumer, const char* source, + const spv_position_t& position, const char* message) { + Log(consumer, SPV_MSG_ERROR, source, position, message); +} + +// Calls the given |consumer| by supplying the error message composed according +// to the given |format|. The |message| is from the given |source| and +// |location|. +template +inline void Errorf(const MessageConsumer& consumer, const char* source, + const spv_position_t& position, const char* format, + Args&&... args) { + Logf(consumer, SPV_MSG_ERROR, source, position, format, + std::forward(args)...); +} + +} // namespace spvtools + +#define SPIRV_ASSERT_IMPL(consumer, ...) \ + PP_EXPAND(SPIRV_CONCATENATE(SPIRV_ASSERT_, PP_NARGS(__VA_ARGS__))( \ + consumer, __VA_ARGS__)) + +#define SPIRV_DEBUG_IMPL(consumer, ...) \ + PP_EXPAND(SPIRV_CONCATENATE(SPIRV_DEBUG_, PP_NARGS(__VA_ARGS__))( \ + consumer, __VA_ARGS__)) + +#define SPIRV_ASSERT_1(consumer, condition) \ + do { \ + if (!(condition)) { \ + spvtools::Log(consumer, SPV_MSG_INTERNAL_ERROR, __FILE__, \ + {__LINE__, 0, 0}, "assertion failed: " #condition); \ + std::exit(EXIT_FAILURE); \ + } \ + } while (0) + +#define SPIRV_ASSERT_2(consumer, condition, message) \ + do { \ + if (!(condition)) { \ + spvtools::Log(consumer, SPV_MSG_INTERNAL_ERROR, __FILE__, \ + {__LINE__, 0, 0}, "assertion failed: " message); \ + std::exit(EXIT_FAILURE); \ + } \ + } while (0) + +#define SPIRV_ASSERT_more(consumer, condition, format, ...) \ + do { \ + if (!(condition)) { \ + spvtools::Logf(consumer, SPV_MSG_INTERNAL_ERROR, __FILE__, \ + {__LINE__, 0, 0}, "assertion failed: " format, \ + __VA_ARGS__); \ + std::exit(EXIT_FAILURE); \ + } \ + } while (0) + +#define SPIRV_ASSERT_3(consumer, condition, format, ...) \ + SPIRV_ASSERT_more(consumer, condition, format, __VA_ARGS__) + +#define SPIRV_ASSERT_4(consumer, condition, format, ...) \ + SPIRV_ASSERT_more(consumer, condition, format, __VA_ARGS__) + +#define SPIRV_ASSERT_5(consumer, condition, format, ...) \ + SPIRV_ASSERT_more(consumer, condition, format, __VA_ARGS__) + +#define SPIRV_DEBUG_1(consumer, message) \ + do { \ + spvtools::Log(consumer, SPV_MSG_DEBUG, __FILE__, {__LINE__, 0, 0}, \ + message); \ + } while (0) + +#define SPIRV_DEBUG_more(consumer, format, ...) \ + do { \ + spvtools::Logf(consumer, SPV_MSG_DEBUG, __FILE__, {__LINE__, 0, 0}, \ + format, __VA_ARGS__); \ + } while (0) + +#define SPIRV_DEBUG_2(consumer, format, ...) \ + SPIRV_DEBUG_more(consumer, format, __VA_ARGS__) + +#define SPIRV_DEBUG_3(consumer, format, ...) \ + SPIRV_DEBUG_more(consumer, format, __VA_ARGS__) + +#define SPIRV_DEBUG_4(consumer, format, ...) \ + SPIRV_DEBUG_more(consumer, format, __VA_ARGS__) + +#define SPIRV_DEBUG_5(consumer, format, ...) \ + SPIRV_DEBUG_more(consumer, format, __VA_ARGS__) + +// Macros for counting the number of arguments passed in. +#define PP_NARGS(...) PP_EXPAND(PP_ARG_N(__VA_ARGS__, 5, 4, 3, 2, 1, 0)) +#define PP_ARG_N(_1, _2, _3, _4, _5, N, ...) N + +// Tests for making sure that PP_NARGS() behaves as expected. +static_assert(PP_NARGS(0) == 1, "PP_NARGS macro error"); +static_assert(PP_NARGS(0, 0) == 2, "PP_NARGS macro error"); +static_assert(PP_NARGS(0, 0, 0) == 3, "PP_NARGS macro error"); +static_assert(PP_NARGS(0, 0, 0, 0) == 4, "PP_NARGS macro error"); +static_assert(PP_NARGS(0, 0, 0, 0, 0) == 5, "PP_NARGS macro error"); +static_assert(PP_NARGS(1 + 1, 2, 3 / 3) == 3, "PP_NARGS macro error"); +static_assert(PP_NARGS((1, 1), 2, (3, 3)) == 3, "PP_NARGS macro error"); + +#endif // SPIRV_TOOLS_LOG_H_ diff --git a/3rdparty/spirv-tools/source/opt/loop_descriptor.cpp b/3rdparty/spirv-tools/source/opt/loop_descriptor.cpp new file mode 100644 index 000000000..0a4ed9250 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/loop_descriptor.cpp @@ -0,0 +1,908 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "opt/loop_descriptor.h" +#include +#include +#include +#include +#include + +#include "constants.h" +#include "opt/cfg.h" +#include "opt/dominator_tree.h" +#include "opt/ir_builder.h" +#include "opt/ir_context.h" +#include "opt/iterator.h" +#include "opt/make_unique.h" +#include "opt/tree_iterator.h" + +namespace spvtools { +namespace ir { + +// Takes in a phi instruction |induction| and the loop |header| and returns the +// step operation of the loop. +ir::Instruction* Loop::GetInductionStepOperation( + const ir::Instruction* induction) const { + // Induction must be a phi instruction. + assert(induction->opcode() == SpvOpPhi); + + ir::Instruction* step = nullptr; + + opt::analysis::DefUseManager* def_use_manager = context_->get_def_use_mgr(); + + // Traverse the incoming operands of the phi instruction. + for (uint32_t operand_id = 1; operand_id < induction->NumInOperands(); + operand_id += 2) { + // Incoming edge. + ir::BasicBlock* incoming_block = + context_->cfg()->block(induction->GetSingleWordInOperand(operand_id)); + + // Check if the block is dominated by header, and thus coming from within + // the loop. + if (IsInsideLoop(incoming_block)) { + step = def_use_manager->GetDef( + induction->GetSingleWordInOperand(operand_id - 1)); + break; + } + } + + if (!step || !IsSupportedStepOp(step->opcode())) { + return nullptr; + } + + // The induction variable which binds the loop must only be modified once. + uint32_t lhs = step->GetSingleWordInOperand(0); + uint32_t rhs = step->GetSingleWordInOperand(1); + + // One of the left hand side or right hand side of the step instruction must + // be the induction phi and the other must be an OpConstant. + if (lhs != induction->result_id() && rhs != induction->result_id()) { + return nullptr; + } + + if (def_use_manager->GetDef(lhs)->opcode() != SpvOp::SpvOpConstant && + def_use_manager->GetDef(rhs)->opcode() != SpvOp::SpvOpConstant) { + return nullptr; + } + + return step; +} + +// Returns true if the |step| operation is an induction variable step operation +// which is currently handled. +bool Loop::IsSupportedStepOp(SpvOp step) const { + switch (step) { + case SpvOp::SpvOpISub: + case SpvOp::SpvOpIAdd: + return true; + default: + return false; + } +} + +bool Loop::IsSupportedCondition(SpvOp condition) const { + switch (condition) { + // < + case SpvOp::SpvOpULessThan: + case SpvOp::SpvOpSLessThan: + // > + case SpvOp::SpvOpUGreaterThan: + case SpvOp::SpvOpSGreaterThan: + + // >= + case SpvOp::SpvOpSGreaterThanEqual: + case SpvOp::SpvOpUGreaterThanEqual: + // <= + case SpvOp::SpvOpSLessThanEqual: + case SpvOp::SpvOpULessThanEqual: + + return true; + default: + return false; + } +} + +int64_t Loop::GetResidualConditionValue(SpvOp condition, int64_t initial_value, + int64_t step_value, + size_t number_of_iterations, + size_t factor) { + int64_t remainder = + initial_value + (number_of_iterations % factor) * step_value; + + // We subtract or add one as the above formula calculates the remainder if the + // loop where just less than or greater than. Adding or subtracting one should + // give a functionally equivalent value. + switch (condition) { + case SpvOp::SpvOpSGreaterThanEqual: + case SpvOp::SpvOpUGreaterThanEqual: { + remainder -= 1; + break; + } + case SpvOp::SpvOpSLessThanEqual: + case SpvOp::SpvOpULessThanEqual: { + remainder += 1; + break; + } + + default: + break; + } + return remainder; +} + +// Extract the initial value from the |induction| OpPhi instruction and store it +// in |value|. If the function couldn't find the initial value of |induction| +// return false. +bool Loop::GetInductionInitValue(const ir::Instruction* induction, + int64_t* value) const { + ir::Instruction* constant_instruction = nullptr; + opt::analysis::DefUseManager* def_use_manager = context_->get_def_use_mgr(); + + for (uint32_t operand_id = 0; operand_id < induction->NumInOperands(); + operand_id += 2) { + ir::BasicBlock* bb = context_->cfg()->block( + induction->GetSingleWordInOperand(operand_id + 1)); + + if (!IsInsideLoop(bb)) { + constant_instruction = def_use_manager->GetDef( + induction->GetSingleWordInOperand(operand_id)); + } + } + + if (!constant_instruction) return false; + + const opt::analysis::Constant* constant = + context_->get_constant_mgr()->FindDeclaredConstant( + constant_instruction->result_id()); + if (!constant) return false; + + if (value) { + const opt::analysis::Integer* type = + constant->AsIntConstant()->type()->AsInteger(); + + if (type->IsSigned()) { + *value = constant->AsIntConstant()->GetS32BitValue(); + } else { + *value = constant->AsIntConstant()->GetU32BitValue(); + } + } + + return true; +} + +Loop::Loop(IRContext* context, opt::DominatorAnalysis* dom_analysis, + BasicBlock* header, BasicBlock* continue_target, + BasicBlock* merge_target) + : context_(context), + loop_header_(header), + loop_continue_(continue_target), + loop_merge_(merge_target), + loop_preheader_(nullptr), + parent_(nullptr), + loop_is_marked_for_removal_(false) { + assert(context); + assert(dom_analysis); + loop_preheader_ = FindLoopPreheader(dom_analysis); +} + +BasicBlock* Loop::FindLoopPreheader(opt::DominatorAnalysis* dom_analysis) { + CFG* cfg = context_->cfg(); + opt::DominatorTree& dom_tree = dom_analysis->GetDomTree(); + opt::DominatorTreeNode* header_node = dom_tree.GetTreeNode(loop_header_); + + // The loop predecessor. + BasicBlock* loop_pred = nullptr; + + auto header_pred = cfg->preds(loop_header_->id()); + for (uint32_t p_id : header_pred) { + opt::DominatorTreeNode* node = dom_tree.GetTreeNode(p_id); + if (node && !dom_tree.Dominates(header_node, node)) { + // The predecessor is not part of the loop, so potential loop preheader. + if (loop_pred && node->bb_ != loop_pred) { + // If we saw 2 distinct predecessors that are outside the loop, we don't + // have a loop preheader. + return nullptr; + } + loop_pred = node->bb_; + } + } + // Safe guard against invalid code, SPIR-V spec forbids loop with the entry + // node as header. + assert(loop_pred && "The header node is the entry block ?"); + + // So we have a unique basic block that can enter this loop. + // If this loop is the unique successor of this block, then it is a loop + // preheader. + bool is_preheader = true; + uint32_t loop_header_id = loop_header_->id(); + const auto* const_loop_pred = loop_pred; + const_loop_pred->ForEachSuccessorLabel( + [&is_preheader, loop_header_id](const uint32_t id) { + if (id != loop_header_id) is_preheader = false; + }); + if (is_preheader) return loop_pred; + return nullptr; +} + +bool Loop::IsInsideLoop(Instruction* inst) const { + const BasicBlock* parent_block = context_->get_instr_block(inst); + if (!parent_block) return false; + return IsInsideLoop(parent_block); +} + +bool Loop::IsBasicBlockInLoopSlow(const BasicBlock* bb) { + assert(bb->GetParent() && "The basic block does not belong to a function"); + opt::DominatorAnalysis* dom_analysis = + context_->GetDominatorAnalysis(bb->GetParent(), *context_->cfg()); + if (dom_analysis->IsReachable(bb) && + !dom_analysis->Dominates(GetHeaderBlock(), bb)) + return false; + + return true; +} + +BasicBlock* Loop::GetOrCreatePreHeaderBlock() { + if (loop_preheader_) return loop_preheader_; + + CFG* cfg = context_->cfg(); + loop_header_ = cfg->SplitLoopHeader(loop_header_); + return loop_preheader_; +} + +void Loop::SetLatchBlock(BasicBlock* latch) { +#ifndef NDEBUG + assert(latch->GetParent() && "The basic block does not belong to a function"); + + const auto* const_latch = latch; + const_latch->ForEachSuccessorLabel([this](uint32_t id) { + assert((!IsInsideLoop(id) || id == GetHeaderBlock()->id()) && + "A predecessor of the continue block does not belong to the loop"); + }); +#endif // NDEBUG + assert(IsInsideLoop(latch) && "The continue block is not in the loop"); + + SetLatchBlockImpl(latch); +} + +void Loop::SetMergeBlock(BasicBlock* merge) { +#ifndef NDEBUG + assert(merge->GetParent() && "The basic block does not belong to a function"); +#endif // NDEBUG + assert(!IsInsideLoop(merge) && "The merge block is in the loop"); + + SetMergeBlockImpl(merge); + if (GetHeaderBlock()->GetLoopMergeInst()) { + UpdateLoopMergeInst(); + } +} + +void Loop::SetPreHeaderBlock(BasicBlock* preheader) { + if (preheader) { + assert(!IsInsideLoop(preheader) && "The preheader block is in the loop"); + assert(preheader->tail()->opcode() == SpvOpBranch && + "The preheader block does not unconditionally branch to the header " + "block"); + assert(preheader->tail()->GetSingleWordOperand(0) == + GetHeaderBlock()->id() && + "The preheader block does not unconditionally branch to the header " + "block"); + } + loop_preheader_ = preheader; +} + +void Loop::GetExitBlocks(std::unordered_set* exit_blocks) const { + ir::CFG* cfg = context_->cfg(); + exit_blocks->clear(); + + for (uint32_t bb_id : GetBlocks()) { + const spvtools::ir::BasicBlock* bb = cfg->block(bb_id); + bb->ForEachSuccessorLabel([exit_blocks, this](uint32_t succ) { + if (!IsInsideLoop(succ)) { + exit_blocks->insert(succ); + } + }); + } +} + +void Loop::GetMergingBlocks( + std::unordered_set* merging_blocks) const { + assert(GetMergeBlock() && "This loop is not structured"); + ir::CFG* cfg = context_->cfg(); + merging_blocks->clear(); + + std::stack to_visit; + to_visit.push(GetMergeBlock()); + while (!to_visit.empty()) { + const ir::BasicBlock* bb = to_visit.top(); + to_visit.pop(); + merging_blocks->insert(bb->id()); + for (uint32_t pred_id : cfg->preds(bb->id())) { + if (!IsInsideLoop(pred_id) && !merging_blocks->count(pred_id)) { + to_visit.push(cfg->block(pred_id)); + } + } + } +} + +namespace { + +static inline bool IsBasicBlockSafeToClone(IRContext* context, BasicBlock* bb) { + for (ir::Instruction& inst : *bb) { + if (!inst.IsBranch() && !context->IsCombinatorInstruction(&inst)) + return false; + } + + return true; +} + +} // namespace + +bool Loop::IsSafeToClone() const { + ir::CFG& cfg = *context_->cfg(); + + for (uint32_t bb_id : GetBlocks()) { + BasicBlock* bb = cfg.block(bb_id); + assert(bb); + if (!IsBasicBlockSafeToClone(context_, bb)) return false; + } + + // Look at the merge construct. + if (GetHeaderBlock()->GetLoopMergeInst()) { + std::unordered_set blocks; + GetMergingBlocks(&blocks); + blocks.erase(GetMergeBlock()->id()); + for (uint32_t bb_id : blocks) { + BasicBlock* bb = cfg.block(bb_id); + assert(bb); + if (!IsBasicBlockSafeToClone(context_, bb)) return false; + } + } + + return true; +} + +bool Loop::IsLCSSA() const { + ir::CFG* cfg = context_->cfg(); + opt::analysis::DefUseManager* def_use_mgr = context_->get_def_use_mgr(); + + std::unordered_set exit_blocks; + GetExitBlocks(&exit_blocks); + + // Declare ir_context so we can capture context_ in the below lambda + ir::IRContext* ir_context = context_; + + for (uint32_t bb_id : GetBlocks()) { + for (Instruction& insn : *cfg->block(bb_id)) { + // All uses must be either: + // - In the loop; + // - In an exit block and in a phi instruction. + if (!def_use_mgr->WhileEachUser( + &insn, + [&exit_blocks, ir_context, this](ir::Instruction* use) -> bool { + BasicBlock* parent = ir_context->get_instr_block(use); + assert(parent && "Invalid analysis"); + if (IsInsideLoop(parent)) return true; + if (use->opcode() != SpvOpPhi) return false; + return exit_blocks.count(parent->id()); + })) + return false; + } + } + return true; +} + +bool Loop::ShouldHoistInstruction(IRContext* context, Instruction* inst) { + return AreAllOperandsOutsideLoop(context, inst) && + inst->IsOpcodeCodeMotionSafe(); +} + +bool Loop::AreAllOperandsOutsideLoop(IRContext* context, Instruction* inst) { + opt::analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); + bool all_outside_loop = true; + + const std::function operand_outside_loop = + [this, &def_use_mgr, &all_outside_loop](uint32_t* id) { + if (this->IsInsideLoop(def_use_mgr->GetDef(*id))) { + all_outside_loop = false; + return; + } + }; + + inst->ForEachInId(operand_outside_loop); + return all_outside_loop; +} + +void Loop::ComputeLoopStructuredOrder( + std::vector* ordered_loop_blocks, bool include_pre_header, + bool include_merge) const { + ir::CFG& cfg = *context_->cfg(); + + // Reserve the memory: all blocks in the loop + extra if needed. + ordered_loop_blocks->reserve(GetBlocks().size() + include_pre_header + + include_merge); + + if (include_pre_header && GetPreHeaderBlock()) + ordered_loop_blocks->push_back(loop_preheader_); + cfg.ForEachBlockInReversePostOrder( + loop_header_, [ordered_loop_blocks, this](BasicBlock* bb) { + if (IsInsideLoop(bb)) ordered_loop_blocks->push_back(bb); + }); + if (include_merge && GetMergeBlock()) + ordered_loop_blocks->push_back(loop_merge_); +} + +LoopDescriptor::LoopDescriptor(const Function* f) + : loops_(), dummy_top_loop_(nullptr) { + PopulateList(f); +} + +LoopDescriptor::~LoopDescriptor() { ClearLoops(); } + +void LoopDescriptor::PopulateList(const Function* f) { + IRContext* context = f->GetParent()->context(); + + opt::DominatorAnalysis* dom_analysis = + context->GetDominatorAnalysis(f, *context->cfg()); + + ClearLoops(); + + // Post-order traversal of the dominator tree to find all the OpLoopMerge + // instructions. + opt::DominatorTree& dom_tree = dom_analysis->GetDomTree(); + for (opt::DominatorTreeNode& node : + ir::make_range(dom_tree.post_begin(), dom_tree.post_end())) { + Instruction* merge_inst = node.bb_->GetLoopMergeInst(); + if (merge_inst) { + bool all_backedge_unreachable = true; + for (uint32_t pid : context->cfg()->preds(node.bb_->id())) { + if (dom_analysis->IsReachable(pid) && + dom_analysis->Dominates(node.bb_->id(), pid)) { + all_backedge_unreachable = false; + break; + } + } + if (all_backedge_unreachable) + continue; // ignore this one, we actually never branch back. + + // The id of the merge basic block of this loop. + uint32_t merge_bb_id = merge_inst->GetSingleWordOperand(0); + + // The id of the continue basic block of this loop. + uint32_t continue_bb_id = merge_inst->GetSingleWordOperand(1); + + // The merge target of this loop. + BasicBlock* merge_bb = context->cfg()->block(merge_bb_id); + + // The continue target of this loop. + BasicBlock* continue_bb = context->cfg()->block(continue_bb_id); + + // The basic block containing the merge instruction. + BasicBlock* header_bb = context->get_instr_block(merge_inst); + + // Add the loop to the list of all the loops in the function. + Loop* current_loop = + new Loop(context, dom_analysis, header_bb, continue_bb, merge_bb); + loops_.push_back(current_loop); + + // We have a bottom-up construction, so if this loop has nested-loops, + // they are by construction at the tail of the loop list. + for (auto itr = loops_.rbegin() + 1; itr != loops_.rend(); ++itr) { + Loop* previous_loop = *itr; + + // If the loop already has a parent, then it has been processed. + if (previous_loop->HasParent()) continue; + + // If the current loop does not dominates the previous loop then it is + // not nested loop. + if (!dom_analysis->Dominates(header_bb, + previous_loop->GetHeaderBlock())) + continue; + // If the current loop merge dominates the previous loop then it is + // not nested loop. + if (dom_analysis->Dominates(merge_bb, previous_loop->GetHeaderBlock())) + continue; + + current_loop->AddNestedLoop(previous_loop); + } + opt::DominatorTreeNode* dom_merge_node = dom_tree.GetTreeNode(merge_bb); + for (opt::DominatorTreeNode& loop_node : + make_range(node.df_begin(), node.df_end())) { + // Check if we are in the loop. + if (dom_tree.Dominates(dom_merge_node, &loop_node)) continue; + current_loop->AddBasicBlock(loop_node.bb_); + basic_block_to_loop_.insert( + std::make_pair(loop_node.bb_->id(), current_loop)); + } + } + } + for (Loop* loop : loops_) { + if (!loop->HasParent()) dummy_top_loop_.nested_loops_.push_back(loop); + } +} + +ir::BasicBlock* Loop::FindConditionBlock() const { + const ir::Function& function = *loop_merge_->GetParent(); + ir::BasicBlock* condition_block = nullptr; + + const opt::DominatorAnalysis* dom_analysis = + context_->GetDominatorAnalysis(&function, *context_->cfg()); + ir::BasicBlock* bb = dom_analysis->ImmediateDominator(loop_merge_); + + if (!bb) return nullptr; + + const ir::Instruction& branch = *bb->ctail(); + + // Make sure the branch is a conditional branch. + if (branch.opcode() != SpvOpBranchConditional) return nullptr; + + // Make sure one of the two possible branches is to the merge block. + if (branch.GetSingleWordInOperand(1) == loop_merge_->id() || + branch.GetSingleWordInOperand(2) == loop_merge_->id()) { + condition_block = bb; + } + + return condition_block; +} + +bool Loop::FindNumberOfIterations(const ir::Instruction* induction, + const ir::Instruction* branch_inst, + size_t* iterations_out, + int64_t* step_value_out, + int64_t* init_value_out) const { + // From the branch instruction find the branch condition. + opt::analysis::DefUseManager* def_use_manager = context_->get_def_use_mgr(); + + // Condition instruction from the OpConditionalBranch. + ir::Instruction* condition = + def_use_manager->GetDef(branch_inst->GetSingleWordOperand(0)); + + assert(IsSupportedCondition(condition->opcode())); + + // Get the constant manager from the ir context. + opt::analysis::ConstantManager* const_manager = context_->get_constant_mgr(); + + // Find the constant value used by the condition variable. Exit out if it + // isn't a constant int. + const opt::analysis::Constant* upper_bound = + const_manager->FindDeclaredConstant(condition->GetSingleWordOperand(3)); + if (!upper_bound) return false; + + // Must be integer because of the opcode on the condition. + int64_t condition_value = 0; + + const opt::analysis::Integer* type = + upper_bound->AsIntConstant()->type()->AsInteger(); + + if (type->IsSigned()) { + condition_value = upper_bound->AsIntConstant()->GetS32BitValue(); + } else { + condition_value = upper_bound->AsIntConstant()->GetU32BitValue(); + } + + // Find the instruction which is stepping through the loop. + ir::Instruction* step_inst = GetInductionStepOperation(induction); + if (!step_inst) return false; + + // Find the constant value used by the condition variable. + const opt::analysis::Constant* step_constant = + const_manager->FindDeclaredConstant(step_inst->GetSingleWordOperand(3)); + if (!step_constant) return false; + + // Must be integer because of the opcode on the condition. + int64_t step_value = 0; + + const opt::analysis::Integer* step_type = + step_constant->AsIntConstant()->type()->AsInteger(); + + if (step_type->IsSigned()) { + step_value = step_constant->AsIntConstant()->GetS32BitValue(); + } else { + step_value = step_constant->AsIntConstant()->GetU32BitValue(); + } + + // If this is a subtraction step we should negate the step value. + if (step_inst->opcode() == SpvOp::SpvOpISub) { + step_value = -step_value; + } + + // Find the inital value of the loop and make sure it is a constant integer. + int64_t init_value = 0; + if (!GetInductionInitValue(induction, &init_value)) return false; + + // If iterations is non null then store the value in that. + int64_t num_itrs = GetIterations(condition->opcode(), condition_value, + init_value, step_value); + + // If the loop body will not be reached return false. + if (num_itrs <= 0) { + return false; + } + + if (iterations_out) { + assert(static_cast(num_itrs) <= std::numeric_limits::max()); + *iterations_out = static_cast(num_itrs); + } + + if (step_value_out) { + *step_value_out = step_value; + } + + if (init_value_out) { + *init_value_out = init_value; + } + + return true; +} + +// We retrieve the number of iterations using the following formula, diff / +// |step_value| where diff is calculated differently according to the +// |condition| and uses the |condition_value| and |init_value|. If diff / +// |step_value| is NOT cleanly divisable then we add one to the sum. +int64_t Loop::GetIterations(SpvOp condition, int64_t condition_value, + int64_t init_value, int64_t step_value) const { + int64_t diff = 0; + + switch (condition) { + case SpvOp::SpvOpSLessThan: + case SpvOp::SpvOpULessThan: { + // If the condition is not met to begin with the loop will never iterate. + if (!(init_value < condition_value)) return 0; + + diff = condition_value - init_value; + + // If the operation is a less then operation then the diff and step must + // have the same sign otherwise the induction will never cross the + // condition (either never true or always true). + if ((diff < 0 && step_value > 0) || (diff > 0 && step_value < 0)) { + return 0; + } + + break; + } + case SpvOp::SpvOpSGreaterThan: + case SpvOp::SpvOpUGreaterThan: { + // If the condition is not met to begin with the loop will never iterate. + if (!(init_value > condition_value)) return 0; + + diff = init_value - condition_value; + + // If the operation is a greater than operation then the diff and step + // must have opposite signs. Otherwise the condition will always be true + // or will never be true. + if ((diff < 0 && step_value < 0) || (diff > 0 && step_value > 0)) { + return 0; + } + + break; + } + + case SpvOp::SpvOpSGreaterThanEqual: + case SpvOp::SpvOpUGreaterThanEqual: { + // If the condition is not met to begin with the loop will never iterate. + if (!(init_value >= condition_value)) return 0; + + // We subract one to make it the same as SpvOpGreaterThan as it is + // functionally equivalent. + diff = init_value - (condition_value - 1); + + // If the operation is a greater than operation then the diff and step + // must have opposite signs. Otherwise the condition will always be true + // or will never be true. + if ((diff > 0 && step_value > 0) || (diff < 0 && step_value < 0)) { + return 0; + } + + break; + } + + case SpvOp::SpvOpSLessThanEqual: + case SpvOp::SpvOpULessThanEqual: { + // If the condition is not met to begin with the loop will never iterate. + if (!(init_value <= condition_value)) return 0; + + // We add one to make it the same as SpvOpLessThan as it is functionally + // equivalent. + diff = (condition_value + 1) - init_value; + + // If the operation is a less than operation then the diff and step must + // have the same sign otherwise the induction will never cross the + // condition (either never true or always true). + if ((diff < 0 && step_value > 0) || (diff > 0 && step_value < 0)) { + return 0; + } + + break; + } + + default: + assert(false && + "Could not retrieve number of iterations from the loop condition. " + "Condition is not supported."); + } + + // Take the abs of - step values. + step_value = llabs(step_value); + diff = llabs(diff); + int64_t result = diff / step_value; + + if (diff % step_value != 0) { + result += 1; + } + return result; +} + +// Returns the list of induction variables within the loop. +void Loop::GetInductionVariables( + std::vector& induction_variables) const { + for (ir::Instruction& inst : *loop_header_) { + if (inst.opcode() == SpvOp::SpvOpPhi) { + induction_variables.push_back(&inst); + } + } +} + +ir::Instruction* Loop::FindConditionVariable( + const ir::BasicBlock* condition_block) const { + // Find the branch instruction. + const ir::Instruction& branch_inst = *condition_block->ctail(); + + ir::Instruction* induction = nullptr; + // Verify that the branch instruction is a conditional branch. + if (branch_inst.opcode() == SpvOp::SpvOpBranchConditional) { + // From the branch instruction find the branch condition. + opt::analysis::DefUseManager* def_use_manager = context_->get_def_use_mgr(); + + // Find the instruction representing the condition used in the conditional + // branch. + ir::Instruction* condition = + def_use_manager->GetDef(branch_inst.GetSingleWordOperand(0)); + + // Ensure that the condition is a less than operation. + if (condition && IsSupportedCondition(condition->opcode())) { + // The left hand side operand of the operation. + ir::Instruction* variable_inst = + def_use_manager->GetDef(condition->GetSingleWordOperand(2)); + + // Make sure the variable instruction used is a phi. + if (!variable_inst || variable_inst->opcode() != SpvOpPhi) return nullptr; + + // Make sure the phi instruction only has two incoming blocks. Each + // incoming block will be represented by two in operands in the phi + // instruction, the value and the block which that value came from. We + // assume the cannocalised phi will have two incoming values, one from the + // preheader and one from the continue block. + size_t max_supported_operands = 4; + if (variable_inst->NumInOperands() == max_supported_operands) { + // The operand index of the first incoming block label. + uint32_t operand_label_1 = 1; + + // The operand index of the second incoming block label. + uint32_t operand_label_2 = 3; + + // Make sure one of them is the preheader. + if (variable_inst->GetSingleWordInOperand(operand_label_1) != + loop_preheader_->id() && + variable_inst->GetSingleWordInOperand(operand_label_2) != + loop_preheader_->id()) { + return nullptr; + } + + // And make sure that the other is the latch block. + if (variable_inst->GetSingleWordInOperand(operand_label_1) != + loop_continue_->id() && + variable_inst->GetSingleWordInOperand(operand_label_2) != + loop_continue_->id()) { + return nullptr; + } + } else { + return nullptr; + } + + if (!FindNumberOfIterations(variable_inst, &branch_inst, nullptr)) + return nullptr; + induction = variable_inst; + } + } + + return induction; +} + +// Add and remove loops which have been marked for addition and removal to +// maintain the state of the loop descriptor class. +void LoopDescriptor::PostModificationCleanup() { + LoopContainerType loops_to_remove_; + for (ir::Loop* loop : loops_) { + if (loop->IsMarkedForRemoval()) { + loops_to_remove_.push_back(loop); + if (loop->HasParent()) { + loop->GetParent()->RemoveChildLoop(loop); + } + } + } + + for (ir::Loop* loop : loops_to_remove_) { + loops_.erase(std::find(loops_.begin(), loops_.end(), loop)); + } + + for (auto& pair : loops_to_add_) { + ir::Loop* parent = pair.first; + ir::Loop* loop = pair.second; + + if (parent) { + loop->SetParent(nullptr); + parent->AddNestedLoop(loop); + + for (uint32_t block_id : loop->GetBlocks()) { + parent->AddBasicBlock(block_id); + } + } + + loops_.emplace_back(loop); + } + + loops_to_add_.clear(); +} + +void LoopDescriptor::ClearLoops() { + for (Loop* loop : loops_) { + delete loop; + } + loops_.clear(); +} + +// Adds a new loop nest to the descriptor set. +ir::Loop* LoopDescriptor::AddLoopNest(std::unique_ptr new_loop) { + ir::Loop* loop = new_loop.release(); + if (!loop->HasParent()) dummy_top_loop_.nested_loops_.push_back(loop); + // Iterate from inner to outer most loop, adding basic block to loop mapping + // as we go. + for (ir::Loop& current_loop : + make_range(iterator::begin(loop), iterator::end(nullptr))) { + loops_.push_back(¤t_loop); + for (uint32_t bb_id : current_loop.GetBlocks()) + basic_block_to_loop_.insert(std::make_pair(bb_id, ¤t_loop)); + } + + return loop; +} + +void LoopDescriptor::RemoveLoop(ir::Loop* loop) { + ir::Loop* parent = loop->GetParent() ? loop->GetParent() : &dummy_top_loop_; + parent->nested_loops_.erase(std::find(parent->nested_loops_.begin(), + parent->nested_loops_.end(), loop)); + std::for_each( + loop->nested_loops_.begin(), loop->nested_loops_.end(), + [loop](ir::Loop* sub_loop) { sub_loop->SetParent(loop->GetParent()); }); + parent->nested_loops_.insert(parent->nested_loops_.end(), + loop->nested_loops_.begin(), + loop->nested_loops_.end()); + for (uint32_t bb_id : loop->GetBlocks()) { + ir::Loop* l = FindLoopForBasicBlock(bb_id); + if (l == loop) { + SetBasicBlockToLoop(bb_id, l->GetParent()); + } else { + ForgetBasicBlock(bb_id); + } + } + + LoopContainerType::iterator it = + std::find(loops_.begin(), loops_.end(), loop); + assert(it != loops_.end()); + delete loop; + loops_.erase(it); +} + +} // namespace ir +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/loop_descriptor.h b/3rdparty/spirv-tools/source/opt/loop_descriptor.h new file mode 100644 index 000000000..05acce207 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/loop_descriptor.h @@ -0,0 +1,527 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_LOOP_DESCRIPTORS_H_ +#define LIBSPIRV_OPT_LOOP_DESCRIPTORS_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "opt/basic_block.h" +#include "opt/module.h" +#include "opt/tree_iterator.h" + +namespace spvtools { +namespace opt { +class DominatorAnalysis; +struct DominatorTreeNode; +} // namespace opt +namespace ir { +class IRContext; +class CFG; +class LoopDescriptor; + +// A class to represent and manipulate a loop in structured control flow. +class Loop { + // The type used to represent nested child loops. + using ChildrenList = std::vector; + + public: + using iterator = ChildrenList::iterator; + using const_iterator = ChildrenList::const_iterator; + using BasicBlockListTy = std::unordered_set; + + explicit Loop(IRContext* context) + : context_(context), + loop_header_(nullptr), + loop_continue_(nullptr), + loop_merge_(nullptr), + loop_preheader_(nullptr), + parent_(nullptr), + loop_is_marked_for_removal_(false) {} + + Loop(IRContext* context, opt::DominatorAnalysis* analysis, BasicBlock* header, + BasicBlock* continue_target, BasicBlock* merge_target); + + ~Loop() {} + + // Iterators over the immediate sub-loops. + inline iterator begin() { return nested_loops_.begin(); } + inline iterator end() { return nested_loops_.end(); } + inline const_iterator begin() const { return cbegin(); } + inline const_iterator end() const { return cend(); } + inline const_iterator cbegin() const { return nested_loops_.begin(); } + inline const_iterator cend() const { return nested_loops_.end(); } + + // Returns the header (first basic block of the loop). This block contains the + // OpLoopMerge instruction. + inline BasicBlock* GetHeaderBlock() { return loop_header_; } + inline const BasicBlock* GetHeaderBlock() const { return loop_header_; } + inline void SetHeaderBlock(BasicBlock* header) { loop_header_ = header; } + + // Updates the OpLoopMerge instruction to reflect the current state of the + // loop. + inline void UpdateLoopMergeInst() { + assert(GetHeaderBlock()->GetLoopMergeInst() && + "The loop is not structured"); + ir::Instruction* merge_inst = GetHeaderBlock()->GetLoopMergeInst(); + merge_inst->SetInOperand(0, {GetMergeBlock()->id()}); + } + + // Returns the latch basic block (basic block that holds the back-edge). + // These functions return nullptr if the loop is not structured (i.e. if it + // has more than one backedge). + inline BasicBlock* GetLatchBlock() { return loop_continue_; } + inline const BasicBlock* GetLatchBlock() const { return loop_continue_; } + // Sets |latch| as the loop unique block branching back to the header. + // A latch block must have the following properties: + // - |latch| must be in the loop; + // - must be the only block branching back to the header block. + void SetLatchBlock(BasicBlock* latch); + + // Returns the basic block which marks the end of the loop. + // These functions return nullptr if the loop is not structured. + inline BasicBlock* GetMergeBlock() { return loop_merge_; } + inline const BasicBlock* GetMergeBlock() const { return loop_merge_; } + // Sets |merge| as the loop merge block. A merge block must have the following + // properties: + // - |merge| must not be in the loop; + // - all its predecessors must be in the loop. + // - it must not be already used as merge block. + // If the loop has an OpLoopMerge in its header, this instruction is also + // updated. + void SetMergeBlock(BasicBlock* merge); + + // Returns the loop pre-header, nullptr means that the loop predecessor does + // not qualify as a preheader. + // The preheader is the unique predecessor that: + // - Dominates the loop header; + // - Has only the loop header as successor. + inline BasicBlock* GetPreHeaderBlock() { return loop_preheader_; } + + // Returns the loop pre-header. + inline const BasicBlock* GetPreHeaderBlock() const { return loop_preheader_; } + // Sets |preheader| as the loop preheader block. A preheader block must have + // the following properties: + // - |merge| must not be in the loop; + // - have an unconditional branch to the loop header. + void SetPreHeaderBlock(BasicBlock* preheader); + + // Returns the loop pre-header, if there is no suitable preheader it will be + // created. + BasicBlock* GetOrCreatePreHeaderBlock(); + + // Returns true if this loop contains any nested loops. + inline bool HasNestedLoops() const { return nested_loops_.size() != 0; } + + // Clears and fills |exit_blocks| with all basic blocks that are not in the + // loop and has at least one predecessor in the loop. + void GetExitBlocks(std::unordered_set* exit_blocks) const; + + // Clears and fills |merging_blocks| with all basic blocks that are + // post-dominated by the merge block. The merge block must exist. + // The set |merging_blocks| will only contain the merge block if it is + // unreachable. + void GetMergingBlocks(std::unordered_set* merging_blocks) const; + + // Returns true if the loop is in a Loop Closed SSA form. + // In LCSSA form, all in-loop definitions are used in the loop or in phi + // instructions in the loop exit blocks. + bool IsLCSSA() const; + + // Returns the depth of this loop in the loop nest. + // The outer-most loop has a depth of 1. + inline size_t GetDepth() const { + size_t lvl = 1; + for (const Loop* loop = GetParent(); loop; loop = loop->GetParent()) lvl++; + return lvl; + } + + inline size_t NumImmediateChildren() const { return nested_loops_.size(); } + + // Adds |nested| as a nested loop of this loop. Automatically register |this| + // as the parent of |nested|. + inline void AddNestedLoop(Loop* nested) { + assert(!nested->GetParent() && "The loop has another parent."); + nested_loops_.push_back(nested); + nested->SetParent(this); + } + + inline Loop* GetParent() { return parent_; } + inline const Loop* GetParent() const { return parent_; } + + inline bool HasParent() const { return parent_; } + + // Returns true if this loop is itself nested within another loop. + inline bool IsNested() const { return parent_ != nullptr; } + + // Returns the set of all basic blocks contained within the loop. Will be all + // BasicBlocks dominated by the header which are not also dominated by the + // loop merge block. + inline const BasicBlockListTy& GetBlocks() const { + return loop_basic_blocks_; + } + + // Returns true if the basic block |bb| is inside this loop. + inline bool IsInsideLoop(const BasicBlock* bb) const { + return IsInsideLoop(bb->id()); + } + + // Returns true if the basic block id |bb_id| is inside this loop. + inline bool IsInsideLoop(uint32_t bb_id) const { + return loop_basic_blocks_.count(bb_id); + } + + // Returns true if the instruction |inst| is inside this loop. + bool IsInsideLoop(Instruction* inst) const; + + // Adds the Basic Block |bb| to this loop and its parents. + void AddBasicBlock(const BasicBlock* bb) { AddBasicBlock(bb->id()); } + + // Adds the Basic Block with |id| to this loop and its parents. + void AddBasicBlock(uint32_t id) { + for (Loop* loop = this; loop != nullptr; loop = loop->parent_) { + loop->loop_basic_blocks_.insert(id); + } + } + + // Removes the Basic Block id |bb_id| from this loop and its parents. + // It the user responsibility to make sure the removed block is not a merge, + // header or continue block. + void RemoveBasicBlock(uint32_t bb_id) { + for (Loop* loop = this; loop != nullptr; loop = loop->parent_) { + loop->loop_basic_blocks_.erase(bb_id); + } + } + + // Removes all the basic blocks from the set of basic blocks within the loop. + // This does not affect any of the stored pointers to the header, preheader, + // merge, or continue blocks. + void ClearBlocks() { loop_basic_blocks_.clear(); } + + // Adds the Basic Block |bb| this loop and its parents. + void AddBasicBlockToLoop(const BasicBlock* bb) { + assert(IsBasicBlockInLoopSlow(bb) && + "Basic block does not belong to the loop"); + + AddBasicBlock(bb); + } + + // Returns the list of induction variables within the loop. + void GetInductionVariables(std::vector& inductions) const; + + // This function uses the |condition| to find the induction variable which is + // used by the loop condition within the loop. This only works if the loop is + // bound by a single condition and single induction variable. + ir::Instruction* FindConditionVariable(const ir::BasicBlock* condition) const; + + // Returns the number of iterations within a loop when given the |induction| + // variable and the loop |condition| check. It stores the found number of + // iterations in the output parameter |iterations| and optionally, the step + // value in |step_value| and the initial value of the induction variable in + // |init_value|. + bool FindNumberOfIterations(const ir::Instruction* induction, + const ir::Instruction* condition, + size_t* iterations, + int64_t* step_amount = nullptr, + int64_t* init_value = nullptr) const; + + // Returns the value of the OpLoopMerge control operand as a bool. Loop + // control can be None(0), Unroll(1), or DontUnroll(2). This function returns + // true if it is set to Unroll. + inline bool HasUnrollLoopControl() const { + assert(loop_header_); + if (!loop_header_->GetLoopMergeInst()) return false; + + return loop_header_->GetLoopMergeInst()->GetSingleWordOperand(2) == 1; + } + + // Finds the conditional block with a branch to the merge and continue blocks + // within the loop body. + ir::BasicBlock* FindConditionBlock() const; + + // Remove the child loop form this loop. + inline void RemoveChildLoop(Loop* loop) { + nested_loops_.erase( + std::find(nested_loops_.begin(), nested_loops_.end(), loop)); + loop->SetParent(nullptr); + } + + // Mark this loop to be removed later by a call to + // LoopDescriptor::PostModificationCleanup. + inline void MarkLoopForRemoval() { loop_is_marked_for_removal_ = true; } + + // Returns whether or not this loop has been marked for removal. + inline bool IsMarkedForRemoval() const { return loop_is_marked_for_removal_; } + + // Returns true if all nested loops have been marked for removal. + inline bool AreAllChildrenMarkedForRemoval() const { + for (const Loop* child : nested_loops_) { + if (!child->IsMarkedForRemoval()) { + return false; + } + } + return true; + } + + // Checks if the loop contains any instruction that will prevent it from being + // cloned. If the loop is structured, the merge construct is also considered. + bool IsSafeToClone() const; + + // Sets the parent loop of this loop, that is, a loop which contains this loop + // as a nested child loop. + inline void SetParent(Loop* parent) { parent_ = parent; } + + // Returns true is the instruction is invariant and safe to move wrt loop + bool ShouldHoistInstruction(IRContext* context, Instruction* inst); + + // Returns true if all operands of inst are in basic blocks not contained in + // loop + bool AreAllOperandsOutsideLoop(IRContext* context, Instruction* inst); + + // Extract the initial value from the |induction| variable and store it in + // |value|. If the function couldn't find the initial value of |induction| + // return false. + bool GetInductionInitValue(const ir::Instruction* induction, + int64_t* value) const; + + // Takes in a phi instruction |induction| and the loop |header| and returns + // the step operation of the loop. + ir::Instruction* GetInductionStepOperation( + const ir::Instruction* induction) const; + + // Returns true if we can deduce the number of loop iterations in the step + // operation |step|. IsSupportedCondition must also be true for the condition + // instruction. + bool IsSupportedStepOp(SpvOp step) const; + + // Returns true if we can deduce the number of loop iterations in the + // condition operation |condition|. IsSupportedStepOp must also be true for + // the step instruction. + bool IsSupportedCondition(SpvOp condition) const; + + // Creates the list of the loop's basic block in structured order and store + // the result in |ordered_loop_blocks|. If |include_pre_header| is true, the + // pre-header block will also be included at the beginning of the list if it + // exist. If |include_merge| is true, the merge block will also be included at + // the end of the list if it exist. + void ComputeLoopStructuredOrder( + std::vector* ordered_loop_blocks, + bool include_pre_header = false, bool include_merge = false) const; + + // Given the loop |condition|, |initial_value|, |step_value|, the trip count + // |number_of_iterations|, and the |unroll_factor| requested, get the new + // condition value for the residual loop. + static int64_t GetResidualConditionValue(SpvOp condition, + int64_t initial_value, + int64_t step_value, + size_t number_of_iterations, + size_t unroll_factor); + + private: + IRContext* context_; + // The block which marks the start of the loop. + BasicBlock* loop_header_; + + // The block which begins the body of the loop. + BasicBlock* loop_continue_; + + // The block which marks the end of the loop. + BasicBlock* loop_merge_; + + // The block immediately before the loop header. + BasicBlock* loop_preheader_; + + // A parent of a loop is the loop which contains it as a nested child loop. + Loop* parent_; + + // Nested child loops of this loop. + ChildrenList nested_loops_; + + // A set of all the basic blocks which comprise the loop structure. Will be + // computed only when needed on demand. + BasicBlockListTy loop_basic_blocks_; + + // Check that |bb| is inside the loop using domination property. + // Note: this is for assertion purposes only, IsInsideLoop should be used + // instead. + bool IsBasicBlockInLoopSlow(const BasicBlock* bb); + + // Returns the loop preheader if it exists, returns nullptr otherwise. + BasicBlock* FindLoopPreheader(opt::DominatorAnalysis* dom_analysis); + + // Sets |latch| as the loop unique continue block. No checks are performed + // here. + inline void SetLatchBlockImpl(BasicBlock* latch) { loop_continue_ = latch; } + // Sets |merge| as the loop merge block. No checks are performed here. + inline void SetMergeBlockImpl(BasicBlock* merge) { loop_merge_ = merge; } + + // Each differnt loop |condition| affects how we calculate the number of + // iterations using the |condition_value|, |init_value|, and |step_values| of + // the induction variable. This method will return the number of iterations in + // a loop with those values for a given |condition|. + int64_t GetIterations(SpvOp condition, int64_t condition_value, + int64_t init_value, int64_t step_value) const; + + // This is to allow for loops to be removed mid iteration without invalidating + // the iterators. + bool loop_is_marked_for_removal_; + + // This is only to allow LoopDescriptor::dummy_top_loop_ to add top level + // loops as child. + friend class LoopDescriptor; + friend class LoopUtils; +}; + +// Loop descriptions class for a given function. +// For a given function, the class builds loop nests information. +// The analysis expects a structured control flow. +class LoopDescriptor { + public: + // Iterator interface (depth first postorder traversal). + using iterator = opt::PostOrderTreeDFIterator; + using const_iterator = opt::PostOrderTreeDFIterator; + + // Creates a loop object for all loops found in |f|. + explicit LoopDescriptor(const Function* f); + + // Disable copy constructor, to avoid double-free on destruction. + LoopDescriptor(const LoopDescriptor&) = delete; + // Move constructor. + LoopDescriptor(LoopDescriptor&& other) : dummy_top_loop_(nullptr) { + // We need to take ownership of the Loop objects in the other + // LoopDescriptor, to avoid double-free. + loops_ = std::move(other.loops_); + other.loops_.clear(); + basic_block_to_loop_ = std::move(other.basic_block_to_loop_); + other.basic_block_to_loop_.clear(); + dummy_top_loop_ = std::move(other.dummy_top_loop_); + } + + // Destructor + ~LoopDescriptor(); + + // Returns the number of loops found in the function. + inline size_t NumLoops() const { return loops_.size(); } + + // Returns the loop at a particular |index|. The |index| must be in bounds, + // check with NumLoops before calling. + inline Loop& GetLoopByIndex(size_t index) const { + assert(loops_.size() > index && + "Index out of range (larger than loop count)"); + return *loops_[index]; + } + + // Returns the inner most loop that contains the basic block id |block_id|. + inline Loop* operator[](uint32_t block_id) const { + return FindLoopForBasicBlock(block_id); + } + + // Returns the inner most loop that contains the basic block |bb|. + inline Loop* operator[](const BasicBlock* bb) const { + return (*this)[bb->id()]; + } + + // Iterators for post order depth first traversal of the loops. + // Inner most loops will be visited first. + inline iterator begin() { return iterator::begin(&dummy_top_loop_); } + inline iterator end() { return iterator::end(&dummy_top_loop_); } + inline const_iterator begin() const { return cbegin(); } + inline const_iterator end() const { return cend(); } + inline const_iterator cbegin() const { + return const_iterator::begin(&dummy_top_loop_); + } + inline const_iterator cend() const { + return const_iterator::end(&dummy_top_loop_); + } + + // Returns the inner most loop that contains the basic block |bb|. + inline void SetBasicBlockToLoop(uint32_t bb_id, Loop* loop) { + basic_block_to_loop_[bb_id] = loop; + } + + // Mark the loop |loop_to_add| as needing to be added when the user calls + // PostModificationCleanup. |parent| may be null. + inline void AddLoop(ir::Loop* loop_to_add, ir::Loop* parent) { + loops_to_add_.emplace_back(std::make_pair(parent, loop_to_add)); + } + + // Should be called to preserve the LoopAnalysis after loops have been marked + // for addition with AddLoop or MarkLoopForRemoval. + void PostModificationCleanup(); + + // Removes the basic block id |bb_id| from the block to loop mapping. + inline void ForgetBasicBlock(uint32_t bb_id) { + basic_block_to_loop_.erase(bb_id); + } + + // Adds the loop |new_loop| and all its nested loops to the descriptor set. + // The object takes ownership of all the loops. + ir::Loop* AddLoopNest(std::unique_ptr new_loop); + + // Remove the loop |loop|. + void RemoveLoop(ir::Loop* loop); + + void SetAsTopLoop(ir::Loop* loop) { + assert(std::find(dummy_top_loop_.begin(), dummy_top_loop_.end(), loop) == + dummy_top_loop_.end() && + "already registered"); + dummy_top_loop_.nested_loops_.push_back(loop); + } + + Loop* GetDummyRootLoop() { return &dummy_top_loop_; } + const Loop* GetDummyRootLoop() const { return &dummy_top_loop_; } + + private: + // TODO(dneto): This should be a vector of unique_ptr. But VisualStudio 2013 + // is unable to compile it. + using LoopContainerType = std::vector; + using LoopsToAddContainerType = std::vector>; + + // Creates loop descriptors for the function |f|. + void PopulateList(const Function* f); + + // Returns the inner most loop that contains the basic block id |block_id|. + inline Loop* FindLoopForBasicBlock(uint32_t block_id) const { + std::unordered_map::const_iterator it = + basic_block_to_loop_.find(block_id); + return it != basic_block_to_loop_.end() ? it->second : nullptr; + } + + // Erase all the loop information. + void ClearLoops(); + + // A list of all the loops in the function. This variable owns the Loop + // objects. + LoopContainerType loops_; + + // Dummy root: this "loop" is only there to help iterators creation. + Loop dummy_top_loop_; + + std::unordered_map basic_block_to_loop_; + + // List of the loops marked for addition when PostModificationCleanup is + // called. + LoopsToAddContainerType loops_to_add_; +}; + +} // namespace ir +} // namespace spvtools + +#endif // LIBSPIRV_OPT_LOOP_DESCRIPTORS_H_ diff --git a/3rdparty/spirv-tools/source/opt/loop_peeling.cpp b/3rdparty/spirv-tools/source/opt/loop_peeling.cpp new file mode 100644 index 000000000..b2d1b088c --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/loop_peeling.cpp @@ -0,0 +1,563 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include + +#include "ir_builder.h" +#include "ir_context.h" +#include "loop_descriptor.h" +#include "loop_peeling.h" +#include "loop_utils.h" + +namespace spvtools { +namespace opt { + +void LoopPeeling::DuplicateAndConnectLoop( + LoopUtils::LoopCloningResult* clone_results) { + ir::CFG& cfg = *context_->cfg(); + analysis::DefUseManager* def_use_mgr = context_->get_def_use_mgr(); + + assert(CanPeelLoop() && "Cannot peel loop!"); + + std::vector ordered_loop_blocks; + ir::BasicBlock* pre_header = loop_->GetOrCreatePreHeaderBlock(); + + loop_->ComputeLoopStructuredOrder(&ordered_loop_blocks); + + cloned_loop_ = loop_utils_.CloneLoop(clone_results, ordered_loop_blocks); + + // Add the basic block to the function. + ir::Function::iterator it = + loop_utils_.GetFunction()->FindBlock(pre_header->id()); + assert(it != loop_utils_.GetFunction()->end() && + "Pre-header not found in the function."); + loop_utils_.GetFunction()->AddBasicBlocks( + clone_results->cloned_bb_.begin(), clone_results->cloned_bb_.end(), ++it); + + // Make the |loop_|'s preheader the |cloned_loop_| one. + ir::BasicBlock* cloned_header = cloned_loop_->GetHeaderBlock(); + pre_header->ForEachSuccessorLabel( + [cloned_header](uint32_t* succ) { *succ = cloned_header->id(); }); + + // Update cfg. + cfg.RemoveEdge(pre_header->id(), loop_->GetHeaderBlock()->id()); + cloned_loop_->SetPreHeaderBlock(pre_header); + loop_->SetPreHeaderBlock(nullptr); + + // When cloning the loop, we didn't cloned the merge block, so currently + // |cloned_loop_| shares the same block as |loop_|. + // We mutate all branches from |cloned_loop_| block to |loop_|'s merge into a + // branch to |loop_|'s header (so header will also be the merge of + // |cloned_loop_|). + uint32_t cloned_loop_exit = 0; + for (uint32_t pred_id : cfg.preds(loop_->GetMergeBlock()->id())) { + if (loop_->IsInsideLoop(pred_id)) continue; + ir::BasicBlock* bb = cfg.block(pred_id); + assert(cloned_loop_exit == 0 && "The loop has multiple exits."); + cloned_loop_exit = bb->id(); + bb->ForEachSuccessorLabel([this](uint32_t* succ) { + if (*succ == loop_->GetMergeBlock()->id()) + *succ = loop_->GetHeaderBlock()->id(); + }); + } + + // Update cfg. + cfg.RemoveNonExistingEdges(loop_->GetMergeBlock()->id()); + cfg.AddEdge(cloned_loop_exit, loop_->GetHeaderBlock()->id()); + + // Patch the phi of the original loop header: + // - Set the loop entry branch to come from the cloned loop exit block; + // - Set the initial value of the phi using the corresponding cloned loop + // exit values. + // + // We patch the iterating value initializers of the original loop using the + // corresponding cloned loop exit values. Connects the cloned loop iterating + // values to the original loop. This make sure that the initial value of the + // second loop starts with the last value of the first loop. + // + // For example, loops like: + // + // int z = 0; + // for (int i = 0; i++ < M; i += cst1) { + // if (cond) + // z += cst2; + // } + // + // Will become: + // + // int z = 0; + // int i = 0; + // for (; i++ < M; i += cst1) { + // if (cond) + // z += cst2; + // } + // for (; i++ < M; i += cst1) { + // if (cond) + // z += cst2; + // } + loop_->GetHeaderBlock()->ForEachPhiInst([cloned_loop_exit, def_use_mgr, + clone_results, + this](ir::Instruction* phi) { + for (uint32_t i = 0; i < phi->NumInOperands(); i += 2) { + if (!loop_->IsInsideLoop(phi->GetSingleWordInOperand(i + 1))) { + phi->SetInOperand(i, + {clone_results->value_map_.at( + exit_value_.at(phi->result_id())->result_id())}); + phi->SetInOperand(i + 1, {cloned_loop_exit}); + def_use_mgr->AnalyzeInstUse(phi); + return; + } + } + }); + + // Force the creation of a new preheader for the original loop and set it as + // the merge block for the cloned loop. + cloned_loop_->SetMergeBlock(loop_->GetOrCreatePreHeaderBlock()); +} + +void LoopPeeling::InsertCanonicalInductionVariable() { + ir::BasicBlock::iterator insert_point = + GetClonedLoop()->GetLatchBlock()->tail(); + if (GetClonedLoop()->GetLatchBlock()->GetMergeInst()) { + --insert_point; + } + InstructionBuilder builder(context_, &*insert_point, + ir::IRContext::kAnalysisDefUse | + ir::IRContext::kAnalysisInstrToBlockMapping); + ir::Instruction* uint_1_cst = + builder.Add32BitConstantInteger(1, int_type_->IsSigned()); + // Create the increment. + // Note that we do "1 + 1" here, one of the operand should the phi + // value but we don't have it yet. The operand will be set latter. + ir::Instruction* iv_inc = builder.AddIAdd( + uint_1_cst->type_id(), uint_1_cst->result_id(), uint_1_cst->result_id()); + + builder.SetInsertPoint(&*GetClonedLoop()->GetHeaderBlock()->begin()); + + canonical_induction_variable_ = builder.AddPhi( + uint_1_cst->type_id(), + {builder.Add32BitConstantInteger(0, int_type_->IsSigned()) + ->result_id(), + GetClonedLoop()->GetPreHeaderBlock()->id(), iv_inc->result_id(), + GetClonedLoop()->GetLatchBlock()->id()}); + // Connect everything. + iv_inc->SetInOperand(0, {canonical_induction_variable_->result_id()}); + + // Update def/use manager. + context_->get_def_use_mgr()->AnalyzeInstUse(iv_inc); + + // If do-while form, use the incremented value. + if (do_while_form_) { + canonical_induction_variable_ = iv_inc; + } +} + +void LoopPeeling::GetIteratorUpdateOperations( + const ir::Loop* loop, ir::Instruction* iterator, + std::unordered_set* operations) { + opt::analysis::DefUseManager* def_use_mgr = context_->get_def_use_mgr(); + operations->insert(iterator); + iterator->ForEachInId([def_use_mgr, loop, operations, this](uint32_t* id) { + ir::Instruction* insn = def_use_mgr->GetDef(*id); + if (insn->opcode() == SpvOpLabel) { + return; + } + if (operations->count(insn)) { + return; + } + if (!loop->IsInsideLoop(insn)) { + return; + } + GetIteratorUpdateOperations(loop, insn, operations); + }); +} + +// Gather the set of blocks for all the path from |entry| to |root|. +static void GetBlocksInPath(uint32_t block, uint32_t entry, + std::unordered_set* blocks_in_path, + const ir::CFG& cfg) { + for (uint32_t pid : cfg.preds(block)) { + if (blocks_in_path->insert(pid).second) { + if (pid != entry) { + GetBlocksInPath(pid, entry, blocks_in_path, cfg); + } + } + } +} + +bool LoopPeeling::IsConditionCheckSideEffectFree() const { + ir::CFG& cfg = *context_->cfg(); + + // The "do-while" form does not cause issues, the algorithm takes into account + // the first iteration. + if (!do_while_form_) { + uint32_t condition_block_id = cfg.preds(loop_->GetMergeBlock()->id())[0]; + + std::unordered_set blocks_in_path; + + blocks_in_path.insert(condition_block_id); + GetBlocksInPath(condition_block_id, loop_->GetHeaderBlock()->id(), + &blocks_in_path, cfg); + + for (uint32_t bb_id : blocks_in_path) { + ir::BasicBlock* bb = cfg.block(bb_id); + if (!bb->WhileEachInst([this](ir::Instruction* insn) { + if (insn->IsBranch()) return true; + switch (insn->opcode()) { + case SpvOpLabel: + case SpvOpSelectionMerge: + case SpvOpLoopMerge: + return true; + default: + break; + } + return context_->IsCombinatorInstruction(insn); + })) { + return false; + } + } + } + + return true; +} + +void LoopPeeling::GetIteratingExitValues() { + ir::CFG& cfg = *context_->cfg(); + + loop_->GetHeaderBlock()->ForEachPhiInst([this](ir::Instruction* phi) { + exit_value_[phi->result_id()] = nullptr; + }); + + if (!loop_->GetMergeBlock()) { + return; + } + if (cfg.preds(loop_->GetMergeBlock()->id()).size() != 1) { + return; + } + opt::analysis::DefUseManager* def_use_mgr = context_->get_def_use_mgr(); + + uint32_t condition_block_id = cfg.preds(loop_->GetMergeBlock()->id())[0]; + + auto& header_pred = cfg.preds(loop_->GetHeaderBlock()->id()); + do_while_form_ = std::find(header_pred.begin(), header_pred.end(), + condition_block_id) != header_pred.end(); + if (do_while_form_) { + loop_->GetHeaderBlock()->ForEachPhiInst( + [condition_block_id, def_use_mgr, this](ir::Instruction* phi) { + std::unordered_set operations; + + for (uint32_t i = 0; i < phi->NumInOperands(); i += 2) { + if (condition_block_id == phi->GetSingleWordInOperand(i + 1)) { + exit_value_[phi->result_id()] = + def_use_mgr->GetDef(phi->GetSingleWordInOperand(i)); + } + } + }); + } else { + DominatorTree* dom_tree = + &context_->GetDominatorAnalysis(loop_utils_.GetFunction(), cfg) + ->GetDomTree(); + ir::BasicBlock* condition_block = cfg.block(condition_block_id); + + loop_->GetHeaderBlock()->ForEachPhiInst( + [dom_tree, condition_block, this](ir::Instruction* phi) { + std::unordered_set operations; + + // Not the back-edge value, check if the phi instruction is the only + // possible candidate. + GetIteratorUpdateOperations(loop_, phi, &operations); + + for (ir::Instruction* insn : operations) { + if (insn == phi) { + continue; + } + if (dom_tree->Dominates(context_->get_instr_block(insn), + condition_block)) { + return; + } + } + exit_value_[phi->result_id()] = phi; + }); + } +} + +void LoopPeeling::FixExitCondition( + const std::function& condition_builder) { + ir::CFG& cfg = *context_->cfg(); + + uint32_t condition_block_id = 0; + for (uint32_t id : cfg.preds(GetClonedLoop()->GetMergeBlock()->id())) { + if (GetClonedLoop()->IsInsideLoop(id)) { + condition_block_id = id; + break; + } + } + assert(condition_block_id != 0 && "2nd loop in improperly connected"); + + ir::BasicBlock* condition_block = cfg.block(condition_block_id); + ir::Instruction* exit_condition = condition_block->terminator(); + assert(exit_condition->opcode() == SpvOpBranchConditional); + ir::BasicBlock::iterator insert_point = condition_block->tail(); + if (condition_block->GetMergeInst()) { + --insert_point; + } + + exit_condition->SetInOperand(0, {condition_builder(&*insert_point)}); + + uint32_t to_continue_block_idx = + GetClonedLoop()->IsInsideLoop(exit_condition->GetSingleWordInOperand(1)) + ? 1 + : 2; + exit_condition->SetInOperand( + 1, {exit_condition->GetSingleWordInOperand(to_continue_block_idx)}); + exit_condition->SetInOperand(2, {GetClonedLoop()->GetMergeBlock()->id()}); + + // Update def/use manager. + context_->get_def_use_mgr()->AnalyzeInstUse(exit_condition); +} + +ir::BasicBlock* LoopPeeling::CreateBlockBefore(ir::BasicBlock* bb) { + analysis::DefUseManager* def_use_mgr = context_->get_def_use_mgr(); + ir::CFG& cfg = *context_->cfg(); + assert(cfg.preds(bb->id()).size() == 1 && "More than one predecessor"); + + std::unique_ptr new_bb = MakeUnique( + std::unique_ptr(new ir::Instruction( + context_, SpvOpLabel, 0, context_->TakeNextId(), {}))); + new_bb->SetParent(loop_utils_.GetFunction()); + // Update the loop descriptor. + ir::Loop* in_loop = (*loop_utils_.GetLoopDescriptor())[bb]; + if (in_loop) { + in_loop->AddBasicBlock(new_bb.get()); + loop_utils_.GetLoopDescriptor()->SetBasicBlockToLoop(new_bb->id(), in_loop); + } + + context_->set_instr_block(new_bb->GetLabelInst(), new_bb.get()); + def_use_mgr->AnalyzeInstDefUse(new_bb->GetLabelInst()); + + ir::BasicBlock* bb_pred = cfg.block(cfg.preds(bb->id())[0]); + bb_pred->tail()->ForEachInId([bb, &new_bb](uint32_t* id) { + if (*id == bb->id()) { + *id = new_bb->id(); + } + }); + cfg.RemoveEdge(bb_pred->id(), bb->id()); + cfg.AddEdge(bb_pred->id(), new_bb->id()); + def_use_mgr->AnalyzeInstUse(&*bb_pred->tail()); + + // Update the incoming branch. + bb->ForEachPhiInst([&new_bb, def_use_mgr](ir::Instruction* phi) { + phi->SetInOperand(1, {new_bb->id()}); + def_use_mgr->AnalyzeInstUse(phi); + }); + InstructionBuilder(context_, new_bb.get(), + ir::IRContext::kAnalysisDefUse | + ir::IRContext::kAnalysisInstrToBlockMapping) + .AddBranch(bb->id()); + cfg.AddEdge(new_bb->id(), bb->id()); + + // Add the basic block to the function. + ir::Function::iterator it = loop_utils_.GetFunction()->FindBlock(bb->id()); + assert(it != loop_utils_.GetFunction()->end() && + "Basic block not found in the function."); + ir::BasicBlock* ret = new_bb.get(); + loop_utils_.GetFunction()->AddBasicBlock(std::move(new_bb), it); + return ret; +} + +ir::BasicBlock* LoopPeeling::ProtectLoop(ir::Loop* loop, + ir::Instruction* condition, + ir::BasicBlock* if_merge) { + ir::BasicBlock* if_block = loop->GetOrCreatePreHeaderBlock(); + // Will no longer be a pre-header because of the if. + loop->SetPreHeaderBlock(nullptr); + // Kill the branch to the header. + context_->KillInst(&*if_block->tail()); + + InstructionBuilder builder(context_, if_block, + ir::IRContext::kAnalysisDefUse | + ir::IRContext::kAnalysisInstrToBlockMapping); + builder.AddConditionalBranch(condition->result_id(), + loop->GetHeaderBlock()->id(), if_merge->id(), + if_merge->id()); + + return if_block; +} + +void LoopPeeling::PeelBefore(uint32_t peel_factor) { + assert(CanPeelLoop() && "Cannot peel loop"); + LoopUtils::LoopCloningResult clone_results; + + // Clone the loop and insert the cloned one before the loop. + DuplicateAndConnectLoop(&clone_results); + + // Add a canonical induction variable "canonical_induction_variable_". + InsertCanonicalInductionVariable(); + + InstructionBuilder builder(context_, + &*cloned_loop_->GetPreHeaderBlock()->tail(), + ir::IRContext::kAnalysisDefUse | + ir::IRContext::kAnalysisInstrToBlockMapping); + ir::Instruction* factor = + builder.Add32BitConstantInteger(peel_factor, int_type_->IsSigned()); + + ir::Instruction* has_remaining_iteration = builder.AddLessThan( + factor->result_id(), loop_iteration_count_->result_id()); + ir::Instruction* max_iteration = builder.AddSelect( + factor->type_id(), has_remaining_iteration->result_id(), + factor->result_id(), loop_iteration_count_->result_id()); + + // Change the exit condition of the cloned loop to be (exit when become + // false): + // "canonical_induction_variable_" < min("factor", "loop_iteration_count_") + FixExitCondition([max_iteration, this](ir::Instruction* insert_before_point) { + return InstructionBuilder(context_, insert_before_point, + ir::IRContext::kAnalysisDefUse | + ir::IRContext::kAnalysisInstrToBlockMapping) + .AddLessThan(canonical_induction_variable_->result_id(), + max_iteration->result_id()) + ->result_id(); + }); + + // "Protect" the second loop: the second loop can only be executed if + // |has_remaining_iteration| is true (i.e. factor < loop_iteration_count_). + ir::BasicBlock* if_merge_block = loop_->GetMergeBlock(); + loop_->SetMergeBlock(CreateBlockBefore(loop_->GetMergeBlock())); + // Prevent the second loop from being executed if we already executed all the + // required iterations. + ir::BasicBlock* if_block = + ProtectLoop(loop_, has_remaining_iteration, if_merge_block); + // Patch the phi of the merge block. + if_merge_block->ForEachPhiInst( + [&clone_results, if_block, this](ir::Instruction* phi) { + // if_merge_block had previously only 1 predecessor. + uint32_t incoming_value = phi->GetSingleWordInOperand(0); + auto def_in_loop = clone_results.value_map_.find(incoming_value); + if (def_in_loop != clone_results.value_map_.end()) + incoming_value = def_in_loop->second; + phi->AddOperand( + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {incoming_value}}); + phi->AddOperand( + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {if_block->id()}}); + context_->get_def_use_mgr()->AnalyzeInstUse(phi); + }); + + context_->InvalidateAnalysesExceptFor( + ir::IRContext::kAnalysisDefUse | + ir::IRContext::kAnalysisInstrToBlockMapping | + ir::IRContext::kAnalysisLoopAnalysis | ir::IRContext::kAnalysisCFG); +} + +void LoopPeeling::PeelAfter(uint32_t peel_factor) { + assert(CanPeelLoop() && "Cannot peel loop"); + LoopUtils::LoopCloningResult clone_results; + + // Clone the loop and insert the cloned one before the loop. + DuplicateAndConnectLoop(&clone_results); + + // Add a canonical induction variable "canonical_induction_variable_". + InsertCanonicalInductionVariable(); + + InstructionBuilder builder(context_, + &*cloned_loop_->GetPreHeaderBlock()->tail(), + ir::IRContext::kAnalysisDefUse | + ir::IRContext::kAnalysisInstrToBlockMapping); + ir::Instruction* factor = + builder.Add32BitConstantInteger(peel_factor, int_type_->IsSigned()); + + ir::Instruction* has_remaining_iteration = builder.AddLessThan( + factor->result_id(), loop_iteration_count_->result_id()); + + // Change the exit condition of the cloned loop to be (exit when become + // false): + // "canonical_induction_variable_" + "factor" < "loop_iteration_count_" + FixExitCondition([factor, this](ir::Instruction* insert_before_point) { + InstructionBuilder cond_builder( + context_, insert_before_point, + ir::IRContext::kAnalysisDefUse | + ir::IRContext::kAnalysisInstrToBlockMapping); + // Build the following check: canonical_induction_variable_ + factor < + // iteration_count + return cond_builder + .AddLessThan(cond_builder + .AddIAdd(canonical_induction_variable_->type_id(), + canonical_induction_variable_->result_id(), + factor->result_id()) + ->result_id(), + loop_iteration_count_->result_id()) + ->result_id(); + }); + + // "Protect" the first loop: the first loop can only be executed if + // factor < loop_iteration_count_. + + // The original loop's pre-header was the cloned loop merge block. + GetClonedLoop()->SetMergeBlock( + CreateBlockBefore(GetOriginalLoop()->GetPreHeaderBlock())); + // Use the second loop preheader as if merge block. + + // Prevent the first loop if only the peeled loop needs it. + ir::BasicBlock* if_block = + ProtectLoop(cloned_loop_, has_remaining_iteration, + GetOriginalLoop()->GetPreHeaderBlock()); + + // Patch the phi of the header block. + // We added an if to enclose the first loop and because the phi node are + // connected to the exit value of the first loop, the definition no longer + // dominate the preheader. + // We had to the preheader (our if merge block) the required phi instruction + // and patch the header phi. + GetOriginalLoop()->GetHeaderBlock()->ForEachPhiInst( + [&clone_results, if_block, this](ir::Instruction* phi) { + opt::analysis::DefUseManager* def_use_mgr = context_->get_def_use_mgr(); + + auto find_value_idx = [](ir::Instruction* phi_inst, ir::Loop* loop) { + uint32_t preheader_value_idx = + !loop->IsInsideLoop(phi_inst->GetSingleWordInOperand(1)) ? 0 : 2; + return preheader_value_idx; + }; + + ir::Instruction* cloned_phi = + def_use_mgr->GetDef(clone_results.value_map_.at(phi->result_id())); + uint32_t cloned_preheader_value = cloned_phi->GetSingleWordInOperand( + find_value_idx(cloned_phi, GetClonedLoop())); + + ir::Instruction* new_phi = + InstructionBuilder(context_, + &*GetOriginalLoop()->GetPreHeaderBlock()->tail(), + ir::IRContext::kAnalysisDefUse | + ir::IRContext::kAnalysisInstrToBlockMapping) + .AddPhi(phi->type_id(), + {phi->GetSingleWordInOperand( + find_value_idx(phi, GetOriginalLoop())), + GetClonedLoop()->GetMergeBlock()->id(), + cloned_preheader_value, if_block->id()}); + + phi->SetInOperand(find_value_idx(phi, GetOriginalLoop()), + {new_phi->result_id()}); + def_use_mgr->AnalyzeInstUse(phi); + }); + + context_->InvalidateAnalysesExceptFor( + ir::IRContext::kAnalysisDefUse | + ir::IRContext::kAnalysisInstrToBlockMapping | + ir::IRContext::kAnalysisLoopAnalysis | ir::IRContext::kAnalysisCFG); +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/loop_peeling.h b/3rdparty/spirv-tools/source/opt/loop_peeling.h new file mode 100644 index 000000000..9b912e560 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/loop_peeling.h @@ -0,0 +1,223 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_OPT_LOOP_PEELING_H_ +#define SOURCE_OPT_LOOP_PEELING_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "opt/ir_context.h" +#include "opt/loop_descriptor.h" +#include "opt/loop_utils.h" + +namespace spvtools { +namespace opt { + +// Utility class to perform the peeling of a given loop. +// The loop peeling transformation make a certain amount of a loop iterations to +// be executed either before (peel before) or after (peel after) the transformed +// loop. +// +// For peeling cases the transformation does the following steps: +// - It clones the loop and inserts the cloned loop before the original loop; +// - It connects all iterating values of the cloned loop with the +// corresponding original loop values so that the second loop starts with +// the appropriate values. +// - It inserts a new induction variable "i" is inserted into the cloned that +// starts with the value 0 and increment by step of one. +// +// The last step is specific to each case: +// - Peel before: the transformation is to peel the "N" first iterations. +// The exit condition of the cloned loop is changed so that the loop +// exits when "i < N" becomes false. The original loop is then protected to +// only execute if there is any iteration left to do. +// - Peel after: the transformation is to peel the "N" last iterations, +// then the exit condition of the cloned loop is changed so that the loop +// exits when "i + N < max_iteration" becomes false, where "max_iteration" +// is the upper bound of the loop. The cloned loop is then protected to +// only execute if there is any iteration left to do no covered by the +// second. +// +// To be peelable: +// - The loop must be in LCSSA form; +// - The loop must not contain any breaks; +// - The loop must not have any ambiguous iterators updates (see +// "CanPeelLoop"). +// The method "CanPeelLoop" checks that those constrained are met. +// +// FIXME(Victor): Allow the utility it accept an canonical induction variable +// rather than automatically create one. +// FIXME(Victor): When possible, evaluate the initial value of the second loop +// iterating values rather than using the exit value of the first loop. +// FIXME(Victor): Make the utility work-out the upper bound without having to +// provide it. This should become easy once the scalar evolution is in. +class LoopPeeling { + public: + // LoopPeeling constructor. + // |loop| is the loop to peel. + // |loop_iteration_count| is the instruction holding the |loop| iteration + // count, must be invariant for |loop| and must be of an int 32 type (signed + // or unsigned). + LoopPeeling(ir::IRContext* context, ir::Loop* loop, + ir::Instruction* loop_iteration_count) + : context_(context), + loop_utils_(context, loop), + loop_(loop), + loop_iteration_count_(!loop->IsInsideLoop(loop_iteration_count) + ? loop_iteration_count + : nullptr), + int_type_(nullptr), + canonical_induction_variable_(nullptr) { + if (loop_iteration_count_) { + int_type_ = context_->get_type_mgr() + ->GetType(loop_iteration_count_->type_id()) + ->AsInteger(); + } + GetIteratingExitValues(); + } + + // Returns true if the loop can be peeled. + // To be peelable, all operation involved in the update of the loop iterators + // must not dominates the exit condition. This restriction is a work around to + // not miss compile code like: + // + // for (int i = 0; i + 1 < N; i++) {} + // for (int i = 0; ++i < N; i++) {} + // + // The increment will happen before the test on the exit condition leading to + // very look-a-like code. + // + // This restriction will not apply if a loop rotate is applied before (i.e. + // becomes a do-while loop). + bool CanPeelLoop() const { + ir::CFG& cfg = *context_->cfg(); + + if (!loop_iteration_count_) { + return false; + } + if (!int_type_) { + return false; + } + if (int_type_->width() != 32) { + return false; + } + if (!loop_->IsLCSSA()) { + return false; + } + if (!loop_->GetMergeBlock()) { + return false; + } + if (cfg.preds(loop_->GetMergeBlock()->id()).size() != 1) { + return false; + } + if (!IsConditionCheckSideEffectFree()) { + return false; + } + + return !std::any_of(exit_value_.cbegin(), exit_value_.cend(), + [](std::pair it) { + return it.second == nullptr; + }); + } + + // Moves the execution of the |factor| first iterations of the loop into a + // dedicated loop. + void PeelBefore(uint32_t factor); + + // Moves the execution of the |factor| last iterations of the loop into a + // dedicated loop. + void PeelAfter(uint32_t factor); + + // Returns the cloned loop. + ir::Loop* GetClonedLoop() { return cloned_loop_; } + // Returns the original loop. + ir::Loop* GetOriginalLoop() { return loop_; } + + private: + ir::IRContext* context_; + LoopUtils loop_utils_; + // The original loop. + ir::Loop* loop_; + // The initial |loop_| upper bound. + ir::Instruction* loop_iteration_count_; + // The int type to use for the canonical_induction_variable_. + analysis::Integer* int_type_; + // The cloned loop. + ir::Loop* cloned_loop_; + // This is set to true when the exit and back-edge branch instruction is the + // same. + bool do_while_form_; + + // The canonical induction variable of the cloned loop. The induction variable + // is initialized to 0 and incremented by step of 1. + ir::Instruction* canonical_induction_variable_; + + // Map between loop iterators and exit values. Loop iterators + std::unordered_map exit_value_; + + // Duplicate |loop_| and place the new loop before the cloned loop. Iterating + // values from the cloned loop are then connected to the original loop as + // initializer. + void DuplicateAndConnectLoop(LoopUtils::LoopCloningResult* clone_results); + + // Insert the canonical induction variable into the first loop as a simplified + // counter. + void InsertCanonicalInductionVariable(); + + // Fixes the exit condition of the before loop. The function calls + // |condition_builder| to get the condition to use in the conditional branch + // of the loop exit. The loop will be exited if the condition evaluate to + // true. |condition_builder| takes an ir::Instruction* that represent the + // insertion point. + void FixExitCondition( + const std::function& condition_builder); + + // Gathers all operations involved in the update of |iterator| into + // |operations|. + void GetIteratorUpdateOperations( + const ir::Loop* loop, ir::Instruction* iterator, + std::unordered_set* operations); + + // Gathers exiting iterator values. The function builds a map between each + // iterating value in the loop (a phi instruction in the loop header) and its + // SSA value when it exit the loop. If no exit value can be accurately found, + // it is map to nullptr (see comment on CanPeelLoop). + void GetIteratingExitValues(); + + // Returns true if a for-loop has no instruction with effects before the + // condition check. + bool IsConditionCheckSideEffectFree() const; + + // Creates a new basic block and insert it between |bb| and the predecessor of + // |bb|. + ir::BasicBlock* CreateBlockBefore(ir::BasicBlock* bb); + + // Inserts code to only execute |loop| only if the given |condition| is true. + // |if_merge| is a suitable basic block to be used by the if condition as + // merge block. + // The function returns the if block protecting the loop. + ir::BasicBlock* ProtectLoop(ir::Loop* loop, ir::Instruction* condition, + ir::BasicBlock* if_merge); +}; + +} // namespace opt +} // namespace spvtools + +#endif // SOURCE_OPT_LOOP_PEELING_H_ diff --git a/3rdparty/spirv-tools/source/opt/loop_unroller.cpp b/3rdparty/spirv-tools/source/opt/loop_unroller.cpp new file mode 100644 index 000000000..39aa10810 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/loop_unroller.cpp @@ -0,0 +1,1057 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "opt/loop_unroller.h" +#include +#include +#include +#include "opt/ir_builder.h" +#include "opt/loop_utils.h" + +// Implements loop util unrolling functionality for fully and partially +// unrolling loops. Given a factor it will duplicate the loop that many times, +// appending each one to the end of the old loop and removing backedges, to +// create a new unrolled loop. +// +// 1 - User calls LoopUtils::FullyUnroll or LoopUtils::PartiallyUnroll with a +// loop they wish to unroll. LoopUtils::CanPerformUnroll is used to +// validate that a given loop can be unrolled. That method (along with the +// constructor of loop) checks that the IR is in the expected canonicalised +// format. +// +// 2 - The LoopUtils methods create a LoopUnrollerUtilsImpl object to actually +// perform the unrolling. This implements helper methods to copy the loop basic +// blocks and remap the ids of instructions used inside them. +// +// 3 - The core of LoopUnrollerUtilsImpl is the Unroll method, this method +// actually performs the loop duplication. It does this by creating a +// LoopUnrollState object and then copying the loop as given by the factor +// parameter. The LoopUnrollState object retains the state of the unroller +// between the loop body copies as each iteration needs information on the last +// to adjust the phi induction variable, adjust the OpLoopMerge instruction in +// the main loop header, and change the previous continue block to point to the +// new header and the new continue block to the main loop header. +// +// 4 - If the loop is to be fully unrolled then it is simply closed after step +// 3, with the OpLoopMerge being deleted, the backedge removed, and the +// condition blocks folded. +// +// 5 - If it is being partially unrolled: if the unrolling factor leaves the +// loop with an even number of bodies with respect to the number of loop +// iterations then step 3 is all that is needed. If it is uneven then we need to +// duplicate the loop completely and unroll the duplicated loop to cover the +// residual part and adjust the first loop to cover only the "even" part. For +// instance if you request an unroll factor of 3 on a loop with 10 iterations +// then copying the body three times would leave you with three bodies in the +// loop +// where the loop still iterates over each 4 times. So we make two loops one +// iterating once then a second loop of three iterating 3 times. + +namespace spvtools { +namespace opt { +namespace { + +// Loop control constant value for DontUnroll flag. +static const uint32_t kLoopControlDontUnrollIndex = 2; + +// Operand index of the loop control parameter of the OpLoopMerge. +static const uint32_t kLoopControlIndex = 2; + +// This utility class encapsulates some of the state we need to maintain between +// loop unrolls. Specifically it maintains key blocks and the induction variable +// in the current loop duplication step and the blocks from the previous one. +// This is because each step of the unroll needs to use data from both the +// preceding step and the original loop. +struct LoopUnrollState { + LoopUnrollState() + : previous_phi_(nullptr), + previous_continue_block_(nullptr), + previous_condition_block_(nullptr), + new_phi(nullptr), + new_continue_block(nullptr), + new_condition_block(nullptr), + new_header_block(nullptr) {} + + // Initialize from the loop descriptor class. + LoopUnrollState(ir::Instruction* induction, ir::BasicBlock* continue_block, + ir::BasicBlock* condition, + std::vector&& phis) + : previous_phi_(induction), + previous_continue_block_(continue_block), + previous_condition_block_(condition), + new_phi(nullptr), + new_continue_block(nullptr), + new_condition_block(nullptr), + new_header_block(nullptr) { + previous_phis_ = std::move(phis); + } + + // Swap the state so that the new nodes are now the previous nodes. + void NextIterationState() { + previous_phi_ = new_phi; + previous_continue_block_ = new_continue_block; + previous_condition_block_ = new_condition_block; + previous_phis_ = std::move(new_phis_); + + // Clear new nodes. + new_phi = nullptr; + new_continue_block = nullptr; + new_condition_block = nullptr; + new_header_block = nullptr; + + // Clear new block/instruction maps. + new_blocks.clear(); + new_inst.clear(); + ids_to_new_inst.clear(); + } + + // The induction variable from the immediately preceding loop body. + ir::Instruction* previous_phi_; + + // All the phi nodes from the previous loop iteration. + std::vector previous_phis_; + + std::vector new_phis_; + // The previous continue block. The backedge will be removed from this and + // added to the new continue block. + ir::BasicBlock* previous_continue_block_; + + // The previous condition block. This may be folded to flatten the loop. + ir::BasicBlock* previous_condition_block_; + + // The new induction variable. + ir::Instruction* new_phi; + + // The new continue block. + ir::BasicBlock* new_continue_block; + + // The new condition block. + ir::BasicBlock* new_condition_block; + + // The new header block. + ir::BasicBlock* new_header_block; + + // A mapping of new block ids to the original blocks which they were copied + // from. + std::unordered_map new_blocks; + + // A mapping of the original instruction ids to the instruction ids to their + // copies. + std::unordered_map new_inst; + + std::unordered_map ids_to_new_inst; +}; + +// This class implements the actual unrolling. It uses a LoopUnrollState to +// maintain the state of the unrolling inbetween steps. +class LoopUnrollerUtilsImpl { + public: + using BasicBlockListTy = std::vector>; + + LoopUnrollerUtilsImpl(ir::IRContext* c, ir::Function* function) + : context_(c), + function_(*function), + loop_condition_block_(nullptr), + loop_induction_variable_(nullptr), + number_of_loop_iterations_(0), + loop_step_value_(0), + loop_init_value_(0) {} + + // Unroll the |loop| by given |factor| by copying the whole body |factor| + // times. The resulting basicblock structure will remain a loop. + void PartiallyUnroll(ir::Loop*, size_t factor); + + // If partially unrolling the |loop| would leave the loop with too many bodies + // for its number of iterations then this method should be used. This method + // will duplicate the |loop| completely, making the duplicated loop the + // successor of the original's merge block. The original loop will have its + // condition changed to loop over the residual part and the duplicate will be + // partially unrolled. The resulting structure will be two loops. + void PartiallyUnrollResidualFactor(ir::Loop* loop, size_t factor); + + // Fully unroll the |loop| by copying the full body by the total number of + // loop iterations, folding all conditions, and removing the backedge from the + // continue block to the header. + void FullyUnroll(ir::Loop* loop); + + // Get the ID of the variable in the |phi| paired with |label|. + uint32_t GetPhiDefID(const ir::Instruction* phi, uint32_t label) const; + + // Close the loop by removing the OpLoopMerge from the |loop| header block and + // making the backedge point to the merge block. + void CloseUnrolledLoop(ir::Loop* loop); + + // Remove the OpConditionalBranch instruction inside |conditional_block| used + // to branch to either exit or continue the loop and replace it with an + // unconditional OpBranch to block |new_target|. + void FoldConditionBlock(ir::BasicBlock* condtion_block, uint32_t new_target); + + // Add all blocks_to_add_ to function_ at the |insert_point|. + void AddBlocksToFunction(const ir::BasicBlock* insert_point); + + // Duplicates the |old_loop|, cloning each body and remaping the ids without + // removing instructions or changing relative structure. Result will be stored + // in |new_loop|. + void DuplicateLoop(ir::Loop* old_loop, ir::Loop* new_loop); + + inline size_t GetLoopIterationCount() const { + return number_of_loop_iterations_; + } + + // Extracts the initial state information from the |loop|. + void Init(ir::Loop* loop); + + // Replace the uses of each induction variable outside the loop with the final + // value of the induction variable before the loop exit. To reflect the proper + // state of a fully unrolled loop. + void ReplaceInductionUseWithFinalValue(ir::Loop* loop); + + // Remove all the instructions in the invalidated_instructions_ vector. + void RemoveDeadInstructions(); + + // Replace any use of induction variables outwith the loop with the final + // value of the induction variable in the unrolled loop. + void ReplaceOutsideLoopUseWithFinalValue(ir::Loop* loop); + + // Set the LoopControl operand of the OpLoopMerge instruction to be + // DontUnroll. + void MarkLoopControlAsDontUnroll(ir::Loop* loop) const; + + private: + // Remap all the in |basic_block| to new IDs and keep the mapping of new ids + // to old + // ids. |loop| is used to identify special loop blocks (header, continue, + // ect). + void AssignNewResultIds(ir::BasicBlock* basic_block); + + // Using the map built by AssignNewResultIds, for each instruction in + // |basic_block| use + // that map to substitute the IDs used by instructions (in the operands) with + // the new ids. + void RemapOperands(ir::BasicBlock* basic_block); + + // Copy the whole body of the loop, all blocks dominated by the |loop| header + // and not dominated by the |loop| merge. The copied body will be linked to by + // the old |loop| continue block and the new body will link to the |loop| + // header via the new continue block. |eliminate_conditions| is used to decide + // whether or not to fold all the condition blocks other than the last one. + void CopyBody(ir::Loop* loop, bool eliminate_conditions); + + // Copy a given |block_to_copy| in the |loop| and record the mapping of the + // old/new ids. |preserve_instructions| determines whether or not the method + // will modify (other than result_id) instructions which are copied. + void CopyBasicBlock(ir::Loop* loop, const ir::BasicBlock* block_to_copy, + bool preserve_instructions); + + // The actual implementation of the unroll step. Unrolls |loop| by given + // |factor| by copying the body by |factor| times. Also propagates the + // induction variable value throughout the copies. + void Unroll(ir::Loop* loop, size_t factor); + + // Fills the loop_blocks_inorder_ field with the ordered list of basic blocks + // as computed by the method ComputeLoopOrderedBlocks. + void ComputeLoopOrderedBlocks(ir::Loop* loop); + + // Adds the blocks_to_add_ to both the |loop| and to the parent of |loop| if + // the parent exists. + void AddBlocksToLoop(ir::Loop* loop) const; + + // After the partially unroll step the phi instructions in the header block + // will be in an illegal format. This function makes the phis legal by making + // the edge from the latch block come from the new latch block and the value + // to be the actual value of the phi at that point. + void LinkLastPhisToStart(ir::Loop* loop) const; + + // A pointer to the IRContext. Used to add/remove instructions and for usedef + // chains. + ir::IRContext* context_; + + // A reference the function the loop is within. + ir::Function& function_; + + // A list of basic blocks to be added to the loop at the end of an unroll + // step. + BasicBlockListTy blocks_to_add_; + + // List of instructions which are now dead and can be removed. + std::vector invalidated_instructions_; + + // Maintains the current state of the transform between calls to unroll. + LoopUnrollState state_; + + // An ordered list containing the loop basic blocks. + std::vector loop_blocks_inorder_; + + // The block containing the condition check which contains a conditional + // branch to the merge and continue block. + ir::BasicBlock* loop_condition_block_; + + // The induction variable of the loop. + ir::Instruction* loop_induction_variable_; + + // Phis used in the loop need to be remapped to use the actual result values + // and then be remapped at the end. + std::vector loop_phi_instructions_; + + // The number of loop iterations that the loop would preform pre-unroll. + size_t number_of_loop_iterations_; + + // The amount that the loop steps each iteration. + int64_t loop_step_value_; + + // The value the loop starts stepping from. + int64_t loop_init_value_; +}; + +/* + * Static helper functions. + */ + +// Retrieve the index of the OpPhi instruction |phi| which corresponds to the +// incoming |block| id. +static uint32_t GetPhiIndexFromLabel(const ir::BasicBlock* block, + const ir::Instruction* phi) { + for (uint32_t i = 1; i < phi->NumInOperands(); i += 2) { + if (block->id() == phi->GetSingleWordInOperand(i)) { + return i; + } + } + assert(false && "Could not find operand in instruction."); + return 0; +} + +void LoopUnrollerUtilsImpl::Init(ir::Loop* loop) { + loop_condition_block_ = loop->FindConditionBlock(); + + // When we reinit the second loop during PartiallyUnrollResidualFactor we need + // to use the cached value from the duplicate step as the dominator tree + // basded solution, loop->FindConditionBlock, requires all the nodes to be + // connected up with the correct branches. They won't be at this point. + if (!loop_condition_block_) { + loop_condition_block_ = state_.new_condition_block; + } + assert(loop_condition_block_); + + loop_induction_variable_ = loop->FindConditionVariable(loop_condition_block_); + assert(loop_induction_variable_); + + bool found = loop->FindNumberOfIterations( + loop_induction_variable_, &*loop_condition_block_->ctail(), + &number_of_loop_iterations_, &loop_step_value_, &loop_init_value_); + (void)found; // To silence unused variable warning on release builds. + assert(found); + + // Blocks are stored in an unordered set of ids in the loop class, we need to + // create the dominator ordered list. + ComputeLoopOrderedBlocks(loop); +} + +// This function is used to partially unroll the loop when the factor provided +// would normally lead to an illegal optimization. Instead of just unrolling the +// loop it creates two loops and unrolls one and adjusts the condition on the +// other. The end result being that the new loop pair iterates over the correct +// number of bodies. +void LoopUnrollerUtilsImpl::PartiallyUnrollResidualFactor(ir::Loop* loop, + size_t factor) { + std::unique_ptr new_label{new ir::Instruction( + context_, SpvOp::SpvOpLabel, 0, context_->TakeNextId(), {})}; + std::unique_ptr new_exit_bb{ + new ir::BasicBlock(std::move(new_label))}; + + // Save the id of the block before we move it. + uint32_t new_merge_id = new_exit_bb->id(); + + // Add the block the list of blocks to add, we want this merge block to be + // right at the start of the new blocks. + blocks_to_add_.push_back(std::move(new_exit_bb)); + ir::BasicBlock* new_exit_bb_raw = blocks_to_add_[0].get(); + ir::Instruction& original_conditional_branch = *loop_condition_block_->tail(); + // Duplicate the loop, providing access to the blocks of both loops. + // This is a naked new due to the VS2013 requirement of not having unique + // pointers in vectors, as it will be inserted into a vector with + // loop_descriptor.AddLoop. + ir::Loop* new_loop = new ir::Loop(*loop); + + // Clear the basic blocks of the new loop. + new_loop->ClearBlocks(); + + DuplicateLoop(loop, new_loop); + + // Add the blocks to the function. + AddBlocksToFunction(loop->GetMergeBlock()); + blocks_to_add_.clear(); + + // Create a new merge block for the first loop. + InstructionBuilder builder{context_, new_exit_bb_raw}; + // Make the first loop branch to the second. + builder.AddBranch(new_loop->GetHeaderBlock()->id()); + + loop_condition_block_ = state_.new_condition_block; + loop_induction_variable_ = state_.new_phi; + // Unroll the new loop by the factor with the usual -1 to account for the + // existing block iteration. + Unroll(new_loop, factor); + + LinkLastPhisToStart(new_loop); + AddBlocksToLoop(new_loop); + + // Add the new merge block to the back of the list of blocks to be added. It + // needs to be the last block added to maintain dominator order in the binary. + blocks_to_add_.push_back( + std::unique_ptr(new_loop->GetMergeBlock())); + + // Add the blocks to the function. + AddBlocksToFunction(loop->GetMergeBlock()); + + // Reset the usedef analysis. + context_->InvalidateAnalysesExceptFor( + ir::IRContext::Analysis::kAnalysisLoopAnalysis); + opt::analysis::DefUseManager* def_use_manager = context_->get_def_use_mgr(); + + // The loop condition. + ir::Instruction* condition_check = def_use_manager->GetDef( + original_conditional_branch.GetSingleWordOperand(0)); + + // This should have been checked by the LoopUtils::CanPerformUnroll function + // before entering this. + assert(loop->IsSupportedCondition(condition_check->opcode())); + + // We need to account for the initial body when calculating the remainder. + int64_t remainder = ir::Loop::GetResidualConditionValue( + condition_check->opcode(), loop_init_value_, loop_step_value_, + number_of_loop_iterations_, factor); + + assert(remainder > std::numeric_limits::min() && + remainder < std::numeric_limits::max()); + + ir::Instruction* new_constant = nullptr; + + // If the remainder is negative then we add a signed constant, otherwise just + // add an unsigned constant. + if (remainder < 0) { + new_constant = + builder.Add32BitSignedIntegerConstant(static_cast(remainder)); + } else { + new_constant = builder.Add32BitUnsignedIntegerConstant( + static_cast(remainder)); + } + + uint32_t constant_id = new_constant->result_id(); + + // Update the condition check. + condition_check->SetInOperand(1, {constant_id}); + + // Update the next phi node. The phi will have a constant value coming in from + // the preheader block. For the duplicated loop we need to update the constant + // to be the amount of iterations covered by the first loop and the incoming + // block to be the first loops new merge block. + std::vector new_inductions; + new_loop->GetInductionVariables(new_inductions); + + std::vector old_inductions; + loop->GetInductionVariables(old_inductions); + for (size_t index = 0; index < new_inductions.size(); ++index) { + ir::Instruction* new_induction = new_inductions[index]; + ir::Instruction* old_induction = old_inductions[index]; + // Get the index of the loop initalizer, the value coming in from the + // preheader. + uint32_t initalizer_index = + GetPhiIndexFromLabel(new_loop->GetPreHeaderBlock(), old_induction); + + // Replace the second loop initalizer with the phi from the first + new_induction->SetInOperand(initalizer_index - 1, + {old_induction->result_id()}); + new_induction->SetInOperand(initalizer_index, {new_merge_id}); + + // If the use of the first loop induction variable is outside of the loop + // then replace that use with the second loop induction variable. + uint32_t second_loop_induction = new_induction->result_id(); + auto replace_use_outside_of_loop = [loop, second_loop_induction]( + ir::Instruction* user, + uint32_t operand_index) { + if (!loop->IsInsideLoop(user)) { + user->SetOperand(operand_index, {second_loop_induction}); + } + }; + + context_->get_def_use_mgr()->ForEachUse(old_induction, + replace_use_outside_of_loop); + } + + context_->InvalidateAnalysesExceptFor( + ir::IRContext::Analysis::kAnalysisLoopAnalysis); + + context_->ReplaceAllUsesWith(loop->GetMergeBlock()->id(), new_merge_id); + + ir::LoopDescriptor& loop_descriptor = + *context_->GetLoopDescriptor(&function_); + + loop_descriptor.AddLoop(new_loop, loop->GetParent()); + + RemoveDeadInstructions(); +} + +// Mark this loop as DontUnroll as it will already be unrolled and it may not +// be safe to unroll a previously partially unrolled loop. +void LoopUnrollerUtilsImpl::MarkLoopControlAsDontUnroll(ir::Loop* loop) const { + ir::Instruction* loop_merge_inst = loop->GetHeaderBlock()->GetLoopMergeInst(); + assert(loop_merge_inst && + "Loop merge instruction could not be found after entering unroller " + "(should have exited before this)"); + loop_merge_inst->SetInOperand(kLoopControlIndex, + {kLoopControlDontUnrollIndex}); +} + +// Duplicate the |loop| body |factor| - 1 number of times while keeping the loop +// backedge intact. This will leave the loop with |factor| number of bodies +// after accounting for the initial body. +void LoopUnrollerUtilsImpl::Unroll(ir::Loop* loop, size_t factor) { + // If we unroll a loop partially it will not be safe to unroll it further. + // This is due to the current method of calculating the number of loop + // iterations. + MarkLoopControlAsDontUnroll(loop); + + std::vector inductions; + loop->GetInductionVariables(inductions); + state_ = LoopUnrollState{loop_induction_variable_, loop->GetLatchBlock(), + loop_condition_block_, std::move(inductions)}; + for (size_t i = 0; i < factor - 1; ++i) { + CopyBody(loop, true); + } +} + +void LoopUnrollerUtilsImpl::RemoveDeadInstructions() { + // Remove the dead instructions. + for (ir::Instruction* inst : invalidated_instructions_) { + context_->KillInst(inst); + } +} + +void LoopUnrollerUtilsImpl::ReplaceInductionUseWithFinalValue(ir::Loop* loop) { + context_->InvalidateAnalysesExceptFor( + ir::IRContext::Analysis::kAnalysisLoopAnalysis); + std::vector inductions; + loop->GetInductionVariables(inductions); + + for (size_t index = 0; index < inductions.size(); ++index) { + uint32_t trip_step_id = GetPhiDefID(state_.previous_phis_[index], + state_.previous_continue_block_->id()); + context_->ReplaceAllUsesWith(inductions[index]->result_id(), trip_step_id); + invalidated_instructions_.push_back(inductions[index]); + } +} + +// Fully unroll the loop by partially unrolling it by the number of loop +// iterations minus one for the body already accounted for. +void LoopUnrollerUtilsImpl::FullyUnroll(ir::Loop* loop) { + // We unroll the loop by number of iterations in the loop. + Unroll(loop, number_of_loop_iterations_); + + // The first condition block is preserved until now so it can be copied. + FoldConditionBlock(loop_condition_block_, 1); + + // Delete the OpLoopMerge and remove the backedge to the header. + CloseUnrolledLoop(loop); + + // Mark the loop for later deletion. This allows us to preserve the loop + // iterators but still disregard dead loops. + loop->MarkLoopForRemoval(); + + // If the loop has a parent add the new blocks to the parent. + if (loop->GetParent()) { + AddBlocksToLoop(loop->GetParent()); + } + + // Add the blocks to the function. + AddBlocksToFunction(loop->GetMergeBlock()); + + ReplaceInductionUseWithFinalValue(loop); + + RemoveDeadInstructions(); + // Invalidate all analyses. + context_->InvalidateAnalysesExceptFor( + ir::IRContext::Analysis::kAnalysisLoopAnalysis); +} + +// Copy a given basic block, give it a new result_id, and store the new block +// and the id mapping in the state. |preserve_instructions| is used to determine +// whether or not this function should edit instructions other than the +// |result_id|. +void LoopUnrollerUtilsImpl::CopyBasicBlock(ir::Loop* loop, + const ir::BasicBlock* itr, + bool preserve_instructions) { + // Clone the block exactly, including the IDs. + ir::BasicBlock* basic_block = itr->Clone(context_); + basic_block->SetParent(itr->GetParent()); + + // Assign each result a new unique ID and keep a mapping of the old ids to + // the new ones. + AssignNewResultIds(basic_block); + + // If this is the continue block we are copying. + if (itr == loop->GetLatchBlock()) { + // Make the OpLoopMerge point to this block for the continue. + if (!preserve_instructions) { + ir::Instruction* merge_inst = loop->GetHeaderBlock()->GetLoopMergeInst(); + merge_inst->SetInOperand(1, {basic_block->id()}); + } + + state_.new_continue_block = basic_block; + } + + // If this is the header block we are copying. + if (itr == loop->GetHeaderBlock()) { + state_.new_header_block = basic_block; + + if (!preserve_instructions) { + // Remove the loop merge instruction if it exists. + ir::Instruction* merge_inst = basic_block->GetLoopMergeInst(); + if (merge_inst) invalidated_instructions_.push_back(merge_inst); + } + } + + // If this is the condition block we are copying. + if (itr == loop_condition_block_) { + state_.new_condition_block = basic_block; + } + + // Add this block to the list of blocks to add to the function at the end of + // the unrolling process. + blocks_to_add_.push_back(std::unique_ptr(basic_block)); + + // Keep tracking the old block via a map. + state_.new_blocks[itr->id()] = basic_block; +} + +void LoopUnrollerUtilsImpl::CopyBody(ir::Loop* loop, + bool eliminate_conditions) { + // Copy each basic block in the loop, give them new ids, and save state + // information. + for (const ir::BasicBlock* itr : loop_blocks_inorder_) { + CopyBasicBlock(loop, itr, false); + } + + // Set the previous continue block to point to the new header. + ir::Instruction& continue_branch = *state_.previous_continue_block_->tail(); + continue_branch.SetInOperand(0, {state_.new_header_block->id()}); + + // As the algorithm copies the original loop blocks exactly, the tail of the + // latch block on iterations after the first one will be a branch to the new + // header and not the actual loop header. The last continue block in the loop + // should always be a backedge to the global header. + ir::Instruction& new_continue_branch = *state_.new_continue_block->tail(); + new_continue_branch.SetInOperand(0, {loop->GetHeaderBlock()->id()}); + + std::vector inductions; + loop->GetInductionVariables(inductions); + for (size_t index = 0; index < inductions.size(); ++index) { + ir::Instruction* master_copy = inductions[index]; + + assert(master_copy->result_id() != 0); + ir::Instruction* induction_clone = + state_.ids_to_new_inst[state_.new_inst[master_copy->result_id()]]; + + state_.new_phis_.push_back(induction_clone); + assert(induction_clone->result_id() != 0); + + if (!state_.previous_phis_.empty()) { + state_.new_inst[master_copy->result_id()] = GetPhiDefID( + state_.previous_phis_[index], state_.previous_continue_block_->id()); + } else { + // Do not replace the first phi block ids. + state_.new_inst[master_copy->result_id()] = master_copy->result_id(); + } + } + + if (eliminate_conditions && + state_.new_condition_block != loop_condition_block_) { + FoldConditionBlock(state_.new_condition_block, 1); + } + + // Only reference to the header block is the backedge in the latch block, + // don't change this. + state_.new_inst[loop->GetHeaderBlock()->id()] = loop->GetHeaderBlock()->id(); + + for (auto& pair : state_.new_blocks) { + RemapOperands(pair.second); + } + + for (ir::Instruction* dead_phi : state_.new_phis_) + invalidated_instructions_.push_back(dead_phi); + + // Swap the state so the new is now the previous. + state_.NextIterationState(); +} + +uint32_t LoopUnrollerUtilsImpl::GetPhiDefID(const ir::Instruction* phi, + uint32_t label) const { + for (uint32_t operand = 3; operand < phi->NumOperands(); operand += 2) { + if (phi->GetSingleWordOperand(operand) == label) { + return phi->GetSingleWordOperand(operand - 1); + } + } + assert(false && "Could not find a phi index matching the provided label"); + return 0; +} + +void LoopUnrollerUtilsImpl::FoldConditionBlock(ir::BasicBlock* condition_block, + uint32_t operand_label) { + // Remove the old conditional branch to the merge and continue blocks. + ir::Instruction& old_branch = *condition_block->tail(); + uint32_t new_target = old_branch.GetSingleWordOperand(operand_label); + + context_->KillInst(&old_branch); + // Add the new unconditional branch to the merge block. + InstructionBuilder builder{context_, condition_block}; + builder.AddBranch(new_target); +} + +void LoopUnrollerUtilsImpl::CloseUnrolledLoop(ir::Loop* loop) { + // Remove the OpLoopMerge instruction from the function. + ir::Instruction* merge_inst = loop->GetHeaderBlock()->GetLoopMergeInst(); + invalidated_instructions_.push_back(merge_inst); + + // Remove the final backedge to the header and make it point instead to the + // merge block. + state_.previous_continue_block_->tail()->SetInOperand( + 0, {loop->GetMergeBlock()->id()}); + + // Remove all induction variables as the phis will now be invalid. Replace all + // uses with the constant initializer value (all uses of phis will be in + // the first iteration with the subsequent phis already having been removed). + std::vector inductions; + loop->GetInductionVariables(inductions); + + // We can use the state instruction mechanism to replace all internal loop + // values within the first loop trip (as the subsequent ones will be updated + // by the copy function) with the value coming in from the preheader and then + // use context ReplaceAllUsesWith for the uses outside the loop with the final + // trip phi value. + state_.new_inst.clear(); + for (ir::Instruction* induction : inductions) { + uint32_t initalizer_id = + GetPhiDefID(induction, loop->GetPreHeaderBlock()->id()); + + state_.new_inst[induction->result_id()] = initalizer_id; + } + + for (ir::BasicBlock* block : loop_blocks_inorder_) { + RemapOperands(block); + } +} + +// Uses the first loop to create a copy of the loop with new IDs. +void LoopUnrollerUtilsImpl::DuplicateLoop(ir::Loop* old_loop, + ir::Loop* new_loop) { + std::vector new_block_order; + + // Copy every block in the old loop. + for (const ir::BasicBlock* itr : loop_blocks_inorder_) { + CopyBasicBlock(old_loop, itr, true); + new_block_order.push_back(blocks_to_add_.back().get()); + } + + // Clone the merge block, give it a new id and record it in the state. + ir::BasicBlock* new_merge = old_loop->GetMergeBlock()->Clone(context_); + new_merge->SetParent(old_loop->GetMergeBlock()->GetParent()); + AssignNewResultIds(new_merge); + state_.new_blocks[old_loop->GetMergeBlock()->id()] = new_merge; + + // Remap the operands of every instruction in the loop to point to the new + // copies. + for (auto& pair : state_.new_blocks) { + RemapOperands(pair.second); + } + + loop_blocks_inorder_ = std::move(new_block_order); + + AddBlocksToLoop(new_loop); + + new_loop->SetHeaderBlock(state_.new_header_block); + new_loop->SetLatchBlock(state_.new_continue_block); + new_loop->SetMergeBlock(new_merge); +} + +// Whenever the utility copies a block it stores it in a tempory buffer, this +// function adds the buffer into the ir::Function. The blocks will be inserted +// after the block |insert_point|. +void LoopUnrollerUtilsImpl::AddBlocksToFunction( + const ir::BasicBlock* insert_point) { + for (auto basic_block_iterator = function_.begin(); + basic_block_iterator != function_.end(); ++basic_block_iterator) { + if (basic_block_iterator->id() == insert_point->id()) { + basic_block_iterator.InsertBefore(&blocks_to_add_); + return; + } + } + + assert( + false && + "Could not add basic blocks to function as insert point was not found."); +} + +// Assign all result_ids in |basic_block| instructions to new IDs and preserve +// the mapping of new ids to old ones. +void LoopUnrollerUtilsImpl::AssignNewResultIds(ir::BasicBlock* basic_block) { + // Label instructions aren't covered by normal traversal of the + // instructions. + uint32_t new_label_id = context_->TakeNextId(); + + // Assign a new id to the label. + state_.new_inst[basic_block->GetLabelInst()->result_id()] = new_label_id; + basic_block->GetLabelInst()->SetResultId(new_label_id); + + for (ir::Instruction& inst : *basic_block) { + uint32_t old_id = inst.result_id(); + + // Ignore stores etc. + if (old_id == 0) { + continue; + } + + // Give the instruction a new id. + inst.SetResultId(context_->TakeNextId()); + + // Save the mapping of old_id -> new_id. + state_.new_inst[old_id] = inst.result_id(); + // Check if this instruction is the induction variable. + if (loop_induction_variable_->result_id() == old_id) { + // Save a pointer to the new copy of it. + state_.new_phi = &inst; + } + state_.ids_to_new_inst[inst.result_id()] = &inst; + } +} + +// For all instructions in |basic_block| check if the operands used are from a +// copied instruction and if so swap out the operand for the copy of it. +void LoopUnrollerUtilsImpl::RemapOperands(ir::BasicBlock* basic_block) { + for (ir::Instruction& inst : *basic_block) { + auto remap_operands_to_new_ids = [this](uint32_t* id) { + auto itr = state_.new_inst.find(*id); + + if (itr != state_.new_inst.end()) { + *id = itr->second; + } + }; + + inst.ForEachInId(remap_operands_to_new_ids); + } +} + +// Generate the ordered list of basic blocks in the |loop| and cache it for +// later use. +void LoopUnrollerUtilsImpl::ComputeLoopOrderedBlocks(ir::Loop* loop) { + loop_blocks_inorder_.clear(); + loop->ComputeLoopStructuredOrder(&loop_blocks_inorder_); +} + +// Adds the blocks_to_add_ to both the loop and to the parent. +void LoopUnrollerUtilsImpl::AddBlocksToLoop(ir::Loop* loop) const { + // Add the blocks to this loop. + for (auto& block_itr : blocks_to_add_) { + loop->AddBasicBlock(block_itr.get()); + } + + // Add the blocks to the parent as well. + if (loop->GetParent()) AddBlocksToLoop(loop->GetParent()); +} + +void LoopUnrollerUtilsImpl::LinkLastPhisToStart(ir::Loop* loop) const { + std::vector inductions; + loop->GetInductionVariables(inductions); + + for (size_t i = 0; i < inductions.size(); ++i) { + ir::Instruction* last_phi_in_block = state_.previous_phis_[i]; + + uint32_t phi_index = GetPhiIndexFromLabel(state_.previous_continue_block_, + last_phi_in_block); + uint32_t phi_variable = + last_phi_in_block->GetSingleWordInOperand(phi_index - 1); + uint32_t phi_label = last_phi_in_block->GetSingleWordInOperand(phi_index); + + ir::Instruction* phi = inductions[i]; + phi->SetInOperand(phi_index - 1, {phi_variable}); + phi->SetInOperand(phi_index, {phi_label}); + } +} + +// Duplicate the |loop| body |factor| number of times while keeping the loop +// backedge intact. +void LoopUnrollerUtilsImpl::PartiallyUnroll(ir::Loop* loop, size_t factor) { + Unroll(loop, factor); + LinkLastPhisToStart(loop); + AddBlocksToLoop(loop); + AddBlocksToFunction(loop->GetMergeBlock()); + RemoveDeadInstructions(); +} + +/* + * End LoopUtilsImpl. + */ + +} // namespace + +/* + * + * Begin Utils. + * + * */ + +bool LoopUtils::CanPerformUnroll() { + // The loop is expected to be in structured order. + if (!loop_->GetHeaderBlock()->GetMergeInst()) { + return false; + } + + // Find check the loop has a condition we can find and evaluate. + const ir::BasicBlock* condition = loop_->FindConditionBlock(); + if (!condition) return false; + + // Check that we can find and process the induction variable. + const ir::Instruction* induction = loop_->FindConditionVariable(condition); + if (!induction || induction->opcode() != SpvOpPhi) return false; + + // Check that we can find the number of loop iterations. + if (!loop_->FindNumberOfIterations(induction, &*condition->ctail(), nullptr)) + return false; + + // Make sure the continue block is a unconditional branch to the header + // block. + const ir::Instruction& branch = *loop_->GetLatchBlock()->ctail(); + bool branching_assumption = + branch.opcode() == SpvOpBranch && + branch.GetSingleWordInOperand(0) == loop_->GetHeaderBlock()->id(); + if (!branching_assumption) { + return false; + } + + std::vector inductions; + loop_->GetInductionVariables(inductions); + + // Ban breaks within the loop. + const std::vector& merge_block_preds = + context_->cfg()->preds(loop_->GetMergeBlock()->id()); + if (merge_block_preds.size() != 1) { + return false; + } + + // Ban continues within the loop. + const std::vector& continue_block_preds = + context_->cfg()->preds(loop_->GetLatchBlock()->id()); + if (continue_block_preds.size() != 1) { + return false; + } + + // Ban returns in the loop. + // Iterate over all the blocks within the loop and check that none of them + // exit the loop. + for (uint32_t label_id : loop_->GetBlocks()) { + const ir::BasicBlock* block = context_->cfg()->block(label_id); + if (block->ctail()->opcode() == SpvOp::SpvOpKill || + block->ctail()->opcode() == SpvOp::SpvOpReturn || + block->ctail()->opcode() == SpvOp::SpvOpReturnValue) { + return false; + } + } + // Can only unroll inner loops. + if (!loop_->AreAllChildrenMarkedForRemoval()) { + return false; + } + + return true; +} + +bool LoopUtils::PartiallyUnroll(size_t factor) { + if (factor == 1 || !CanPerformUnroll()) return false; + + // Create the unroller utility. + LoopUnrollerUtilsImpl unroller{context_, + loop_->GetHeaderBlock()->GetParent()}; + unroller.Init(loop_); + + // If the unrolling factor is larger than or the same size as the loop just + // fully unroll the loop. + if (factor >= unroller.GetLoopIterationCount()) { + unroller.FullyUnroll(loop_); + return true; + } + + // If the loop unrolling factor is an residual number of iterations we need to + // let run the loop for the residual part then let it branch into the unrolled + // remaining part. We add one when calucating the remainder to take into + // account the one iteration already in the loop. + if (unroller.GetLoopIterationCount() % factor != 0) { + unroller.PartiallyUnrollResidualFactor(loop_, factor); + } else { + unroller.PartiallyUnroll(loop_, factor); + } + + return true; +} + +bool LoopUtils::FullyUnroll() { + if (!CanPerformUnroll()) return false; + + std::vector inductions; + loop_->GetInductionVariables(inductions); + + LoopUnrollerUtilsImpl unroller{context_, + loop_->GetHeaderBlock()->GetParent()}; + + unroller.Init(loop_); + unroller.FullyUnroll(loop_); + + return true; +} + +void LoopUtils::Finalize() { + // Clean up the loop descriptor to preserve the analysis. + + ir::LoopDescriptor* LD = context_->GetLoopDescriptor(&function_); + LD->PostModificationCleanup(); +} + +/* + * + * Begin Pass. + * + */ + +Pass::Status LoopUnroller::Process(ir::IRContext* c) { + context_ = c; + bool changed = false; + for (ir::Function& f : *c->module()) { + ir::LoopDescriptor* LD = context_->GetLoopDescriptor(&f); + for (ir::Loop& loop : *LD) { + LoopUtils loop_utils{c, &loop}; + if (!loop.HasUnrollLoopControl() || !loop_utils.CanPerformUnroll()) { + continue; + } + + if (fully_unroll_) { + loop_utils.FullyUnroll(); + } else { + loop_utils.PartiallyUnroll(unroll_factor_); + } + changed = true; + } + LD->PostModificationCleanup(); + } + + return changed ? Status::SuccessWithChange : Status::SuccessWithoutChange; +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/loop_unroller.h b/3rdparty/spirv-tools/source/opt/loop_unroller.h new file mode 100644 index 000000000..caf0a8ed3 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/loop_unroller.h @@ -0,0 +1,41 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_OPT_LOOP_UNROLLER_H_ +#define SOURCE_OPT_LOOP_UNROLLER_H_ +#include "opt/pass.h" + +namespace spvtools { +namespace opt { + +class LoopUnroller : public Pass { + public: + LoopUnroller() : Pass(), fully_unroll_(true), unroll_factor_(0) {} + LoopUnroller(bool fully_unroll, int unroll_factor) + : Pass(), fully_unroll_(fully_unroll), unroll_factor_(unroll_factor) {} + + const char* name() const override { return "Loop unroller"; } + + Status Process(ir::IRContext* context) override; + + private: + ir::IRContext* context_; + bool fully_unroll_; + int unroll_factor_; +}; + +} // namespace opt +} // namespace spvtools + +#endif // SOURCE_OPT_LOOP_UNROLLER_H_ diff --git a/3rdparty/spirv-tools/source/opt/loop_unswitch_pass.cpp b/3rdparty/spirv-tools/source/opt/loop_unswitch_pass.cpp new file mode 100644 index 000000000..964e765b9 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/loop_unswitch_pass.cpp @@ -0,0 +1,906 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "loop_unswitch_pass.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "basic_block.h" +#include "dominator_tree.h" +#include "fold.h" +#include "function.h" +#include "instruction.h" +#include "ir_builder.h" +#include "ir_context.h" +#include "loop_descriptor.h" + +#include "loop_utils.h" + +namespace spvtools { +namespace opt { +namespace { + +static const uint32_t kTypePointerStorageClassInIdx = 0; +static const uint32_t kBranchCondTrueLabIdInIdx = 1; +static const uint32_t kBranchCondFalseLabIdInIdx = 2; + +} // anonymous namespace + +namespace { + +// This class handle the unswitch procedure for a given loop. +// The unswitch will not happen if: +// - The loop has any instruction that will prevent it; +// - The loop invariant condition is not uniform. +class LoopUnswitch { + public: + LoopUnswitch(ir::IRContext* context, ir::Function* function, ir::Loop* loop, + ir::LoopDescriptor* loop_desc) + : function_(function), + loop_(loop), + loop_desc_(*loop_desc), + context_(context), + switch_block_(nullptr) {} + + // Returns true if the loop can be unswitched. + // Can be unswitch if: + // - The loop has no instructions that prevents it (such as barrier); + // - The loop has one conditional branch or switch that do not depends on the + // loop; + // - The loop invariant condition is uniform; + bool CanUnswitchLoop() { + if (switch_block_) return true; + if (loop_->IsSafeToClone()) return false; + + ir::CFG& cfg = *context_->cfg(); + + for (uint32_t bb_id : loop_->GetBlocks()) { + ir::BasicBlock* bb = cfg.block(bb_id); + if (bb->terminator()->IsBranch() && + bb->terminator()->opcode() != SpvOpBranch) { + if (IsConditionLoopInvariant(bb->terminator())) { + switch_block_ = bb; + break; + } + } + } + + return switch_block_; + } + + // Return the iterator to the basic block |bb|. + ir::Function::iterator FindBasicBlockPosition(ir::BasicBlock* bb_to_find) { + ir::Function::iterator it = function_->FindBlock(bb_to_find->id()); + assert(it != function_->end() && "Basic Block not found"); + return it; + } + + // Creates a new basic block and insert it into the function |fn| at the + // position |ip|. This function preserves the def/use and instr to block + // managers. + ir::BasicBlock* CreateBasicBlock(ir::Function::iterator ip) { + analysis::DefUseManager* def_use_mgr = context_->get_def_use_mgr(); + + ir::BasicBlock* bb = &*ip.InsertBefore(std::unique_ptr( + new ir::BasicBlock(std::unique_ptr(new ir::Instruction( + context_, SpvOpLabel, 0, context_->TakeNextId(), {}))))); + bb->SetParent(function_); + def_use_mgr->AnalyzeInstDef(bb->GetLabelInst()); + context_->set_instr_block(bb->GetLabelInst(), bb); + + return bb; + } + + // Unswitches |loop_|. + void PerformUnswitch() { + assert(CanUnswitchLoop() && + "Cannot unswitch if there is not constant condition"); + assert(loop_->GetPreHeaderBlock() && "This loop has no pre-header block"); + assert(loop_->IsLCSSA() && "This loop is not in LCSSA form"); + + ir::CFG& cfg = *context_->cfg(); + DominatorTree* dom_tree = + &context_->GetDominatorAnalysis(function_, *context_->cfg()) + ->GetDomTree(); + analysis::DefUseManager* def_use_mgr = context_->get_def_use_mgr(); + LoopUtils loop_utils(context_, loop_); + + ////////////////////////////////////////////////////////////////////////////// + // Step 1: Create the if merge block for structured modules. + // To do so, the |loop_| merge block will become the if's one and we + // create a merge for the loop. This will limit the amount of duplicated + // code the structured control flow imposes. + // For non structured program, the new loop will be connected to + // the old loop's exit blocks. + ////////////////////////////////////////////////////////////////////////////// + + // Get the merge block if it exists. + ir::BasicBlock* if_merge_block = loop_->GetMergeBlock(); + // The merge block is only created if the loop has a unique exit block. We + // have this guarantee for structured loops, for compute loop it will + // trivially help maintain both a structured-like form and LCSAA. + ir::BasicBlock* loop_merge_block = + if_merge_block + ? CreateBasicBlock(FindBasicBlockPosition(if_merge_block)) + : nullptr; + if (loop_merge_block) { + // Add the instruction and update managers. + opt::InstructionBuilder builder( + context_, loop_merge_block, + ir::IRContext::kAnalysisDefUse | + ir::IRContext::kAnalysisInstrToBlockMapping); + builder.AddBranch(if_merge_block->id()); + builder.SetInsertPoint(&*loop_merge_block->begin()); + cfg.RegisterBlock(loop_merge_block); + def_use_mgr->AnalyzeInstDef(loop_merge_block->GetLabelInst()); + // Update CFG. + if_merge_block->ForEachPhiInst( + [loop_merge_block, &builder, this](ir::Instruction* phi) { + ir::Instruction* cloned = phi->Clone(context_); + builder.AddInstruction(std::unique_ptr(cloned)); + phi->SetInOperand(0, {cloned->result_id()}); + phi->SetInOperand(1, {loop_merge_block->id()}); + for (uint32_t j = phi->NumInOperands() - 1; j > 1; j--) + phi->RemoveInOperand(j); + }); + // Copy the predecessor list (will get invalidated otherwise). + std::vector preds = cfg.preds(if_merge_block->id()); + for (uint32_t pid : preds) { + if (pid == loop_merge_block->id()) continue; + ir::BasicBlock* p_bb = cfg.block(pid); + p_bb->ForEachSuccessorLabel( + [if_merge_block, loop_merge_block](uint32_t* id) { + if (*id == if_merge_block->id()) *id = loop_merge_block->id(); + }); + cfg.AddEdge(pid, loop_merge_block->id()); + } + cfg.RemoveNonExistingEdges(if_merge_block->id()); + // Update loop descriptor. + if (ir::Loop* ploop = loop_->GetParent()) { + ploop->AddBasicBlock(loop_merge_block); + loop_desc_.SetBasicBlockToLoop(loop_merge_block->id(), ploop); + } + + // Update the dominator tree. + DominatorTreeNode* loop_merge_dtn = + dom_tree->GetOrInsertNode(loop_merge_block); + DominatorTreeNode* if_merge_block_dtn = + dom_tree->GetOrInsertNode(if_merge_block); + loop_merge_dtn->parent_ = if_merge_block_dtn->parent_; + loop_merge_dtn->children_.push_back(if_merge_block_dtn); + loop_merge_dtn->parent_->children_.push_back(loop_merge_dtn); + if_merge_block_dtn->parent_->children_.erase(std::find( + if_merge_block_dtn->parent_->children_.begin(), + if_merge_block_dtn->parent_->children_.end(), if_merge_block_dtn)); + + loop_->SetMergeBlock(loop_merge_block); + } + + //////////////////////////////////////////////////////////////////////////// + // Step 2: Build a new preheader for |loop_|, use the old one + // for the constant branch. + //////////////////////////////////////////////////////////////////////////// + + ir::BasicBlock* if_block = loop_->GetPreHeaderBlock(); + // If this preheader is the parent loop header, + // we need to create a dedicated block for the if. + ir::BasicBlock* loop_pre_header = + CreateBasicBlock(++FindBasicBlockPosition(if_block)); + opt::InstructionBuilder(context_, loop_pre_header, + ir::IRContext::kAnalysisDefUse | + ir::IRContext::kAnalysisInstrToBlockMapping) + .AddBranch(loop_->GetHeaderBlock()->id()); + + if_block->tail()->SetInOperand(0, {loop_pre_header->id()}); + + // Update loop descriptor. + if (ir::Loop* ploop = loop_desc_[if_block]) { + ploop->AddBasicBlock(loop_pre_header); + loop_desc_.SetBasicBlockToLoop(loop_pre_header->id(), ploop); + } + + // Update the CFG. + cfg.RegisterBlock(loop_pre_header); + def_use_mgr->AnalyzeInstDef(loop_pre_header->GetLabelInst()); + cfg.AddEdge(if_block->id(), loop_pre_header->id()); + cfg.RemoveNonExistingEdges(loop_->GetHeaderBlock()->id()); + + loop_->GetHeaderBlock()->ForEachPhiInst( + [loop_pre_header, if_block](ir::Instruction* phi) { + phi->ForEachInId([loop_pre_header, if_block](uint32_t* id) { + if (*id == if_block->id()) { + *id = loop_pre_header->id(); + } + }); + }); + loop_->SetPreHeaderBlock(loop_pre_header); + + // Update the dominator tree. + DominatorTreeNode* loop_pre_header_dtn = + dom_tree->GetOrInsertNode(loop_pre_header); + DominatorTreeNode* if_block_dtn = dom_tree->GetTreeNode(if_block); + loop_pre_header_dtn->parent_ = if_block_dtn; + assert( + if_block_dtn->children_.size() == 1 && + "A loop preheader should only have the header block as a child in the " + "dominator tree"); + loop_pre_header_dtn->children_.push_back(if_block_dtn->children_[0]); + if_block_dtn->children_.clear(); + if_block_dtn->children_.push_back(loop_pre_header_dtn); + + // Make domination queries valid. + dom_tree->ResetDFNumbering(); + + // Compute an ordered list of basic block to clone: loop blocks + pre-header + // + merge block. + loop_->ComputeLoopStructuredOrder(&ordered_loop_blocks_, true, true); + + ///////////////////////////// + // Do the actual unswitch: // + // - Clone the loop // + // - Connect exits // + // - Specialize the loop // + ///////////////////////////// + + ir::Instruction* iv_condition = &*switch_block_->tail(); + SpvOp iv_opcode = iv_condition->opcode(); + ir::Instruction* condition = + def_use_mgr->GetDef(iv_condition->GetOperand(0).words[0]); + + analysis::ConstantManager* cst_mgr = context_->get_constant_mgr(); + const analysis::Type* cond_type = + context_->get_type_mgr()->GetType(condition->type_id()); + + // Build the list of value for which we need to clone and specialize the + // loop. + std::vector> constant_branch; + // Special case for the original loop + ir::Instruction* original_loop_constant_value; + ir::BasicBlock* original_loop_target; + if (iv_opcode == SpvOpBranchConditional) { + constant_branch.emplace_back( + cst_mgr->GetDefiningInstruction(cst_mgr->GetConstant(cond_type, {0})), + nullptr); + original_loop_constant_value = + cst_mgr->GetDefiningInstruction(cst_mgr->GetConstant(cond_type, {1})); + } else { + // We are looking to take the default branch, so we can't provide a + // specific value. + original_loop_constant_value = nullptr; + for (uint32_t i = 2; i < iv_condition->NumInOperands(); i += 2) { + constant_branch.emplace_back( + cst_mgr->GetDefiningInstruction(cst_mgr->GetConstant( + cond_type, iv_condition->GetInOperand(i).words)), + nullptr); + } + } + + // Get the loop landing pads. + std::unordered_set if_merging_blocks; + std::function is_from_original_loop; + if (loop_->GetHeaderBlock()->GetLoopMergeInst()) { + if_merging_blocks.insert(if_merge_block->id()); + is_from_original_loop = [this](uint32_t id) { + return loop_->IsInsideLoop(id) || loop_->GetMergeBlock()->id() == id; + }; + } else { + loop_->GetExitBlocks(&if_merging_blocks); + is_from_original_loop = [this](uint32_t id) { + return loop_->IsInsideLoop(id); + }; + } + + for (auto& specialisation_pair : constant_branch) { + ir::Instruction* specialisation_value = specialisation_pair.first; + ////////////////////////////////////////////////////////// + // Step 3: Duplicate |loop_|. + ////////////////////////////////////////////////////////// + LoopUtils::LoopCloningResult clone_result; + + ir::Loop* cloned_loop = + loop_utils.CloneLoop(&clone_result, ordered_loop_blocks_); + specialisation_pair.second = cloned_loop->GetPreHeaderBlock(); + + //////////////////////////////////// + // Step 4: Specialize the loop. // + //////////////////////////////////// + + { + std::unordered_set dead_blocks; + std::unordered_set unreachable_merges; + SimplifyLoop( + ir::make_range( + ir::UptrVectorIterator( + &clone_result.cloned_bb_, clone_result.cloned_bb_.begin()), + ir::UptrVectorIterator( + &clone_result.cloned_bb_, clone_result.cloned_bb_.end())), + cloned_loop, condition, specialisation_value, &dead_blocks); + + // We tagged dead blocks, create the loop before we invalidate any basic + // block. + cloned_loop = + CleanLoopNest(cloned_loop, dead_blocks, &unreachable_merges); + CleanUpCFG( + ir::UptrVectorIterator( + &clone_result.cloned_bb_, clone_result.cloned_bb_.begin()), + dead_blocks, unreachable_merges); + + /////////////////////////////////////////////////////////// + // Step 5: Connect convergent edges to the landing pads. // + /////////////////////////////////////////////////////////// + + for (uint32_t merge_bb_id : if_merging_blocks) { + ir::BasicBlock* merge = context_->cfg()->block(merge_bb_id); + // We are in LCSSA so we only care about phi instructions. + merge->ForEachPhiInst([is_from_original_loop, &dead_blocks, + &clone_result](ir::Instruction* phi) { + uint32_t num_in_operands = phi->NumInOperands(); + for (uint32_t i = 0; i < num_in_operands; i += 2) { + uint32_t pred = phi->GetSingleWordInOperand(i + 1); + if (is_from_original_loop(pred)) { + pred = clone_result.value_map_.at(pred); + if (!dead_blocks.count(pred)) { + uint32_t incoming_value_id = phi->GetSingleWordInOperand(i); + // Not all the incoming value are coming from the loop. + ValueMapTy::iterator new_value = + clone_result.value_map_.find(incoming_value_id); + if (new_value != clone_result.value_map_.end()) { + incoming_value_id = new_value->second; + } + phi->AddOperand({SPV_OPERAND_TYPE_ID, {incoming_value_id}}); + phi->AddOperand({SPV_OPERAND_TYPE_ID, {pred}}); + } + } + } + }); + } + } + function_->AddBasicBlocks(clone_result.cloned_bb_.begin(), + clone_result.cloned_bb_.end(), + ++FindBasicBlockPosition(if_block)); + } + + // Same as above but specialize the existing loop + { + std::unordered_set dead_blocks; + std::unordered_set unreachable_merges; + SimplifyLoop(ir::make_range(function_->begin(), function_->end()), loop_, + condition, original_loop_constant_value, &dead_blocks); + + for (uint32_t merge_bb_id : if_merging_blocks) { + ir::BasicBlock* merge = context_->cfg()->block(merge_bb_id); + // LCSSA, so we only care about phi instructions. + // If we the phi is reduced to a single incoming branch, do not + // propagate it to preserve LCSSA. + PatchPhis(merge, dead_blocks, true); + } + if (if_merge_block) { + bool has_live_pred = false; + for (uint32_t pid : cfg.preds(if_merge_block->id())) { + if (!dead_blocks.count(pid)) { + has_live_pred = true; + break; + } + } + if (!has_live_pred) unreachable_merges.insert(if_merge_block->id()); + } + original_loop_target = loop_->GetPreHeaderBlock(); + // We tagged dead blocks, prune the loop descriptor from any dead loops. + // After this call, |loop_| can be nullptr (i.e. the unswitch killed this + // loop). + loop_ = CleanLoopNest(loop_, dead_blocks, &unreachable_merges); + + CleanUpCFG(function_->begin(), dead_blocks, unreachable_merges); + } + + ///////////////////////////////////// + // Finally: connect the new loops. // + ///////////////////////////////////// + + // Delete the old jump + context_->KillInst(&*if_block->tail()); + opt::InstructionBuilder builder(context_, if_block); + if (iv_opcode == SpvOpBranchConditional) { + assert(constant_branch.size() == 1); + builder.AddConditionalBranch( + condition->result_id(), original_loop_target->id(), + constant_branch[0].second->id(), + if_merge_block ? if_merge_block->id() : kInvalidId); + } else { + std::vector, uint32_t>> targets; + for (auto& t : constant_branch) { + targets.emplace_back(t.first->GetInOperand(0).words, t.second->id()); + } + + builder.AddSwitch(condition->result_id(), original_loop_target->id(), + targets, + if_merge_block ? if_merge_block->id() : kInvalidId); + } + + switch_block_ = nullptr; + ordered_loop_blocks_.clear(); + + context_->InvalidateAnalysesExceptFor( + ir::IRContext::Analysis::kAnalysisLoopAnalysis); + } + + // Returns true if the unswitch killed the original |loop_|. + bool WasLoopKilled() const { return loop_ == nullptr; } + + private: + using ValueMapTy = std::unordered_map; + using BlockMapTy = std::unordered_map; + + ir::Function* function_; + ir::Loop* loop_; + ir::LoopDescriptor& loop_desc_; + ir::IRContext* context_; + + ir::BasicBlock* switch_block_; + // Map between instructions and if they are dynamically uniform. + std::unordered_map dynamically_uniform_; + // The loop basic blocks in structured order. + std::vector ordered_loop_blocks_; + + // Returns the next usable id for the context. + uint32_t TakeNextId() { return context_->TakeNextId(); } + + // Patches |bb|'s phi instruction by removing incoming value from unexisting + // or tagged as dead branches. + void PatchPhis(ir::BasicBlock* bb, + const std::unordered_set& dead_blocks, + bool preserve_phi) { + ir::CFG& cfg = *context_->cfg(); + + std::vector phi_to_kill; + const std::vector& bb_preds = cfg.preds(bb->id()); + auto is_branch_dead = [&bb_preds, &dead_blocks](uint32_t id) { + return dead_blocks.count(id) || + std::find(bb_preds.begin(), bb_preds.end(), id) == bb_preds.end(); + }; + bb->ForEachPhiInst([&phi_to_kill, &is_branch_dead, preserve_phi, + this](ir::Instruction* insn) { + uint32_t i = 0; + while (i < insn->NumInOperands()) { + uint32_t incoming_id = insn->GetSingleWordInOperand(i + 1); + if (is_branch_dead(incoming_id)) { + // Remove the incoming block id operand. + insn->RemoveInOperand(i + 1); + // Remove the definition id operand. + insn->RemoveInOperand(i); + continue; + } + i += 2; + } + // If there is only 1 remaining edge, propagate the value and + // kill the instruction. + if (insn->NumInOperands() == 2 && !preserve_phi) { + phi_to_kill.push_back(insn); + context_->ReplaceAllUsesWith(insn->result_id(), + insn->GetSingleWordInOperand(0)); + } + }); + for (ir::Instruction* insn : phi_to_kill) { + context_->KillInst(insn); + } + } + + // Removes any block that is tagged as dead, if the block is in + // |unreachable_merges| then all block's instructions are replaced by a + // OpUnreachable. + void CleanUpCFG(ir::UptrVectorIterator bb_it, + const std::unordered_set& dead_blocks, + const std::unordered_set& unreachable_merges) { + ir::CFG& cfg = *context_->cfg(); + + while (bb_it != bb_it.End()) { + ir::BasicBlock& bb = *bb_it; + + if (unreachable_merges.count(bb.id())) { + if (bb.begin() != bb.tail() || + bb.terminator()->opcode() != SpvOpUnreachable) { + // Make unreachable, but leave the label. + bb.KillAllInsts(false); + opt::InstructionBuilder(context_, &bb).AddUnreachable(); + cfg.RemoveNonExistingEdges(bb.id()); + } + ++bb_it; + } else if (dead_blocks.count(bb.id())) { + cfg.ForgetBlock(&bb); + // Kill this block. + bb.KillAllInsts(true); + bb_it = bb_it.Erase(); + } else { + cfg.RemoveNonExistingEdges(bb.id()); + ++bb_it; + } + } + } + + // Return true if |c_inst| is a Boolean constant and set |cond_val| with the + // value that |c_inst| + bool GetConstCondition(const ir::Instruction* c_inst, bool* cond_val) { + bool cond_is_const; + switch (c_inst->opcode()) { + case SpvOpConstantFalse: { + *cond_val = false; + cond_is_const = true; + } break; + case SpvOpConstantTrue: { + *cond_val = true; + cond_is_const = true; + } break; + default: { cond_is_const = false; } break; + } + return cond_is_const; + } + + // Simplifies |loop| assuming the instruction |to_version_insn| takes the + // value |cst_value|. |block_range| is an iterator range returning the loop + // basic blocks in a structured order (dominator first). + // The function will ignore basic blocks returned by |block_range| if they + // does not belong to the loop. + // The set |dead_blocks| will contain all the dead basic blocks. + // + // Requirements: + // - |loop| must be in the LCSSA form; + // - |cst_value| must be constant or null (to represent the default target + // of an OpSwitch). + void SimplifyLoop( + ir::IteratorRange> block_range, + ir::Loop* loop, ir::Instruction* to_version_insn, + ir::Instruction* cst_value, std::unordered_set* dead_blocks) { + ir::CFG& cfg = *context_->cfg(); + analysis::DefUseManager* def_use_mgr = context_->get_def_use_mgr(); + + std::function ignore_node; + ignore_node = [loop](uint32_t bb_id) { return !loop->IsInsideLoop(bb_id); }; + + std::vector> use_list; + def_use_mgr->ForEachUse( + to_version_insn, [&use_list, &ignore_node, this]( + ir::Instruction* inst, uint32_t operand_index) { + ir::BasicBlock* bb = context_->get_instr_block(inst); + + if (!bb || ignore_node(bb->id())) { + // Out of the loop, the specialization does not apply any more. + return; + } + use_list.emplace_back(inst, operand_index); + }); + + // First pass: inject the specialized value into the loop (and only the + // loop). + for (auto use : use_list) { + ir::Instruction* inst = use.first; + uint32_t operand_index = use.second; + ir::BasicBlock* bb = context_->get_instr_block(inst); + + // If it is not a branch, simply inject the value. + if (!inst->IsBranch()) { + // To also handle switch, cst_value can be nullptr: this case + // means that we are looking to branch to the default target of + // the switch. We don't actually know its value so we don't touch + // it if it not a switch. + if (cst_value) { + inst->SetOperand(operand_index, {cst_value->result_id()}); + def_use_mgr->AnalyzeInstUse(inst); + } + } + + // The user is a branch, kill dead branches. + uint32_t live_target = 0; + std::unordered_set dead_branches; + switch (inst->opcode()) { + case SpvOpBranchConditional: { + assert(cst_value && "No constant value to specialize !"); + bool branch_cond = false; + if (GetConstCondition(cst_value, &branch_cond)) { + uint32_t true_label = + inst->GetSingleWordInOperand(kBranchCondTrueLabIdInIdx); + uint32_t false_label = + inst->GetSingleWordInOperand(kBranchCondFalseLabIdInIdx); + live_target = branch_cond ? true_label : false_label; + uint32_t dead_target = !branch_cond ? true_label : false_label; + cfg.RemoveEdge(bb->id(), dead_target); + } + break; + } + case SpvOpSwitch: { + live_target = inst->GetSingleWordInOperand(1); + if (cst_value) { + if (!cst_value->IsConstant()) break; + const ir::Operand& cst = cst_value->GetInOperand(0); + for (uint32_t i = 2; i < inst->NumInOperands(); i += 2) { + const ir::Operand& literal = inst->GetInOperand(i); + if (literal == cst) { + live_target = inst->GetSingleWordInOperand(i + 1); + break; + } + } + } + for (uint32_t i = 1; i < inst->NumInOperands(); i += 2) { + uint32_t id = inst->GetSingleWordInOperand(i); + if (id != live_target) { + cfg.RemoveEdge(bb->id(), id); + } + } + } + default: + break; + } + if (live_target != 0) { + // Check for the presence of the merge block. + if (ir::Instruction* merge = bb->GetMergeInst()) + context_->KillInst(merge); + context_->KillInst(&*bb->tail()); + opt::InstructionBuilder builder( + context_, bb, + ir::IRContext::kAnalysisDefUse | + ir::IRContext::kAnalysisInstrToBlockMapping); + builder.AddBranch(live_target); + } + } + + // Go through the loop basic block and tag all blocks that are obviously + // dead. + std::unordered_set visited; + for (ir::BasicBlock& bb : block_range) { + if (ignore_node(bb.id())) continue; + visited.insert(bb.id()); + + // Check if this block is dead, if so tag it as dead otherwise patch phi + // instructions. + bool has_live_pred = false; + for (uint32_t pid : cfg.preds(bb.id())) { + if (!dead_blocks->count(pid)) { + has_live_pred = true; + break; + } + } + if (!has_live_pred) { + dead_blocks->insert(bb.id()); + const ir::BasicBlock& cbb = bb; + // Patch the phis for any back-edge. + cbb.ForEachSuccessorLabel( + [dead_blocks, &visited, &cfg, this](uint32_t id) { + if (!visited.count(id) || dead_blocks->count(id)) return; + ir::BasicBlock* succ = cfg.block(id); + PatchPhis(succ, *dead_blocks, false); + }); + continue; + } + // Update the phi instructions, some incoming branch have/will disappear. + PatchPhis(&bb, *dead_blocks, /* preserve_phi = */ false); + } + } + + // Returns true if the header is not reachable or tagged as dead or if we + // never loop back. + bool IsLoopDead(ir::BasicBlock* header, ir::BasicBlock* latch, + const std::unordered_set& dead_blocks) { + if (!header || dead_blocks.count(header->id())) return true; + if (!latch || dead_blocks.count(latch->id())) return true; + for (uint32_t pid : context_->cfg()->preds(header->id())) { + if (!dead_blocks.count(pid)) { + // Seems reachable. + return false; + } + } + return true; + } + + // Cleans the loop nest under |loop| and reflect changes to the loop + // descriptor. This will kill all descriptors that represent dead loops. + // If |loop_| is killed, it will be set to nullptr. + // Any merge blocks that become unreachable will be added to + // |unreachable_merges|. + // The function returns the pointer to |loop| or nullptr if the loop was + // killed. + ir::Loop* CleanLoopNest(ir::Loop* loop, + const std::unordered_set& dead_blocks, + std::unordered_set* unreachable_merges) { + // This represent the pair of dead loop and nearest alive parent (nullptr if + // no parent). + std::unordered_map dead_loops; + auto get_parent = [&dead_loops](ir::Loop* l) -> ir::Loop* { + std::unordered_map::iterator it = + dead_loops.find(l); + if (it != dead_loops.end()) return it->second; + return nullptr; + }; + + bool is_main_loop_dead = + IsLoopDead(loop->GetHeaderBlock(), loop->GetLatchBlock(), dead_blocks); + if (is_main_loop_dead) { + if (ir::Instruction* merge = loop->GetHeaderBlock()->GetLoopMergeInst()) { + context_->KillInst(merge); + } + dead_loops[loop] = loop->GetParent(); + } else + dead_loops[loop] = loop; + // For each loop, check if we killed it. If we did, find a suitable parent + // for its children. + for (ir::Loop& sub_loop : + ir::make_range(++opt::TreeDFIterator(loop), + opt::TreeDFIterator())) { + if (IsLoopDead(sub_loop.GetHeaderBlock(), sub_loop.GetLatchBlock(), + dead_blocks)) { + if (ir::Instruction* merge = + sub_loop.GetHeaderBlock()->GetLoopMergeInst()) { + context_->KillInst(merge); + } + dead_loops[&sub_loop] = get_parent(&sub_loop); + } else { + // The loop is alive, check if its merge block is dead, if it is, tag it + // as required. + if (sub_loop.GetMergeBlock()) { + uint32_t merge_id = sub_loop.GetMergeBlock()->id(); + if (dead_blocks.count(merge_id)) { + unreachable_merges->insert(sub_loop.GetMergeBlock()->id()); + } + } + } + } + if (!is_main_loop_dead) dead_loops.erase(loop); + + // Remove dead blocks from live loops. + for (uint32_t bb_id : dead_blocks) { + ir::Loop* l = loop_desc_[bb_id]; + if (l) { + l->RemoveBasicBlock(bb_id); + loop_desc_.ForgetBasicBlock(bb_id); + } + } + + std::for_each( + dead_loops.begin(), dead_loops.end(), + [&loop, this]( + std::unordered_map::iterator::reference it) { + if (it.first == loop) loop = nullptr; + loop_desc_.RemoveLoop(it.first); + }); + + return loop; + } + + // Returns true if |var| is dynamically uniform. + // Note: this is currently approximated as uniform. + bool IsDynamicallyUniform(ir::Instruction* var, const ir::BasicBlock* entry, + const DominatorTree& post_dom_tree) { + assert(post_dom_tree.IsPostDominator()); + analysis::DefUseManager* def_use_mgr = context_->get_def_use_mgr(); + + auto it = dynamically_uniform_.find(var->result_id()); + + if (it != dynamically_uniform_.end()) return it->second; + + analysis::DecorationManager* dec_mgr = context_->get_decoration_mgr(); + + bool& is_uniform = dynamically_uniform_[var->result_id()]; + is_uniform = false; + + dec_mgr->WhileEachDecoration(var->result_id(), SpvDecorationUniform, + [&is_uniform](const ir::Instruction&) { + is_uniform = true; + return false; + }); + if (is_uniform) { + return is_uniform; + } + + ir::BasicBlock* parent = context_->get_instr_block(var); + if (!parent) { + return is_uniform = true; + } + + if (!post_dom_tree.Dominates(parent->id(), entry->id())) { + return is_uniform = false; + } + if (var->opcode() == SpvOpLoad) { + const uint32_t PtrTypeId = + def_use_mgr->GetDef(var->GetSingleWordInOperand(0))->type_id(); + const ir::Instruction* PtrTypeInst = def_use_mgr->GetDef(PtrTypeId); + uint32_t storage_class = + PtrTypeInst->GetSingleWordInOperand(kTypePointerStorageClassInIdx); + if (storage_class != SpvStorageClassUniform && + storage_class != SpvStorageClassUniformConstant) { + return is_uniform = false; + } + } else { + if (!context_->IsCombinatorInstruction(var)) { + return is_uniform = false; + } + } + + return is_uniform = var->WhileEachInId([entry, &post_dom_tree, + this](const uint32_t* id) { + return IsDynamicallyUniform(context_->get_def_use_mgr()->GetDef(*id), + entry, post_dom_tree); + }); + } + + // Returns true if |insn| is constant and dynamically uniform within the loop. + bool IsConditionLoopInvariant(ir::Instruction* insn) { + assert(insn->IsBranch()); + assert(insn->opcode() != SpvOpBranch); + analysis::DefUseManager* def_use_mgr = context_->get_def_use_mgr(); + + ir::Instruction* condition = + def_use_mgr->GetDef(insn->GetOperand(0).words[0]); + return !loop_->IsInsideLoop(condition) && + IsDynamicallyUniform( + condition, function_->entry().get(), + context_->GetPostDominatorAnalysis(function_, *context_->cfg()) + ->GetDomTree()); + } +}; + +} // namespace + +Pass::Status LoopUnswitchPass::Process(ir::IRContext* c) { + InitializeProcessing(c); + + bool modified = false; + ir::Module* module = c->module(); + + // Process each function in the module + for (ir::Function& f : *module) { + modified |= ProcessFunction(&f); + } + + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; +} + +bool LoopUnswitchPass::ProcessFunction(ir::Function* f) { + bool modified = false; + std::unordered_set processed_loop; + + ir::LoopDescriptor& loop_descriptor = *context()->GetLoopDescriptor(f); + + bool loop_changed = true; + while (loop_changed) { + loop_changed = false; + for (ir::Loop& loop : + ir::make_range(++opt::TreeDFIterator( + loop_descriptor.GetDummyRootLoop()), + opt::TreeDFIterator())) { + if (processed_loop.count(&loop)) continue; + processed_loop.insert(&loop); + + LoopUnswitch unswitcher(context(), f, &loop, &loop_descriptor); + while (!unswitcher.WasLoopKilled() && unswitcher.CanUnswitchLoop()) { + if (!loop.IsLCSSA()) { + LoopUtils(context(), &loop).MakeLoopClosedSSA(); + } + modified = true; + loop_changed = true; + unswitcher.PerformUnswitch(); + } + if (loop_changed) break; + } + } + + return modified; +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/loop_unswitch_pass.h b/3rdparty/spirv-tools/source/opt/loop_unswitch_pass.h new file mode 100644 index 000000000..dbe581479 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/loop_unswitch_pass.h @@ -0,0 +1,43 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_LOOP_UNSWITCH_PASS_H_ +#define LIBSPIRV_OPT_LOOP_UNSWITCH_PASS_H_ + +#include "opt/loop_descriptor.h" +#include "opt/pass.h" + +namespace spvtools { +namespace opt { + +// Implements the loop unswitch optimization. +// The loop unswitch hoists invariant "if" statements if the conditions are +// constant within the loop and clones the loop for each branch. +class LoopUnswitchPass : public Pass { + public: + const char* name() const override { return "loop-unswitch"; } + + // Processes the given |module|. Returns Status::Failure if errors occur when + // processing. Returns the corresponding Status::Success if processing is + // succesful to indicate whether changes have been made to the modue. + Pass::Status Process(ir::IRContext* context) override; + + private: + bool ProcessFunction(ir::Function* f); +}; + +} // namespace opt +} // namespace spvtools + +#endif // !LIBSPIRV_OPT_LOOP_UNSWITCH_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/loop_utils.cpp b/3rdparty/spirv-tools/source/opt/loop_utils.cpp new file mode 100644 index 000000000..3c99b6a35 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/loop_utils.cpp @@ -0,0 +1,602 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include + +#include "cfa.h" +#include "opt/cfg.h" +#include "opt/ir_builder.h" +#include "opt/ir_context.h" +#include "opt/loop_descriptor.h" +#include "opt/loop_utils.h" + +namespace spvtools { +namespace opt { + +namespace { +// Return true if |bb| is dominated by at least one block in |exits| +static inline bool DominatesAnExit( + ir::BasicBlock* bb, const std::unordered_set& exits, + const opt::DominatorTree& dom_tree) { + for (ir::BasicBlock* e_bb : exits) + if (dom_tree.Dominates(bb, e_bb)) return true; + return false; +} + +// Utility class to rewrite out-of-loop uses of an in-loop definition in terms +// of phi instructions to achieve a LCSSA form. +// For a given definition, the class user registers phi instructions using that +// definition in all loop exit blocks by which the definition escapes. +// Then, when rewriting a use of the definition, the rewriter walks the +// paths from the use the loop exits. At each step, it will insert a phi +// instruction to merge the incoming value according to exit blocks definition. +class LCSSARewriter { + public: + LCSSARewriter(ir::IRContext* context, const opt::DominatorTree& dom_tree, + const std::unordered_set& exit_bb, + ir::BasicBlock* merge_block) + : context_(context), + cfg_(context_->cfg()), + dom_tree_(dom_tree), + exit_bb_(exit_bb), + merge_block_id_(merge_block ? merge_block->id() : 0) {} + + struct UseRewriter { + explicit UseRewriter(LCSSARewriter* base, const ir::Instruction& def_insn) + : base_(base), def_insn_(def_insn) {} + // Rewrites the use of |def_insn_| by the instruction |user| at the index + // |operand_index| in terms of phi instruction. This recursively builds new + // phi instructions from |user| to the loop exit blocks' phis. The use of + // |def_insn_| in |user| is replaced by the relevant phi instruction at the + // end of the operation. + // It is assumed that |user| does not dominates any of the loop exit basic + // block. This operation does not update the def/use manager, instead it + // records what needs to be updated. The actual update is performed by + // UpdateManagers. + void RewriteUse(ir::BasicBlock* bb, ir::Instruction* user, + uint32_t operand_index) { + assert( + (user->opcode() != SpvOpPhi || bb != GetParent(user)) && + "The root basic block must be the incoming edge if |user| is a phi " + "instruction"); + assert((user->opcode() == SpvOpPhi || bb == GetParent(user)) && + "The root basic block must be the instruction parent if |user| is " + "not " + "phi instruction"); + + ir::Instruction* new_def = GetOrBuildIncoming(bb->id()); + + user->SetOperand(operand_index, {new_def->result_id()}); + rewritten_.insert(user); + } + + // In-place update of some managers (avoid full invalidation). + inline void UpdateManagers() { + opt::analysis::DefUseManager* def_use_mgr = + base_->context_->get_def_use_mgr(); + // Register all new definitions. + for (ir::Instruction* insn : rewritten_) { + def_use_mgr->AnalyzeInstDef(insn); + } + // Register all new uses. + for (ir::Instruction* insn : rewritten_) { + def_use_mgr->AnalyzeInstUse(insn); + } + } + + private: + // Return the basic block that |instr| belongs to. + ir::BasicBlock* GetParent(ir::Instruction* instr) { + return base_->context_->get_instr_block(instr); + } + + // Builds a phi instruction for the basic block |bb|. The function assumes + // that |defining_blocks| contains the list of basic block that define the + // usable value for each predecessor of |bb|. + inline ir::Instruction* CreatePhiInstruction( + ir::BasicBlock* bb, const std::vector& defining_blocks) { + std::vector incomings; + const std::vector& bb_preds = base_->cfg_->preds(bb->id()); + assert(bb_preds.size() == defining_blocks.size()); + for (size_t i = 0; i < bb_preds.size(); i++) { + incomings.push_back( + GetOrBuildIncoming(defining_blocks[i])->result_id()); + incomings.push_back(bb_preds[i]); + } + opt::InstructionBuilder builder( + base_->context_, &*bb->begin(), + ir::IRContext::kAnalysisInstrToBlockMapping); + ir::Instruction* incoming_phi = + builder.AddPhi(def_insn_.type_id(), incomings); + + rewritten_.insert(incoming_phi); + return incoming_phi; + } + + // Builds a phi instruction for the basic block |bb|, all incoming values + // will be |value|. + inline ir::Instruction* CreatePhiInstruction(ir::BasicBlock* bb, + const ir::Instruction& value) { + std::vector incomings; + const std::vector& bb_preds = base_->cfg_->preds(bb->id()); + for (size_t i = 0; i < bb_preds.size(); i++) { + incomings.push_back(value.result_id()); + incomings.push_back(bb_preds[i]); + } + opt::InstructionBuilder builder( + base_->context_, &*bb->begin(), + ir::IRContext::kAnalysisInstrToBlockMapping); + ir::Instruction* incoming_phi = + builder.AddPhi(def_insn_.type_id(), incomings); + + rewritten_.insert(incoming_phi); + return incoming_phi; + } + + // Return the new def to use for the basic block |bb_id|. + // If |bb_id| does not have a suitable def to use then we: + // - return the common def used by all predecessors; + // - if there is no common def, then we build a new phi instr at the + // beginning of |bb_id| and return this new instruction. + ir::Instruction* GetOrBuildIncoming(uint32_t bb_id) { + assert(base_->cfg_->block(bb_id) != nullptr && "Unknown basic block"); + + ir::Instruction*& incoming_phi = bb_to_phi_[bb_id]; + if (incoming_phi) { + return incoming_phi; + } + + ir::BasicBlock* bb = &*base_->cfg_->block(bb_id); + // If this is an exit basic block, look if there already is an eligible + // phi instruction. An eligible phi has |def_insn_| as all incoming + // values. + if (base_->exit_bb_.count(bb)) { + // Look if there is an eligible phi in this block. + if (!bb->WhileEachPhiInst([&incoming_phi, this](ir::Instruction* phi) { + for (uint32_t i = 0; i < phi->NumInOperands(); i += 2) { + if (phi->GetSingleWordInOperand(i) != def_insn_.result_id()) + return true; + } + incoming_phi = phi; + rewritten_.insert(incoming_phi); + return false; + })) { + return incoming_phi; + } + incoming_phi = CreatePhiInstruction(bb, def_insn_); + return incoming_phi; + } + + // Get the block that defines the value to use for each predecessor. + // If the vector has 1 value, then it means that this block does not need + // to build a phi instruction unless |bb_id| is the loop merge block. + const std::vector& defining_blocks = + base_->GetDefiningBlocks(bb_id); + + // Special case for structured loops: merge block might be different from + // the exit block set. To maintain structured properties it will ease + // transformations if the merge block also holds a phi instruction like + // the exit ones. + if (defining_blocks.size() > 1 || bb_id == base_->merge_block_id_) { + if (defining_blocks.size() > 1) { + incoming_phi = CreatePhiInstruction(bb, defining_blocks); + } else { + assert(bb_id == base_->merge_block_id_); + incoming_phi = + CreatePhiInstruction(bb, *GetOrBuildIncoming(defining_blocks[0])); + } + } else { + incoming_phi = GetOrBuildIncoming(defining_blocks[0]); + } + + return incoming_phi; + } + + LCSSARewriter* base_; + const ir::Instruction& def_insn_; + std::unordered_map bb_to_phi_; + std::unordered_set rewritten_; + }; + + private: + // Return the new def to use for the basic block |bb_id|. + // If |bb_id| does not have a suitable def to use then we: + // - return the common def used by all predecessors; + // - if there is no common def, then we build a new phi instr at the + // beginning of |bb_id| and return this new instruction. + const std::vector& GetDefiningBlocks(uint32_t bb_id) { + assert(cfg_->block(bb_id) != nullptr && "Unknown basic block"); + std::vector& defining_blocks = bb_to_defining_blocks_[bb_id]; + + if (defining_blocks.size()) return defining_blocks; + + // Check if one of the loop exit basic block dominates |bb_id|. + for (const ir::BasicBlock* e_bb : exit_bb_) { + if (dom_tree_.Dominates(e_bb->id(), bb_id)) { + defining_blocks.push_back(e_bb->id()); + return defining_blocks; + } + } + + // Process parents, they will returns their suitable blocks. + // If they are all the same, this means this basic block is dominated by a + // common block, so we won't need to build a phi instruction. + for (uint32_t pred_id : cfg_->preds(bb_id)) { + const std::vector& pred_blocks = GetDefiningBlocks(pred_id); + if (pred_blocks.size() == 1) + defining_blocks.push_back(pred_blocks[0]); + else + defining_blocks.push_back(pred_id); + } + assert(defining_blocks.size()); + if (std::all_of(defining_blocks.begin(), defining_blocks.end(), + [&defining_blocks](uint32_t id) { + return id == defining_blocks[0]; + })) { + // No need for a phi. + defining_blocks.resize(1); + } + + return defining_blocks; + } + + ir::IRContext* context_; + ir::CFG* cfg_; + const opt::DominatorTree& dom_tree_; + const std::unordered_set& exit_bb_; + uint32_t merge_block_id_; + // This map represent the set of known paths. For each key, the vector + // represent the set of blocks holding the definition to be used to build the + // phi instruction. + // If the vector has 0 value, then the path is unknown yet, and must be built. + // If the vector has 1 value, then the value defined by that basic block + // should be used. + // If the vector has more than 1 value, then a phi node must be created, the + // basic block ordering is the same as the predecessor ordering. + std::unordered_map> bb_to_defining_blocks_; +}; + +// Make the set |blocks| closed SSA. The set is closed SSA if all the uses +// outside the set are phi instructions in exiting basic block set (hold by +// |lcssa_rewriter|). +inline void MakeSetClosedSSA(ir::IRContext* context, ir::Function* function, + const std::unordered_set& blocks, + const std::unordered_set& exit_bb, + LCSSARewriter* lcssa_rewriter) { + ir::CFG& cfg = *context->cfg(); + opt::DominatorTree& dom_tree = + context->GetDominatorAnalysis(function, cfg)->GetDomTree(); + opt::analysis::DefUseManager* def_use_manager = context->get_def_use_mgr(); + + for (uint32_t bb_id : blocks) { + ir::BasicBlock* bb = cfg.block(bb_id); + // If bb does not dominate an exit block, then it cannot have escaping defs. + if (!DominatesAnExit(bb, exit_bb, dom_tree)) continue; + for (ir::Instruction& inst : *bb) { + LCSSARewriter::UseRewriter rewriter(lcssa_rewriter, inst); + def_use_manager->ForEachUse( + &inst, [&blocks, &rewriter, &exit_bb, context]( + ir::Instruction* use, uint32_t operand_index) { + ir::BasicBlock* use_parent = context->get_instr_block(use); + assert(use_parent); + if (blocks.count(use_parent->id())) return; + + if (use->opcode() == SpvOpPhi) { + // If the use is a Phi instruction and the incoming block is + // coming from the loop, then that's consistent with LCSSA form. + if (exit_bb.count(use_parent)) { + return; + } else { + // That's not an exit block, but the user is a phi instruction. + // Consider the incoming branch only. + use_parent = context->get_instr_block( + use->GetSingleWordOperand(operand_index + 1)); + } + } + // Rewrite the use. Note that this call does not invalidate the + // def/use manager. So this operation is safe. + rewriter.RewriteUse(use_parent, use, operand_index); + }); + rewriter.UpdateManagers(); + } + } +} + +} // namespace + +void LoopUtils::CreateLoopDedicatedExits() { + ir::Function* function = loop_->GetHeaderBlock()->GetParent(); + ir::LoopDescriptor& loop_desc = *context_->GetLoopDescriptor(function); + ir::CFG& cfg = *context_->cfg(); + opt::analysis::DefUseManager* def_use_mgr = context_->get_def_use_mgr(); + + const ir::IRContext::Analysis PreservedAnalyses = + ir::IRContext::kAnalysisDefUse | + ir::IRContext::kAnalysisInstrToBlockMapping; + + // Gathers the set of basic block that are not in this loop and have at least + // one predecessor in the loop and one not in the loop. + std::unordered_set exit_bb_set; + loop_->GetExitBlocks(&exit_bb_set); + + std::unordered_set new_loop_exits; + bool made_change = false; + // For each block, we create a new one that gathers all branches from + // the loop and fall into the block. + for (uint32_t non_dedicate_id : exit_bb_set) { + ir::BasicBlock* non_dedicate = cfg.block(non_dedicate_id); + const std::vector& bb_pred = cfg.preds(non_dedicate_id); + // Ignore the block if all the predecessors are in the loop. + if (std::all_of(bb_pred.begin(), bb_pred.end(), + [this](uint32_t id) { return loop_->IsInsideLoop(id); })) { + new_loop_exits.insert(non_dedicate); + continue; + } + + made_change = true; + ir::Function::iterator insert_pt = function->begin(); + for (; insert_pt != function->end() && &*insert_pt != non_dedicate; + ++insert_pt) { + } + assert(insert_pt != function->end() && "Basic Block not found"); + + // Create the dedicate exit basic block. + ir::BasicBlock& exit = *insert_pt.InsertBefore( + std::unique_ptr(new ir::BasicBlock( + std::unique_ptr(new ir::Instruction( + context_, SpvOpLabel, 0, context_->TakeNextId(), {}))))); + exit.SetParent(function); + + // Redirect in loop predecessors to |exit| block. + for (uint32_t exit_pred_id : bb_pred) { + if (loop_->IsInsideLoop(exit_pred_id)) { + ir::BasicBlock* pred_block = cfg.block(exit_pred_id); + pred_block->ForEachSuccessorLabel([non_dedicate, &exit](uint32_t* id) { + if (*id == non_dedicate->id()) *id = exit.id(); + }); + // Update the CFG. + // |non_dedicate|'s predecessor list will be updated at the end of the + // loop. + cfg.RegisterBlock(pred_block); + } + } + + // Register the label to the def/use manager, requires for the phi patching. + def_use_mgr->AnalyzeInstDefUse(exit.GetLabelInst()); + context_->set_instr_block(exit.GetLabelInst(), &exit); + + opt::InstructionBuilder builder(context_, &exit, PreservedAnalyses); + // Now jump from our dedicate basic block to the old exit. + // We also reset the insert point so all instructions are inserted before + // the branch. + builder.SetInsertPoint(builder.AddBranch(non_dedicate->id())); + non_dedicate->ForEachPhiInst([&builder, &exit, def_use_mgr, + this](ir::Instruction* phi) { + // New phi operands for this instruction. + std::vector new_phi_op; + // Phi operands for the dedicated exit block. + std::vector exit_phi_op; + for (uint32_t i = 0; i < phi->NumInOperands(); i += 2) { + uint32_t def_id = phi->GetSingleWordInOperand(i); + uint32_t incoming_id = phi->GetSingleWordInOperand(i + 1); + if (loop_->IsInsideLoop(incoming_id)) { + exit_phi_op.push_back(def_id); + exit_phi_op.push_back(incoming_id); + } else { + new_phi_op.push_back(def_id); + new_phi_op.push_back(incoming_id); + } + } + + // Build the new phi instruction dedicated exit block. + ir::Instruction* exit_phi = builder.AddPhi(phi->type_id(), exit_phi_op); + // Build the new incoming branch. + new_phi_op.push_back(exit_phi->result_id()); + new_phi_op.push_back(exit.id()); + // Rewrite operands. + uint32_t idx = 0; + for (; idx < new_phi_op.size(); idx++) + phi->SetInOperand(idx, {new_phi_op[idx]}); + // Remove extra operands, from last to first (more efficient). + for (uint32_t j = phi->NumInOperands() - 1; j >= idx; j--) + phi->RemoveInOperand(j); + // Update the def/use manager for this |phi|. + def_use_mgr->AnalyzeInstUse(phi); + }); + // Update the CFG. + cfg.RegisterBlock(&exit); + cfg.RemoveNonExistingEdges(non_dedicate->id()); + new_loop_exits.insert(&exit); + // If non_dedicate is in a loop, add the new dedicated exit in that loop. + if (ir::Loop* parent_loop = loop_desc[non_dedicate]) + parent_loop->AddBasicBlock(&exit); + } + + if (new_loop_exits.size() == 1) { + loop_->SetMergeBlock(*new_loop_exits.begin()); + } + + if (made_change) { + context_->InvalidateAnalysesExceptFor( + PreservedAnalyses | ir::IRContext::kAnalysisCFG | + ir::IRContext::Analysis::kAnalysisLoopAnalysis); + } +} + +void LoopUtils::MakeLoopClosedSSA() { + CreateLoopDedicatedExits(); + + ir::Function* function = loop_->GetHeaderBlock()->GetParent(); + ir::CFG& cfg = *context_->cfg(); + opt::DominatorTree& dom_tree = + context_->GetDominatorAnalysis(function, cfg)->GetDomTree(); + + std::unordered_set exit_bb; + { + std::unordered_set exit_bb_id; + loop_->GetExitBlocks(&exit_bb_id); + for (uint32_t bb_id : exit_bb_id) { + exit_bb.insert(cfg.block(bb_id)); + } + } + + LCSSARewriter lcssa_rewriter(context_, dom_tree, exit_bb, + loop_->GetMergeBlock()); + MakeSetClosedSSA(context_, function, loop_->GetBlocks(), exit_bb, + &lcssa_rewriter); + + // Make sure all defs post-dominated by the merge block have their last use no + // further than the merge block. + if (loop_->GetMergeBlock()) { + std::unordered_set merging_bb_id; + loop_->GetMergingBlocks(&merging_bb_id); + merging_bb_id.erase(loop_->GetMergeBlock()->id()); + // Reset the exit set, now only the merge block is the exit. + exit_bb.clear(); + exit_bb.insert(loop_->GetMergeBlock()); + // LCSSARewriter is reusable here only because it forces the creation of a + // phi instruction in the merge block. + MakeSetClosedSSA(context_, function, merging_bb_id, exit_bb, + &lcssa_rewriter); + } + + context_->InvalidateAnalysesExceptFor( + ir::IRContext::Analysis::kAnalysisDefUse | + ir::IRContext::Analysis::kAnalysisCFG | + ir::IRContext::Analysis::kAnalysisDominatorAnalysis | + ir::IRContext::Analysis::kAnalysisLoopAnalysis); +} + +ir::Loop* LoopUtils::CloneLoop( + LoopCloningResult* cloning_result, + const std::vector& ordered_loop_blocks) const { + analysis::DefUseManager* def_use_mgr = context_->get_def_use_mgr(); + + std::unique_ptr new_loop = MakeUnique(context_); + if (loop_->HasParent()) new_loop->SetParent(loop_->GetParent()); + + ir::CFG& cfg = *context_->cfg(); + + // Clone and place blocks in a SPIR-V compliant order (dominators first). + for (ir::BasicBlock* old_bb : ordered_loop_blocks) { + // For each basic block in the loop, we clone it and register the mapping + // between old and new ids. + ir::BasicBlock* new_bb = old_bb->Clone(context_); + new_bb->SetParent(&function_); + new_bb->GetLabelInst()->SetResultId(context_->TakeNextId()); + def_use_mgr->AnalyzeInstDef(new_bb->GetLabelInst()); + context_->set_instr_block(new_bb->GetLabelInst(), new_bb); + cloning_result->cloned_bb_.emplace_back(new_bb); + + cloning_result->old_to_new_bb_[old_bb->id()] = new_bb; + cloning_result->new_to_old_bb_[new_bb->id()] = old_bb; + cloning_result->value_map_[old_bb->id()] = new_bb->id(); + + if (loop_->IsInsideLoop(old_bb)) new_loop->AddBasicBlock(new_bb); + + for (auto& inst : *new_bb) { + if (inst.HasResultId()) { + uint32_t old_result_id = inst.result_id(); + inst.SetResultId(context_->TakeNextId()); + cloning_result->value_map_[old_result_id] = inst.result_id(); + + // Only look at the defs for now, uses are not updated yet. + def_use_mgr->AnalyzeInstDef(&inst); + } + } + } + + // All instructions (including all labels) have been cloned, + // remap instruction operands id with the new ones. + for (std::unique_ptr& bb_ref : cloning_result->cloned_bb_) { + ir::BasicBlock* bb = bb_ref.get(); + + for (ir::Instruction& insn : *bb) { + insn.ForEachInId([cloning_result](uint32_t* old_id) { + // If the operand is defined in the loop, remap the id. + auto id_it = cloning_result->value_map_.find(*old_id); + if (id_it != cloning_result->value_map_.end()) { + *old_id = id_it->second; + } + }); + // Only look at what the instruction uses. All defs are register, so all + // should be fine now. + def_use_mgr->AnalyzeInstUse(&insn); + context_->set_instr_block(&insn, bb); + } + cfg.RegisterBlock(bb); + } + + PopulateLoopNest(new_loop.get(), *cloning_result); + + return new_loop.release(); +} + +void LoopUtils::PopulateLoopNest( + ir::Loop* new_loop, const LoopCloningResult& cloning_result) const { + std::unordered_map loop_mapping; + loop_mapping[loop_] = new_loop; + + if (loop_->HasParent()) loop_->GetParent()->AddNestedLoop(new_loop); + PopulateLoopDesc(new_loop, loop_, cloning_result); + + for (ir::Loop& sub_loop : + ir::make_range(++opt::TreeDFIterator(loop_), + opt::TreeDFIterator())) { + ir::Loop* cloned = new ir::Loop(context_); + if (ir::Loop* parent = loop_mapping[sub_loop.GetParent()]) + parent->AddNestedLoop(cloned); + loop_mapping[&sub_loop] = cloned; + PopulateLoopDesc(cloned, &sub_loop, cloning_result); + } + + loop_desc_->AddLoopNest(std::unique_ptr(new_loop)); +} + +// Populates |new_loop| descriptor according to |old_loop|'s one. +void LoopUtils::PopulateLoopDesc( + ir::Loop* new_loop, ir::Loop* old_loop, + const LoopCloningResult& cloning_result) const { + for (uint32_t bb_id : old_loop->GetBlocks()) { + ir::BasicBlock* bb = cloning_result.old_to_new_bb_.at(bb_id); + new_loop->AddBasicBlock(bb); + } + new_loop->SetHeaderBlock( + cloning_result.old_to_new_bb_.at(old_loop->GetHeaderBlock()->id())); + if (old_loop->GetLatchBlock()) + new_loop->SetLatchBlock( + cloning_result.old_to_new_bb_.at(old_loop->GetLatchBlock()->id())); + if (old_loop->GetMergeBlock()) { + auto it = + cloning_result.old_to_new_bb_.find(old_loop->GetMergeBlock()->id()); + ir::BasicBlock* bb = it != cloning_result.old_to_new_bb_.end() + ? it->second + : old_loop->GetMergeBlock(); + new_loop->SetMergeBlock(bb); + } + if (old_loop->GetPreHeaderBlock()) { + auto it = + cloning_result.old_to_new_bb_.find(old_loop->GetPreHeaderBlock()->id()); + if (it != cloning_result.old_to_new_bb_.end()) { + new_loop->SetPreHeaderBlock(it->second); + } + } +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/loop_utils.h b/3rdparty/spirv-tools/source/opt/loop_utils.h new file mode 100644 index 000000000..a50be3321 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/loop_utils.h @@ -0,0 +1,158 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_OPT_LOOP_UTILS_H_ +#define SOURCE_OPT_LOOP_UTILS_H_ +#include +#include +#include +#include "opt/ir_context.h" +#include "opt/loop_descriptor.h" + +namespace spvtools { + +namespace opt { + +// LoopUtils is used to encapsulte loop optimizations and from the passes which +// use them. Any pass which needs a loop optimization should do it through this +// or through a pass which is using this. +class LoopUtils { + public: + // Holds a auxiliary results of the loop cloning procedure. + struct LoopCloningResult { + using ValueMapTy = std::unordered_map; + using BlockMapTy = std::unordered_map; + + // Mapping between the original loop ids and the new one. + ValueMapTy value_map_; + // Mapping between original loop blocks to the cloned one. + BlockMapTy old_to_new_bb_; + // Mapping between the cloned loop blocks to original one. + BlockMapTy new_to_old_bb_; + // List of cloned basic block. + std::vector> cloned_bb_; + }; + + LoopUtils(ir::IRContext* context, ir::Loop* loop) + : context_(context), + loop_desc_( + context->GetLoopDescriptor(loop->GetHeaderBlock()->GetParent())), + loop_(loop), + function_(*loop_->GetHeaderBlock()->GetParent()) {} + + // The converts the current loop to loop closed SSA form. + // In the loop closed SSA, all loop exiting values go through a dedicated Phi + // instruction. For instance: + // + // for (...) { + // A1 = ... + // if (...) + // A2 = ... + // A = phi A1, A2 + // } + // ... = op A ... + // + // Becomes + // + // for (...) { + // A1 = ... + // if (...) + // A2 = ... + // A = phi A1, A2 + // } + // C = phi A + // ... = op C ... + // + // This makes some loop transformations (such as loop unswitch) simpler + // (removes the needs to take care of exiting variables). + void MakeLoopClosedSSA(); + + // Create dedicate exit basic block. This ensure all exit basic blocks has the + // loop as sole predecessors. + // By construction, structured control flow already has a dedicated exit + // block. + // Preserves: CFG, def/use and instruction to block mapping. + void CreateLoopDedicatedExits(); + + // Clone |loop_| and remap its instructions. Newly created blocks + // will be added to the |cloning_result.cloned_bb_| list, correctly ordered to + // be inserted into a function. + // It is assumed that |ordered_loop_blocks| is compatible with the result of + // |Loop::ComputeLoopStructuredOrder|. If the preheader and merge block are in + // the list they will also be cloned. If not, the resulting loop will share + // them with the original loop. + // The function preserves the def/use, cfg and instr to block analyses. + // The cloned loop nest will be added to the loop descriptor and will have + // ownership. + ir::Loop* CloneLoop( + LoopCloningResult* cloning_result, + const std::vector& ordered_loop_blocks) const; + + // Perfom a partial unroll of |loop| by given |factor|. This will copy the + // body of the loop |factor| times. So a |factor| of one would give a new loop + // with the original body plus one unrolled copy body. + bool PartiallyUnroll(size_t factor); + + // Fully unroll |loop|. + bool FullyUnroll(); + + // This function validates that |loop| meets the assumptions made by the + // implementation of the loop unroller. As the implementation accommodates + // more types of loops this function can reduce its checks. + // + // The conditions checked to ensure the loop can be unrolled are as follows: + // 1. That the loop is in structured order. + // 2. That the continue block is a branch to the header. + // 3. That the only phi used in the loop is the induction variable. + // TODO(stephen@codeplay.com): This is a temporary mesure, after the loop is + // converted into LCSAA form and has a single entry and exit we can rewrite + // the other phis. + // 4. That this is an inner most loop, or that loops contained within this + // loop have already been fully unrolled. + // 5. That each instruction in the loop is only used within the loop. + // (Related to the above phi condition). + bool CanPerformUnroll(); + + // Maintains the loop descriptor object after the unroll functions have been + // called, otherwise the analysis should be invalidated. + void Finalize(); + + // Returns the context associate to |loop_|. + ir::IRContext* GetContext() { return context_; } + // Returns the loop descriptor owning |loop_|. + ir::LoopDescriptor* GetLoopDescriptor() { return loop_desc_; } + // Returns the loop on which the object operates on. + ir::Loop* GetLoop() const { return loop_; } + // Returns the function that |loop_| belong to. + ir::Function* GetFunction() const { return &function_; } + + private: + ir::IRContext* context_; + ir::LoopDescriptor* loop_desc_; + ir::Loop* loop_; + ir::Function& function_; + + // Populates the loop nest of |new_loop| according to |loop_| nest. + void PopulateLoopNest(ir::Loop* new_loop, + const LoopCloningResult& cloning_result) const; + + // Populates |new_loop| descriptor according to |old_loop|'s one. + void PopulateLoopDesc(ir::Loop* new_loop, ir::Loop* old_loop, + const LoopCloningResult& cloning_result) const; +}; + +} // namespace opt +} // namespace spvtools + +#endif // SOURCE_OPT_LOOP_UTILS_H_ diff --git a/3rdparty/spirv-tools/source/opt/make_unique.h b/3rdparty/spirv-tools/source/opt/make_unique.h new file mode 100644 index 000000000..f6b8aae28 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/make_unique.h @@ -0,0 +1,30 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_MAKE_UNIQUE_H_ +#define LIBSPIRV_OPT_MAKE_UNIQUE_H_ + +#include +#include + +namespace spvtools { + +template +std::unique_ptr MakeUnique(Args&&... args) { + return std::unique_ptr(new T(std::forward(args)...)); +} + +} // namespace spvtools + +#endif // LIBSPIRV_OPT_MAKE_UNIQUE_H_ diff --git a/3rdparty/spirv-tools/source/opt/mem_pass.cpp b/3rdparty/spirv-tools/source/opt/mem_pass.cpp new file mode 100644 index 000000000..13364da0c --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/mem_pass.cpp @@ -0,0 +1,880 @@ +// Copyright (c) 2017 The Khronos Group Inc. +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "mem_pass.h" + +#include "basic_block.h" +#include "cfa.h" +#include "dominator_analysis.h" +#include "ir_context.h" +#include "iterator.h" + +namespace spvtools { +namespace opt { + +namespace { + +const uint32_t kCopyObjectOperandInIdx = 0; +const uint32_t kLoopMergeMergeBlockIdInIdx = 0; +const uint32_t kStoreValIdInIdx = 1; +const uint32_t kTypePointerStorageClassInIdx = 0; +const uint32_t kTypePointerTypeIdInIdx = 1; +const uint32_t kVariableInitIdInIdx = 1; + +} // namespace + +bool MemPass::IsBaseTargetType(const ir::Instruction* typeInst) const { + switch (typeInst->opcode()) { + case SpvOpTypeInt: + case SpvOpTypeFloat: + case SpvOpTypeBool: + case SpvOpTypeVector: + case SpvOpTypeMatrix: + case SpvOpTypeImage: + case SpvOpTypeSampler: + case SpvOpTypeSampledImage: + case SpvOpTypePointer: + return true; + default: + break; + } + return false; +} + +bool MemPass::IsTargetType(const ir::Instruction* typeInst) const { + if (IsBaseTargetType(typeInst)) return true; + if (typeInst->opcode() == SpvOpTypeArray) { + if (!IsTargetType( + get_def_use_mgr()->GetDef(typeInst->GetSingleWordOperand(1)))) { + return false; + } + return true; + } + if (typeInst->opcode() != SpvOpTypeStruct) return false; + // All struct members must be math type + return typeInst->WhileEachInId([this](const uint32_t* tid) { + ir::Instruction* compTypeInst = get_def_use_mgr()->GetDef(*tid); + if (!IsTargetType(compTypeInst)) return false; + return true; + }); +} + +bool MemPass::IsNonPtrAccessChain(const SpvOp opcode) const { + return opcode == SpvOpAccessChain || opcode == SpvOpInBoundsAccessChain; +} + +bool MemPass::IsPtr(uint32_t ptrId) { + uint32_t varId = ptrId; + ir::Instruction* ptrInst = get_def_use_mgr()->GetDef(varId); + while (ptrInst->opcode() == SpvOpCopyObject) { + varId = ptrInst->GetSingleWordInOperand(kCopyObjectOperandInIdx); + ptrInst = get_def_use_mgr()->GetDef(varId); + } + const SpvOp op = ptrInst->opcode(); + if (op == SpvOpVariable || IsNonPtrAccessChain(op)) return true; + if (op != SpvOpFunctionParameter) return false; + const uint32_t varTypeId = ptrInst->type_id(); + const ir::Instruction* varTypeInst = get_def_use_mgr()->GetDef(varTypeId); + return varTypeInst->opcode() == SpvOpTypePointer; +} + +ir::Instruction* MemPass::GetPtr(uint32_t ptrId, uint32_t* varId) { + *varId = ptrId; + ir::Instruction* ptrInst = get_def_use_mgr()->GetDef(*varId); + ir::Instruction* varInst; + + if (ptrInst->opcode() != SpvOpVariable && + ptrInst->opcode() != SpvOpFunctionParameter) { + varInst = ptrInst->GetBaseAddress(); + } else { + varInst = ptrInst; + } + if (varInst->opcode() == SpvOpVariable) { + *varId = varInst->result_id(); + } else { + *varId = 0; + } + + while (ptrInst->opcode() == SpvOpCopyObject) { + uint32_t temp = ptrInst->GetSingleWordInOperand(0); + ptrInst = get_def_use_mgr()->GetDef(temp); + } + + return ptrInst; +} + +ir::Instruction* MemPass::GetPtr(ir::Instruction* ip, uint32_t* varId) { + assert(ip->opcode() == SpvOpStore || ip->opcode() == SpvOpLoad || + ip->opcode() == SpvOpImageTexelPointer); + + // All of these opcode place the pointer in position 0. + const uint32_t ptrId = ip->GetSingleWordInOperand(0); + return GetPtr(ptrId, varId); +} + +bool MemPass::HasOnlyNamesAndDecorates(uint32_t id) const { + return get_def_use_mgr()->WhileEachUser(id, [this](ir::Instruction* user) { + SpvOp op = user->opcode(); + if (op != SpvOpName && !IsNonTypeDecorate(op)) { + return false; + } + return true; + }); +} + +void MemPass::KillAllInsts(ir::BasicBlock* bp, bool killLabel) { + bp->KillAllInsts(killLabel); +} + +bool MemPass::HasLoads(uint32_t varId) const { + return !get_def_use_mgr()->WhileEachUser(varId, [this]( + ir::Instruction* user) { + SpvOp op = user->opcode(); + // TODO(): The following is slightly conservative. Could be + // better handling of non-store/name. + if (IsNonPtrAccessChain(op) || op == SpvOpCopyObject) { + if (HasLoads(user->result_id())) { + return false; + } + } else if (op != SpvOpStore && op != SpvOpName && !IsNonTypeDecorate(op)) { + return false; + } + return true; + }); +} + +bool MemPass::IsLiveVar(uint32_t varId) const { + const ir::Instruction* varInst = get_def_use_mgr()->GetDef(varId); + // assume live if not a variable eg. function parameter + if (varInst->opcode() != SpvOpVariable) return true; + // non-function scope vars are live + const uint32_t varTypeId = varInst->type_id(); + const ir::Instruction* varTypeInst = get_def_use_mgr()->GetDef(varTypeId); + if (varTypeInst->GetSingleWordInOperand(kTypePointerStorageClassInIdx) != + SpvStorageClassFunction) + return true; + // test if variable is loaded from + return HasLoads(varId); +} + +bool MemPass::IsLiveStore(ir::Instruction* storeInst) { + // get store's variable + uint32_t varId; + (void)GetPtr(storeInst, &varId); + if (varId == 0) { + // If we do not know which variable we are accessing, assume the store is + // live. + return true; + } + return IsLiveVar(varId); +} + +void MemPass::AddStores(uint32_t ptr_id, std::queue* insts) { + get_def_use_mgr()->ForEachUser(ptr_id, [this, insts](ir::Instruction* user) { + SpvOp op = user->opcode(); + if (IsNonPtrAccessChain(op)) { + AddStores(user->result_id(), insts); + } else if (op == SpvOpStore) { + insts->push(user); + } + }); +} + +void MemPass::DCEInst(ir::Instruction* inst, + const function& call_back) { + std::queue deadInsts; + deadInsts.push(inst); + while (!deadInsts.empty()) { + ir::Instruction* di = deadInsts.front(); + // Don't delete labels + if (di->opcode() == SpvOpLabel) { + deadInsts.pop(); + continue; + } + // Remember operands + std::set ids; + di->ForEachInId([&ids](uint32_t* iid) { ids.insert(*iid); }); + uint32_t varId = 0; + // Remember variable if dead load + if (di->opcode() == SpvOpLoad) (void)GetPtr(di, &varId); + if (call_back) { + call_back(di); + } + context()->KillInst(di); + // For all operands with no remaining uses, add their instruction + // to the dead instruction queue. + for (auto id : ids) + if (HasOnlyNamesAndDecorates(id)) { + ir::Instruction* odi = get_def_use_mgr()->GetDef(id); + if (context()->IsCombinatorInstruction(odi)) deadInsts.push(odi); + } + // if a load was deleted and it was the variable's + // last load, add all its stores to dead queue + if (varId != 0 && !IsLiveVar(varId)) AddStores(varId, &deadInsts); + deadInsts.pop(); + } +} + +MemPass::MemPass() {} + +bool MemPass::HasOnlySupportedRefs(uint32_t varId) { + if (supported_ref_vars_.find(varId) != supported_ref_vars_.end()) return true; + return get_def_use_mgr()->WhileEachUser(varId, [this](ir::Instruction* user) { + SpvOp op = user->opcode(); + if (op != SpvOpStore && op != SpvOpLoad && op != SpvOpName && + !IsNonTypeDecorate(op)) { + return false; + } + return true; + }); +} + +void MemPass::InitSSARewrite(ir::Function* func) { + // Clear collections. + visitedBlocks_.clear(); + block_defs_map_.clear(); + phis_to_patch_.clear(); + dominator_ = context()->GetDominatorAnalysis(func, *cfg()); + CollectTargetVars(func); +} + +bool MemPass::IsLiveAfter(uint32_t var_id, uint32_t label) const { + // For now, return very conservative result: true. This will result in + // correct, but possibly usused, phi code to be generated. A subsequent + // DCE pass should eliminate this code. + // TODO(greg-lunarg): Return more accurate information + (void)var_id; + (void)label; + return true; +} + +uint32_t MemPass::Type2Undef(uint32_t type_id) { + const auto uitr = type2undefs_.find(type_id); + if (uitr != type2undefs_.end()) return uitr->second; + const uint32_t undefId = TakeNextId(); + std::unique_ptr undef_inst( + new ir::Instruction(context(), SpvOpUndef, type_id, undefId, {})); + get_def_use_mgr()->AnalyzeInstDefUse(&*undef_inst); + get_module()->AddGlobalValue(std::move(undef_inst)); + type2undefs_[type_id] = undefId; + return undefId; +} + +void MemPass::CollectLiveVars(uint32_t block_label, + std::map* live_vars) { + // Walk up the dominator chain starting at |block_label| looking for variables + // defined at each block in the chain. Since we are only interested for the + // most recent value for each live variable, we only add a + // pair to |live_vars| if this is the first time we find the variable in the + // chain. + for (ir::BasicBlock* block = cfg()->block(block_label); block != nullptr; + block = dominator_->ImmediateDominator(block)) { + for (const auto& var_val : block_defs_map_[block->id()]) { + auto live_vars_it = live_vars->find(var_val.first); + if (live_vars_it == live_vars->end()) live_vars->insert(var_val); + } + } +} + +uint32_t MemPass::GetCurrentValue(uint32_t var_id, uint32_t block_label) { + // Walk up the dominator chain starting at |block_label| looking for the + // current value of variable |var_id|. The first block we find containing a + // definition for |var_id| is the one we are interested in. + for (ir::BasicBlock* block = cfg()->block(block_label); block != nullptr; + block = dominator_->ImmediateDominator(block)) { + const auto& block_defs = block_defs_map_[block->id()]; + const auto& var_val_it = block_defs.find(var_id); + if (var_val_it != block_defs.end()) return var_val_it->second; + } + return 0; +} + +bool MemPass::SSABlockInitLoopHeader( + std::list::iterator block_itr) { + bool modified = false; + const uint32_t label = (*block_itr)->id(); + + // Determine the back-edge label. + uint32_t backLabel = 0; + for (uint32_t predLabel : cfg()->preds(label)) + if (visitedBlocks_.find(predLabel) == visitedBlocks_.end()) { + assert(backLabel == 0); + backLabel = predLabel; + break; + } + assert(backLabel != 0); + + // Determine merge block. + auto mergeInst = (*block_itr)->end(); + --mergeInst; + --mergeInst; + uint32_t mergeLabel = + mergeInst->GetSingleWordInOperand(kLoopMergeMergeBlockIdInIdx); + + // Collect all live variables and a default value for each across all + // non-backedge predecesors. Must be ordered map because phis are + // generated based on order and test results will otherwise vary across + // platforms. + std::map liveVars; + for (uint32_t predLabel : cfg()->preds(label)) { + CollectLiveVars(predLabel, &liveVars); + } + + // Add all stored variables in loop. Set their default value id to zero. + for (auto bi = block_itr; (*bi)->id() != mergeLabel; ++bi) { + ir::BasicBlock* bp = *bi; + for (auto ii = bp->begin(); ii != bp->end(); ++ii) { + if (ii->opcode() != SpvOpStore) { + continue; + } + uint32_t varId; + (void)GetPtr(&*ii, &varId); + if (!IsTargetVar(varId)) { + continue; + } + liveVars[varId] = 0; + } + } + // Insert phi for all live variables that require them. All variables + // defined in loop require a phi. Otherwise all variables + // with differing predecessor values require a phi. + auto insertItr = (*block_itr)->begin(); + for (auto var_val : liveVars) { + const uint32_t varId = var_val.first; + if (!IsLiveAfter(varId, label)) { + continue; + } + const uint32_t val0Id = var_val.second; + bool needsPhi = false; + if (val0Id != 0) { + for (uint32_t predLabel : cfg()->preds(label)) { + // Skip back edge predecessor. + if (predLabel == backLabel) continue; + uint32_t current_value = GetCurrentValue(varId, predLabel); + // Missing (undef) values always cause difference with (defined) value + if (current_value == 0) { + needsPhi = true; + break; + } + if (current_value != val0Id) { + needsPhi = true; + break; + } + } + } else { + needsPhi = true; + } + + // If val is the same for all predecessors, enter it in map + if (!needsPhi) { + block_defs_map_[label].insert(var_val); + continue; + } + + // Val differs across predecessors. Add phi op to block and + // add its result id to the map. For back edge predecessor, + // use the variable id. We will patch this after visiting back + // edge predecessor. For predecessors that do not define a value, + // use undef. + modified = true; + std::vector phi_in_operands; + uint32_t typeId = GetPointeeTypeId(get_def_use_mgr()->GetDef(varId)); + for (uint32_t predLabel : cfg()->preds(label)) { + uint32_t valId; + if (predLabel == backLabel) { + valId = varId; + } else { + uint32_t current_value = GetCurrentValue(varId, predLabel); + if (current_value == 0) + valId = Type2Undef(typeId); + else + valId = current_value; + } + phi_in_operands.push_back( + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {valId}}); + phi_in_operands.push_back( + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {predLabel}}); + } + const uint32_t phiId = TakeNextId(); + std::unique_ptr newPhi(new ir::Instruction( + context(), SpvOpPhi, typeId, phiId, phi_in_operands)); + // The only phis requiring patching are the ones we create. + phis_to_patch_.insert(phiId); + // Only analyze the phi define now; analyze the phi uses after the + // phi backedge predecessor value is patched. + get_def_use_mgr()->AnalyzeInstDef(&*newPhi); + context()->set_instr_block(&*newPhi, *block_itr); + insertItr = insertItr.InsertBefore(std::move(newPhi)); + ++insertItr; + block_defs_map_[label].insert({varId, phiId}); + } + return modified; +} + +bool MemPass::SSABlockInitMultiPred(ir::BasicBlock* block_ptr) { + bool modified = false; + const uint32_t label = block_ptr->id(); + // Collect all live variables and a default value for each across all + // predecesors. Must be ordered map because phis are generated based on + // order and test results will otherwise vary across platforms. + std::map liveVars; + for (uint32_t predLabel : cfg()->preds(label)) { + assert(visitedBlocks_.find(predLabel) != visitedBlocks_.end()); + CollectLiveVars(predLabel, &liveVars); + } + + // For each live variable, look for a difference in values across + // predecessors that would require a phi and insert one. + auto insertItr = block_ptr->begin(); + for (auto var_val : liveVars) { + const uint32_t varId = var_val.first; + if (!IsLiveAfter(varId, label)) continue; + const uint32_t val0Id = var_val.second; + bool differs = false; + for (uint32_t predLabel : cfg()->preds(label)) { + uint32_t current_value = GetCurrentValue(varId, predLabel); + // Missing values cause a difference because we'll need to create an + // undef for that predecessor. + if (current_value == 0) { + differs = true; + break; + } + if (current_value != val0Id) { + differs = true; + break; + } + } + // If val is the same for all predecessors, enter it in map + if (!differs) { + block_defs_map_[label].insert(var_val); + continue; + } + + modified = true; + + // Val differs across predecessors. Add phi op to block and add its result + // id to the map. + std::vector phi_in_operands; + const uint32_t typeId = GetPointeeTypeId(get_def_use_mgr()->GetDef(varId)); + for (uint32_t predLabel : cfg()->preds(label)) { + uint32_t current_value = GetCurrentValue(varId, predLabel); + // If variable not defined on this path, use undef + const uint32_t valId = + (current_value > 0) ? current_value : Type2Undef(typeId); + phi_in_operands.push_back( + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {valId}}); + phi_in_operands.push_back( + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {predLabel}}); + } + const uint32_t phiId = TakeNextId(); + std::unique_ptr newPhi(new ir::Instruction( + context(), SpvOpPhi, typeId, phiId, phi_in_operands)); + get_def_use_mgr()->AnalyzeInstDefUse(&*newPhi); + context()->set_instr_block(&*newPhi, block_ptr); + insertItr = insertItr.InsertBefore(std::move(newPhi)); + ++insertItr; + block_defs_map_[label].insert({varId, phiId}); + } + return modified; +} + +bool MemPass::SSABlockInit(std::list::iterator block_itr) { + const size_t numPreds = cfg()->preds((*block_itr)->id()).size(); + if (numPreds == 0) return false; + if ((*block_itr)->IsLoopHeader()) + return SSABlockInitLoopHeader(block_itr); + else + return SSABlockInitMultiPred(*block_itr); +} + +bool MemPass::IsTargetVar(uint32_t varId) { + if (varId == 0) { + return false; + } + + if (seen_non_target_vars_.find(varId) != seen_non_target_vars_.end()) + return false; + if (seen_target_vars_.find(varId) != seen_target_vars_.end()) return true; + const ir::Instruction* varInst = get_def_use_mgr()->GetDef(varId); + if (varInst->opcode() != SpvOpVariable) return false; + const uint32_t varTypeId = varInst->type_id(); + const ir::Instruction* varTypeInst = get_def_use_mgr()->GetDef(varTypeId); + if (varTypeInst->GetSingleWordInOperand(kTypePointerStorageClassInIdx) != + SpvStorageClassFunction) { + seen_non_target_vars_.insert(varId); + return false; + } + const uint32_t varPteTypeId = + varTypeInst->GetSingleWordInOperand(kTypePointerTypeIdInIdx); + ir::Instruction* varPteTypeInst = get_def_use_mgr()->GetDef(varPteTypeId); + if (!IsTargetType(varPteTypeInst)) { + seen_non_target_vars_.insert(varId); + return false; + } + seen_target_vars_.insert(varId); + return true; +} + +void MemPass::PatchPhis(uint32_t header_id, uint32_t back_id) { + ir::BasicBlock* header = cfg()->block(header_id); + auto phiItr = header->begin(); + for (; phiItr->opcode() == SpvOpPhi; ++phiItr) { + // Only patch phis that we created in a loop header. + // There might be other phis unrelated to our optimizations. + if (0 == phis_to_patch_.count(phiItr->result_id())) continue; + + // Find phi operand index for back edge + uint32_t cnt = 0; + uint32_t idx = phiItr->NumInOperands(); + phiItr->ForEachInId([&cnt, &back_id, &idx](uint32_t* iid) { + if (cnt % 2 == 1 && *iid == back_id) idx = cnt - 1; + ++cnt; + }); + assert(idx != phiItr->NumInOperands()); + + // Replace temporary phi operand with variable's value in backedge block + // map. Use undef if variable not in map. + const uint32_t varId = phiItr->GetSingleWordInOperand(idx); + uint32_t current_value = GetCurrentValue(varId, back_id); + uint32_t valId = + (current_value > 0) + ? current_value + : Type2Undef(GetPointeeTypeId(get_def_use_mgr()->GetDef(varId))); + phiItr->SetInOperand(idx, {valId}); + // Analyze uses now that they are complete + get_def_use_mgr()->AnalyzeInstUse(&*phiItr); + } +} + +bool MemPass::InsertPhiInstructions(ir::Function* func) { + // TODO(dnovillo) the current Phi placement mechanism assumes structured + // control-flow. This should be generalized + // (https://github.com/KhronosGroup/SPIRV-Tools/issues/893). + assert(context()->get_feature_mgr()->HasCapability(SpvCapabilityShader) && + "This only works on structured control flow"); + + bool modified = false; + + // Initialize the data structures used to insert Phi instructions. + InitSSARewrite(func); + + // Process all blocks in structured order. This is just one way (the + // simplest?) to make sure all predecessors blocks are processed before + // a block itself. + std::list structuredOrder; + cfg()->ComputeStructuredOrder(func, cfg()->pseudo_entry_block(), + &structuredOrder); + for (auto bi = structuredOrder.begin(); bi != structuredOrder.end(); ++bi) { + // Skip pseudo entry block + if (cfg()->IsPseudoEntryBlock(*bi)) { + continue; + } + + // Process all stores and loads of targeted variables. + if (SSABlockInit(bi)) { + modified = true; + } + + ir::BasicBlock* bp = *bi; + const uint32_t label = bp->id(); + ir::Instruction* inst = &*bp->begin(); + while (inst) { + ir::Instruction* next_instruction = inst->NextNode(); + switch (inst->opcode()) { + case SpvOpStore: { + uint32_t varId; + (void)GetPtr(inst, &varId); + if (!IsTargetVar(varId)) break; + // Register new stored value for the variable + block_defs_map_[label][varId] = + inst->GetSingleWordInOperand(kStoreValIdInIdx); + } break; + case SpvOpVariable: { + // Treat initialized OpVariable like an OpStore + if (inst->NumInOperands() < 2) break; + uint32_t varId = inst->result_id(); + if (!IsTargetVar(varId)) break; + // Register new stored value for the variable + block_defs_map_[label][varId] = + inst->GetSingleWordInOperand(kVariableInitIdInIdx); + } break; + case SpvOpLoad: { + uint32_t varId; + (void)GetPtr(inst, &varId); + if (!IsTargetVar(varId)) break; + modified = true; + uint32_t replId = GetCurrentValue(varId, label); + // If the variable is not defined, use undef. + if (replId == 0) { + replId = + Type2Undef(GetPointeeTypeId(get_def_use_mgr()->GetDef(varId))); + } + + // Replace load's id with the last stored value id for variable + // and delete load. Kill any names or decorates using id before + // replacing to prevent incorrect replacement in those instructions. + const uint32_t loadId = inst->result_id(); + context()->KillNamesAndDecorates(loadId); + (void)context()->ReplaceAllUsesWith(loadId, replId); + context()->KillInst(inst); + } break; + default: + break; + } + inst = next_instruction; + } + visitedBlocks_.insert(label); + // Look for successor backedge and patch phis in loop header + // if found. + uint32_t header = 0; + const auto* const_bp = bp; + const_bp->ForEachSuccessorLabel([&header, this](uint32_t succ) { + if (visitedBlocks_.find(succ) == visitedBlocks_.end()) return; + assert(header == 0); + header = succ; + }); + if (header != 0) PatchPhis(header, label); + } + + return modified; +} + +// Remove all |phi| operands coming from unreachable blocks (i.e., blocks not in +// |reachable_blocks|). There are two types of removal that this function can +// perform: +// +// 1- Any operand that comes directly from an unreachable block is completely +// removed. Since the block is unreachable, the edge between the unreachable +// block and the block holding |phi| has been removed. +// +// 2- Any operand that comes via a live block and was defined at an unreachable +// block gets its value replaced with an OpUndef value. Since the argument +// was generated in an unreachable block, it no longer exists, so it cannot +// be referenced. However, since the value does not reach |phi| directly +// from the unreachable block, the operand cannot be removed from |phi|. +// Therefore, we replace the argument value with OpUndef. +// +// For example, in the switch() below, assume that we want to remove the +// argument with value %11 coming from block %41. +// +// [ ... ] +// %41 = OpLabel <--- Unreachable block +// %11 = OpLoad %int %y +// [ ... ] +// OpSelectionMerge %16 None +// OpSwitch %12 %16 10 %13 13 %14 18 %15 +// %13 = OpLabel +// OpBranch %16 +// %14 = OpLabel +// OpStore %outparm %int_14 +// OpBranch %16 +// %15 = OpLabel +// OpStore %outparm %int_15 +// OpBranch %16 +// %16 = OpLabel +// %30 = OpPhi %int %11 %41 %int_42 %13 %11 %14 %11 %15 +// +// Since %41 is now an unreachable block, the first operand of |phi| needs to +// be removed completely. But the operands (%11 %14) and (%11 %15) cannot be +// removed because %14 and %15 are reachable blocks. Since %11 no longer exist, +// in those arguments, we replace all references to %11 with an OpUndef value. +// This results in |phi| looking like: +// +// %50 = OpUndef %int +// [ ... ] +// %30 = OpPhi %int %int_42 %13 %50 %14 %50 %15 +void MemPass::RemovePhiOperands( + ir::Instruction* phi, + std::unordered_set reachable_blocks) { + std::vector keep_operands; + uint32_t type_id = 0; + // The id of an undefined value we've generated. + uint32_t undef_id = 0; + + // Traverse all the operands in |phi|. Build the new operand vector by adding + // all the original operands from |phi| except the unwanted ones. + for (uint32_t i = 0; i < phi->NumOperands();) { + if (i < 2) { + // The first two arguments are always preserved. + keep_operands.push_back(phi->GetOperand(i)); + ++i; + continue; + } + + // The remaining Phi arguments come in pairs. Index 'i' contains the + // variable id, index 'i + 1' is the originating block id. + assert(i % 2 == 0 && i < phi->NumOperands() - 1 && + "malformed Phi arguments"); + + ir::BasicBlock* in_block = cfg()->block(phi->GetSingleWordOperand(i + 1)); + if (reachable_blocks.find(in_block) == reachable_blocks.end()) { + // If the incoming block is unreachable, remove both operands as this + // means that the |phi| has lost an incoming edge. + i += 2; + continue; + } + + // In all other cases, the operand must be kept but may need to be changed. + uint32_t arg_id = phi->GetSingleWordOperand(i); + ir::Instruction* arg_def_instr = get_def_use_mgr()->GetDef(arg_id); + ir::BasicBlock* def_block = context()->get_instr_block(arg_def_instr); + if (def_block && + reachable_blocks.find(def_block) == reachable_blocks.end()) { + // If the current |phi| argument was defined in an unreachable block, it + // means that this |phi| argument is no longer defined. Replace it with + // |undef_id|. + if (!undef_id) { + type_id = arg_def_instr->type_id(); + undef_id = Type2Undef(type_id); + } + keep_operands.push_back( + ir::Operand(spv_operand_type_t::SPV_OPERAND_TYPE_ID, {undef_id})); + } else { + // Otherwise, the argument comes from a reachable block or from no block + // at all (meaning that it was defined in the global section of the + // program). In both cases, keep the argument intact. + keep_operands.push_back(phi->GetOperand(i)); + } + + keep_operands.push_back(phi->GetOperand(i + 1)); + + i += 2; + } + + context()->ForgetUses(phi); + phi->ReplaceOperands(keep_operands); + context()->AnalyzeUses(phi); +} + +void MemPass::RemoveBlock(ir::Function::iterator* bi) { + auto& rm_block = **bi; + + // Remove instructions from the block. + rm_block.ForEachInst([&rm_block, this](ir::Instruction* inst) { + // Note that we do not kill the block label instruction here. The label + // instruction is needed to identify the block, which is needed by the + // removal of phi operands. + if (inst != rm_block.GetLabelInst()) { + context()->KillInst(inst); + } + }); + + // Remove the label instruction last. + auto label = rm_block.GetLabelInst(); + context()->KillInst(label); + + *bi = bi->Erase(); +} + +bool MemPass::RemoveUnreachableBlocks(ir::Function* func) { + bool modified = false; + + // Mark reachable all blocks reachable from the function's entry block. + std::unordered_set reachable_blocks; + std::unordered_set visited_blocks; + std::queue worklist; + reachable_blocks.insert(func->entry().get()); + + // Initially mark the function entry point as reachable. + worklist.push(func->entry().get()); + + auto mark_reachable = [&reachable_blocks, &visited_blocks, &worklist, + this](uint32_t label_id) { + auto successor = cfg()->block(label_id); + if (visited_blocks.count(successor) == 0) { + reachable_blocks.insert(successor); + worklist.push(successor); + visited_blocks.insert(successor); + } + }; + + // Transitively mark all blocks reachable from the entry as reachable. + while (!worklist.empty()) { + ir::BasicBlock* block = worklist.front(); + worklist.pop(); + + // All the successors of a live block are also live. + static_cast(block)->ForEachSuccessorLabel( + mark_reachable); + + // All the Merge and ContinueTarget blocks of a live block are also live. + block->ForMergeAndContinueLabel(mark_reachable); + } + + // Update operands of Phi nodes that reference unreachable blocks. + for (auto& block : *func) { + // If the block is about to be removed, don't bother updating its + // Phi instructions. + if (reachable_blocks.count(&block) == 0) { + continue; + } + + // If the block is reachable and has Phi instructions, remove all + // operands from its Phi instructions that reference unreachable blocks. + // If the block has no Phi instructions, this is a no-op. + block.ForEachPhiInst([&reachable_blocks, this](ir::Instruction* phi) { + RemovePhiOperands(phi, reachable_blocks); + }); + } + + // Erase unreachable blocks. + for (auto ebi = func->begin(); ebi != func->end();) { + if (reachable_blocks.count(&*ebi) == 0) { + RemoveBlock(&ebi); + modified = true; + } else { + ++ebi; + } + } + + return modified; +} + +bool MemPass::CFGCleanup(ir::Function* func) { + bool modified = false; + modified |= RemoveUnreachableBlocks(func); + return modified; +} + +void MemPass::CollectTargetVars(ir::Function* func) { + seen_target_vars_.clear(); + seen_non_target_vars_.clear(); + supported_ref_vars_.clear(); + type2undefs_.clear(); + + // Collect target (and non-) variable sets. Remove variables with + // non-load/store refs from target variable set + for (auto& blk : *func) { + for (auto& inst : blk) { + switch (inst.opcode()) { + case SpvOpStore: + case SpvOpLoad: { + uint32_t varId; + (void)GetPtr(&inst, &varId); + if (!IsTargetVar(varId)) break; + if (HasOnlySupportedRefs(varId)) break; + seen_non_target_vars_.insert(varId); + seen_target_vars_.erase(varId); + } break; + default: + break; + } + } + } +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/mem_pass.h b/3rdparty/spirv-tools/source/opt/mem_pass.h new file mode 100644 index 000000000..f3952c3a5 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/mem_pass.h @@ -0,0 +1,250 @@ +// Copyright (c) 2017 The Khronos Group Inc. +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_MEM_PASS_H_ +#define LIBSPIRV_OPT_MEM_PASS_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "basic_block.h" +#include "def_use_manager.h" +#include "dominator_analysis.h" +#include "module.h" +#include "pass.h" + +namespace spvtools { +namespace opt { + +// A common base class for mem2reg-type passes. Provides common +// utility functions and supporting state. +class MemPass : public Pass { + public: + MemPass(); + virtual ~MemPass() = default; + + // Returns an undef value for the given |var_id|'s type. + uint32_t GetUndefVal(uint32_t var_id) { + return Type2Undef(GetPointeeTypeId(get_def_use_mgr()->GetDef(var_id))); + } + + // Given a load or store |ip|, return the pointer instruction. + // Also return the base variable's id in |varId|. If no base variable is + // found, |varId| will be 0. + ir::Instruction* GetPtr(ir::Instruction* ip, uint32_t* varId); + + // Return true if |varId| is a previously identified target variable. + // Return false if |varId| is a previously identified non-target variable. + // + // Non-target variables are variable of function scope of a target type that + // are accessed with constant-index access chains. not accessed with + // non-constant-index access chains. Also cache non-target variables. + // + // If variable is not cached, return true if variable is a function scope + // variable of target type, false otherwise. Updates caches of target and + // non-target variables. + bool IsTargetVar(uint32_t varId); + + // Collect target SSA variables. This traverses all the loads and stores in + // function |func| looking for variables that can be replaced with SSA IDs. It + // populates the sets |seen_target_vars_|, |seen_non_target_vars_| and + // |supported_ref_vars_|. + void CollectTargetVars(ir::Function* func); + + protected: + // Returns true if |typeInst| is a scalar type + // or a vector or matrix + bool IsBaseTargetType(const ir::Instruction* typeInst) const; + + // Returns true if |typeInst| is a math type or a struct or array + // of a math type. + // TODO(): Add more complex types to convert + bool IsTargetType(const ir::Instruction* typeInst) const; + + // Returns true if |opcode| is a non-ptr access chain op + bool IsNonPtrAccessChain(const SpvOp opcode) const; + + // Given the id |ptrId|, return true if the top-most non-CopyObj is + // a variable, a non-ptr access chain or a parameter of pointer type. + bool IsPtr(uint32_t ptrId); + + // Given the id of a pointer |ptrId|, return the top-most non-CopyObj. + // Also return the base variable's id in |varId|. If no base variable is + // found, |varId| will be 0. + ir::Instruction* GetPtr(uint32_t ptrId, uint32_t* varId); + + // Return true if all uses of |id| are only name or decorate ops. + bool HasOnlyNamesAndDecorates(uint32_t id) const; + + // Kill all instructions in block |bp|. Whether or not to kill the label is + // indicated by |killLabel|. + void KillAllInsts(ir::BasicBlock* bp, bool killLabel = true); + + // Return true if any instruction loads from |varId| + bool HasLoads(uint32_t varId) const; + + // Return true if |varId| is not a function variable or if it has + // a load + bool IsLiveVar(uint32_t varId) const; + + // Return true if |storeInst| is not a function variable or if its + // base variable has a load + bool IsLiveStore(ir::Instruction* storeInst); + + // Add stores using |ptr_id| to |insts| + void AddStores(uint32_t ptr_id, std::queue* insts); + + // Delete |inst| and iterate DCE on all its operands if they are now + // useless. If a load is deleted and its variable has no other loads, + // delete all its variable's stores. + void DCEInst(ir::Instruction* inst, + const std::function&); + + // Call all the cleanup helper functions on |func|. + bool CFGCleanup(ir::Function* func); + + // Return true if |op| is supported decorate. + inline bool IsNonTypeDecorate(uint32_t op) const { + return (op == SpvOpDecorate || op == SpvOpDecorateId); + } + + // Return undef in function for type. Create and insert an undef after the + // first non-variable in the function if it doesn't already exist. Add + // undef to function undef map. + uint32_t Type2Undef(uint32_t type_id); + + // Insert Phi instructions in the CFG of |func|. This removes extra + // load/store operations to local storage while preserving the SSA form of the + // code. Returns true if the code was modified. + bool InsertPhiInstructions(ir::Function* func); + + // Cache of verified target vars + std::unordered_set seen_target_vars_; + + // Cache of verified non-target vars + std::unordered_set seen_non_target_vars_; + + private: + // Return true if all uses of |varId| are only through supported reference + // operations ie. loads and store. Also cache in supported_ref_vars_. + // TODO(dnovillo): This function is replicated in other passes and it's + // slightly different in every pass. Is it possible to make one common + // implementation? + bool HasOnlySupportedRefs(uint32_t varId); + + // Patch phis in loop header block |header_id| now that the map is complete + // for the backedge predecessor |back_id|. Specifically, for each phi, find + // the value corresponding to the backedge predecessor. That was temporarily + // set with the variable id that this phi corresponds to. Change this phi + // operand to the the value which corresponds to that variable in the + // predecessor map. + void PatchPhis(uint32_t header_id, uint32_t back_id); + + // Initialize data structures used by EliminateLocalMultiStore for + // function |func|, specifically block predecessors and target variables. + void InitSSARewrite(ir::Function* func); + + // Initialize block_defs_map_ entry for loop header block pointed to + // |block_itr| by merging entries from all predecessors. If any value + // ids differ for any variable across predecessors, create a phi function + // in the block and use that value id for the variable in the new map. + // Assumes all predecessors have been visited by EliminateLocalMultiStore + // except the back edge. Use a dummy value in the phi for the back edge + // until the back edge block is visited and patch the phi value then. + // Returns true if the code was modified. + bool SSABlockInitLoopHeader(std::list::iterator block_itr); + + // Initialize block_defs_map_ entry for multiple predecessor block + // |block_ptr| by merging block_defs_map_ entries for all predecessors. + // If any value ids differ for any variable across predecessors, create + // a phi function in the block and use that value id for the variable in + // the new map. Assumes all predecessors have been visited by + // EliminateLocalMultiStore. + // Returns true if the code was modified. + bool SSABlockInitMultiPred(ir::BasicBlock* block_ptr); + + // Initialize the label2ssa_map entry for a block pointed to by |block_itr|. + // Insert phi instructions into block when necessary. All predecessor + // blocks must have been visited by EliminateLocalMultiStore except for + // backedges. + // Returns true if the code was modified. + bool SSABlockInit(std::list::iterator block_itr); + + // Return true if variable is loaded in block with |label| or in any + // succeeding block in structured order. + bool IsLiveAfter(uint32_t var_id, uint32_t label) const; + + // Remove all the unreachable basic blocks in |func|. + bool RemoveUnreachableBlocks(ir::Function* func); + + // Remove the block pointed by the iterator |*bi|. This also removes + // all the instructions in the pointed-to block. + void RemoveBlock(ir::Function::iterator* bi); + + // Remove Phi operands in |phi| that are coming from blocks not in + // |reachable_blocks|. + void RemovePhiOperands(ir::Instruction* phi, + std::unordered_set reachable_blocks); + + // Collects a map of all the live variables and their values along the path of + // dominator parents starting at |block_label|. Each entry + // |live_vars[var_id]| returns the latest value of |var_id| along that + // dominator path. Note that the mapping |live_vars| is never cleared, + // multiple calls to this function will accumulate new + // mappings. This is done to support the logic in + // MemPass::SSABlockInitLoopHeader. + void CollectLiveVars(uint32_t block_label, + std::map* live_vars); + + // Returns the ID of the most current value taken by variable |var_id| on the + // dominator path starting at |block_id|. This walks the dominator parents + // starting at |block_id| and returns the first value it finds for |var_id|. + // If no value for |var_id| is found along the dominator path, this returns 0. + uint32_t GetCurrentValue(uint32_t var_id, uint32_t block_label); + + // Dominator information. + DominatorAnalysis* dominator_; + + // Each entry |block_defs_map_[block_id]| contains a map {variable_id, + // value_id} associating all the variables |variable_id| stored in |block_id| + // to their respective value |value_id|. + std::unordered_map> + block_defs_map_; + + // Set of label ids of visited blocks + std::unordered_set visitedBlocks_; + + // Variables that are only referenced by supported operations for this + // pass ie. loads and stores. + std::unordered_set supported_ref_vars_; + + // Map from type to undef + std::unordered_map type2undefs_; + + // The Ids of OpPhi instructions that are in a loop header and which require + // patching of the value for the loop back-edge. + std::unordered_set phis_to_patch_; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_MEM_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/merge_return_pass.cpp b/3rdparty/spirv-tools/source/opt/merge_return_pass.cpp new file mode 100644 index 000000000..7fafd5830 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/merge_return_pass.cpp @@ -0,0 +1,661 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "merge_return_pass.h" + +#include "instruction.h" +#include "ir_builder.h" +#include "ir_context.h" +#include "make_unique.h" +#include "reflect.h" + +namespace spvtools { +namespace opt { + +Pass::Status MergeReturnPass::Process(ir::IRContext* irContext) { + InitializeProcessing(irContext); + + bool modified = false; + bool is_shader = + context()->get_feature_mgr()->HasCapability(SpvCapabilityShader); + for (auto& function : *get_module()) { + std::vector return_blocks = CollectReturnBlocks(&function); + if (return_blocks.size() <= 1) continue; + + function_ = &function; + return_flag_ = nullptr; + return_value_ = nullptr; + final_return_block_ = nullptr; + + modified = true; + if (is_shader) { + ProcessStructured(&function, return_blocks); + } else { + MergeReturnBlocks(&function, return_blocks); + } + } + + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; +} + +void MergeReturnPass::ProcessStructured( + ir::Function* function, const std::vector& return_blocks) { + std::list order; + cfg()->ComputeStructuredOrder(function, &*function->begin(), &order); + + // Create the new return block + CreateReturnBlock(); + + // Create the return + CreateReturn(final_return_block_); + + cfg()->RegisterBlock(final_return_block_); + + state_.clear(); + state_.emplace_back(nullptr, nullptr); + for (auto block : order) { + if (cfg()->IsPseudoEntryBlock(block) || cfg()->IsPseudoExitBlock(block)) { + continue; + } + + auto blockId = block->GetLabelInst()->result_id(); + if (blockId == CurrentState().CurrentMergeId()) { + // Pop the current state as we've hit the merge + state_.pop_back(); + } + + ProcessStructuredBlock(block); + + // Generate state for next block + if (ir::Instruction* mergeInst = block->GetMergeInst()) { + ir::Instruction* loopMergeInst = block->GetLoopMergeInst(); + if (!loopMergeInst) loopMergeInst = state_.back().LoopMergeInst(); + state_.emplace_back(loopMergeInst, mergeInst); + } + } + + // Predicate successors of the original return blocks as necessary. + PredicateBlocks(return_blocks); + + // We have not kept the dominator tree up-to-date. + // Invalidate it at this point to make sure it will be rebuilt. + context()->RemoveDominatorAnalysis(function); + AddNewPhiNodes(); +} + +void MergeReturnPass::CreateReturnBlock() { + // Create a label for the new return block + std::unique_ptr return_label( + new ir::Instruction(context(), SpvOpLabel, 0u, TakeNextId(), {})); + + // Create the new basic block + std::unique_ptr return_block( + new ir::BasicBlock(std::move(return_label))); + function_->AddBasicBlock(std::move(return_block)); + final_return_block_ = &*(--function_->end()); + context()->AnalyzeDefUse(final_return_block_->GetLabelInst()); + context()->set_instr_block(final_return_block_->GetLabelInst(), + final_return_block_); + final_return_block_->SetParent(function_); +} + +void MergeReturnPass::CreateReturn(ir::BasicBlock* block) { + AddReturnValue(); + + if (return_value_) { + // Load and return the final return value + uint32_t loadId = TakeNextId(); + block->AddInstruction(MakeUnique( + context(), SpvOpLoad, function_->type_id(), loadId, + std::initializer_list{ + {SPV_OPERAND_TYPE_ID, {return_value_->result_id()}}})); + ir::Instruction* var_inst = block->terminator(); + context()->AnalyzeDefUse(var_inst); + context()->set_instr_block(var_inst, block); + + block->AddInstruction(MakeUnique( + context(), SpvOpReturnValue, 0, 0, + std::initializer_list{{SPV_OPERAND_TYPE_ID, {loadId}}})); + context()->AnalyzeDefUse(block->terminator()); + context()->set_instr_block(block->terminator(), block); + } else { + block->AddInstruction(MakeUnique(context(), SpvOpReturn)); + context()->AnalyzeDefUse(block->terminator()); + context()->set_instr_block(block->terminator(), block); + } +} + +void MergeReturnPass::ProcessStructuredBlock(ir::BasicBlock* block) { + SpvOp tail_opcode = block->tail()->opcode(); + if (tail_opcode == SpvOpReturn || tail_opcode == SpvOpReturnValue) { + if (!return_flag_) { + AddReturnFlag(); + } + } + + if (tail_opcode == SpvOpReturn || tail_opcode == SpvOpReturnValue || + tail_opcode == SpvOpUnreachable) { + if (CurrentState().InLoop()) { + // Can always break out of innermost loop + BranchToBlock(block, CurrentState().LoopMergeId()); + } else if (CurrentState().InStructuredFlow()) { + BranchToBlock(block, CurrentState().CurrentMergeId()); + } else { + BranchToBlock(block, final_return_block_->id()); + } + } +} + +void MergeReturnPass::BranchToBlock(ir::BasicBlock* block, uint32_t target) { + if (block->tail()->opcode() == SpvOpReturn || + block->tail()->opcode() == SpvOpReturnValue) { + RecordReturned(block); + RecordReturnValue(block); + } + + // Fix up existing phi nodes. + // + // A new edge is being added from |block| to |target|, so go through + // |target|'s phi nodes add an undef incoming value for |block|. + ir::BasicBlock* target_block = context()->get_instr_block(target); + target_block->ForEachPhiInst([this, block](ir::Instruction* inst) { + uint32_t undefId = Type2Undef(inst->type_id()); + inst->AddOperand({SPV_OPERAND_TYPE_ID, {undefId}}); + inst->AddOperand({SPV_OPERAND_TYPE_ID, {block->id()}}); + context()->UpdateDefUse(inst); + }); + + const auto& target_pred = cfg()->preds(target); + if (target_pred.size() == 1) { + MarkForNewPhiNodes(target_block, + context()->get_instr_block(target_pred[0])); + } + + ir::Instruction* return_inst = block->terminator(); + return_inst->SetOpcode(SpvOpBranch); + return_inst->ReplaceOperands({{SPV_OPERAND_TYPE_ID, {target}}}); + context()->get_def_use_mgr()->AnalyzeInstDefUse(return_inst); + cfg()->AddEdge(block->id(), target); +} + +void MergeReturnPass::CreatePhiNodesForInst(ir::BasicBlock* merge_block, + uint32_t predecessor, + ir::Instruction& inst) { + opt::DominatorAnalysis* dom_tree = + context()->GetDominatorAnalysis(merge_block->GetParent(), *cfg()); + ir::BasicBlock* inst_bb = context()->get_instr_block(&inst); + + if (inst.result_id() != 0) { + std::vector users_to_update; + context()->get_def_use_mgr()->ForEachUser( + &inst, + [&users_to_update, &dom_tree, inst_bb, this](ir::Instruction* user) { + if (!dom_tree->Dominates(inst_bb, context()->get_instr_block(user))) { + users_to_update.push_back(user); + } + }); + + if (users_to_update.empty()) { + return; + } + + // There is at least one values that needs to be replaced. + // First create the OpPhi instruction. + InstructionBuilder builder(context(), &*merge_block->begin(), + ir::IRContext::kAnalysisDefUse); + uint32_t undef_id = Type2Undef(inst.type_id()); + std::vector phi_operands; + + // Add the operands for the defining instructions. + phi_operands.push_back(inst.result_id()); + phi_operands.push_back(predecessor); + + // Add undef from all other blocks. + std::vector preds = cfg()->preds(merge_block->id()); + for (uint32_t pred_id : preds) { + if (pred_id != predecessor) { + phi_operands.push_back(undef_id); + phi_operands.push_back(pred_id); + } + } + + ir::Instruction* new_phi = builder.AddPhi(inst.type_id(), phi_operands); + uint32_t result_of_phi = new_phi->result_id(); + + // Update all of the users to use the result of the new OpPhi. + for (ir::Instruction* user : users_to_update) { + user->ForEachInId([&inst, result_of_phi](uint32_t* id) { + if (*id == inst.result_id()) { + *id = result_of_phi; + } + }); + context()->AnalyzeUses(user); + } + } +} + +void MergeReturnPass::PredicateBlocks( + const std::vector& return_blocks) { + // The CFG is being modified as the function proceeds so avoid caching + // successors. + std::vector stack; + auto add_successors = [this, &stack](ir::BasicBlock* block) { + const ir::BasicBlock* const_block = + const_cast(block); + const_block->ForEachSuccessorLabel([this, &stack](const uint32_t idx) { + stack.push_back(context()->get_instr_block(idx)); + }); + }; + + std::unordered_set seen; + std::unordered_set predicated; + for (auto b : return_blocks) { + seen.clear(); + add_successors(b); + + while (!stack.empty()) { + ir::BasicBlock* block = stack.back(); + assert(block); + stack.pop_back(); + + if (block == b) continue; + if (block == final_return_block_) continue; + if (!seen.insert(block).second) continue; + if (!predicated.insert(block).second) continue; + + // Skip structured subgraphs. + ir::BasicBlock* next = block; + while (next->GetMergeInst()) { + next = context()->get_instr_block(next->MergeBlockIdIfAny()); + } + add_successors(next); + PredicateBlock(block, next, &predicated); + } + } +} + +bool MergeReturnPass::RequiresPredication( + const ir::BasicBlock* block, const ir::BasicBlock* tail_block) const { + // This is intentionally conservative. + // TODO(alanbaker): re-visit this when more performance data is available. + if (block != tail_block) return true; + + bool requires_predicate = false; + block->ForEachInst([&requires_predicate](const ir::Instruction* inst) { + if (inst->opcode() != SpvOpPhi && inst->opcode() != SpvOpLabel && + !ir::IsTerminatorInst(inst->opcode())) { + requires_predicate = true; + } + }); + return requires_predicate; +} + +void MergeReturnPass::PredicateBlock( + ir::BasicBlock* block, ir::BasicBlock* tail_block, + std::unordered_set* predicated) { + if (!RequiresPredication(block, tail_block)) { + return; + } + + // Make sure the cfg is build here. If we don't then it becomes very hard to + // know which new blocks need to be updated. + context()->BuildInvalidAnalyses(ir::IRContext::kAnalysisCFG); + + // When predicating, be aware of whether this block is a header block, a merge + // block or both. + // + // If this block is a merge block, ensure the appropriate header stays + // up-to-date with any changes (i.e. points to the pre-header). + // + // If this block is a header block, predicate the entire structured subgraph. + // This can act recursively. + + // If |block| is a loop head, then the back edge must jump to the original + // code, not the new header. + if (block->GetLoopMergeInst()) { + cfg()->SplitLoopHeader(block); + } + + // Leave the phi instructions behind. + auto iter = block->begin(); + while (iter->opcode() == SpvOpPhi) { + ++iter; + } + + // Forget about the edges leaving block. They will be removed. + cfg()->RemoveSuccessorEdges(block); + + std::unique_ptr new_block( + block->SplitBasicBlock(context(), TakeNextId(), iter)); + ir::BasicBlock* old_body = + function_->InsertBasicBlockAfter(std::move(new_block), block); + predicated->insert(old_body); + + if (tail_block == block) { + tail_block = old_body; + } + + const ir::BasicBlock* const_old_body = + static_cast(old_body); + const_old_body->ForEachSuccessorLabel( + [old_body, block, this](const uint32_t label) { + ir::BasicBlock* target_bb = context()->get_instr_block(label); + if (MarkedSinglePred(target_bb) == block) { + MarkForNewPhiNodes(target_bb, old_body); + } + }); + + std::unique_ptr new_merge_block(new ir::BasicBlock( + MakeUnique(context(), SpvOpLabel, 0, TakeNextId(), + std::initializer_list{}))); + + ir::BasicBlock* new_merge = + function_->InsertBasicBlockAfter(std::move(new_merge_block), tail_block); + predicated->insert(new_merge); + new_merge->SetParent(function_); + + // Register the new labels. + get_def_use_mgr()->AnalyzeInstDef(old_body->GetLabelInst()); + context()->set_instr_block(old_body->GetLabelInst(), old_body); + get_def_use_mgr()->AnalyzeInstDef(new_merge->GetLabelInst()); + context()->set_instr_block(new_merge->GetLabelInst(), new_merge); + + // Move the tail branch into the new merge and fix the mapping. If a single + // block is being predicated then its branch was moved to the old body + // previously. + std::unique_ptr inst; + ir::Instruction* i = tail_block->terminator(); + cfg()->RemoveSuccessorEdges(tail_block); + get_def_use_mgr()->ClearInst(i); + inst.reset(std::move(i)); + inst->RemoveFromList(); + new_merge->end().InsertBefore(std::move(inst)); + get_def_use_mgr()->AnalyzeInstUse(new_merge->terminator()); + context()->set_instr_block(new_merge->terminator(), new_merge); + + // Add a branch to the new merge. If we jumped multiple blocks, the branch is + // added to tail_block, otherwise the branch belongs in old_body. + tail_block->AddInstruction(MakeUnique( + context(), SpvOpBranch, 0, 0, + std::initializer_list{ + {SPV_OPERAND_TYPE_ID, {new_merge->id()}}})); + get_def_use_mgr()->AnalyzeInstUse(tail_block->terminator()); + context()->set_instr_block(tail_block->terminator(), tail_block); + + // Within the new header we need the following: + // 1. Load of the return status flag + // 2. Declare the merge block + // 3. Branch to new merge (true) or old body (false) + + // 1. Load of the return status flag + analysis::Bool bool_type; + uint32_t bool_id = context()->get_type_mgr()->GetId(&bool_type); + assert(bool_id != 0); + uint32_t load_id = TakeNextId(); + block->AddInstruction(MakeUnique( + context(), SpvOpLoad, bool_id, load_id, + std::initializer_list{ + {SPV_OPERAND_TYPE_ID, {return_flag_->result_id()}}})); + get_def_use_mgr()->AnalyzeInstDefUse(block->terminator()); + context()->set_instr_block(block->terminator(), block); + + // 2. Declare the merge block + block->AddInstruction( + MakeUnique(context(), SpvOpSelectionMerge, 0, 0, + std::initializer_list{ + {SPV_OPERAND_TYPE_ID, {new_merge->id()}}, + {SPV_OPERAND_TYPE_SELECTION_CONTROL, + {SpvSelectionControlMaskNone}}})); + get_def_use_mgr()->AnalyzeInstUse(block->terminator()); + context()->set_instr_block(block->terminator(), block); + + // 3. Branch to new merge (true) or old body (false) + block->AddInstruction(MakeUnique( + context(), SpvOpBranchConditional, 0, 0, + std::initializer_list{ + {SPV_OPERAND_TYPE_ID, {load_id}}, + {SPV_OPERAND_TYPE_ID, {new_merge->id()}}, + {SPV_OPERAND_TYPE_ID, {old_body->id()}}})); + get_def_use_mgr()->AnalyzeInstUse(block->terminator()); + context()->set_instr_block(block->terminator(), block); + + assert(old_body->begin() != old_body->end()); + assert(block->begin() != block->end()); + assert(new_merge->begin() != new_merge->end()); + + // Update the cfg + cfg()->AddEdges(block); + cfg()->RegisterBlock(old_body); + if (old_body != tail_block) { + cfg()->AddEdges(tail_block); + } + cfg()->RegisterBlock(new_merge); + MarkForNewPhiNodes(new_merge, tail_block); +} + +void MergeReturnPass::RecordReturned(ir::BasicBlock* block) { + if (block->tail()->opcode() != SpvOpReturn && + block->tail()->opcode() != SpvOpReturnValue) + return; + + assert(return_flag_ && "Did not generate the return flag variable."); + + if (!constant_true_) { + analysis::Bool temp; + const analysis::Bool* bool_type = + context()->get_type_mgr()->GetRegisteredType(&temp)->AsBool(); + + analysis::ConstantManager* const_mgr = context()->get_constant_mgr(); + const analysis::Constant* true_const = + const_mgr->GetConstant(bool_type, {true}); + constant_true_ = const_mgr->GetDefiningInstruction(true_const); + context()->UpdateDefUse(constant_true_); + } + + std::unique_ptr return_store(new ir::Instruction( + context(), SpvOpStore, 0, 0, + std::initializer_list{ + {SPV_OPERAND_TYPE_ID, {return_flag_->result_id()}}, + {SPV_OPERAND_TYPE_ID, {constant_true_->result_id()}}})); + + ir::Instruction* store_inst = + &*block->tail().InsertBefore(std::move(return_store)); + context()->set_instr_block(store_inst, block); + context()->AnalyzeDefUse(store_inst); +} + +void MergeReturnPass::RecordReturnValue(ir::BasicBlock* block) { + auto terminator = *block->tail(); + if (terminator.opcode() != SpvOpReturnValue) { + return; + } + + assert(return_value_ && + "Did not generate the variable to hold the return value."); + + std::unique_ptr value_store(new ir::Instruction( + context(), SpvOpStore, 0, 0, + std::initializer_list{ + {SPV_OPERAND_TYPE_ID, {return_value_->result_id()}}, + {SPV_OPERAND_TYPE_ID, {terminator.GetSingleWordInOperand(0u)}}})); + + ir::Instruction* store_inst = + &*block->tail().InsertBefore(std::move(value_store)); + context()->set_instr_block(store_inst, block); + context()->AnalyzeDefUse(store_inst); +} + +void MergeReturnPass::AddReturnValue() { + if (return_value_) return; + + uint32_t return_type_id = function_->type_id(); + if (get_def_use_mgr()->GetDef(return_type_id)->opcode() == SpvOpTypeVoid) + return; + + uint32_t return_ptr_type = context()->get_type_mgr()->FindPointerToType( + return_type_id, SpvStorageClassFunction); + + uint32_t var_id = TakeNextId(); + std::unique_ptr returnValue(new ir::Instruction( + context(), SpvOpVariable, return_ptr_type, var_id, + std::initializer_list{ + {SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}}})); + + auto insert_iter = function_->begin()->begin(); + insert_iter.InsertBefore(std::move(returnValue)); + ir::BasicBlock* entry_block = &*function_->begin(); + return_value_ = &*entry_block->begin(); + context()->AnalyzeDefUse(return_value_); + context()->set_instr_block(return_value_, entry_block); +} + +void MergeReturnPass::AddReturnFlag() { + if (return_flag_) return; + + analysis::TypeManager* type_mgr = context()->get_type_mgr(); + analysis::ConstantManager* const_mgr = context()->get_constant_mgr(); + + analysis::Bool temp; + uint32_t bool_id = type_mgr->GetTypeInstruction(&temp); + analysis::Bool* bool_type = type_mgr->GetType(bool_id)->AsBool(); + + const analysis::Constant* false_const = + const_mgr->GetConstant(bool_type, {false}); + uint32_t const_false_id = + const_mgr->GetDefiningInstruction(false_const)->result_id(); + + uint32_t bool_ptr_id = + type_mgr->FindPointerToType(bool_id, SpvStorageClassFunction); + + uint32_t var_id = TakeNextId(); + std::unique_ptr returnFlag(new ir::Instruction( + context(), SpvOpVariable, bool_ptr_id, var_id, + std::initializer_list{ + {SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}}, + {SPV_OPERAND_TYPE_ID, {const_false_id}}})); + + auto insert_iter = function_->begin()->begin(); + + insert_iter.InsertBefore(std::move(returnFlag)); + ir::BasicBlock* entry_block = &*function_->begin(); + return_flag_ = &*entry_block->begin(); + context()->AnalyzeDefUse(return_flag_); + context()->set_instr_block(return_flag_, entry_block); +} + +std::vector MergeReturnPass::CollectReturnBlocks( + ir::Function* function) { + std::vector return_blocks; + for (auto& block : *function) { + ir::Instruction& terminator = *block.tail(); + if (terminator.opcode() == SpvOpReturn || + terminator.opcode() == SpvOpReturnValue) { + return_blocks.push_back(&block); + } + } + return return_blocks; +} + +void MergeReturnPass::MergeReturnBlocks( + ir::Function* function, const std::vector& return_blocks) { + if (return_blocks.size() <= 1) { + // No work to do. + return; + } + + CreateReturnBlock(); + uint32_t return_id = final_return_block_->id(); + auto ret_block_iter = --function->end(); + // Create the PHI for the merged block (if necessary). + // Create new return. + std::vector phi_ops; + for (auto block : return_blocks) { + if (block->tail()->opcode() == SpvOpReturnValue) { + phi_ops.push_back( + {SPV_OPERAND_TYPE_ID, {block->tail()->GetSingleWordInOperand(0u)}}); + phi_ops.push_back({SPV_OPERAND_TYPE_ID, {block->id()}}); + } + } + + if (!phi_ops.empty()) { + // Need a PHI node to select the correct return value. + uint32_t phi_result_id = TakeNextId(); + uint32_t phi_type_id = function->type_id(); + std::unique_ptr phi_inst(new ir::Instruction( + context(), SpvOpPhi, phi_type_id, phi_result_id, phi_ops)); + ret_block_iter->AddInstruction(std::move(phi_inst)); + ir::BasicBlock::iterator phiIter = ret_block_iter->tail(); + + std::unique_ptr return_inst( + new ir::Instruction(context(), SpvOpReturnValue, 0u, 0u, + {{SPV_OPERAND_TYPE_ID, {phi_result_id}}})); + ret_block_iter->AddInstruction(std::move(return_inst)); + ir::BasicBlock::iterator ret = ret_block_iter->tail(); + + // Register the phi def and mark instructions for use updates. + get_def_use_mgr()->AnalyzeInstDefUse(&*phiIter); + get_def_use_mgr()->AnalyzeInstDef(&*ret); + } else { + std::unique_ptr return_inst( + new ir::Instruction(context(), SpvOpReturn)); + ret_block_iter->AddInstruction(std::move(return_inst)); + } + + // Replace returns with branches + for (auto block : return_blocks) { + context()->ForgetUses(block->terminator()); + block->tail()->SetOpcode(SpvOpBranch); + block->tail()->ReplaceOperands({{SPV_OPERAND_TYPE_ID, {return_id}}}); + get_def_use_mgr()->AnalyzeInstUse(block->terminator()); + get_def_use_mgr()->AnalyzeInstUse(block->GetLabelInst()); + } + + get_def_use_mgr()->AnalyzeInstDefUse(ret_block_iter->GetLabelInst()); +} + +void MergeReturnPass::AddNewPhiNodes() { + opt::DominatorAnalysis* dom_tree = + context()->GetDominatorAnalysis(function_, *cfg()); + std::list order; + cfg()->ComputeStructuredOrder(function_, &*function_->begin(), &order); + + for (ir::BasicBlock* bb : order) { + AddNewPhiNodes(bb, new_merge_nodes_[bb], + dom_tree->ImmediateDominator(bb)->id()); + } +} + +void MergeReturnPass::AddNewPhiNodes(ir::BasicBlock* bb, ir::BasicBlock* pred, + uint32_t header_id) { + opt::DominatorAnalysis* dom_tree = + context()->GetDominatorAnalysis(function_, *cfg()); + // Insert as a stopping point. We do not have to add anything in the block or + // above because the header dominates |bb|. + + ir::BasicBlock* current_bb = pred; + while (current_bb != nullptr && current_bb->id() != header_id) { + for (ir::Instruction& inst : *current_bb) { + CreatePhiNodesForInst(bb, pred->id(), inst); + } + current_bb = dom_tree->ImmediateDominator(current_bb); + } +} + +void MergeReturnPass::MarkForNewPhiNodes(ir::BasicBlock* block, + ir::BasicBlock* single_original_pred) { + new_merge_nodes_[block] = single_original_pred; +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/merge_return_pass.h b/3rdparty/spirv-tools/source/opt/merge_return_pass.h new file mode 100644 index 000000000..b4f47e3ad --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/merge_return_pass.h @@ -0,0 +1,307 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_MERGE_RETURN_PASS_H_ +#define LIBSPIRV_OPT_MERGE_RETURN_PASS_H_ + +#include "basic_block.h" +#include "function.h" +#include "mem_pass.h" + +#include +#include + +namespace spvtools { +namespace opt { + +/******************************************************************************* + * + * Handling Structured Control Flow: + * + * Structured control flow guarantees that the CFG will reconverge at a given + * point (the merge block). Within structured control flow, all blocks must be + * post-dominated by the merge block, except return blocks and break blocks. + * A break block is a block that branches to the innermost loop's merge block. + * + * Beyond this, we further assume that all unreachable blocks have been + * cleanedup. This means that the only unreachable blocks are those necessary + * for valid structured control flow. + * + * Algorithm: + * + * If a return is encountered, it should record that: i) the function has + * "returned" and ii) the value of the return. The return should be replaced + * with a branch. If current block is not within structured control flow, this + * is the final return. This block should branch to the new return block (its + * direct successor). If the current block is within structured control flow, + * the branch destination should be the innermost loop's merge (if it exists) + * or the merge block of the immediate structured control flow. If the merge + * block produces any live values it will need to be predicated. While the merge + * is nested in structured control flow, the predication path should branch to + * the next best merge block available. Once structured control flow has been + * exited, remaining blocks must be predicated with new structured control flow + * (OpSelectionMerge). These should be nested correctly in case of straight line + * branching to reach the final return block. + * + * In the final return block, the return value should be loaded and returned. + * Memory promotion passes should be able to promote the newly introduced + * variables ("has returned" and "return value"). + * + * Predicating the Final Merge: + * + * At each merge block predication needs to be introduced (optimization: only if + * that block produces value live beyond it). This needs to be done carefully. + * The merge block should be split into multiple blocks. + * + * 1 (header) + * / \ + * (ret) 2 3 (merge) + * + * || + * \/ + * + * 1 (header) + * / \ + * 2 | + * \ / + * 3 (merge for 1, new header) + * / \ + * | 3 (old body) + * \ / + * (ret) 4 (new merge) + * + * In the above (simple) example, the return originally in |2| is passed through + * the merge. That merge is predicated such that the old body of the block is + * the else branch. The branch condition is based on the value of the "has + * returned" variable. In more complicated examples (blocks between |1| and + * |3|), the SSA would need to fixed up due the newly reconvergent path at the + * merge for |1|. Assuming |3| originally was also a return block, the old body + * of |3| should also store the return value for that case. The return value in + * |4| just requires loading the return value variable. + * + ******************************************************************************/ + +// Documented in optimizer.hpp +class MergeReturnPass : public MemPass { + public: + MergeReturnPass() + : function_(nullptr), + return_flag_(nullptr), + return_value_(nullptr), + constant_true_(nullptr), + final_return_block_(nullptr) {} + + const char* name() const override { return "merge-return"; } + Status Process(ir::IRContext*) override; + + ir::IRContext::Analysis GetPreservedAnalyses() override { + // return ir::IRContext::kAnalysisDefUse; + return ir::IRContext::kAnalysisNone; + } + + private: + // This class is used to store the a loop merge instruction and a selection + // merge instruction. The intended use is that is represent the inner most + // contain selection construct and the inner most loop construct. + class StructuredControlState { + public: + StructuredControlState(ir::Instruction* loop, ir::Instruction* merge) + : loop_merge_(loop), current_merge_(merge) {} + + StructuredControlState(const StructuredControlState&) = default; + + bool InLoop() const { return loop_merge_; } + bool InStructuredFlow() const { return CurrentMergeId() != 0; } + + uint32_t CurrentMergeId() const { + return current_merge_ ? current_merge_->GetSingleWordInOperand(0u) : 0u; + } + + uint32_t CurrentMergeHeader() const { + return current_merge_ ? current_merge_->context() + ->get_instr_block(current_merge_) + ->id() + : 0; + } + + uint32_t LoopMergeId() const { + return loop_merge_ ? loop_merge_->GetSingleWordInOperand(0u) : 0u; + } + + uint32_t CurrentLoopHeader() const { + return loop_merge_ + ? loop_merge_->context()->get_instr_block(loop_merge_)->id() + : 0; + } + + ir::Instruction* LoopMergeInst() const { return loop_merge_; } + + private: + ir::Instruction* loop_merge_; + ir::Instruction* current_merge_; + }; + + // Returns all BasicBlocks terminated by OpReturn or OpReturnValue in + // |function|. + std::vector CollectReturnBlocks(ir::Function* function); + + // Creates a new basic block with a single return. If |function| returns a + // value, a phi node is created to select the correct value to return. + // Replaces old returns with an unconditional branch to the new block. + void MergeReturnBlocks(ir::Function* function, + const std::vector& returnBlocks); + + // Merges the return instruction in |function| so that it has a single return + // statement. It is assumed that |function| has structured control flow, and + // that |return_blocks| is a list of all of the basic blocks in |function| + // that have a return. + void ProcessStructured(ir::Function* function, + const std::vector& return_blocks); + + // Changes an OpReturn* or OpUnreachable instruction at the end of |block| + // into a store to |return_flag_|, a store to |return_value_| (if necessary), + // and a branch to the appropriate merge block. + // + // Is is assumed that |AddReturnValue| have already been called to created the + // variable to store a return value if there is one. + // + // Note this will break the semantics. To fix this, PredicateBlock will have + // to be called on the merge block the branch targets. + void ProcessStructuredBlock(ir::BasicBlock* block); + + // Creates a variable used to store whether or not the control flow has + // traversed a block that used to have a return. A pointer to the instruction + // declaring the variable is stored in |return_flag_|. + void AddReturnFlag(); + + // Creates the variable used to store the return value when passing through + // a block that use to contain an OpReturnValue. + void AddReturnValue(); + + // Adds a store that stores true to |return_flag_| immediately before the + // terminator of |block|. It is assumed that |AddReturnFlag| has already been + // called. + void RecordReturned(ir::BasicBlock* block); + + // Adds an instruction that stores the value being returned in the + // OpReturnValue in |block|. The value is stored to |return_value_|, and the + // store is placed before the OpReturnValue. + // + // If |block| does not contain an OpReturnValue, then this function has no + // effect. If |block| contains an OpReturnValue, then |AddReturnValue| must + // have already been called to create the variable to store to. + void RecordReturnValue(ir::BasicBlock* block); + + // Adds an unconditional branch in |block| that branches to |target|. It also + // adds stores to |return_flag_| and |return_value_| as needed. + // |AddReturnFlag| and |AddReturnValue| must have already been called. + void BranchToBlock(ir::BasicBlock* block, uint32_t target); + + // Returns true if we need to pridicate |block| where |tail_block| is the + // merge point. (See |PredicateBlocks|). There is no need to predicate if + // there is no code that could be executed. + bool RequiresPredication(const ir::BasicBlock* block, + const ir::BasicBlock* tail_block) const; + + // For every basic block that is reachable from a basic block in + // |return_blocks|, extra code is added to jump around any code that should + // not be executed because the original code would have already returned. This + // involves adding new selections constructs to jump around these + // instructions. + void PredicateBlocks(const std::vector& return_blocks); + + // Add the predication code (see |PredicateBlocks|) to |tail_block| if it + // requires predication. |tail_block| and any new blocks that are known to + // not require predication will be added to |predicated|. + void PredicateBlock(ir::BasicBlock* block, ir::BasicBlock* tail_block, + std::unordered_set* predicated); + + // Add an |OpReturn| or |OpReturnValue| to the end of |block|. If an + // |OpReturnValue| is needed, the return value is loaded from |return_value_|. + void CreateReturn(ir::BasicBlock* block); + + // Creates a block at the end of the function that will become the single + // return block at the end of the pass. + void CreateReturnBlock(); + + // Creates a Phi node in |merge_block| for the result of |inst| coming from + // |predecessor|. Any uses of the result of |inst| that are no longer + // dominated by |inst|, are replaced with the result of the new |OpPhi| + // instruction. + void CreatePhiNodesForInst(ir::BasicBlock* merge_block, uint32_t predecessor, + ir::Instruction& inst); + + // Traverse the nodes in |new_merge_nodes_|, and adds the OpPhi instructions + // that are needed to make the code correct. It is assumed that at this point + // there are no unreachable blocks in the control flow graph. + void AddNewPhiNodes(); + + // Creates any new phi nodes that are needed in |bb| now that |pred| is no + // longer the only block that preceedes |bb|. |header_id| is the id of the + // basic block for the loop or selection construct that merges at |bb|. + void AddNewPhiNodes(ir::BasicBlock* bb, ir::BasicBlock* pred, + uint32_t header_id); + + // Saves |block| to a list of basic block that will require OpPhi nodes to be + // added by calling |AddNewPhiNodes|. It is assumed that |block| used to have + // a single predecessor, |single_original_pred|, but now has more. + void MarkForNewPhiNodes(ir::BasicBlock* block, + ir::BasicBlock* single_original_pred); + + // Return the original single predcessor of |block| if it was flagged as + // having a single predecessor. |nullptr| is returned otherwise. + ir::BasicBlock* MarkedSinglePred(ir::BasicBlock* block) { + auto it = new_merge_nodes_.find(block); + if (it != new_merge_nodes_.end()) { + return it->second; + } else { + return nullptr; + } + } + + StructuredControlState& CurrentState() { return state_.back(); } + + // A stack used to keep track of the innermost contain loop and selection + // constructs. + std::vector state_; + + // The current function being transformed. + ir::Function* function_; + + // The |OpVariable| instruction defining a boolean variable used to keep track + // of whether or not the function is trying to return. + ir::Instruction* return_flag_; + + // The |OpVariable| instruction defining a variabled to used to keep track of + // the value that was returned when passing through a block that use to + // contain an |OpReturnValue|. + ir::Instruction* return_value_; + + // The instruction defining the boolean constant true. + ir::Instruction* constant_true_; + + // The basic block that is suppose to become the contain the only return value + // after processing the current function. + ir::BasicBlock* final_return_block_; + // This map contains the set of nodes that use to have a single predcessor, + // but now have more. They will need new OpPhi nodes. For each of the nodes, + // it is mapped to it original single predcessor. It is assumed there are no + // values that will need a phi on the new edges. + std::unordered_map new_merge_nodes_; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_MERGE_RETURN_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/module.cpp b/3rdparty/spirv-tools/source/opt/module.cpp new file mode 100644 index 000000000..1e87c2c04 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/module.cpp @@ -0,0 +1,173 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "module.h" + +#include +#include +#include + +#include "operand.h" +#include "reflect.h" + +namespace spvtools { +namespace ir { + +std::vector Module::GetTypes() { + std::vector type_insts; + for (auto& inst : types_values_) { + if (IsTypeInst(inst.opcode())) type_insts.push_back(&inst); + } + return type_insts; +} + +std::vector Module::GetTypes() const { + std::vector type_insts; + for (auto& inst : types_values_) { + if (IsTypeInst(inst.opcode())) type_insts.push_back(&inst); + } + return type_insts; +} + +std::vector Module::GetConstants() { + std::vector const_insts; + for (auto& inst : types_values_) { + if (IsConstantInst(inst.opcode())) const_insts.push_back(&inst); + } + return const_insts; +} + +std::vector Module::GetConstants() const { + std::vector const_insts; + for (auto& inst : types_values_) { + if (IsConstantInst(inst.opcode())) const_insts.push_back(&inst); + } + return const_insts; +} + +uint32_t Module::GetGlobalValue(SpvOp opcode) const { + for (auto& inst : types_values_) { + if (inst.opcode() == opcode) return inst.result_id(); + } + return 0; +} + +void Module::AddGlobalValue(SpvOp opcode, uint32_t result_id, + uint32_t type_id) { + std::unique_ptr newGlobal( + new ir::Instruction(context(), opcode, type_id, result_id, {})); + AddGlobalValue(std::move(newGlobal)); +} + +void Module::ForEachInst(const std::function& f, + bool run_on_debug_line_insts) { +#define DELEGATE(list) list.ForEachInst(f, run_on_debug_line_insts) + DELEGATE(capabilities_); + DELEGATE(extensions_); + DELEGATE(ext_inst_imports_); + if (memory_model_) memory_model_->ForEachInst(f, run_on_debug_line_insts); + DELEGATE(entry_points_); + DELEGATE(execution_modes_); + DELEGATE(debugs1_); + DELEGATE(debugs2_); + DELEGATE(debugs3_); + DELEGATE(annotations_); + DELEGATE(types_values_); + for (auto& i : functions_) i->ForEachInst(f, run_on_debug_line_insts); +#undef DELEGATE +} + +void Module::ForEachInst(const std::function& f, + bool run_on_debug_line_insts) const { +#define DELEGATE(i) i.ForEachInst(f, run_on_debug_line_insts) + for (auto& i : capabilities_) DELEGATE(i); + for (auto& i : extensions_) DELEGATE(i); + for (auto& i : ext_inst_imports_) DELEGATE(i); + if (memory_model_) + static_cast(memory_model_.get()) + ->ForEachInst(f, run_on_debug_line_insts); + for (auto& i : entry_points_) DELEGATE(i); + for (auto& i : execution_modes_) DELEGATE(i); + for (auto& i : debugs1_) DELEGATE(i); + for (auto& i : debugs2_) DELEGATE(i); + for (auto& i : debugs3_) DELEGATE(i); + for (auto& i : annotations_) DELEGATE(i); + for (auto& i : types_values_) DELEGATE(i); + for (auto& i : functions_) { + static_cast(i.get())->ForEachInst(f, + run_on_debug_line_insts); + } +#undef DELEGATE +} + +void Module::ToBinary(std::vector* binary, bool skip_nop) const { + binary->push_back(header_.magic_number); + binary->push_back(header_.version); + // TODO(antiagainst): should we change the generator number? + binary->push_back(header_.generator); + binary->push_back(header_.bound); + binary->push_back(header_.reserved); + + auto write_inst = [binary, skip_nop](const Instruction* i) { + if (!(skip_nop && i->IsNop())) i->ToBinaryWithoutAttachedDebugInsts(binary); + }; + ForEachInst(write_inst, true); +} + +uint32_t Module::ComputeIdBound() const { + uint32_t highest = 0; + + ForEachInst( + [&highest](const Instruction* inst) { + for (const auto& operand : *inst) { + if (spvIsIdType(operand.type)) { + highest = std::max(highest, operand.words[0]); + } + } + }, + true /* scan debug line insts as well */); + + return highest + 1; +} + +bool Module::HasExplicitCapability(uint32_t cap) { + for (auto& ci : capabilities_) { + uint32_t tcap = ci.GetSingleWordOperand(0); + if (tcap == cap) { + return true; + } + } + return false; +} + +uint32_t Module::GetExtInstImportId(const char* extstr) { + for (auto& ei : ext_inst_imports_) + if (!strcmp(extstr, + reinterpret_cast(&(ei.GetInOperand(0).words[0])))) + return ei.result_id(); + return 0; +} + +std::ostream& operator<<(std::ostream& str, const Module& module) { + module.ForEachInst([&str](const ir::Instruction* inst) { + str << *inst; + if (inst->opcode() != SpvOpFunctionEnd) { + str << std::endl; + } + }); + return str; +} + +} // namespace ir +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/module.h b/3rdparty/spirv-tools/source/opt/module.h new file mode 100644 index 000000000..163c4e30e --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/module.h @@ -0,0 +1,476 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_MODULE_H_ +#define LIBSPIRV_OPT_MODULE_H_ + +#include +#include +#include +#include + +#include "function.h" +#include "instruction.h" +#include "iterator.h" + +namespace spvtools { +namespace ir { + +class IRContext; + +// A struct for containing the module header information. +struct ModuleHeader { + uint32_t magic_number; + uint32_t version; + uint32_t generator; + uint32_t bound; + uint32_t reserved; +}; + +// A SPIR-V module. It contains all the information for a SPIR-V module and +// serves as the backbone of optimization transformations. +class Module { + public: + using iterator = UptrVectorIterator; + using const_iterator = UptrVectorIterator; + using inst_iterator = InstructionList::iterator; + using const_inst_iterator = InstructionList::const_iterator; + + // Creates an empty module with zero'd header. + Module() : header_({}) {} + + // Sets the header to the given |header|. + void SetHeader(const ModuleHeader& header) { header_ = header; } + + // Sets the Id bound. + void SetIdBound(uint32_t bound) { header_.bound = bound; } + + // Returns the Id bound. + uint32_t IdBound() { return header_.bound; } + + // Returns the current Id bound and increases it to the next available value. + uint32_t TakeNextIdBound() { return header_.bound++; } + + // Appends a capability instruction to this module. + inline void AddCapability(std::unique_ptr c); + + // Appends an extension instruction to this module. + inline void AddExtension(std::unique_ptr e); + + // Appends an extended instruction set instruction to this module. + inline void AddExtInstImport(std::unique_ptr e); + + // Set the memory model for this module. + inline void SetMemoryModel(std::unique_ptr m); + + // Appends an entry point instruction to this module. + inline void AddEntryPoint(std::unique_ptr e); + + // Appends an execution mode instruction to this module. + inline void AddExecutionMode(std::unique_ptr e); + + // Appends a debug 1 instruction (excluding OpLine & OpNoLine) to this module. + // "debug 1" instructions are the ones in layout section 7.a), see section + // 2.4 Logical Layout of a Module from the SPIR-V specification. + inline void AddDebug1Inst(std::unique_ptr d); + + // Appends a debug 2 instruction (excluding OpLine & OpNoLine) to this module. + // "debug 2" instructions are the ones in layout section 7.b), see section + // 2.4 Logical Layout of a Module from the SPIR-V specification. + inline void AddDebug2Inst(std::unique_ptr d); + + // Appends a debug 3 instruction (OpModuleProcessed) to this module. + // This is due to decision by the SPIR Working Group, pending publication. + inline void AddDebug3Inst(std::unique_ptr d); + + // Appends an annotation instruction to this module. + inline void AddAnnotationInst(std::unique_ptr a); + + // Appends a type-declaration instruction to this module. + inline void AddType(std::unique_ptr t); + + // Appends a constant, global variable, or OpUndef instruction to this module. + inline void AddGlobalValue(std::unique_ptr v); + + // Appends a function to this module. + inline void AddFunction(std::unique_ptr f); + + // Returns a vector of pointers to type-declaration instructions in this + // module. + std::vector GetTypes(); + std::vector GetTypes() const; + // Returns a vector of pointers to constant-creation instructions in this + // module. + std::vector GetConstants(); + std::vector GetConstants() const; + + // Return result id of global value with |opcode|, 0 if not present. + uint32_t GetGlobalValue(SpvOp opcode) const; + + // Add global value with |opcode|, |result_id| and |type_id| + void AddGlobalValue(SpvOp opcode, uint32_t result_id, uint32_t type_id); + + inline uint32_t id_bound() const { return header_.bound; } + + inline uint32_t version() const { return header_.version; } + + // Iterators for capabilities instructions contained in this module. + inline inst_iterator capability_begin(); + inline inst_iterator capability_end(); + inline IteratorRange capabilities(); + inline IteratorRange capabilities() const; + + // Iterators for ext_inst_imports instructions contained in this module. + inline inst_iterator ext_inst_import_begin(); + inline inst_iterator ext_inst_import_end(); + inline IteratorRange ext_inst_imports(); + inline IteratorRange ext_inst_imports() const; + + // Return the memory model instruction contained inthis module. + inline Instruction* GetMemoryModel() { return memory_model_.get(); } + inline const Instruction* GetMemoryModel() const { + return memory_model_.get(); + } + + // There are several kinds of debug instructions, according to where they can + // appear in the logical layout of a module: + // - Section 7a: OpString, OpSourceExtension, OpSource, OpSourceContinued + // - Section 7b: OpName, OpMemberName + // - Section 7c: OpModuleProcessed + // - Mostly anywhere: OpLine and OpNoLine + // + + // Iterators for debug 1 instructions (excluding OpLine & OpNoLine) contained + // in this module. These are for layout section 7a. + inline inst_iterator debug1_begin(); + inline inst_iterator debug1_end(); + inline IteratorRange debugs1(); + inline IteratorRange debugs1() const; + + // Iterators for debug 2 instructions (excluding OpLine & OpNoLine) contained + // in this module. These are for layout section 7b. + inline inst_iterator debug2_begin(); + inline inst_iterator debug2_end(); + inline IteratorRange debugs2(); + inline IteratorRange debugs2() const; + + // Iterators for debug 3 instructions (excluding OpLine & OpNoLine) contained + // in this module. These are for layout section 7c. + inline inst_iterator debug3_begin(); + inline inst_iterator debug3_end(); + inline IteratorRange debugs3(); + inline IteratorRange debugs3() const; + + // Iterators for entry point instructions contained in this module + inline IteratorRange entry_points(); + inline IteratorRange entry_points() const; + + // Iterators for execution_modes instructions contained in this module. + inline inst_iterator execution_mode_begin(); + inline inst_iterator execution_mode_end(); + inline IteratorRange execution_modes(); + inline IteratorRange execution_modes() const; + + // Clears all debug instructions (excluding OpLine & OpNoLine). + void debug_clear() { + debug1_clear(); + debug2_clear(); + debug3_clear(); + } + + // Clears all debug 1 instructions (excluding OpLine & OpNoLine). + void debug1_clear() { debugs1_.clear(); } + + // Clears all debug 2 instructions (excluding OpLine & OpNoLine). + void debug2_clear() { debugs2_.clear(); } + + // Clears all debug 3 instructions (excluding OpLine & OpNoLine). + void debug3_clear() { debugs3_.clear(); } + + // Iterators for annotation instructions contained in this module. + inline inst_iterator annotation_begin(); + inline inst_iterator annotation_end(); + IteratorRange annotations(); + IteratorRange annotations() const; + + // Iterators for extension instructions contained in this module. + inline inst_iterator extension_begin(); + inline inst_iterator extension_end(); + IteratorRange extensions(); + IteratorRange extensions() const; + + // Iterators for types, constants and global variables instructions. + inline inst_iterator types_values_begin(); + inline inst_iterator types_values_end(); + inline IteratorRange types_values(); + inline IteratorRange types_values() const; + + // Iterators for functions contained in this module. + iterator begin() { return iterator(&functions_, functions_.begin()); } + iterator end() { return iterator(&functions_, functions_.end()); } + const_iterator begin() const { return cbegin(); } + const_iterator end() const { return cend(); } + inline const_iterator cbegin() const; + inline const_iterator cend() const; + + // Invokes function |f| on all instructions in this module, and optionally on + // the debug line instructions that precede them. + void ForEachInst(const std::function& f, + bool run_on_debug_line_insts = false); + void ForEachInst(const std::function& f, + bool run_on_debug_line_insts = false) const; + + // Pushes the binary segments for this instruction into the back of *|binary|. + // If |skip_nop| is true and this is a OpNop, do nothing. + void ToBinary(std::vector* binary, bool skip_nop) const; + + // Returns 1 more than the maximum Id value mentioned in the module. + uint32_t ComputeIdBound() const; + + // Returns true if module has capability |cap| + bool HasExplicitCapability(uint32_t cap); + + // Returns id for OpExtInst instruction for extension |extstr|. + // Returns 0 if not found. + uint32_t GetExtInstImportId(const char* extstr); + + // Sets the associated context for this module + void SetContext(IRContext* c) { context_ = c; } + + // Gets the associated context for this module + IRContext* context() const { return context_; } + + private: + ModuleHeader header_; // Module header + + // The following fields respect the "Logical Layout of a Module" in + // Section 2.4 of the SPIR-V specification. + IRContext* context_; + InstructionList capabilities_; + InstructionList extensions_; + InstructionList ext_inst_imports_; + // A module only has one memory model instruction. + std::unique_ptr memory_model_; + InstructionList entry_points_; + InstructionList execution_modes_; + InstructionList debugs1_; + InstructionList debugs2_; + InstructionList debugs3_; + InstructionList annotations_; + // Type declarations, constants, and global variable declarations. + InstructionList types_values_; + std::vector> functions_; +}; + +// Pretty-prints |module| to |str|. Returns |str|. +std::ostream& operator<<(std::ostream& str, const Module& module); + +inline void Module::AddCapability(std::unique_ptr c) { + capabilities_.push_back(std::move(c)); +} + +inline void Module::AddExtension(std::unique_ptr e) { + extensions_.push_back(std::move(e)); +} + +inline void Module::AddExtInstImport(std::unique_ptr e) { + ext_inst_imports_.push_back(std::move(e)); +} + +inline void Module::SetMemoryModel(std::unique_ptr m) { + memory_model_ = std::move(m); +} + +inline void Module::AddEntryPoint(std::unique_ptr e) { + entry_points_.push_back(std::move(e)); +} + +inline void Module::AddExecutionMode(std::unique_ptr e) { + execution_modes_.push_back(std::move(e)); +} + +inline void Module::AddDebug1Inst(std::unique_ptr d) { + debugs1_.push_back(std::move(d)); +} + +inline void Module::AddDebug2Inst(std::unique_ptr d) { + debugs2_.push_back(std::move(d)); +} + +inline void Module::AddDebug3Inst(std::unique_ptr d) { + debugs3_.push_back(std::move(d)); +} + +inline void Module::AddAnnotationInst(std::unique_ptr a) { + annotations_.push_back(std::move(a)); +} + +inline void Module::AddType(std::unique_ptr t) { + types_values_.push_back(std::move(t)); +} + +inline void Module::AddGlobalValue(std::unique_ptr v) { + types_values_.push_back(std::move(v)); +} + +inline void Module::AddFunction(std::unique_ptr f) { + functions_.emplace_back(std::move(f)); +} + +inline Module::inst_iterator Module::capability_begin() { + return capabilities_.begin(); +} +inline Module::inst_iterator Module::capability_end() { + return capabilities_.end(); +} + +inline IteratorRange Module::capabilities() { + return make_range(capabilities_.begin(), capabilities_.end()); +} + +inline IteratorRange Module::capabilities() const { + return make_range(capabilities_.begin(), capabilities_.end()); +} + +inline Module::inst_iterator Module::ext_inst_import_begin() { + return ext_inst_imports_.begin(); +} +inline Module::inst_iterator Module::ext_inst_import_end() { + return ext_inst_imports_.end(); +} + +inline IteratorRange Module::ext_inst_imports() { + return make_range(ext_inst_imports_.begin(), ext_inst_imports_.end()); +} + +inline IteratorRange Module::ext_inst_imports() + const { + return make_range(ext_inst_imports_.begin(), ext_inst_imports_.end()); +} + +inline Module::inst_iterator Module::debug1_begin() { return debugs1_.begin(); } +inline Module::inst_iterator Module::debug1_end() { return debugs1_.end(); } + +inline IteratorRange Module::debugs1() { + return make_range(debugs1_.begin(), debugs1_.end()); +} + +inline IteratorRange Module::debugs1() const { + return make_range(debugs1_.begin(), debugs1_.end()); +} + +inline Module::inst_iterator Module::debug2_begin() { return debugs2_.begin(); } +inline Module::inst_iterator Module::debug2_end() { return debugs2_.end(); } + +inline IteratorRange Module::debugs2() { + return make_range(debugs2_.begin(), debugs2_.end()); +} + +inline IteratorRange Module::debugs2() const { + return make_range(debugs2_.begin(), debugs2_.end()); +} + +inline Module::inst_iterator Module::debug3_begin() { return debugs3_.begin(); } +inline Module::inst_iterator Module::debug3_end() { return debugs3_.end(); } + +inline IteratorRange Module::debugs3() { + return make_range(debugs3_.begin(), debugs3_.end()); +} + +inline IteratorRange Module::debugs3() const { + return make_range(debugs3_.begin(), debugs3_.end()); +} + +inline IteratorRange Module::entry_points() { + return make_range(entry_points_.begin(), entry_points_.end()); +} + +inline IteratorRange Module::entry_points() const { + return make_range(entry_points_.begin(), entry_points_.end()); +} + +inline Module::inst_iterator Module::execution_mode_begin() { + return execution_modes_.begin(); +} +inline Module::inst_iterator Module::execution_mode_end() { + return execution_modes_.end(); +} + +inline IteratorRange Module::execution_modes() { + return make_range(execution_modes_.begin(), execution_modes_.end()); +} + +inline IteratorRange Module::execution_modes() + const { + return make_range(execution_modes_.begin(), execution_modes_.end()); +} + +inline Module::inst_iterator Module::annotation_begin() { + return annotations_.begin(); +} +inline Module::inst_iterator Module::annotation_end() { + return annotations_.end(); +} + +inline IteratorRange Module::annotations() { + return make_range(annotations_.begin(), annotations_.end()); +} + +inline IteratorRange Module::annotations() const { + return make_range(annotations_.begin(), annotations_.end()); +} + +inline Module::inst_iterator Module::extension_begin() { + return extensions_.begin(); +} +inline Module::inst_iterator Module::extension_end() { + return extensions_.end(); +} + +inline IteratorRange Module::extensions() { + return make_range(extensions_.begin(), extensions_.end()); +} + +inline IteratorRange Module::extensions() const { + return make_range(extensions_.begin(), extensions_.end()); +} + +inline Module::inst_iterator Module::types_values_begin() { + return types_values_.begin(); +} + +inline Module::inst_iterator Module::types_values_end() { + return types_values_.end(); +} + +inline IteratorRange Module::types_values() { + return make_range(types_values_.begin(), types_values_.end()); +} + +inline IteratorRange Module::types_values() const { + return make_range(types_values_.begin(), types_values_.end()); +} + +inline Module::const_iterator Module::cbegin() const { + return const_iterator(&functions_, functions_.cbegin()); +} + +inline Module::const_iterator Module::cend() const { + return const_iterator(&functions_, functions_.cend()); +} + +} // namespace ir +} // namespace spvtools + +#endif // LIBSPIRV_OPT_MODULE_H_ diff --git a/3rdparty/spirv-tools/source/opt/null_pass.h b/3rdparty/spirv-tools/source/opt/null_pass.h new file mode 100644 index 000000000..54ea06e35 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/null_pass.h @@ -0,0 +1,36 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_NULL_PASS_H_ +#define LIBSPIRV_OPT_NULL_PASS_H_ + +#include "module.h" +#include "pass.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class NullPass : public Pass { + public: + const char* name() const override { return "null"; } + Status Process(ir::IRContext*) override { + return Status::SuccessWithoutChange; + } +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_NULL_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/optimizer.cpp b/3rdparty/spirv-tools/source/opt/optimizer.cpp new file mode 100644 index 000000000..65ab365a5 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/optimizer.cpp @@ -0,0 +1,448 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "spirv-tools/optimizer.hpp" + +#include "build_module.h" +#include "make_unique.h" +#include "pass_manager.h" +#include "passes.h" +#include "simplification_pass.h" + +namespace spvtools { + +struct Optimizer::PassToken::Impl { + Impl(std::unique_ptr p) : pass(std::move(p)) {} + + std::unique_ptr pass; // Internal implementation pass. +}; + +Optimizer::PassToken::PassToken( + std::unique_ptr impl) + : impl_(std::move(impl)) {} +Optimizer::PassToken::PassToken(PassToken&& that) + : impl_(std::move(that.impl_)) {} + +Optimizer::PassToken& Optimizer::PassToken::operator=(PassToken&& that) { + impl_ = std::move(that.impl_); + return *this; +} + +Optimizer::PassToken::~PassToken() {} + +struct Optimizer::Impl { + explicit Impl(spv_target_env env) : target_env(env), pass_manager() {} + + const spv_target_env target_env; // Target environment. + opt::PassManager pass_manager; // Internal implementation pass manager. +}; + +Optimizer::Optimizer(spv_target_env env) : impl_(new Impl(env)) {} + +Optimizer::~Optimizer() {} + +void Optimizer::SetMessageConsumer(MessageConsumer c) { + // All passes' message consumer needs to be updated. + for (uint32_t i = 0; i < impl_->pass_manager.NumPasses(); ++i) { + impl_->pass_manager.GetPass(i)->SetMessageConsumer(c); + } + impl_->pass_manager.SetMessageConsumer(std::move(c)); +} + +Optimizer& Optimizer::RegisterPass(PassToken&& p) { + // Change to use the pass manager's consumer. + p.impl_->pass->SetMessageConsumer(impl_->pass_manager.consumer()); + impl_->pass_manager.AddPass(std::move(p.impl_->pass)); + return *this; +} + +// The legalization passes take a spir-v shader generated by an HLSL front-end +// and turn it into a valid vulkan spir-v shader. There are two ways in which +// the code will be invalid at the start: +// +// 1) There will be opaque objects, like images, which will be passed around +// in intermediate objects. Valid spir-v will have to replace the use of +// the opaque object with an intermediate object that is the result of the +// load of the global opaque object. +// +// 2) There will be variables that contain pointers to structured or uniform +// buffers. It be legal, the variables must be eliminated, and the +// references to the structured buffers must use the result of OpVariable +// in the Uniform storage class. +// +// Optimization in this list must accept shaders with these relaxation of the +// rules. There is not guarantee that this list of optimizations is able to +// legalize all inputs, but it is on a best effort basis. +// +// The legalization problem is essentially a very general copy propagation +// problem. The optimization we use are all used to either do copy propagation +// or enable more copy propagation. +Optimizer& Optimizer::RegisterLegalizationPasses() { + return + // Remove unreachable block so that merge return works. + RegisterPass(CreateDeadBranchElimPass()) + // Merge the returns so we can inline. + .RegisterPass(CreateMergeReturnPass()) + // Make sure uses and definitions are in the same function. + .RegisterPass(CreateInlineExhaustivePass()) + // Make private variable function scope + .RegisterPass(CreateEliminateDeadFunctionsPass()) + .RegisterPass(CreatePrivateToLocalPass()) + // Propagate the value stored to the loads in very simple cases. + .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass()) + .RegisterPass(CreateLocalSingleStoreElimPass()) + .RegisterPass(CreateAggressiveDCEPass()) + // Split up aggragates so they are easier to deal with. + .RegisterPass(CreateScalarReplacementPass()) + // Remove loads and stores so everything is in intermediate values. + // Takes care of copy propagation of non-members. + .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass()) + .RegisterPass(CreateLocalSingleStoreElimPass()) + .RegisterPass(CreateAggressiveDCEPass()) + .RegisterPass(CreateLocalMultiStoreElimPass()) + .RegisterPass(CreateAggressiveDCEPass()) + // Propagate constants to get as many constant conditions on branches + // as possible. + .RegisterPass(CreateCCPPass()) + .RegisterPass(CreateDeadBranchElimPass()) + // Copy propagate members. Cleans up code sequences generated by + // scalar replacement. Also important for removing OpPhi nodes. + .RegisterPass(CreateSimplificationPass()) + .RegisterPass(CreateInsertExtractElimPass()) + .RegisterPass(CreateAggressiveDCEPass()) + .RegisterPass(CreateCopyPropagateArraysPass()) + // May need loop unrolling here see + // https://github.com/Microsoft/DirectXShaderCompiler/pull/930 + // Get rid of unused code that contain traces of illegal code + // or unused references to unbound external objects + .RegisterPass(CreateDeadInsertElimPass()) + .RegisterPass(CreateAggressiveDCEPass()); +} + +Optimizer& Optimizer::RegisterPerformancePasses() { + return RegisterPass(CreateRemoveDuplicatesPass()) + .RegisterPass(CreateMergeReturnPass()) + .RegisterPass(CreateInlineExhaustivePass()) + .RegisterPass(CreateAggressiveDCEPass()) + .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass()) + .RegisterPass(CreateLocalSingleStoreElimPass()) + .RegisterPass(CreateAggressiveDCEPass()) + .RegisterPass(CreateScalarReplacementPass()) + .RegisterPass(CreateLocalAccessChainConvertPass()) + .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass()) + .RegisterPass(CreateLocalSingleStoreElimPass()) + .RegisterPass(CreateAggressiveDCEPass()) + .RegisterPass(CreateLocalMultiStoreElimPass()) + .RegisterPass(CreateAggressiveDCEPass()) + .RegisterPass(CreateCCPPass()) + .RegisterPass(CreateAggressiveDCEPass()) + .RegisterPass(CreateRedundancyEliminationPass()) + .RegisterPass(CreateInsertExtractElimPass()) + .RegisterPass(CreateDeadInsertElimPass()) + .RegisterPass(CreateDeadBranchElimPass()) + .RegisterPass(CreateSimplificationPass()) + .RegisterPass(CreateIfConversionPass()) + .RegisterPass(CreateCopyPropagateArraysPass()) + .RegisterPass(CreateAggressiveDCEPass()) + .RegisterPass(CreateBlockMergePass()) + .RegisterPass(CreateRedundancyEliminationPass()) + .RegisterPass(CreateDeadBranchElimPass()) + .RegisterPass(CreateBlockMergePass()) + .RegisterPass(CreateInsertExtractElimPass()); + // Currently exposing driver bugs resulting in crashes (#946) + // .RegisterPass(CreateCommonUniformElimPass()) +} + +Optimizer& Optimizer::RegisterSizePasses() { + return RegisterPass(CreateRemoveDuplicatesPass()) + .RegisterPass(CreateMergeReturnPass()) + .RegisterPass(CreateInlineExhaustivePass()) + .RegisterPass(CreateAggressiveDCEPass()) + .RegisterPass(CreateScalarReplacementPass()) + .RegisterPass(CreateLocalAccessChainConvertPass()) + .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass()) + .RegisterPass(CreateLocalSingleStoreElimPass()) + .RegisterPass(CreateAggressiveDCEPass()) + .RegisterPass(CreateInsertExtractElimPass()) + .RegisterPass(CreateDeadInsertElimPass()) + .RegisterPass(CreateLocalMultiStoreElimPass()) + .RegisterPass(CreateAggressiveDCEPass()) + .RegisterPass(CreateCCPPass()) + .RegisterPass(CreateAggressiveDCEPass()) + .RegisterPass(CreateDeadBranchElimPass()) + .RegisterPass(CreateIfConversionPass()) + .RegisterPass(CreateAggressiveDCEPass()) + .RegisterPass(CreateBlockMergePass()) + .RegisterPass(CreateInsertExtractElimPass()) + .RegisterPass(CreateDeadInsertElimPass()) + .RegisterPass(CreateRedundancyEliminationPass()) + .RegisterPass(CreateCFGCleanupPass()) + // Currently exposing driver bugs resulting in crashes (#946) + // .RegisterPass(CreateCommonUniformElimPass()) + .RegisterPass(CreateAggressiveDCEPass()); +} + +bool Optimizer::Run(const uint32_t* original_binary, + const size_t original_binary_size, + std::vector* optimized_binary) const { + std::unique_ptr context = + BuildModule(impl_->target_env, impl_->pass_manager.consumer(), + original_binary, original_binary_size); + if (context == nullptr) return false; + + auto status = impl_->pass_manager.Run(context.get()); + if (status == opt::Pass::Status::SuccessWithChange || + (status == opt::Pass::Status::SuccessWithoutChange && + (optimized_binary->data() != original_binary || + optimized_binary->size() != original_binary_size))) { + optimized_binary->clear(); + context->module()->ToBinary(optimized_binary, /* skip_nop = */ true); + } + + return status != opt::Pass::Status::Failure; +} + +Optimizer& Optimizer::SetPrintAll(std::ostream* out) { + impl_->pass_manager.SetPrintAll(out); + return *this; +} + +Optimizer& Optimizer::SetTimeReport(std::ostream* out) { + impl_->pass_manager.SetTimeReport(out); + return *this; +} + +Optimizer::PassToken CreateNullPass() { + return MakeUnique(MakeUnique()); +} + +Optimizer::PassToken CreateStripDebugInfoPass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateStripReflectInfoPass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateEliminateDeadFunctionsPass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateSetSpecConstantDefaultValuePass( + const std::unordered_map& id_value_map) { + return MakeUnique( + MakeUnique(id_value_map)); +} + +Optimizer::PassToken CreateSetSpecConstantDefaultValuePass( + const std::unordered_map>& id_value_map) { + return MakeUnique( + MakeUnique(id_value_map)); +} + +Optimizer::PassToken CreateFlattenDecorationPass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateFreezeSpecConstantValuePass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateFoldSpecConstantOpAndCompositePass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateUnifyConstantPass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateEliminateDeadConstantPass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateDeadVariableEliminationPass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateStrengthReductionPass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateBlockMergePass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateInlineExhaustivePass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateInlineOpaquePass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateLocalAccessChainConvertPass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateLocalSingleBlockLoadStoreElimPass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateLocalSingleStoreElimPass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateInsertExtractElimPass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateDeadInsertElimPass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateDeadBranchElimPass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateLocalMultiStoreElimPass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateAggressiveDCEPass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateCommonUniformElimPass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateCompactIdsPass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateMergeReturnPass() { + return MakeUnique( + MakeUnique()); +} + +std::vector Optimizer::GetPassNames() const { + std::vector v; + for (uint32_t i = 0; i < impl_->pass_manager.NumPasses(); i++) { + v.push_back(impl_->pass_manager.GetPass(i)->name()); + } + return v; +} + +Optimizer::PassToken CreateCFGCleanupPass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateLocalRedundancyEliminationPass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateLoopInvariantCodeMotionPass() { + return MakeUnique(MakeUnique()); +} + +Optimizer::PassToken CreateLoopUnswitchPass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateRedundancyEliminationPass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateRemoveDuplicatesPass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateScalarReplacementPass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreatePrivateToLocalPass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateCCPPass() { + return MakeUnique(MakeUnique()); +} + +Optimizer::PassToken CreateWorkaround1209Pass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateIfConversionPass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateReplaceInvalidOpcodePass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateSimplificationPass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateLoopUnrollPass(bool fully_unroll, int factor) { + return MakeUnique( + MakeUnique(fully_unroll, factor)); +} + +Optimizer::PassToken CreateSSARewritePass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateCopyPropagateArraysPass() { + return MakeUnique( + MakeUnique()); +} + +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/pass.cpp b/3rdparty/spirv-tools/source/opt/pass.cpp new file mode 100644 index 000000000..c4a4befe4 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/pass.cpp @@ -0,0 +1,121 @@ +// Copyright (c) 2017 The Khronos Group Inc. +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "pass.h" + +#include "iterator.h" + +namespace spvtools { +namespace opt { + +namespace { + +const uint32_t kEntryPointFunctionIdInIdx = 1; +const uint32_t kTypePointerTypeIdInIdx = 1; + +} // namespace + +Pass::Pass() : consumer_(nullptr), context_(nullptr) {} + +void Pass::AddCalls(ir::Function* func, std::queue* todo) { + for (auto bi = func->begin(); bi != func->end(); ++bi) + for (auto ii = bi->begin(); ii != bi->end(); ++ii) + if (ii->opcode() == SpvOpFunctionCall) + todo->push(ii->GetSingleWordInOperand(0)); +} + +bool Pass::ProcessEntryPointCallTree(ProcessFunction& pfn, ir::Module* module) { + // Map from function's result id to function + std::unordered_map id2function; + for (auto& fn : *module) id2function[fn.result_id()] = &fn; + + // Collect all of the entry points as the roots. + std::queue roots; + for (auto& e : module->entry_points()) + roots.push(e.GetSingleWordInOperand(kEntryPointFunctionIdInIdx)); + return ProcessCallTreeFromRoots(pfn, id2function, &roots); +} + +bool Pass::ProcessReachableCallTree(ProcessFunction& pfn, + ir::IRContext* irContext) { + // Map from function's result id to function + std::unordered_map id2function; + for (auto& fn : *irContext->module()) id2function[fn.result_id()] = &fn; + + std::queue roots; + + // Add all entry points since they can be reached from outside the module. + for (auto& e : irContext->module()->entry_points()) + roots.push(e.GetSingleWordInOperand(kEntryPointFunctionIdInIdx)); + + // Add all exported functions since they can be reached from outside the + // module. + for (auto& a : irContext->annotations()) { + // TODO: Handle group decorations as well. Currently not generate by any + // front-end, but could be coming. + if (a.opcode() == SpvOp::SpvOpDecorate) { + if (a.GetSingleWordOperand(1) == + SpvDecoration::SpvDecorationLinkageAttributes) { + uint32_t lastOperand = a.NumOperands() - 1; + if (a.GetSingleWordOperand(lastOperand) == + SpvLinkageType::SpvLinkageTypeExport) { + uint32_t id = a.GetSingleWordOperand(0); + if (id2function.count(id) != 0) roots.push(id); + } + } + } + } + + return ProcessCallTreeFromRoots(pfn, id2function, &roots); +} + +bool Pass::ProcessCallTreeFromRoots( + ProcessFunction& pfn, + const std::unordered_map& id2function, + std::queue* roots) { + // Process call tree + bool modified = false; + std::unordered_set done; + + while (!roots->empty()) { + const uint32_t fi = roots->front(); + roots->pop(); + if (done.insert(fi).second) { + ir::Function* fn = id2function.at(fi); + modified = pfn(fn) || modified; + AddCalls(fn, roots); + } + } + return modified; +} + +Pass::Status Pass::Run(ir::IRContext* ctx) { + Pass::Status status = Process(ctx); + if (status == Status::SuccessWithChange) { + ctx->InvalidateAnalysesExceptFor(GetPreservedAnalyses()); + } + assert(ctx->IsConsistent()); + return status; +} + +uint32_t Pass::GetPointeeTypeId(const ir::Instruction* ptrInst) const { + const uint32_t ptrTypeId = ptrInst->type_id(); + const ir::Instruction* ptrTypeInst = get_def_use_mgr()->GetDef(ptrTypeId); + return ptrTypeInst->GetSingleWordInOperand(kTypePointerTypeIdInIdx); +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/pass.h b/3rdparty/spirv-tools/source/opt/pass.h new file mode 100644 index 000000000..3077112df --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/pass.h @@ -0,0 +1,159 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_PASS_H_ +#define LIBSPIRV_OPT_PASS_H_ + +#include +#include +#include +#include +#include +#include + +#include "basic_block.h" +#include "def_use_manager.h" +#include "ir_context.h" +#include "module.h" +#include "spirv-tools/libspirv.hpp" + +namespace spvtools { +namespace opt { + +// Abstract class of a pass. All passes should implement this abstract class +// and all analysis and transformation is done via the Process() method. +class Pass { + public: + // The status of processing a module using a pass. + // + // The numbers for the cases are assigned to make sure that Failure & anything + // is Failure, SuccessWithChange & any success is SuccessWithChange. + enum class Status { + Failure = 0x00, + SuccessWithChange = 0x10, + SuccessWithoutChange = 0x11, + }; + + using ProcessFunction = std::function; + + // Constructs a new pass. + // + // The constructed instance will have an empty message consumer, which just + // ignores all messages from the library. Use SetMessageConsumer() to supply + // one if messages are of concern. + Pass(); + + // Destructs the pass. + virtual ~Pass() = default; + + // Returns a descriptive name for this pass. + // + // NOTE: When deriving a new pass class, make sure you make the name + // compatible with the corresponding spirv-opt command-line flag. For example, + // if you add the flag --my-pass to spirv-opt, make this function return + // "my-pass" (no leading hyphens). + virtual const char* name() const = 0; + + // Sets the message consumer to the given |consumer|. |consumer| which will be + // invoked every time there is a message to be communicated to the outside. + void SetMessageConsumer(MessageConsumer c) { consumer_ = std::move(c); } + + // Returns the reference to the message consumer for this pass. + const MessageConsumer& consumer() const { return consumer_; } + + // Returns the def-use manager used for this pass. TODO(dnovillo): This should + // be handled by the pass manager. + analysis::DefUseManager* get_def_use_mgr() const { + return context()->get_def_use_mgr(); + } + + analysis::DecorationManager* get_decoration_mgr() const { + return context()->get_decoration_mgr(); + } + + FeatureManager* get_feature_mgr() const { + return context()->get_feature_mgr(); + } + + // Returns a pointer to the current module for this pass. + ir::Module* get_module() const { return context_->module(); } + + // Returns a pointer to the current context for this pass. + ir::IRContext* context() const { return context_; } + + // Returns a pointer to the CFG for current module. + ir::CFG* cfg() const { return context()->cfg(); } + + // Add to |todo| all ids of functions called in |func|. + void AddCalls(ir::Function* func, std::queue* todo); + + // Applies |pfn| to every function in the call trees that are rooted at the + // entry points. Returns true if any call |pfn| returns true. By convention + // |pfn| should return true if it modified the module. + bool ProcessEntryPointCallTree(ProcessFunction& pfn, ir::Module* module); + + // Applies |pfn| to every function in the call trees rooted at the entry + // points and exported functions. Returns true if any call |pfn| returns + // true. By convention |pfn| should return true if it modified the module. + bool ProcessReachableCallTree(ProcessFunction& pfn, ir::IRContext* irContext); + + // Applies |pfn| to every function in the call trees rooted at the elements of + // |roots|. Returns true if any call to |pfn| returns true. By convention + // |pfn| should return true if it modified the module. After returning + // |roots| will be empty. + bool ProcessCallTreeFromRoots( + ProcessFunction& pfn, + const std::unordered_map& id2function, + std::queue* roots); + + // Run the pass on the given |module|. Returns Status::Failure if errors occur + // when + // processing. Returns the corresponding Status::Success if processing is + // successful to indicate whether changes are made to the module. If there + // were any changes it will also invalidate the analyses in the IRContext + // that are not preserved. + virtual Status Run(ir::IRContext* ctx) final; + + // Returns the set of analyses that the pass is guaranteed to preserve. + virtual ir::IRContext::Analysis GetPreservedAnalyses() { + return ir::IRContext::kAnalysisNone; + } + + // Return type id for |ptrInst|'s pointee + uint32_t GetPointeeTypeId(const ir::Instruction* ptrInst) const; + + protected: + // Initialize basic data structures for the pass. This sets up the def-use + // manager, module and other attributes. + virtual void InitializeProcessing(ir::IRContext* c) { context_ = c; } + + // Processes the given |module|. Returns Status::Failure if errors occur when + // processing. Returns the corresponding Status::Success if processing is + // succesful to indicate whether changes are made to the module. + virtual Status Process(ir::IRContext* context) = 0; + + // Return the next available SSA id and increment it. + uint32_t TakeNextId() { return context_->TakeNextId(); } + + private: + MessageConsumer consumer_; // Message consumer. + + // The context that this pass belongs to. + ir::IRContext* context_; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/pass_manager.cpp b/3rdparty/spirv-tools/source/opt/pass_manager.cpp new file mode 100644 index 000000000..c735bbb07 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/pass_manager.cpp @@ -0,0 +1,67 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "pass_manager.h" + +#include +#include + +#include "ir_context.h" +#include "spirv-tools/libspirv.hpp" +#include "util/timer.h" + +namespace spvtools { + +namespace opt { + +Pass::Status PassManager::Run(ir::IRContext* context) { + auto status = Pass::Status::SuccessWithoutChange; + + // If print_all_stream_ is not null, prints the disassembly of the module + // to that stream, with the given preamble and optionally the pass name. + auto print_disassembly = [&context, this](const char* preamble, Pass* pass) { + if (print_all_stream_) { + std::vector binary; + context->module()->ToBinary(&binary, false); + SpirvTools t(SPV_ENV_UNIVERSAL_1_2); + std::string disassembly; + t.Disassemble(binary, &disassembly, 0); + *print_all_stream_ << preamble << (pass ? pass->name() : "") << "\n" + << disassembly << std::endl; + } + }; + + SPIRV_TIMER_DESCRIPTION(time_report_stream_, /* measure_mem_usage = */ true); + for (const auto& pass : passes_) { + print_disassembly("; IR before pass ", pass.get()); + SPIRV_TIMER_SCOPED(time_report_stream_, (pass ? pass->name() : ""), true); + const auto one_status = pass->Run(context); + if (one_status == Pass::Status::Failure) return one_status; + if (one_status == Pass::Status::SuccessWithChange) status = one_status; + } + print_disassembly("; IR after last pass", nullptr); + + // Set the Id bound in the header in case a pass forgot to do so. + // + // TODO(dnovillo): This should be unnecessary and automatically maintained by + // the IRContext. + if (status == Pass::Status::SuccessWithChange) { + context->module()->SetIdBound(context->module()->ComputeIdBound()); + } + passes_.clear(); + return status; +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/pass_manager.h b/3rdparty/spirv-tools/source/opt/pass_manager.h new file mode 100644 index 000000000..da3620a9e --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/pass_manager.h @@ -0,0 +1,130 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_PASS_MANAGER_H_ +#define LIBSPIRV_OPT_PASS_MANAGER_H_ + +#include +#include +#include + +#include "log.h" +#include "module.h" +#include "pass.h" + +#include "ir_context.h" +#include "spirv-tools/libspirv.hpp" + +namespace spvtools { +namespace opt { + +// The pass manager, responsible for tracking and running passes. +// Clients should first call AddPass() to add passes and then call Run() +// to run on a module. Passes are executed in the exact order of addition. +class PassManager { + public: + // Constructs a pass manager. + // + // The constructed instance will have an empty message consumer, which just + // ignores all messages from the library. Use SetMessageConsumer() to supply + // one if messages are of concern. + PassManager() + : consumer_(nullptr), + print_all_stream_(nullptr), + time_report_stream_(nullptr) {} + + // Sets the message consumer to the given |consumer|. + void SetMessageConsumer(MessageConsumer c) { consumer_ = std::move(c); } + + // Adds an externally constructed pass. + void AddPass(std::unique_ptr pass); + // Uses the argument |args| to construct a pass instance of type |T|, and adds + // the pass instance to this pass manger. The pass added will use this pass + // manager's message consumer. + template + void AddPass(Args&&... args); + + // Returns the number of passes added. + uint32_t NumPasses() const; + // Returns a pointer to the |index|th pass added. + inline Pass* GetPass(uint32_t index) const; + + // Returns the message consumer. + inline const MessageConsumer& consumer() const; + + // Runs all passes on the given |module|. Returns Status::Failure if errors + // occur when processing using one of the registered passes. All passes + // registered after the error-reporting pass will be skipped. Returns the + // corresponding Status::Success if processing is succesful to indicate + // whether changes are made to the module. + // + // After running all the passes, they are removed from the list. + Pass::Status Run(ir::IRContext* context); + + // Sets the option to print the disassembly before each pass and after the + // last pass. Output is written to |out| if that is not null. No output + // is generated if |out| is null. + PassManager& SetPrintAll(std::ostream* out) { + print_all_stream_ = out; + return *this; + } + + // Sets the option to print the resource utilization of each pass. Output is + // written to |out| if that is not null. No output is generated if |out| is + // null. + PassManager& SetTimeReport(std::ostream* out) { + time_report_stream_ = out; + return *this; + } + + private: + // Consumer for messages. + MessageConsumer consumer_; + // A vector of passes. Order matters. + std::vector> passes_; + // The output stream to write disassembly to before each pass, and after + // the last pass. If this is null, no output is generated. + std::ostream* print_all_stream_; + // The output stream to write the resource utilization of each pass. If this + // is null, no output is generated. + std::ostream* time_report_stream_; +}; + +inline void PassManager::AddPass(std::unique_ptr pass) { + passes_.push_back(std::move(pass)); +} + +template +inline void PassManager::AddPass(Args&&... args) { + passes_.emplace_back(new T(std::forward(args)...)); + passes_.back()->SetMessageConsumer(consumer_); +} + +inline uint32_t PassManager::NumPasses() const { + return static_cast(passes_.size()); +} + +inline Pass* PassManager::GetPass(uint32_t index) const { + SPIRV_ASSERT(consumer_, index < passes_.size(), "index out of bound"); + return passes_[index].get(); +} + +inline const MessageConsumer& PassManager::consumer() const { + return consumer_; +} + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_PASS_MANAGER_H_ diff --git a/3rdparty/spirv-tools/source/opt/passes.h b/3rdparty/spirv-tools/source/opt/passes.h new file mode 100644 index 000000000..eccda9c7e --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/passes.h @@ -0,0 +1,61 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_PASSES_H_ +#define LIBSPIRV_OPT_PASSES_H_ + +// A single header to include all passes. + +#include "aggressive_dead_code_elim_pass.h" +#include "block_merge_pass.h" +#include "ccp_pass.h" +#include "cfg_cleanup_pass.h" +#include "common_uniform_elim_pass.h" +#include "compact_ids_pass.h" +#include "copy_prop_arrays.h" +#include "dead_branch_elim_pass.h" +#include "dead_insert_elim_pass.h" +#include "dead_variable_elimination.h" +#include "eliminate_dead_constant_pass.h" +#include "eliminate_dead_functions_pass.h" +#include "flatten_decoration_pass.h" +#include "fold_spec_constant_op_and_composite_pass.h" +#include "freeze_spec_constant_value_pass.h" +#include "if_conversion.h" +#include "inline_exhaustive_pass.h" +#include "inline_opaque_pass.h" +#include "insert_extract_elim.h" +#include "licm_pass.h" +#include "local_access_chain_convert_pass.h" +#include "local_redundancy_elimination.h" +#include "local_single_block_elim_pass.h" +#include "local_single_store_elim_pass.h" +#include "local_ssa_elim_pass.h" +#include "loop_unroller.h" +#include "loop_unswitch_pass.h" +#include "merge_return_pass.h" +#include "null_pass.h" +#include "private_to_local_pass.h" +#include "redundancy_elimination.h" +#include "remove_duplicates_pass.h" +#include "replace_invalid_opc.h" +#include "scalar_replacement_pass.h" +#include "set_spec_constant_default_value_pass.h" +#include "ssa_rewrite_pass.h" +#include "strength_reduction_pass.h" +#include "strip_debug_info_pass.h" +#include "strip_reflect_info_pass.h" +#include "unify_const_pass.h" +#include "workaround1209.h" +#endif // LIBSPIRV_OPT_PASSES_H_ diff --git a/3rdparty/spirv-tools/source/opt/private_to_local_pass.cpp b/3rdparty/spirv-tools/source/opt/private_to_local_pass.cpp new file mode 100644 index 000000000..cc8ef53c1 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/private_to_local_pass.cpp @@ -0,0 +1,181 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "private_to_local_pass.h" + +#include "ir_context.h" + +namespace { +const uint32_t kVariableStorageClassInIdx = 0; +const uint32_t kSpvTypePointerTypeIdInIdx = 1; +} // namespace + +namespace spvtools { +namespace opt { + +Pass::Status PrivateToLocalPass::Process(ir::IRContext* c) { + InitializeProcessing(c); + bool modified = false; + + // Private variables require the shader capability. If this is not a shader, + // there is no work to do. + if (context()->get_feature_mgr()->HasCapability(SpvCapabilityAddresses)) + return Status::SuccessWithoutChange; + + std::vector> variables_to_move; + for (auto& inst : context()->types_values()) { + if (inst.opcode() != SpvOpVariable) { + continue; + } + + if (inst.GetSingleWordInOperand(kVariableStorageClassInIdx) != + SpvStorageClassPrivate) { + continue; + } + + ir::Function* target_function = FindLocalFunction(inst); + if (target_function != nullptr) { + variables_to_move.push_back({&inst, target_function}); + } + } + + modified = !variables_to_move.empty(); + for (auto p : variables_to_move) { + MoveVariable(p.first, p.second); + } + + return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange); +} + +ir::Function* PrivateToLocalPass::FindLocalFunction( + const ir::Instruction& inst) const { + bool found_first_use = false; + ir::Function* target_function = nullptr; + context()->get_def_use_mgr()->ForEachUser( + inst.result_id(), + [&target_function, &found_first_use, this](ir::Instruction* use) { + ir::BasicBlock* current_block = context()->get_instr_block(use); + if (current_block == nullptr) { + return; + } + + if (!IsValidUse(use)) { + found_first_use = true; + target_function = nullptr; + return; + } + ir::Function* current_function = current_block->GetParent(); + if (!found_first_use) { + found_first_use = true; + target_function = current_function; + } else if (target_function != current_function) { + target_function = nullptr; + } + }); + return target_function; +} // namespace opt + +void PrivateToLocalPass::MoveVariable(ir::Instruction* variable, + ir::Function* function) { + // The variable needs to be removed from the global section, and placed in the + // header of the function. First step remove from the global list. + variable->RemoveFromList(); + std::unique_ptr var(variable); // Take ownership. + context()->ForgetUses(variable); + + // Update the storage class of the variable. + variable->SetInOperand(kVariableStorageClassInIdx, {SpvStorageClassFunction}); + + // Update the type as well. + uint32_t new_type_id = GetNewType(variable->type_id()); + variable->SetResultType(new_type_id); + + // Place the variable at the start of the first basic block. + context()->AnalyzeUses(variable); + function->begin()->begin()->InsertBefore(move(var)); + + // Update uses where the type may have changed. + UpdateUses(variable->result_id()); +} + +uint32_t PrivateToLocalPass::GetNewType(uint32_t old_type_id) { + auto type_mgr = context()->get_type_mgr(); + ir::Instruction* old_type_inst = get_def_use_mgr()->GetDef(old_type_id); + uint32_t pointee_type_id = + old_type_inst->GetSingleWordInOperand(kSpvTypePointerTypeIdInIdx); + uint32_t new_type_id = + type_mgr->FindPointerToType(pointee_type_id, SpvStorageClassFunction); + return new_type_id; +} + +bool PrivateToLocalPass::IsValidUse(const ir::Instruction* inst) const { + // The cases in this switch have to match the cases in |UpdateUse|. + // If we don't know how to update it, it is not valid. + switch (inst->opcode()) { + case SpvOpLoad: + case SpvOpStore: + case SpvOpImageTexelPointer: // Treat like a load + return true; + case SpvOpAccessChain: + return context()->get_def_use_mgr()->WhileEachUser( + inst, [this](const ir::Instruction* user) { + if (!IsValidUse(user)) return false; + return true; + }); + case SpvOpName: + return true; + default: + return spvOpcodeIsDecoration(inst->opcode()); + } +} + +void PrivateToLocalPass::UpdateUse(ir::Instruction* inst) { + // The cases in this switch have to match the cases in |IsValidUse|. If we + // don't think it is valid, the optimization will not view the variable as a + // candidate, and therefore the use will not be updated. + switch (inst->opcode()) { + case SpvOpLoad: + case SpvOpStore: + case SpvOpImageTexelPointer: // Treat like a load + // The type is fine because it is the type pointed to, and that does not + // change. + break; + case SpvOpAccessChain: + context()->ForgetUses(inst); + inst->SetResultType(GetNewType(inst->type_id())); + context()->AnalyzeUses(inst); + + // Update uses where the type may have changed. + UpdateUses(inst->result_id()); + break; + case SpvOpName: + break; + default: + assert(spvOpcodeIsDecoration(inst->opcode()) && + "Do not know how to update the type for this instruction."); + break; + } +} +void PrivateToLocalPass::UpdateUses(uint32_t id) { + std::vector uses; + this->context()->get_def_use_mgr()->ForEachUser( + id, [&uses](ir::Instruction* use) { uses.push_back(use); }); + + for (ir::Instruction* use : uses) { + UpdateUse(use); + } +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/private_to_local_pass.h b/3rdparty/spirv-tools/source/opt/private_to_local_pass.h new file mode 100644 index 000000000..89cd994f1 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/private_to_local_pass.h @@ -0,0 +1,71 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_PRIVATE_TO_LOCAL_PASS_H_ +#define LIBSPIRV_OPT_PRIVATE_TO_LOCAL_PASS_H_ + +#include "ir_context.h" +#include "pass.h" + +namespace spvtools { +namespace opt { + +// This pass implements total redundancy elimination. This is the same as +// local redundancy elimination except it looks across basic block boundaries. +// An instruction, inst, is totally redundant if there is another instruction +// that dominates inst, and also computes the same value. +class PrivateToLocalPass : public Pass { + public: + const char* name() const override { return "private-to-local"; } + Status Process(ir::IRContext*) override; + ir::IRContext::Analysis GetPreservedAnalyses() override { + return ir::IRContext::kAnalysisDefUse | + ir::IRContext::kAnalysisDecorations | + ir::IRContext::kAnalysisCombinators | ir::IRContext::kAnalysisCFG | + ir::IRContext::kAnalysisDominatorAnalysis | + ir::IRContext::kAnalysisNameMap; + } + + private: + // Moves |variable| from the private storage class to the function storage + // class of |function|. + void MoveVariable(ir::Instruction* variable, ir::Function* function); + + // |inst| is an instruction declaring a varible. If that variable is + // referenced in a single function and all of uses are valid as defined by + // |IsValidUse|, then that function is returned. Otherwise, the return + // value is |nullptr|. + ir::Function* FindLocalFunction(const ir::Instruction& inst) const; + + // Returns true is |inst| is a valid use of a pointer. In this case, a + // valid use is one where the transformation is able to rewrite the type to + // match a change in storage class of the original variable. + bool IsValidUse(const ir::Instruction* inst) const; + + // Given the result id of a pointer type, |old_type_id|, this function + // returns the id of a the same pointer type except the storage class has + // been changed to function. If the type does not already exist, it will be + // created. + uint32_t GetNewType(uint32_t old_type_id); + + // Updates |inst|, and any instruction dependent on |inst|, to reflect the + // change of the base pointer now pointing to the function storage class. + void UpdateUse(ir::Instruction* inst); + void UpdateUses(uint32_t id); +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_PRIVATE_TO_LOCAL_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/propagator.cpp b/3rdparty/spirv-tools/source/opt/propagator.cpp new file mode 100644 index 000000000..d5d76127a --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/propagator.cpp @@ -0,0 +1,291 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "propagator.h" + +namespace spvtools { +namespace opt { + +void SSAPropagator::AddControlEdge(const Edge& edge) { + ir::BasicBlock* dest_bb = edge.dest; + + // Refuse to add the exit block to the work list. + if (dest_bb == ctx_->cfg()->pseudo_exit_block()) { + return; + } + + // Try to mark the edge executable. If it was already in the set of + // executable edges, do nothing. + if (!MarkEdgeExecutable(edge)) { + return; + } + + // If the edge had not already been marked executable, add the destination + // basic block to the work list. + blocks_.push(dest_bb); +} + +void SSAPropagator::AddSSAEdges(ir::Instruction* instr) { + // Ignore instructions that produce no result. + if (instr->result_id() == 0) { + return; + } + + get_def_use_mgr()->ForEachUser( + instr->result_id(), [this](ir::Instruction* use_instr) { + // If the basic block for |use_instr| has not been simulated yet, do + // nothing. The instruction |use_instr| will be simulated next time the + // block is scheduled. + if (!BlockHasBeenSimulated(ctx_->get_instr_block(use_instr))) { + return; + } + + if (ShouldSimulateAgain(use_instr)) { + ssa_edge_uses_.push(use_instr); + } + }); +} + +bool SSAPropagator::IsPhiArgExecutable(ir::Instruction* phi, uint32_t i) const { + ir::BasicBlock* phi_bb = ctx_->get_instr_block(phi); + + uint32_t in_label_id = phi->GetSingleWordOperand(i + 1); + ir::Instruction* in_label_instr = get_def_use_mgr()->GetDef(in_label_id); + ir::BasicBlock* in_bb = ctx_->get_instr_block(in_label_instr); + + return IsEdgeExecutable(Edge(in_bb, phi_bb)); +} + +bool SSAPropagator::SetStatus(ir::Instruction* inst, PropStatus status) { + bool has_old_status = false; + PropStatus old_status = kVarying; + if (HasStatus(inst)) { + has_old_status = true; + old_status = Status(inst); + } + + assert((!has_old_status || old_status <= status) && + "Invalid lattice transition"); + + bool status_changed = !has_old_status || (old_status != status); + if (status_changed) statuses_[inst] = status; + + return status_changed; +} + +bool SSAPropagator::Simulate(ir::Instruction* instr) { + bool changed = false; + + // Don't bother visiting instructions that should not be simulated again. + if (!ShouldSimulateAgain(instr)) { + return changed; + } + + ir::BasicBlock* dest_bb = nullptr; + PropStatus status = visit_fn_(instr, &dest_bb); + bool status_changed = SetStatus(instr, status); + + if (status == kVarying) { + // The statement produces a varying result, add it to the list of statements + // not to simulate anymore and add its SSA def-use edges for simulation. + DontSimulateAgain(instr); + if (status_changed) { + AddSSAEdges(instr); + } + + // If |instr| is a block terminator, add all the control edges out of its + // block. + if (instr->IsBlockTerminator()) { + ir::BasicBlock* block = ctx_->get_instr_block(instr); + for (const auto& e : bb_succs_.at(block)) { + AddControlEdge(e); + } + } + return false; + } else if (status == kInteresting) { + // Add the SSA edges coming out of this instruction if the propagation + // status has changed. + if (status_changed) { + AddSSAEdges(instr); + } + + // If there are multiple outgoing control flow edges and we know which one + // will be taken, add the destination block to the CFG work list. + if (dest_bb) { + AddControlEdge(Edge(ctx_->get_instr_block(instr), dest_bb)); + } + changed = true; + } + + // At this point, we are dealing with instructions that are in status + // kInteresting or kNotInteresting. To decide whether this instruction should + // be simulated again, we examine its operands. If at least one operand O is + // defined at an instruction D that should be simulated again, then the output + // of D might affect |instr|, so we should simulate |instr| again. + bool has_operands_to_simulate = false; + if (instr->opcode() == SpvOpPhi) { + // For Phi instructions, an operand causes the Phi to be simulated again if + // the operand comes from an edge that has not yet been traversed or if its + // definition should be simulated again. + for (uint32_t i = 2; i < instr->NumOperands(); i += 2) { + // Phi arguments come in pairs. Index 'i' contains the + // variable id, index 'i + 1' is the originating block id. + assert(i % 2 == 0 && i < instr->NumOperands() - 1 && + "malformed Phi arguments"); + + uint32_t arg_id = instr->GetSingleWordOperand(i); + ir::Instruction* arg_def_instr = get_def_use_mgr()->GetDef(arg_id); + if (!IsPhiArgExecutable(instr, i) || ShouldSimulateAgain(arg_def_instr)) { + has_operands_to_simulate = true; + break; + } + } + } else { + // For regular instructions, check if the defining instruction of each + // operand needs to be simulated again. If so, then this instruction should + // also be simulated again. + has_operands_to_simulate = + !instr->WhileEachInId([this](const uint32_t* use) { + ir::Instruction* def_instr = get_def_use_mgr()->GetDef(*use); + if (ShouldSimulateAgain(def_instr)) { + return false; + } + return true; + }); + } + + if (!has_operands_to_simulate) { + DontSimulateAgain(instr); + } + + return changed; +} + +bool SSAPropagator::Simulate(ir::BasicBlock* block) { + if (block == ctx_->cfg()->pseudo_exit_block()) { + return false; + } + + // Always simulate Phi instructions, even if we have simulated this block + // before. We do this because Phi instructions receive their inputs from + // incoming edges. When those edges are marked executable, the corresponding + // operand can be simulated. + bool changed = false; + block->ForEachPhiInst( + [&changed, this](ir::Instruction* instr) { changed |= Simulate(instr); }); + + // If this is the first time this block is being simulated, simulate every + // statement in it. + if (!BlockHasBeenSimulated(block)) { + block->ForEachInst([this, &changed](ir::Instruction* instr) { + if (instr->opcode() != SpvOpPhi) { + changed |= Simulate(instr); + } + }); + + MarkBlockSimulated(block); + + // If this block has exactly one successor, mark the edge to its successor + // as executable. + if (bb_succs_.at(block).size() == 1) { + AddControlEdge(bb_succs_.at(block).at(0)); + } + } + + return changed; +} + +void SSAPropagator::Initialize(ir::Function* fn) { + // Compute predecessor and successor blocks for every block in |fn|'s CFG. + // TODO(dnovillo): Move this to ir::CFG and always build them. Alternately, + // move it to IRContext and build CFG preds/succs on-demand. + bb_succs_[ctx_->cfg()->pseudo_entry_block()].push_back( + Edge(ctx_->cfg()->pseudo_entry_block(), fn->entry().get())); + + for (auto& block : *fn) { + const auto& const_block = block; + const_block.ForEachSuccessorLabel([this, &block](const uint32_t label_id) { + ir::BasicBlock* succ_bb = + ctx_->get_instr_block(get_def_use_mgr()->GetDef(label_id)); + bb_succs_[&block].push_back(Edge(&block, succ_bb)); + bb_preds_[succ_bb].push_back(Edge(succ_bb, &block)); + }); + if (block.IsReturnOrAbort()) { + bb_succs_[&block].push_back( + Edge(&block, ctx_->cfg()->pseudo_exit_block())); + bb_preds_[ctx_->cfg()->pseudo_exit_block()].push_back( + Edge(ctx_->cfg()->pseudo_exit_block(), &block)); + } + } + + // Add the edges out of the entry block to seed the propagator. + const auto& entry_succs = bb_succs_[ctx_->cfg()->pseudo_entry_block()]; + for (const auto& e : entry_succs) { + AddControlEdge(e); + } +} + +bool SSAPropagator::Run(ir::Function* fn) { + Initialize(fn); + + bool changed = false; + while (!blocks_.empty() || !ssa_edge_uses_.empty()) { + // Simulate all blocks first. Simulating blocks will add SSA edges to + // follow after all the blocks have been simulated. + if (!blocks_.empty()) { + auto block = blocks_.front(); + changed |= Simulate(block); + blocks_.pop(); + continue; + } + + // Simulate edges from the SSA queue. + if (!ssa_edge_uses_.empty()) { + ir::Instruction* instr = ssa_edge_uses_.front(); + changed |= Simulate(instr); + ssa_edge_uses_.pop(); + } + } + +#ifndef NDEBUG + // Verify all visited values have settled. No value that has been simulated + // should end on not interesting. + fn->ForEachInst([this](ir::Instruction* inst) { + assert( + (!HasStatus(inst) || Status(inst) != SSAPropagator::kNotInteresting) && + "Unsettled value"); + }); +#endif + + return changed; +} + +std::ostream& operator<<(std::ostream& str, + const SSAPropagator::PropStatus& status) { + switch (status) { + case SSAPropagator::kVarying: + str << "Varying"; + break; + case SSAPropagator::kInteresting: + str << "Interesting"; + break; + default: + str << "Not interesting"; + break; + } + return str; +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/propagator.h b/3rdparty/spirv-tools/source/opt/propagator.h new file mode 100644 index 000000000..f81690c77 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/propagator.h @@ -0,0 +1,319 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_PROPAGATOR_H_ +#define LIBSPIRV_OPT_PROPAGATOR_H_ + +#include +#include +#include +#include +#include +#include + +#include "ir_context.h" +#include "module.h" + +namespace spvtools { +namespace opt { + +// Represents a CFG control edge. +struct Edge { + Edge(ir::BasicBlock* b1, ir::BasicBlock* b2) : source(b1), dest(b2) { + assert(source && "CFG edges cannot have a null source block."); + assert(dest && "CFG edges cannot have a null destination block."); + } + ir::BasicBlock* source; + ir::BasicBlock* dest; + bool operator<(const Edge& o) const { + return std::make_pair(source->id(), dest->id()) < + std::make_pair(o.source->id(), o.dest->id()); + } +}; + +// This class implements a generic value propagation algorithm based on the +// conditional constant propagation algorithm proposed in +// +// Constant propagation with conditional branches, +// Wegman and Zadeck, ACM TOPLAS 13(2):181-210. +// +// A Propagation Engine for GCC +// Diego Novillo, GCC Summit 2005 +// http://ols.fedoraproject.org/GCC/Reprints-2005/novillo-Reprint.pdf +// +// The purpose of this implementation is to act as a common framework for any +// transformation that needs to propagate values from statements producing new +// values to statements using those values. Simulation proceeds as follows: +// +// 1- Initially, all edges of the CFG are marked not executable and the CFG +// worklist is seeded with all the statements in the entry basic block. +// +// 2- Every instruction I is simulated by calling a pass-provided function +// |visit_fn|. This function is responsible for three things: +// +// (a) Keep a value table of interesting values. This table maps SSA IDs to +// their values. For instance, when implementing constant propagation, +// given a store operation 'OpStore %f %int_3', |visit_fn| should assign +// the value 3 to the table slot for %f. +// +// In general, |visit_fn| will need to use the value table to replace its +// operands, fold the result and decide whether a new value needs to be +// stored in the table. |visit_fn| should only create a new mapping in +// the value table if all the operands in the instruction are known and +// present in the value table. +// +// (b) Return a status indicator to direct the propagator logic. Once the +// instruction is simulated, the propagator needs to know whether this +// instruction produced something interesting. This is indicated via +// |visit_fn|'s return value: +// +// SSAPropagator::kNotInteresting: Instruction I produces nothing of +// interest and does not affect any of the work lists. The +// propagator will visit the statement again if any of its operands +// produce an interesting value in the future. +// +// |visit_fn| should always return this value when it is not sure +// whether the instruction will produce an interesting value in the +// future or not. For instance, for constant propagation, an OpIAdd +// instruction may produce a constant if its two operands are +// constant, but the first time we visit the instruction, we still +// may not have its operands in the value table. +// +// SSAPropagator::kVarying: The value produced by I cannot be determined +// at compile time. Further simulation of I is not required. The +// propagator will not visit this instruction again. Additionally, +// the propagator will add all the instructions at the end of SSA +// def-use edges to be simulated again. +// +// If I is a basic block terminator, it will mark all outgoing edges +// as executable so they are traversed one more time. Eventually +// the kVarying attribute will be spread out to all the data and +// control dependents for I. +// +// It is important for propagation to use kVarying as a bottom value +// for the propagation lattice. It should never be possible for an +// instruction to return kVarying once and kInteresting on a second +// visit. Otherwise, propagation would not stabilize. +// +// SSAPropagator::kInteresting: Instruction I produces a value that can +// be computed at compile time. In this case, |visit_fn| should +// create a new mapping between I's result ID and the produced +// value. Much like the kNotInteresting case, the propagator will +// visit this instruction again if any of its operands changes. +// This is useful when the statement changes from one interesting +// state to another. +// +// (c) For conditional branches, |visit_fn| may decide which edge to take out +// of I's basic block. For example, if the operand for an OpSwitch is +// known to take a specific constant value, |visit_fn| should figure out +// the destination basic block and pass it back by setting the second +// argument to |visit_fn|. +// +// At the end of propagation, values in the value table are guaranteed to be +// stable and can be replaced in the IR. +// +// 3- The propagator keeps two work queues. Instructions are only added to +// these queues if they produce an interesting or varying value. None of this +// should be handled by |visit_fn|. The propagator keeps track of this +// automatically (see SSAPropagator::Simulate for implementation). +// +// CFG blocks: contains the queue of blocks to be simulated. +// Blocks are added to this queue if their incoming edges are +// executable. +// +// SSA Edges: An SSA edge is a def-use edge between a value-producing +// instruction and its use instruction. The SSA edges list +// contains the statements at the end of a def-use edge that need +// to be re-visited when an instruction produces a kVarying or +// kInteresting result. +// +// 4- Simulation terminates when all work queues are drained. +// +// +// EXAMPLE: Basic constant store propagator. +// +// Suppose we want to propagate all constant assignments of the form "OpStore +// %id %cst" where "%id" is some variable and "%cst" an OpConstant. The +// following code builds a table |values| where every id that was assigned a +// constant value is mapped to the constant value it was assigned. +// +// auto ctx = spvtools::BuildModule(...); +// std::map values; +// const auto visit_fn = [&ctx, &values](ir::Instruction* instr, +// ir::BasicBlock** dest_bb) { +// if (instr->opcode() == SpvOpStore) { +// uint32_t rhs_id = instr->GetSingleWordOperand(1); +// ir::Instruction* rhs_def = ctx->get_def_use_mgr()->GetDef(rhs_id); +// if (rhs_def->opcode() == SpvOpConstant) { +// uint32_t val = rhs_def->GetSingleWordOperand(2); +// values[rhs_id] = val; +// return opt::SSAPropagator::kInteresting; +// } +// } +// return opt::SSAPropagator::kVarying; +// }; +// opt::SSAPropagator propagator(ctx.get(), &cfg, visit_fn); +// propagator.Run(&fn); +// +// Given the code: +// +// %int_4 = OpConstant %int 4 +// %int_3 = OpConstant %int 3 +// %int_1 = OpConstant %int 1 +// OpStore %x %int_4 +// OpStore %y %int_3 +// OpStore %z %int_1 +// +// After SSAPropagator::Run returns, the |values| map will contain the entries: +// values[%x] = 4, values[%y] = 3, and, values[%z] = 1. +class SSAPropagator { + public: + // Lattice values used for propagation. See class documentation for + // a description. + enum PropStatus { kNotInteresting, kInteresting, kVarying }; + + using VisitFunction = + std::function; + + SSAPropagator(ir::IRContext* context, const VisitFunction& visit_fn) + : ctx_(context), visit_fn_(visit_fn) {} + + // Runs the propagator on function |fn|. Returns true if changes were made to + // the function. Otherwise, it returns false. + bool Run(ir::Function* fn); + + // Returns true if the |i|th argument for |phi| comes through a CFG edge that + // has been marked executable. |i| should be an index value accepted by + // Instruction::GetSingleWordOperand. + bool IsPhiArgExecutable(ir::Instruction* phi, uint32_t i) const; + + // Returns true if |inst| has a recorded status. This will be true once |inst| + // has been simulated once. + bool HasStatus(ir::Instruction* inst) const { return statuses_.count(inst); } + + // Returns the current propagation status of |inst|. Assumes + // |HasStatus(inst)| returns true. + PropStatus Status(ir::Instruction* inst) const { + return statuses_.find(inst)->second; + } + + // Records the propagation status |status| for |inst|. Returns true if the + // status for |inst| has changed or set was set for the first time. + bool SetStatus(ir::Instruction* inst, PropStatus status); + + private: + // Initialize processing. + void Initialize(ir::Function* fn); + + // Simulate the execution |block| by calling |visit_fn_| on every instruction + // in it. + bool Simulate(ir::BasicBlock* block); + + // Simulate the execution of |instr| by replacing all the known values in + // every operand and determining whether the result is interesting for + // propagation. This invokes the callback function |visit_fn_| to determine + // the value computed by |instr|. + bool Simulate(ir::Instruction* instr); + + // Returns true if |instr| should be simulated again. + bool ShouldSimulateAgain(ir::Instruction* instr) const { + return do_not_simulate_.find(instr) == do_not_simulate_.end(); + } + + // Add |instr| to the set of instructions not to simulate again. + void DontSimulateAgain(ir::Instruction* instr) { + do_not_simulate_.insert(instr); + } + + // Returns true if |block| has been simulated already. + bool BlockHasBeenSimulated(ir::BasicBlock* block) const { + return simulated_blocks_.find(block) != simulated_blocks_.end(); + } + + // Marks block |block| as simulated. + void MarkBlockSimulated(ir::BasicBlock* block) { + simulated_blocks_.insert(block); + } + + // Marks |edge| as executable. Returns false if the edge was already marked + // as executable. + bool MarkEdgeExecutable(const Edge& edge) { + return executable_edges_.insert(edge).second; + } + + // Returns true if |edge| has been marked as executable. + bool IsEdgeExecutable(const Edge& edge) const { + return executable_edges_.find(edge) != executable_edges_.end(); + } + + // Returns a pointer to the def-use manager for |ctx_|. + analysis::DefUseManager* get_def_use_mgr() const { + return ctx_->get_def_use_mgr(); + } + + // If the CFG edge |e| has not been executed, this function adds |e|'s + // destination block to the work list. + void AddControlEdge(const Edge& e); + + // Adds all the instructions that use the result of |instr| to the SSA edges + // work list. If |instr| produces no result id, this does nothing. + void AddSSAEdges(ir::Instruction* instr); + + // IR context to use. + ir::IRContext* ctx_; + + // Function that visits instructions during simulation. The output of this + // function is used to determine if the simulated instruction produced a value + // interesting for propagation. The function is responsible for keeping + // track of interesting values by storing them in some user-provided map. + VisitFunction visit_fn_; + + // SSA def-use edges to traverse. Each entry is a destination statement for an + // SSA def-use edge as returned by |def_use_manager_|. + std::queue ssa_edge_uses_; + + // Blocks to simulate. + std::queue blocks_; + + // Blocks simulated during propagation. + std::unordered_set simulated_blocks_; + + // Set of instructions that should not be simulated again because they have + // been found to be in the kVarying state. + std::unordered_set do_not_simulate_; + + // Map between a basic block and its predecessor edges. + // TODO(dnovillo): Move this to ir::CFG and always build them. Alternately, + // move it to IRContext and build CFG preds/succs on-demand. + std::unordered_map> bb_preds_; + + // Map between a basic block and its successor edges. + // TODO(dnovillo): Move this to ir::CFG and always build them. Alternately, + // move it to IRContext and build CFG preds/succs on-demand. + std::unordered_map> bb_succs_; + + // Set of executable CFG edges. + std::set executable_edges_; + + // Tracks instruction propagation status. + std::unordered_map statuses_; +}; + +std::ostream& operator<<(std::ostream& str, + const SSAPropagator::PropStatus& status); + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_PROPAGATOR_H_ diff --git a/3rdparty/spirv-tools/source/opt/redundancy_elimination.cpp b/3rdparty/spirv-tools/source/opt/redundancy_elimination.cpp new file mode 100644 index 000000000..7e5c01d5f --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/redundancy_elimination.cpp @@ -0,0 +1,58 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "redundancy_elimination.h" + +#include "value_number_table.h" + +namespace spvtools { +namespace opt { + +Pass::Status RedundancyEliminationPass::Process(ir::IRContext* c) { + InitializeProcessing(c); + + bool modified = false; + ValueNumberTable vnTable(context()); + + for (auto& func : *get_module()) { + // Build the dominator tree for this function. It is how the code is + // traversed. + opt::DominatorTree& dom_tree = + context()->GetDominatorAnalysis(&func, *context()->cfg())->GetDomTree(); + + // Keeps track of all ids that contain a given value number. We keep + // track of multiple values because they could have the same value, but + // different decorations. + std::map value_to_ids; + + if (EliminateRedundanciesFrom(dom_tree.GetRoot(), vnTable, value_to_ids)) { + modified = true; + } + } + return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange); +} + +bool RedundancyEliminationPass::EliminateRedundanciesFrom( + DominatorTreeNode* bb, const ValueNumberTable& vnTable, + std::map value_to_ids) { + bool modified = EliminateRedundanciesInBB(bb->bb_, vnTable, &value_to_ids); + + for (auto dominated_bb : bb->children_) { + modified |= EliminateRedundanciesFrom(dominated_bb, vnTable, value_to_ids); + } + + return modified; +} +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/redundancy_elimination.h b/3rdparty/spirv-tools/source/opt/redundancy_elimination.h new file mode 100644 index 000000000..634ecc330 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/redundancy_elimination.h @@ -0,0 +1,54 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_REDUNDANCY_ELIMINATION_H_ +#define LIBSPIRV_OPT_REDUNDANCY_ELIMINATION_H_ + +#include "ir_context.h" +#include "local_redundancy_elimination.h" +#include "pass.h" +#include "value_number_table.h" + +namespace spvtools { +namespace opt { + +// This pass implements total redundancy elimination. This is the same as +// local redundancy elimination except it looks across basic block boundaries. +// An instruction, inst, is totally redundant if there is another instruction +// that dominates inst, and also computes the same value. +class RedundancyEliminationPass : public LocalRedundancyEliminationPass { + public: + const char* name() const override { return "redundancy-elimination"; } + Status Process(ir::IRContext*) override; + + protected: + // Removes for all total redundancies in the function starting at |bb|. + // + // |vnTable| must have computed a value number for every result id defined + // in the function containing |bb|. + // + // |value_to_ids| is a map from value number to ids. If {vn, id} is in + // |value_to_ids| then vn is the value number of id, and the defintion of id + // dominates |bb|. + // + // Returns true if at least one instruction is deleted. + bool EliminateRedundanciesFrom(DominatorTreeNode* bb, + const ValueNumberTable& vnTable, + std::map value_to_ids); +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_REDUNDANCY_ELIMINATION_H_ diff --git a/3rdparty/spirv-tools/source/opt/reflect.h b/3rdparty/spirv-tools/source/opt/reflect.h new file mode 100644 index 000000000..ef2d84947 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/reflect.h @@ -0,0 +1,65 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_REFLECT_H_ +#define LIBSPIRV_OPT_REFLECT_H_ + +#include "latest_version_spirv_header.h" + +namespace spvtools { +namespace ir { + +// Note that as SPIR-V evolves over time, new opcodes may appear. So the +// following functions tend to be outdated and should be updated when SPIR-V +// version bumps. + +inline bool IsDebug1Inst(SpvOp opcode) { + return (opcode >= SpvOpSourceContinued && opcode <= SpvOpSourceExtension) || + opcode == SpvOpString; +} +inline bool IsDebug2Inst(SpvOp opcode) { + return opcode == SpvOpName || opcode == SpvOpMemberName; +} +inline bool IsDebug3Inst(SpvOp opcode) { + return opcode == SpvOpModuleProcessed; +} +inline bool IsDebugLineInst(SpvOp opcode) { + return opcode == SpvOpLine || opcode == SpvOpNoLine; +} +inline bool IsAnnotationInst(SpvOp opcode) { + return (opcode >= SpvOpDecorate && opcode <= SpvOpGroupMemberDecorate) || + opcode == SpvOpDecorateId || opcode == SpvOpDecorateStringGOOGLE || + opcode == SpvOpMemberDecorateStringGOOGLE; +} +inline bool IsTypeInst(SpvOp opcode) { + return (opcode >= SpvOpTypeVoid && opcode <= SpvOpTypeForwardPointer) || + opcode == SpvOpTypePipeStorage || opcode == SpvOpTypeNamedBarrier; +} +inline bool IsConstantInst(SpvOp opcode) { + return opcode >= SpvOpConstantTrue && opcode <= SpvOpSpecConstantOp; +} +inline bool IsCompileTimeConstantInst(SpvOp opcode) { + return opcode >= SpvOpConstantTrue && opcode <= SpvOpConstantNull; +} +inline bool IsSpecConstantInst(SpvOp opcode) { + return opcode >= SpvOpSpecConstantTrue && opcode <= SpvOpSpecConstantOp; +} +inline bool IsTerminatorInst(SpvOp opcode) { + return opcode >= SpvOpBranch && opcode <= SpvOpUnreachable; +} + +} // namespace ir +} // namespace spvtools + +#endif // LIBSPIRV_OPT_REFLECT_H_ diff --git a/3rdparty/spirv-tools/source/opt/remove_duplicates_pass.cpp b/3rdparty/spirv-tools/source/opt/remove_duplicates_pass.cpp new file mode 100644 index 000000000..0a54d76ea --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/remove_duplicates_pass.cpp @@ -0,0 +1,206 @@ +// Copyright (c) 2017 Pierre Moreau +// +// 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 "remove_duplicates_pass.h" + +#include + +#include +#include +#include +#include +#include + +#include "decoration_manager.h" +#include "ir_context.h" +#include "opcode.h" +#include "reflect.h" + +namespace spvtools { +namespace opt { + +using ir::Instruction; +using ir::Module; +using ir::Operand; +using opt::analysis::DecorationManager; +using opt::analysis::DefUseManager; + +Pass::Status RemoveDuplicatesPass::Process(ir::IRContext* ir_context) { + bool modified = RemoveDuplicateCapabilities(ir_context); + modified |= RemoveDuplicatesExtInstImports(ir_context); + modified |= RemoveDuplicateTypes(ir_context); + modified |= RemoveDuplicateDecorations(ir_context); + + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; +} + +bool RemoveDuplicatesPass::RemoveDuplicateCapabilities( + ir::IRContext* ir_context) const { + bool modified = false; + + if (ir_context->capabilities().empty()) { + return modified; + } + + std::unordered_set capabilities; + for (auto* i = &*ir_context->capability_begin(); i;) { + auto res = capabilities.insert(i->GetSingleWordOperand(0u)); + + if (res.second) { + // Never seen before, keep it. + i = i->NextNode(); + } else { + // It's a duplicate, remove it. + i = ir_context->KillInst(i); + modified = true; + } + } + + return modified; +} + +bool RemoveDuplicatesPass::RemoveDuplicatesExtInstImports( + ir::IRContext* ir_context) const { + bool modified = false; + + if (ir_context->ext_inst_imports().empty()) { + return modified; + } + + std::unordered_map ext_inst_imports; + for (auto* i = &*ir_context->ext_inst_import_begin(); i;) { + auto res = ext_inst_imports.emplace( + reinterpret_cast(i->GetInOperand(0u).words.data()), + i->result_id()); + if (res.second) { + // Never seen before, keep it. + i = i->NextNode(); + } else { + // It's a duplicate, remove it. + ir_context->ReplaceAllUsesWith(i->result_id(), res.first->second); + i = ir_context->KillInst(i); + modified = true; + } + } + + return modified; +} + +bool RemoveDuplicatesPass::RemoveDuplicateTypes( + ir::IRContext* ir_context) const { + bool modified = false; + + if (ir_context->types_values().empty()) { + return modified; + } + + std::vector visited_types; + std::vector to_delete; + for (auto* i = &*ir_context->types_values_begin(); i; i = i->NextNode()) { + // We only care about types. + if (!spvOpcodeGeneratesType((i->opcode())) && + i->opcode() != SpvOpTypeForwardPointer) { + continue; + } + + // Is the current type equal to one of the types we have aready visited? + SpvId id_to_keep = 0u; + // TODO(dneto0): Use a trie to avoid quadratic behaviour? Extract the + // ResultIdTrie from unify_const_pass.cpp for this. + for (auto j : visited_types) { + if (AreTypesEqual(*i, *j, ir_context)) { + id_to_keep = j->result_id(); + break; + } + } + + if (id_to_keep == 0u) { + // This is a never seen before type, keep it around. + visited_types.emplace_back(i); + } else { + // The same type has already been seen before, remove this one. + ir_context->KillNamesAndDecorates(i->result_id()); + ir_context->ReplaceAllUsesWith(i->result_id(), id_to_keep); + modified = true; + to_delete.emplace_back(i); + } + } + + for (auto i : to_delete) { + ir_context->KillInst(i); + } + + return modified; +} + +// TODO(pierremoreau): Duplicate decoration groups should be removed. For +// example, in +// OpDecorate %1 Constant +// %1 = OpDecorationGroup +// OpDecorate %2 Constant +// %2 = OpDecorationGroup +// OpGroupDecorate %1 %3 +// OpGroupDecorate %2 %4 +// group %2 could be removed. +bool RemoveDuplicatesPass::RemoveDuplicateDecorations( + ir::IRContext* ir_context) const { + bool modified = false; + + std::vector visited_decorations; + + opt::analysis::DecorationManager decoration_manager(ir_context->module()); + for (auto* i = &*ir_context->annotation_begin(); i;) { + // Is the current decoration equal to one of the decorations we have aready + // visited? + bool already_visited = false; + // TODO(dneto0): Use a trie to avoid quadratic behaviour? Extract the + // ResultIdTrie from unify_const_pass.cpp for this. + for (const Instruction* j : visited_decorations) { + if (decoration_manager.AreDecorationsTheSame(&*i, j, false)) { + already_visited = true; + break; + } + } + + if (!already_visited) { + // This is a never seen before decoration, keep it around. + visited_decorations.emplace_back(&*i); + i = i->NextNode(); + } else { + // The same decoration has already been seen before, remove this one. + modified = true; + i = ir_context->KillInst(i); + } + } + + return modified; +} + +bool RemoveDuplicatesPass::AreTypesEqual(const Instruction& inst1, + const Instruction& inst2, + ir::IRContext* context) { + if (inst1.opcode() != inst2.opcode()) return false; + if (!ir::IsTypeInst(inst1.opcode())) return false; + + const analysis::Type* type1 = + context->get_type_mgr()->GetType(inst1.result_id()); + const analysis::Type* type2 = + context->get_type_mgr()->GetType(inst2.result_id()); + if (type1 && type2 && *type1 == *type2) return true; + + return false; +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/remove_duplicates_pass.h b/3rdparty/spirv-tools/source/opt/remove_duplicates_pass.h new file mode 100644 index 000000000..d766f6733 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/remove_duplicates_pass.h @@ -0,0 +1,67 @@ +// Copyright (c) 2017 Pierre Moreau +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_REMOVE_DUPLICATES_PASS_H_ +#define LIBSPIRV_OPT_REMOVE_DUPLICATES_PASS_H_ + +#include + +#include "decoration_manager.h" +#include "def_use_manager.h" +#include "ir_context.h" +#include "module.h" +#include "pass.h" + +namespace spvtools { +namespace opt { + +using IdDecorationsList = + std::unordered_map>; + +// See optimizer.hpp for documentation. +class RemoveDuplicatesPass : public Pass { + public: + const char* name() const override { return "remove-duplicates"; } + Status Process(ir::IRContext*) override; + // TODO(pierremoreau): Move this function somewhere else (e.g. pass.h or + // within the type manager) + // Returns whether two types are equal, and have the same decorations. + static bool AreTypesEqual(const ir::Instruction& inst1, + const ir::Instruction& inst2, + ir::IRContext* context); + + private: + // Remove duplicate capabilities from the module attached to |ir_context|. + // + // Returns true if the module was modified, false otherwise. + bool RemoveDuplicateCapabilities(ir::IRContext* ir_context) const; + // Remove duplicate extended instruction imports from the module attached to + // |ir_context|. + // + // Returns true if the module was modified, false otherwise. + bool RemoveDuplicatesExtInstImports(ir::IRContext* ir_context) const; + // Remove duplicate types from the module attached to |ir_context|. + // + // Returns true if the module was modified, false otherwise. + bool RemoveDuplicateTypes(ir::IRContext* ir_context) const; + // Remove duplicate decorations from the module attached to |ir_context|. + // + // Returns true if the module was modified, false otherwise. + bool RemoveDuplicateDecorations(ir::IRContext* ir_context) const; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_REMOVE_DUPLICATES_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/replace_invalid_opc.cpp b/3rdparty/spirv-tools/source/opt/replace_invalid_opc.cpp new file mode 100644 index 000000000..a025c3ce9 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/replace_invalid_opc.cpp @@ -0,0 +1,207 @@ +// Copyright (c) 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "replace_invalid_opc.h" + +#include + +namespace spvtools { +namespace opt { + +Pass::Status ReplaceInvalidOpcodePass::Process(ir::IRContext* c) { + InitializeProcessing(c); + bool modified = false; + + if (context()->get_feature_mgr()->HasCapability(SpvCapabilityLinkage)) { + return Status::SuccessWithoutChange; + } + + SpvExecutionModel execution_model = GetExecutionModel(); + if (execution_model == SpvExecutionModelKernel) { + // We do not handle kernels. + return Status::SuccessWithoutChange; + } + if (execution_model == SpvExecutionModelMax) { + // Mixed execution models for the entry points. This case is not currently + // handled. + return Status::SuccessWithoutChange; + } + + for (ir::Function& func : *get_module()) { + modified |= RewriteFunction(&func, execution_model); + } + return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange); +} + +SpvExecutionModel ReplaceInvalidOpcodePass::GetExecutionModel() { + SpvExecutionModel result = SpvExecutionModelMax; + bool first = true; + for (ir::Instruction& entry_point : get_module()->entry_points()) { + if (first) { + result = + static_cast(entry_point.GetSingleWordInOperand(0)); + first = false; + } else { + SpvExecutionModel current_model = + static_cast(entry_point.GetSingleWordInOperand(0)); + if (current_model != result) { + result = SpvExecutionModelMax; + break; + } + } + } + return result; +} + +bool ReplaceInvalidOpcodePass::RewriteFunction(ir::Function* function, + SpvExecutionModel model) { + bool modified = false; + ir::Instruction* last_line_dbg_inst = nullptr; + function->ForEachInst( + [model, &modified, &last_line_dbg_inst, this](ir::Instruction* inst) { + // Track the debug information so we can have a meaningful message. + if (inst->opcode() == SpvOpLabel || inst->opcode() == SpvOpNoLine) { + last_line_dbg_inst = nullptr; + return; + } else if (inst->opcode() == SpvOpLine) { + last_line_dbg_inst = inst; + return; + } + + bool replace = false; + if (model != SpvExecutionModelFragment && + IsFragmentShaderOnlyInstruction(inst)) { + replace = true; + } + + if (model != SpvExecutionModelTessellationControl && + model != SpvExecutionModelGLCompute) { + if (inst->opcode() == SpvOpControlBarrier) { + assert(model != SpvExecutionModelKernel && + "Expecting to be working on a shader module."); + replace = true; + } + } + + if (replace) { + modified = true; + if (last_line_dbg_inst == nullptr) { + ReplaceInstruction(inst, nullptr, 0, 0); + } else { + // Get the name of the source file. + ir::Instruction* file_name = context()->get_def_use_mgr()->GetDef( + last_line_dbg_inst->GetSingleWordInOperand(0)); + const char* source = reinterpret_cast( + &file_name->GetInOperand(0).words[0]); + + // Get the line number and column number. + uint32_t line_number = + last_line_dbg_inst->GetSingleWordInOperand(1); + uint32_t col_number = last_line_dbg_inst->GetSingleWordInOperand(2); + + // Replace the instruction. + ReplaceInstruction(inst, source, line_number, col_number); + } + } + }, + /* run_on_debug_line_insts = */ true); + return modified; +} + +bool ReplaceInvalidOpcodePass::IsFragmentShaderOnlyInstruction( + ir::Instruction* inst) { + switch (inst->opcode()) { + case SpvOpDPdx: + case SpvOpDPdy: + case SpvOpFwidth: + case SpvOpDPdxFine: + case SpvOpDPdyFine: + case SpvOpFwidthFine: + case SpvOpDPdxCoarse: + case SpvOpDPdyCoarse: + case SpvOpFwidthCoarse: + case SpvOpImageSampleImplicitLod: + case SpvOpImageSampleDrefImplicitLod: + case SpvOpImageSampleProjImplicitLod: + case SpvOpImageSampleProjDrefImplicitLod: + case SpvOpImageSparseSampleImplicitLod: + case SpvOpImageSparseSampleDrefImplicitLod: + case SpvOpImageQueryLod: + // TODO: Teach |ReplaceInstruction| to handle block terminators. Then + // uncomment the OpKill case. + // case SpvOpKill: + return true; + default: + return false; + } +} + +void ReplaceInvalidOpcodePass::ReplaceInstruction(ir::Instruction* inst, + const char* source, + uint32_t line_number, + uint32_t column_number) { + if (inst->result_id() != 0) { + uint32_t const_id = GetSpecialConstant(inst->type_id()); + context()->KillNamesAndDecorates(inst); + context()->ReplaceAllUsesWith(inst->result_id(), const_id); + } + assert(!inst->IsBlockTerminator() && + "We cannot simply delete a block terminator. It must be replaced " + "with something."); + if (consumer()) { + std::string message = BuildWarningMessage(inst->opcode()); + consumer()(SPV_MSG_WARNING, source, {line_number, column_number, 0}, + message.c_str()); + } + context()->KillInst(inst); +} + +uint32_t ReplaceInvalidOpcodePass::GetSpecialConstant(uint32_t type_id) { + const analysis::Constant* special_const = nullptr; + analysis::ConstantManager* const_mgr = context()->get_constant_mgr(); + analysis::TypeManager* type_mgr = context()->get_type_mgr(); + + ir::Instruction* type = context()->get_def_use_mgr()->GetDef(type_id); + if (type->opcode() == SpvOpTypeVector) { + uint32_t component_const = + GetSpecialConstant(type->GetSingleWordInOperand(0)); + std::vector ids; + for (uint32_t i = 0; i < type->GetSingleWordInOperand(1); ++i) { + ids.push_back(component_const); + } + special_const = const_mgr->GetConstant(type_mgr->GetType(type_id), ids); + } else { + assert(type->opcode() == SpvOpTypeInt || type->opcode() == SpvOpTypeFloat); + std::vector literal_words; + for (uint32_t i = 0; i < type->GetSingleWordInOperand(0); i += 32) { + literal_words.push_back(0xDEADBEEF); + } + special_const = + const_mgr->GetConstant(type_mgr->GetType(type_id), literal_words); + } + assert(special_const != nullptr); + return const_mgr->GetDefiningInstruction(special_const)->result_id(); +} + +std::string ReplaceInvalidOpcodePass::BuildWarningMessage(SpvOp opcode) { + spv_opcode_desc opcode_info; + context()->grammar().lookupOpcode(opcode, &opcode_info); + std::string message = "Removing "; + message += opcode_info->name; + message += " instruction because of incompatible execution model."; + return message; +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/replace_invalid_opc.h b/3rdparty/spirv-tools/source/opt/replace_invalid_opc.h new file mode 100644 index 000000000..e661fcec0 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/replace_invalid_opc.h @@ -0,0 +1,65 @@ +// Copyright (c) 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_REPLACE_INVALID_OPC_H_ +#define LIBSPIRV_OPT_REPLACE_INVALID_OPC_H_ + +#include "pass.h" + +namespace spvtools { +namespace opt { + +// This pass will runs on shader modules only. It will replace the result of +// instructions that are valid for shader modules, but not the current shader +// stage, with a constant value. If the instruction does not have a return +// value, the instruction will simply be deleted. +class ReplaceInvalidOpcodePass : public Pass { + public: + const char* name() const override { return "replace-invalid-opcodes"; } + Status Process(ir::IRContext*) override; + + private: + // Returns the execution model that is used by every entry point in the + // module. If more than one execution model is used in the module, then the + // return value is SpvExecutionModelMax. + SpvExecutionModel GetExecutionModel(); + + // Replaces all instructions in |function| that are invalid with execution + // model |mode|, but valid for another shader model, with a special constant + // value. See |GetSpecialConstant|. + bool RewriteFunction(ir::Function* function, SpvExecutionModel mode); + + // Returns true if |inst| is valid for fragment shaders only. + bool IsFragmentShaderOnlyInstruction(ir::Instruction* inst); + + // Replaces all uses of the result of |inst|, if there is one, with the id of + // a special constant. Then |inst| is killed. |inst| cannot be a block + // terminator because the basic block will then become invalid. |inst| is no + // longer valid after calling this function. + void ReplaceInstruction(ir::Instruction* inst, const char* source, + uint32_t line_number, uint32_t column_number); + + // Returns the id of a constant with type |type_id|. The type must be an + // integer, float, or vector. For scalar types, the hex representation of the + // constant will be the concatenation of 0xDEADBEEF with itself until the + // width of the type has been reached. For a vector, each element of the + // constant will be constructed the same way. + uint32_t GetSpecialConstant(uint32_t type_id); + std::string BuildWarningMessage(SpvOp opcode); +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_REPLACE_INVALID_OPC_H_ diff --git a/3rdparty/spirv-tools/source/opt/scalar_analysis.cpp b/3rdparty/spirv-tools/source/opt/scalar_analysis.cpp new file mode 100644 index 000000000..ccdb66c82 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/scalar_analysis.cpp @@ -0,0 +1,638 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "opt/scalar_analysis.h" + +#include +#include +#include +#include + +#include "opt/ir_context.h" + +// Transforms a given scalar operation instruction into a DAG representation. +// +// 1. Take an instruction and traverse its operands until we reach a +// constant node or an instruction which we do not know how to compute the +// value, such as a load. +// +// 2. Create a new node for each instruction traversed and build the nodes for +// the in operands of that instruction as well. +// +// 3. Add the operand nodes as children of the first and hash the node. Use the +// hash to see if the node is already in the cache. We ensure the children are +// always in sorted order so that two nodes with the same children but inserted +// in a different order have the same hash and so that the overloaded operator== +// will return true. If the node is already in the cache return the cached +// version instead. +// +// 4. The created DAG can then be simplified by +// ScalarAnalysis::SimplifyExpression, implemented in +// scalar_analysis_simplification.cpp. See that file for further information on +// the simplification process. +// + +namespace spvtools { +namespace opt { + +uint32_t SENode::NumberOfNodes = 0; + +ScalarEvolutionAnalysis::ScalarEvolutionAnalysis(ir::IRContext* context) + : context_(context) { + // Create and cached the CantComputeNode. + cached_cant_compute_ = + GetCachedOrAdd(std::unique_ptr(new SECantCompute(this))); +} + +SENode* ScalarEvolutionAnalysis::CreateNegation(SENode* operand) { + // If operand is can't compute then the whole graph is can't compute. + if (operand->IsCantCompute()) return CreateCantComputeNode(); + + if (operand->GetType() == SENode::Constant) { + return CreateConstant(-operand->AsSEConstantNode()->FoldToSingleValue()); + } + std::unique_ptr negation_node{new SENegative(this)}; + negation_node->AddChild(operand); + return GetCachedOrAdd(std::move(negation_node)); +} + +SENode* ScalarEvolutionAnalysis::CreateConstant(int64_t integer) { + return GetCachedOrAdd( + std::unique_ptr(new SEConstantNode(this, integer))); +} + +SENode* ScalarEvolutionAnalysis::CreateRecurrentExpression( + const ir::Loop* loop, SENode* offset, SENode* coefficient) { + assert(loop && "Recurrent add expressions must have a valid loop."); + + // If operands are can't compute then the whole graph is can't compute. + if (offset->IsCantCompute() || coefficient->IsCantCompute()) + return CreateCantComputeNode(); + + std::unique_ptr phi_node{new SERecurrentNode(this, loop)}; + phi_node->AddOffset(offset); + phi_node->AddCoefficient(coefficient); + + return GetCachedOrAdd(std::move(phi_node)); +} + +SENode* ScalarEvolutionAnalysis::AnalyzeMultiplyOp( + const ir::Instruction* multiply) { + assert(multiply->opcode() == SpvOp::SpvOpIMul && + "Multiply node did not come from a multiply instruction"); + opt::analysis::DefUseManager* def_use = context_->get_def_use_mgr(); + + SENode* op1 = + AnalyzeInstruction(def_use->GetDef(multiply->GetSingleWordInOperand(0))); + SENode* op2 = + AnalyzeInstruction(def_use->GetDef(multiply->GetSingleWordInOperand(1))); + + return CreateMultiplyNode(op1, op2); +} + +SENode* ScalarEvolutionAnalysis::CreateMultiplyNode(SENode* operand_1, + SENode* operand_2) { + // If operands are can't compute then the whole graph is can't compute. + if (operand_1->IsCantCompute() || operand_2->IsCantCompute()) + return CreateCantComputeNode(); + + if (operand_1->GetType() == SENode::Constant && + operand_2->GetType() == SENode::Constant) { + return CreateConstant(operand_1->AsSEConstantNode()->FoldToSingleValue() * + operand_2->AsSEConstantNode()->FoldToSingleValue()); + } + + std::unique_ptr multiply_node{new SEMultiplyNode(this)}; + + multiply_node->AddChild(operand_1); + multiply_node->AddChild(operand_2); + + return GetCachedOrAdd(std::move(multiply_node)); +} + +SENode* ScalarEvolutionAnalysis::CreateSubtraction(SENode* operand_1, + SENode* operand_2) { + // Fold if both operands are constant. + if (operand_1->GetType() == SENode::Constant && + operand_2->GetType() == SENode::Constant) { + return CreateConstant(operand_1->AsSEConstantNode()->FoldToSingleValue() - + operand_2->AsSEConstantNode()->FoldToSingleValue()); + } + + return CreateAddNode(operand_1, CreateNegation(operand_2)); +} + +SENode* ScalarEvolutionAnalysis::CreateAddNode(SENode* operand_1, + SENode* operand_2) { + // Fold if both operands are constant and the |simplify| flag is true. + if (operand_1->GetType() == SENode::Constant && + operand_2->GetType() == SENode::Constant) { + return CreateConstant(operand_1->AsSEConstantNode()->FoldToSingleValue() + + operand_2->AsSEConstantNode()->FoldToSingleValue()); + } + + // If operands are can't compute then the whole graph is can't compute. + if (operand_1->IsCantCompute() || operand_2->IsCantCompute()) + return CreateCantComputeNode(); + + std::unique_ptr add_node{new SEAddNode(this)}; + + add_node->AddChild(operand_1); + add_node->AddChild(operand_2); + + return GetCachedOrAdd(std::move(add_node)); +} + +SENode* ScalarEvolutionAnalysis::AnalyzeInstruction( + const ir::Instruction* inst) { + auto itr = recurrent_node_map_.find(inst); + if (itr != recurrent_node_map_.end()) return itr->second; + + SENode* output = nullptr; + switch (inst->opcode()) { + case SpvOp::SpvOpPhi: { + output = AnalyzePhiInstruction(inst); + break; + } + case SpvOp::SpvOpConstant: + case SpvOp::SpvOpConstantNull: { + output = AnalyzeConstant(inst); + break; + } + case SpvOp::SpvOpISub: + case SpvOp::SpvOpIAdd: { + output = AnalyzeAddOp(inst); + break; + } + case SpvOp::SpvOpIMul: { + output = AnalyzeMultiplyOp(inst); + break; + } + default: { + output = CreateValueUnknownNode(inst); + break; + } + } + + return output; +} + +SENode* ScalarEvolutionAnalysis::AnalyzeConstant(const ir::Instruction* inst) { + if (inst->opcode() == SpvOp::SpvOpConstantNull) return CreateConstant(0); + + assert(inst->opcode() == SpvOp::SpvOpConstant); + assert(inst->NumInOperands() == 1); + int64_t value = 0; + + // Look up the instruction in the constant manager. + const opt::analysis::Constant* constant = + context_->get_constant_mgr()->FindDeclaredConstant(inst->result_id()); + + if (!constant) return CreateCantComputeNode(); + + const opt::analysis::IntConstant* int_constant = constant->AsIntConstant(); + + // Exit out if it is a 64 bit integer. + if (!int_constant || int_constant->words().size() != 1) + return CreateCantComputeNode(); + + if (int_constant->type()->AsInteger()->IsSigned()) { + value = int_constant->GetS32BitValue(); + } else { + value = int_constant->GetU32BitValue(); + } + + return CreateConstant(value); +} + +// Handles both addition and subtraction. If the |sub| flag is set then the +// addition will be op1+(-op2) otherwise op1+op2. +SENode* ScalarEvolutionAnalysis::AnalyzeAddOp(const ir::Instruction* inst) { + assert((inst->opcode() == SpvOp::SpvOpIAdd || + inst->opcode() == SpvOp::SpvOpISub) && + "Add node must be created from a OpIAdd or OpISub instruction"); + + opt::analysis::DefUseManager* def_use = context_->get_def_use_mgr(); + + SENode* op1 = + AnalyzeInstruction(def_use->GetDef(inst->GetSingleWordInOperand(0))); + + SENode* op2 = + AnalyzeInstruction(def_use->GetDef(inst->GetSingleWordInOperand(1))); + + // To handle subtraction we wrap the second operand in a unary negation node. + if (inst->opcode() == SpvOp::SpvOpISub) { + op2 = CreateNegation(op2); + } + + return CreateAddNode(op1, op2); +} + +SENode* ScalarEvolutionAnalysis::AnalyzePhiInstruction( + const ir::Instruction* phi) { + // The phi should only have two incoming value pairs. + if (phi->NumInOperands() != 4) { + return CreateCantComputeNode(); + } + + opt::analysis::DefUseManager* def_use = context_->get_def_use_mgr(); + + // Get the basic block this instruction belongs to. + ir::BasicBlock* basic_block = + context_->get_instr_block(const_cast(phi)); + + // And then the function that the basic blocks belongs to. + ir::Function* function = basic_block->GetParent(); + + // Use the function to get the loop descriptor. + ir::LoopDescriptor* loop_descriptor = context_->GetLoopDescriptor(function); + + // We only handle phis in loops at the moment. + if (!loop_descriptor) return CreateCantComputeNode(); + + // Get the innermost loop which this block belongs to. + ir::Loop* loop = (*loop_descriptor)[basic_block->id()]; + + // If the loop doesn't exist or doesn't have a preheader or latch block, exit + // out. + if (!loop || !loop->GetLatchBlock() || !loop->GetPreHeaderBlock() || + loop->GetHeaderBlock() != basic_block) + return recurrent_node_map_[phi] = CreateCantComputeNode(); + + std::unique_ptr phi_node{new SERecurrentNode(this, loop)}; + + // We add the node to this map to allow it to be returned before the node is + // fully built. This is needed as the subsequent call to AnalyzeInstruction + // could lead back to this |phi| instruction so we return the pointer + // immediately in AnalyzeInstruction to break the recursion. + recurrent_node_map_[phi] = phi_node.get(); + + // Traverse the operands of the instruction an create new nodes for each one. + for (uint32_t i = 0; i < phi->NumInOperands(); i += 2) { + uint32_t value_id = phi->GetSingleWordInOperand(i); + uint32_t incoming_label_id = phi->GetSingleWordInOperand(i + 1); + + ir::Instruction* value_inst = def_use->GetDef(value_id); + SENode* value_node = AnalyzeInstruction(value_inst); + + // If any operand is CantCompute then the whole graph is CantCompute. + if (value_node->IsCantCompute()) + return recurrent_node_map_[phi] = CreateCantComputeNode(); + + // If the value is coming from the preheader block then the value is the + // initial value of the phi. + if (incoming_label_id == loop->GetPreHeaderBlock()->id()) { + phi_node->AddOffset(value_node); + } else if (incoming_label_id == loop->GetLatchBlock()->id()) { + // Assumed to be in the form of step + phi. + if (value_node->GetType() != SENode::Add) + return recurrent_node_map_[phi] = CreateCantComputeNode(); + + SENode* step_node = nullptr; + SENode* phi_operand = nullptr; + SENode* operand_1 = value_node->GetChild(0); + SENode* operand_2 = value_node->GetChild(1); + + // Find which node is the step term. + if (!operand_1->AsSERecurrentNode()) + step_node = operand_1; + else if (!operand_2->AsSERecurrentNode()) + step_node = operand_2; + + // Find which node is the recurrent expression. + if (operand_1->AsSERecurrentNode()) + phi_operand = operand_1; + else if (operand_2->AsSERecurrentNode()) + phi_operand = operand_2; + + // If it is not in the form step + phi exit out. + if (!(step_node && phi_operand)) + return recurrent_node_map_[phi] = CreateCantComputeNode(); + + // If the phi operand is not the same phi node exit out. + if (phi_operand != phi_node.get()) + return recurrent_node_map_[phi] = CreateCantComputeNode(); + + if (!IsLoopInvariant(loop, step_node)) + return recurrent_node_map_[phi] = CreateCantComputeNode(); + + phi_node->AddCoefficient(step_node); + } + } + + // Once the node is fully built we update the map with the version from the + // cache (if it has already been added to the cache). + return recurrent_node_map_[phi] = GetCachedOrAdd(std::move(phi_node)); +} + +SENode* ScalarEvolutionAnalysis::CreateValueUnknownNode( + const ir::Instruction* inst) { + std::unique_ptr load_node{ + new SEValueUnknown(this, inst->result_id())}; + return GetCachedOrAdd(std::move(load_node)); +} + +SENode* ScalarEvolutionAnalysis::CreateCantComputeNode() { + return cached_cant_compute_; +} + +// Add the created node into the cache of nodes. If it already exists return it. +SENode* ScalarEvolutionAnalysis::GetCachedOrAdd( + std::unique_ptr prospective_node) { + auto itr = node_cache_.find(prospective_node); + if (itr != node_cache_.end()) { + return (*itr).get(); + } + + SENode* raw_ptr_to_node = prospective_node.get(); + node_cache_.insert(std::move(prospective_node)); + return raw_ptr_to_node; +} + +bool ScalarEvolutionAnalysis::IsLoopInvariant(const ir::Loop* loop, + const SENode* node) const { + for (auto itr = node->graph_cbegin(); itr != node->graph_cend(); ++itr) { + if (const SERecurrentNode* rec = itr->AsSERecurrentNode()) { + const ir::BasicBlock* header = rec->GetLoop()->GetHeaderBlock(); + + // If the loop which the recurrent expression belongs to is either |loop + // or a nested loop inside |loop| then we assume it is variant. + if (loop->IsInsideLoop(header)) { + return false; + } + } else if (const SEValueUnknown* unknown = itr->AsSEValueUnknown()) { + // If the instruction is inside the loop we conservatively assume it is + // loop variant. + if (loop->IsInsideLoop(unknown->ResultId())) return false; + } + } + + return true; +} + +SENode* ScalarEvolutionAnalysis::GetCoefficientFromRecurrentTerm( + SENode* node, const ir::Loop* loop) { + // Traverse the DAG to find the recurrent expression belonging to |loop|. + for (auto itr = node->graph_begin(); itr != node->graph_end(); ++itr) { + SERecurrentNode* rec = itr->AsSERecurrentNode(); + if (rec && rec->GetLoop() == loop) { + return rec->GetCoefficient(); + } + } + return CreateConstant(0); +} + +SENode* ScalarEvolutionAnalysis::UpdateChildNode(SENode* parent, + SENode* old_child, + SENode* new_child) { + // Only handles add. + if (parent->GetType() != SENode::Add) return parent; + + std::vector new_children; + for (SENode* child : *parent) { + if (child == old_child) { + new_children.push_back(new_child); + } else { + new_children.push_back(child); + } + } + + std::unique_ptr add_node{new SEAddNode(this)}; + for (SENode* child : new_children) { + add_node->AddChild(child); + } + + return SimplifyExpression(GetCachedOrAdd(std::move(add_node))); +} + +// Rebuild the |node| eliminating, if it exists, the recurrent term which +// belongs to the |loop|. +SENode* ScalarEvolutionAnalysis::BuildGraphWithoutRecurrentTerm( + SENode* node, const ir::Loop* loop) { + // If the node is already a recurrent expression belonging to loop then just + // return the offset. + SERecurrentNode* recurrent = node->AsSERecurrentNode(); + if (recurrent) { + if (recurrent->GetLoop() == loop) { + return recurrent->GetOffset(); + } else { + return node; + } + } + + std::vector new_children; + // Otherwise find the recurrent node in the children of this node. + for (auto itr : *node) { + recurrent = itr->AsSERecurrentNode(); + if (recurrent && recurrent->GetLoop() == loop) { + new_children.push_back(recurrent->GetOffset()); + } else { + new_children.push_back(itr); + } + } + + std::unique_ptr add_node{new SEAddNode(this)}; + for (SENode* child : new_children) { + add_node->AddChild(child); + } + + return SimplifyExpression(GetCachedOrAdd(std::move(add_node))); +} + +// Return the recurrent term belonging to |loop| if it appears in the graph +// starting at |node| or null if it doesn't. +SERecurrentNode* ScalarEvolutionAnalysis::GetRecurrentTerm( + SENode* node, const ir::Loop* loop) { + for (auto itr = node->graph_begin(); itr != node->graph_end(); ++itr) { + SERecurrentNode* rec = itr->AsSERecurrentNode(); + if (rec && rec->GetLoop() == loop) { + return rec; + } + } + return nullptr; +} +std::string SENode::AsString() const { + switch (GetType()) { + case Constant: + return "Constant"; + case RecurrentAddExpr: + return "RecurrentAddExpr"; + case Add: + return "Add"; + case Negative: + return "Negative"; + case Multiply: + return "Multiply"; + case ValueUnknown: + return "Value Unknown"; + case CanNotCompute: + return "Can not compute"; + } + return "NULL"; +} + +bool SENode::operator==(const SENode& other) const { + if (GetType() != other.GetType()) return false; + + if (other.GetChildren().size() != children_.size()) return false; + + const SERecurrentNode* this_as_recurrent = AsSERecurrentNode(); + + // Check the children are the same, for SERecurrentNodes we need to check the + // offset and coefficient manually as the child vector is sorted by ids so the + // offset/coefficient information is lost. + if (!this_as_recurrent) { + for (size_t index = 0; index < children_.size(); ++index) { + if (other.GetChildren()[index] != children_[index]) return false; + } + } else { + const SERecurrentNode* other_as_recurrent = other.AsSERecurrentNode(); + + // We've already checked the types are the same, this should not fail if + // this->AsSERecurrentNode() succeeded. + assert(other_as_recurrent); + + if (this_as_recurrent->GetCoefficient() != + other_as_recurrent->GetCoefficient()) + return false; + + if (this_as_recurrent->GetOffset() != other_as_recurrent->GetOffset()) + return false; + + if (this_as_recurrent->GetLoop() != other_as_recurrent->GetLoop()) + return false; + } + + // If we're dealing with a value unknown node check both nodes were created by + // the same instruction. + if (GetType() == SENode::ValueUnknown) { + if (AsSEValueUnknown()->ResultId() != + other.AsSEValueUnknown()->ResultId()) { + return false; + } + } + + if (AsSEConstantNode()) { + if (AsSEConstantNode()->FoldToSingleValue() != + other.AsSEConstantNode()->FoldToSingleValue()) + return false; + } + + return true; +} + +bool SENode::operator!=(const SENode& other) const { return !(*this == other); } + +namespace { +// Helper functions to insert 32/64 bit values into the 32 bit hash string. This +// allows us to add pointers to the string by reinterpreting the pointers as +// uintptr_t. PushToString will deduce the type, call sizeof on it and use +// that size to call into the correct PushToStringImpl functor depending on +// whether it is 32 or 64 bit. + +template +struct PushToStringImpl; + +template +struct PushToStringImpl { + void operator()(T id, std::u32string* str) { + str->push_back(static_cast(id >> 32)); + str->push_back(static_cast(id)); + } +}; + +template +struct PushToStringImpl { + void operator()(T id, std::u32string* str) { + str->push_back(static_cast(id)); + } +}; + +template +static void PushToString(T id, std::u32string* str) { + PushToStringImpl{}(id, str); +} + +} // namespace + +// Implements the hashing of SENodes. +size_t SENodeHash::operator()(const SENode* node) const { + // Concatinate the terms into a string which we can hash. + std::u32string hash_string{}; + + // Hashing the type as a string is safer than hashing the enum as the enum is + // very likely to collide with constants. + for (char ch : node->AsString()) { + hash_string.push_back(static_cast(ch)); + } + + // We just ignore the literal value unless it is a constant. + if (node->GetType() == SENode::Constant) + PushToString(node->AsSEConstantNode()->FoldToSingleValue(), &hash_string); + + const SERecurrentNode* recurrent = node->AsSERecurrentNode(); + + // If we're dealing with a recurrent expression hash the loop as well so that + // nested inductions like i=0,i++ and j=0,j++ correspond to different nodes. + if (recurrent) { + PushToString(reinterpret_cast(recurrent->GetLoop()), + &hash_string); + + // Recurrent expressions can't be hashed using the normal method as the + // order of coefficient and offset matters to the hash. + PushToString(reinterpret_cast(recurrent->GetCoefficient()), + &hash_string); + PushToString(reinterpret_cast(recurrent->GetOffset()), + &hash_string); + + return std::hash{}(hash_string); + } + + // Hash the result id of the original instruction which created this node if + // it is a value unknown node. + if (node->GetType() == SENode::ValueUnknown) { + PushToString(node->AsSEValueUnknown()->ResultId(), &hash_string); + } + + // Hash the pointers of the child nodes, each SENode has a unique pointer + // associated with it. + const std::vector& children = node->GetChildren(); + for (const SENode* child : children) { + PushToString(reinterpret_cast(child), &hash_string); + } + + return std::hash{}(hash_string); +} + +// This overload is the actual overload used by the node_cache_ set. +size_t SENodeHash::operator()(const std::unique_ptr& node) const { + return this->operator()(node.get()); +} + +void SENode::DumpDot(std::ostream& out, bool recurse) const { + size_t unique_id = std::hash{}(this); + out << unique_id << " [label=\"" << AsString() << " "; + if (GetType() == SENode::Constant) { + out << "\nwith value: " << this->AsSEConstantNode()->FoldToSingleValue(); + } + out << "\"]\n"; + for (const SENode* child : children_) { + size_t child_unique_id = std::hash{}(child); + out << unique_id << " -> " << child_unique_id << " \n"; + if (recurse) child->DumpDot(out, true); + } +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/scalar_analysis.h b/3rdparty/spirv-tools/source/opt/scalar_analysis.h new file mode 100644 index 000000000..71cc424b5 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/scalar_analysis.h @@ -0,0 +1,156 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_OPT_SCALAR_ANALYSIS_H_ +#define SOURCE_OPT_SCALAR_ANALYSIS_H_ + +#include +#include +#include +#include +#include +#include + +#include "opt/basic_block.h" +#include "opt/instruction.h" +#include "opt/scalar_analysis_nodes.h" + +namespace spvtools { +namespace ir { +class IRContext; +class Loop; +} // namespace ir + +namespace opt { + +// Manager for the Scalar Evolution analysis. Creates and maintains a DAG of +// scalar operations generated from analysing the use def graph from incoming +// instructions. Each node is hashed as it is added so like node (for instance, +// two induction variables i=0,i++ and j=0,j++) become the same node. After +// creating a DAG with AnalyzeInstruction it can the be simplified into a more +// usable form with SimplifyExpression. +class ScalarEvolutionAnalysis { + public: + explicit ScalarEvolutionAnalysis(ir::IRContext* context); + + // Create a unary negative node on |operand|. + SENode* CreateNegation(SENode* operand); + + // Creates a subtraction between the two operands by adding |operand_1| to the + // negation of |operand_2|. + SENode* CreateSubtraction(SENode* operand_1, SENode* operand_2); + + // Create an addition node between two operands. The |simplify| when set will + // allow the function to return an SEConstant instead of an addition if the + // two input operands are also constant. + SENode* CreateAddNode(SENode* operand_1, SENode* operand_2); + + // Create a multiply node between two operands. + SENode* CreateMultiplyNode(SENode* operand_1, SENode* operand_2); + + // Create a node representing a constant integer. + SENode* CreateConstant(int64_t integer); + + // Create a value unknown node, such as a load. + SENode* CreateValueUnknownNode(const ir::Instruction* inst); + + // Create a CantComputeNode. Used to exit out of analysis. + SENode* CreateCantComputeNode(); + + // Create a new recurrent node with |offset| and |coefficient|, with respect + // to |loop|. + SENode* CreateRecurrentExpression(const ir::Loop* loop, SENode* offset, + SENode* coefficient); + + // Construct the DAG by traversing use def chain of |inst|. + SENode* AnalyzeInstruction(const ir::Instruction* inst); + + // Simplify the |node| by grouping like terms or if contains a recurrent + // expression, rewrite the graph so the whole DAG (from |node| down) is in + // terms of that recurrent expression. + // + // For example. + // Induction variable i=0, i++ would produce Rec(0,1) so i+1 could be + // transformed into Rec(1,1). + // + // X+X*2+Y-Y+34-17 would be transformed into 3*X + 17, where X and Y are + // ValueUnknown nodes (such as a load instruction). + SENode* SimplifyExpression(SENode* node); + + // Add |prospective_node| into the cache and return a raw pointer to it. If + // |prospective_node| is already in the cache just return the raw pointer. + SENode* GetCachedOrAdd(std::unique_ptr prospective_node); + + // Checks that the graph starting from |node| is invariant to the |loop|. + bool IsLoopInvariant(const ir::Loop* loop, const SENode* node) const; + + // Find the recurrent term belonging to |loop| in the graph starting from + // |node| and return the coefficient of that recurrent term. Constant zero + // will be returned if no recurrent could be found. |node| should be in + // simplest form. + SENode* GetCoefficientFromRecurrentTerm(SENode* node, const ir::Loop* loop); + + // Return a rebuilt graph starting from |node| with the recurrent expression + // belonging to |loop| being zeroed out. Returned node will be simplified. + SENode* BuildGraphWithoutRecurrentTerm(SENode* node, const ir::Loop* loop); + + // Return the recurrent term belonging to |loop| if it appears in the graph + // starting at |node| or null if it doesn't. + SERecurrentNode* GetRecurrentTerm(SENode* node, const ir::Loop* loop); + + SENode* UpdateChildNode(SENode* parent, SENode* child, SENode* new_child); + + private: + SENode* AnalyzeConstant(const ir::Instruction* inst); + + // Handles both addition and subtraction. If the |instruction| is OpISub + // then the resulting node will be op1+(-op2) otherwise if it is OpIAdd then + // the result will be op1+op2. |instruction| must be OpIAdd or OpISub. + SENode* AnalyzeAddOp(const ir::Instruction* instruction); + + SENode* AnalyzeMultiplyOp(const ir::Instruction* multiply); + + SENode* AnalyzePhiInstruction(const ir::Instruction* phi); + + ir::IRContext* context_; + + // A map of instructions to SENodes. This is used to track recurrent + // expressions as they are added when analyzing instructions. Recurrent + // expressions come from phi nodes which by nature can include recursion so we + // check if nodes have already been built when analyzing instructions. + std::map recurrent_node_map_; + + // On creation we create and cache the CantCompute node so we not need to + // perform a needless create step. + SENode* cached_cant_compute_; + + // Helper functor to allow two unique_ptr to nodes to be compare. Only + // needed + // for the unordered_set implementation. + struct NodePointersEquality { + bool operator()(const std::unique_ptr& lhs, + const std::unique_ptr& rhs) const { + return *lhs == *rhs; + } + }; + + // Cache of nodes. All pointers to the nodes are references to the memory + // managed by they set. + std::unordered_set, SENodeHash, NodePointersEquality> + node_cache_; +}; + +} // namespace opt +} // namespace spvtools +#endif // SOURCE_OPT_SCALAR_ANALYSIS_H__ diff --git a/3rdparty/spirv-tools/source/opt/scalar_analysis_nodes.h b/3rdparty/spirv-tools/source/opt/scalar_analysis_nodes.h new file mode 100644 index 000000000..094ee8e24 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/scalar_analysis_nodes.h @@ -0,0 +1,313 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASI, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_OPT_SCALAR_ANALYSIS_NODES_H_ +#define SOURCE_OPT_SCALAR_ANALYSIS_NODES_H_ + +#include +#include +#include +#include +#include "opt/tree_iterator.h" + +namespace spvtools { +namespace ir { +class Loop; +} // namespace ir + +namespace opt { + +class ScalarEvolutionAnalysis; +class SEConstantNode; +class SERecurrentNode; +class SEAddNode; +class SEMultiplyNode; +class SENegative; +class SEValueUnknown; +class SECantCompute; + +// Abstract class representing a node in the scalar evolution DAG. Each node +// contains a vector of pointers to its children and each subclass of SENode +// implements GetType and an As method to allow casting. SENodes can be hashed +// using the SENodeHash functor. The vector of children is sorted when a node is +// added. This is important as it allows the hash of X+Y to be the same as Y+X. +class SENode { + public: + enum SENodeType { + Constant, + RecurrentAddExpr, + Add, + Multiply, + Negative, + ValueUnknown, + CanNotCompute + }; + + using ChildContainerType = std::vector; + + explicit SENode(opt::ScalarEvolutionAnalysis* parent_analysis) + : parent_analysis_(parent_analysis), unique_id_(++NumberOfNodes) {} + + virtual SENodeType GetType() const = 0; + + virtual ~SENode() {} + + virtual inline void AddChild(SENode* child) { + // If this is a constant node, assert. + if (AsSEConstantNode()) { + assert(false && "Trying to add a child node to a constant!"); + } + + // Find the first point in the vector where |child| is greater than the node + // currently in the vector. + auto find_first_less_than = [child](const SENode* node) { + return child->unique_id_ <= node->unique_id_; + }; + + auto position = std::find_if_not(children_.begin(), children_.end(), + find_first_less_than); + // Children are sorted so the hashing and equality operator will be the same + // for a node with the same children. X+Y should be the same as Y+X. + children_.insert(position, child); + } + + // Get the type as an std::string. This is used to represent the node in the + // dot output and is used to hash the type as well. + std::string AsString() const; + + // Dump the SENode and its immediate children, if |recurse| is true then it + // will recurse through all children to print the DAG starting from this node + // as a root. + void DumpDot(std::ostream& out, bool recurse = false) const; + + // Checks if two nodes are the same by hashing them. + bool operator==(const SENode& other) const; + + // Checks if two nodes are not the same by comparing the hashes. + bool operator!=(const SENode& other) const; + + // Return the child node at |index|. + inline SENode* GetChild(size_t index) { return children_[index]; } + inline const SENode* GetChild(size_t index) const { return children_[index]; } + + // Iterator to iterate over the child nodes. + using iterator = ChildContainerType::iterator; + using const_iterator = ChildContainerType::const_iterator; + + // Iterate over immediate child nodes. + iterator begin() { return children_.begin(); } + iterator end() { return children_.end(); } + + // Constant overloads for iterating over immediate child nodes. + const_iterator begin() const { return children_.cbegin(); } + const_iterator end() const { return children_.cend(); } + const_iterator cbegin() { return children_.cbegin(); } + const_iterator cend() { return children_.cend(); } + + // Iterator to iterate over the entire DAG. Even though we are using the tree + // iterator it should still be safe to iterate over. However, nodes with + // multiple parents will be visited multiple times, unlike in a tree. + using dag_iterator = TreeDFIterator; + using const_dag_iterator = TreeDFIterator; + + // Iterate over all child nodes in the graph. + dag_iterator graph_begin() { return dag_iterator(this); } + dag_iterator graph_end() { return dag_iterator(); } + const_dag_iterator graph_begin() const { return graph_cbegin(); } + const_dag_iterator graph_end() const { return graph_cend(); } + const_dag_iterator graph_cbegin() const { return const_dag_iterator(this); } + const_dag_iterator graph_cend() const { return const_dag_iterator(); } + + // Return the vector of immediate children. + const ChildContainerType& GetChildren() const { return children_; } + ChildContainerType& GetChildren() { return children_; } + + // Return true if this node is a cant compute node. + bool IsCantCompute() const { return GetType() == CanNotCompute; } + +// Implements a casting method for each type. +#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); +#undef DeclareCastMethod + + // Get the analysis which has this node in its cache. + inline opt::ScalarEvolutionAnalysis* GetParentAnalysis() const { + return parent_analysis_; + } + + protected: + ChildContainerType children_; + + opt::ScalarEvolutionAnalysis* parent_analysis_; + + // The unique id of this node, assigned on creation by incrementing the static + // node count. + uint32_t unique_id_; + + // The number of nodes created. + static uint32_t NumberOfNodes; +}; + +// 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 +// pointers which are assumed to be unique. +struct SENodeHash { + size_t operator()(const std::unique_ptr& node) const; + size_t operator()(const SENode* node) const; +}; + +// A node representing a constant integer. +class SEConstantNode : public SENode { + public: + SEConstantNode(opt::ScalarEvolutionAnalysis* parent_analysis, int64_t value) + : SENode(parent_analysis), literal_value_(value) {} + + SENodeType GetType() const final { return Constant; } + + int64_t FoldToSingleValue() const { return literal_value_; } + + SEConstantNode* AsSEConstantNode() override { return this; } + const SEConstantNode* AsSEConstantNode() const override { return this; } + + inline void AddChild(SENode*) final { + assert(false && "Attempting to add a child to a constant node!"); + } + + protected: + int64_t literal_value_; +}; + +// A node representing a recurrent expression in the code. A recurrent +// expression is an expression whose value can be expressed as a linear +// expression of the loop iterations. Such as an induction variable. The actual +// value of a recurrent expression is coefficent_ * iteration + offset_, hence +// an induction variable i=0, i++ becomes a recurrent expression with an offset +// of zero and a coefficient of one. +class SERecurrentNode : public SENode { + public: + SERecurrentNode(opt::ScalarEvolutionAnalysis* parent_analysis, + const ir::Loop* loop) + : SENode(parent_analysis), loop_(loop) {} + + SENodeType GetType() const final { return RecurrentAddExpr; } + + inline void AddCoefficient(SENode* child) { + coefficient_ = child; + SENode::AddChild(child); + } + + inline void AddOffset(SENode* child) { + offset_ = child; + SENode::AddChild(child); + } + + inline const SENode* GetCoefficient() const { return coefficient_; } + inline SENode* GetCoefficient() { return coefficient_; } + + inline const SENode* GetOffset() const { return offset_; } + inline SENode* GetOffset() { return offset_; } + + // Return the loop which this recurrent expression is recurring within. + const ir::Loop* GetLoop() const { return loop_; } + + SERecurrentNode* AsSERecurrentNode() override { return this; } + const SERecurrentNode* AsSERecurrentNode() const override { return this; } + + private: + SENode* coefficient_; + SENode* offset_; + const ir::Loop* loop_; +}; + +// A node representing an addition operation between child nodes. +class SEAddNode : public SENode { + public: + explicit SEAddNode(opt::ScalarEvolutionAnalysis* parent_analysis) + : SENode(parent_analysis) {} + + SENodeType GetType() const final { return Add; } + + SEAddNode* AsSEAddNode() override { return this; } + const SEAddNode* AsSEAddNode() const override { return this; } +}; + +// A node representing a multiply operation between child nodes. +class SEMultiplyNode : public SENode { + public: + explicit SEMultiplyNode(opt::ScalarEvolutionAnalysis* parent_analysis) + : SENode(parent_analysis) {} + + SENodeType GetType() const final { return Multiply; } + + SEMultiplyNode* AsSEMultiplyNode() override { return this; } + const SEMultiplyNode* AsSEMultiplyNode() const override { return this; } +}; + +// A node representing a unary negative operation. +class SENegative : public SENode { + public: + explicit SENegative(opt::ScalarEvolutionAnalysis* parent_analysis) + : SENode(parent_analysis) {} + + SENodeType GetType() const final { return Negative; } + + SENegative* AsSENegative() override { return this; } + const SENegative* AsSENegative() const override { return this; } +}; + +// A node representing a value which we do not know the value of, such as a load +// instruction. +class SEValueUnknown : public SENode { + public: + // SEValueUnknowns must come from an instruction |unique_id| is the unique id + // of that instruction. This is so we cancompare value unknowns and have a + // unique value unknown for each instruction. + SEValueUnknown(opt::ScalarEvolutionAnalysis* parent_analysis, + uint32_t result_id) + : SENode(parent_analysis), result_id_(result_id) {} + + SENodeType GetType() const final { return ValueUnknown; } + + SEValueUnknown* AsSEValueUnknown() override { return this; } + const SEValueUnknown* AsSEValueUnknown() const override { return this; } + + inline uint32_t ResultId() const { return result_id_; } + + private: + uint32_t result_id_; +}; + +// A node which we cannot reason about at all. +class SECantCompute : public SENode { + public: + explicit SECantCompute(opt::ScalarEvolutionAnalysis* parent_analysis) + : SENode(parent_analysis) {} + + SENodeType GetType() const final { return CanNotCompute; } + + SECantCompute* AsSECantCompute() override { return this; } + const SECantCompute* AsSECantCompute() const override { return this; } +}; + +} // namespace opt +} // namespace spvtools +#endif // SOURCE_OPT_SCALAR_ANALYSIS_NODES_H_ diff --git a/3rdparty/spirv-tools/source/opt/scalar_analysis_simplification.cpp b/3rdparty/spirv-tools/source/opt/scalar_analysis_simplification.cpp new file mode 100644 index 000000000..018896a46 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/scalar_analysis_simplification.cpp @@ -0,0 +1,539 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "opt/scalar_analysis.h" + +#include +#include +#include +#include +#include +#include +#include + +// Simplifies scalar analysis DAGs. +// +// 1. Given a node passed to SimplifyExpression we first simplify the graph by +// calling SimplifyPolynomial. This groups like nodes following basic arithmetic +// rules, so multiple adds of the same load instruction could be grouped into a +// single multiply of that instruction. SimplifyPolynomial will traverse the DAG +// and build up an accumulator buffer for each class of instruction it finds. +// For example take the loop: +// for (i=0, i accumulators_; +}; + +// From a |multiply| build up the accumulator objects. +bool SENodeSimplifyImpl::AccumulatorsFromMultiply(SENode* multiply, + bool negation) { + if (multiply->GetChildren().size() != 2 || + multiply->GetType() != SENode::Multiply) + return false; + + SENode* operand_1 = multiply->GetChild(0); + SENode* operand_2 = multiply->GetChild(1); + + SENode* value_unknown = nullptr; + SENode* constant = nullptr; + + // Work out which operand is the unknown value. + if (operand_1->GetType() == SENode::ValueUnknown || + operand_1->GetType() == SENode::RecurrentAddExpr) + value_unknown = operand_1; + else if (operand_2->GetType() == SENode::ValueUnknown || + operand_2->GetType() == SENode::RecurrentAddExpr) + value_unknown = operand_2; + + // Work out which operand is the constant coefficient. + if (operand_1->GetType() == SENode::Constant) + constant = operand_1; + else if (operand_2->GetType() == SENode::Constant) + constant = operand_2; + + // If the expression is not a variable multiplied by a constant coefficient, + // exit out. + if (!(value_unknown && constant)) { + return false; + } + + int64_t sign = negation ? -1 : 1; + + auto iterator = accumulators_.find(value_unknown); + int64_t new_value = constant->AsSEConstantNode()->FoldToSingleValue() * sign; + // Add the result of the multiplication to the accumulators. + if (iterator != accumulators_.end()) { + (*iterator).second += new_value; + } else { + accumulators_.insert({value_unknown, new_value}); + } + + return true; +} + +SENode* SENodeSimplifyImpl::Simplify() { + // We only handle graphs with an addition, multiplication, or negation, at the + // root. + if (node_->GetType() != SENode::Add && node_->GetType() != SENode::Multiply && + node_->GetType() != SENode::Negative) + return node_; + + SENode* simplified_polynomial = SimplifyPolynomial(); + + SERecurrentNode* recurrent_expr = nullptr; + node_ = simplified_polynomial; + + // Fold recurrent expressions which are with respect to the same loop into a + // single recurrent expression. + simplified_polynomial = FoldRecurrentAddExpressions(simplified_polynomial); + + simplified_polynomial = + EliminateZeroCoefficientRecurrents(simplified_polynomial); + + // Traverse the immediate children of the new node to find the recurrent + // expression. If there is more than one there is nothing further we can do. + for (SENode* child : simplified_polynomial->GetChildren()) { + if (child->GetType() == SENode::RecurrentAddExpr) { + recurrent_expr = child->AsSERecurrentNode(); + } + } + + // We need to count the number of unique recurrent expressions in the DAG to + // ensure there is only one. + for (auto child_iterator = simplified_polynomial->graph_begin(); + child_iterator != simplified_polynomial->graph_end(); ++child_iterator) { + if (child_iterator->GetType() == SENode::RecurrentAddExpr && + recurrent_expr != child_iterator->AsSERecurrentNode()) { + return simplified_polynomial; + } + } + + if (recurrent_expr) { + return SimplifyRecurrentAddExpression(recurrent_expr); + } + + return simplified_polynomial; +} + +// Traverse the graph to build up the accumulator objects. +void SENodeSimplifyImpl::GatherAccumulatorsFromChildNodes(SENode* new_node, + SENode* child, + bool negation) { + int32_t sign = negation ? -1 : 1; + + if (child->GetType() == SENode::Constant) { + // Collect all the constants and add them together. + constant_accumulator_ += + child->AsSEConstantNode()->FoldToSingleValue() * sign; + + } else if (child->GetType() == SENode::ValueUnknown || + child->GetType() == SENode::RecurrentAddExpr) { + // To rebuild the graph of X+X+X*2 into 4*X we count the occurrences of X + // and create a new node of count*X after. X can either be a ValueUnknown or + // a RecurrentAddExpr. The count for each X is stored in the accumulators_ + // map. + + auto iterator = accumulators_.find(child); + // If we've encountered this term before add to the accumulator for it. + if (iterator == accumulators_.end()) + accumulators_.insert({child, sign}); + else + iterator->second += sign; + + } else if (child->GetType() == SENode::Multiply) { + if (!AccumulatorsFromMultiply(child, negation)) { + new_node->AddChild(child); + } + + } else if (child->GetType() == SENode::Add) { + for (SENode* next_child : *child) { + GatherAccumulatorsFromChildNodes(new_node, next_child, negation); + } + + } else if (child->GetType() == SENode::Negative) { + SENode* negated_node = child->GetChild(0); + GatherAccumulatorsFromChildNodes(new_node, negated_node, !negation); + } else { + // If we can't work out how to fold the expression just add it back into + // the graph. + new_node->AddChild(child); + } +} + +SERecurrentNode* SENodeSimplifyImpl::UpdateCoefficient( + SERecurrentNode* recurrent, int64_t coefficient_update) const { + std::unique_ptr new_recurrent_node{new SERecurrentNode( + recurrent->GetParentAnalysis(), recurrent->GetLoop())}; + + SENode* new_coefficient = analysis_.CreateMultiplyNode( + recurrent->GetCoefficient(), + analysis_.CreateConstant(coefficient_update)); + + // See if the node can be simplified. + SENode* simplified = analysis_.SimplifyExpression(new_coefficient); + if (simplified->GetType() != SENode::CanNotCompute) + new_coefficient = simplified; + + if (coefficient_update < 0) { + new_recurrent_node->AddOffset( + analysis_.CreateNegation(recurrent->GetOffset())); + } else { + new_recurrent_node->AddOffset(recurrent->GetOffset()); + } + + new_recurrent_node->AddCoefficient(new_coefficient); + + return analysis_.GetCachedOrAdd(std::move(new_recurrent_node)) + ->AsSERecurrentNode(); +} + +// Simplify all the terms in the polynomial function. +SENode* SENodeSimplifyImpl::SimplifyPolynomial() { + std::unique_ptr new_add{new SEAddNode(node_->GetParentAnalysis())}; + + // Traverse the graph and gather the accumulators from it. + GatherAccumulatorsFromChildNodes(new_add.get(), node_, false); + + // Fold all the constants into a single constant node. + if (constant_accumulator_ != 0) { + new_add->AddChild(analysis_.CreateConstant(constant_accumulator_)); + } + + for (auto& pair : accumulators_) { + SENode* term = pair.first; + int64_t count = pair.second; + + // We can eliminate the term completely. + if (count == 0) continue; + + if (count == 1) { + new_add->AddChild(term); + } else if (count == -1 && term->GetType() != SENode::RecurrentAddExpr) { + // If the count is -1 we can just add a negative version of that node, + // unless it is a recurrent expression as we would rather the negative + // goes on the recurrent expressions children. This makes it easier to + // work with in other places. + new_add->AddChild(analysis_.CreateNegation(term)); + } else { + // Output value unknown terms as count*term and output recurrent + // expression terms as rec(offset, coefficient + count) offset and + // coefficient are the same as in the original expression. + if (term->GetType() == SENode::ValueUnknown) { + SENode* count_as_constant = analysis_.CreateConstant(count); + new_add->AddChild( + analysis_.CreateMultiplyNode(count_as_constant, term)); + } else { + assert(term->GetType() == SENode::RecurrentAddExpr && + "We only handle value unknowns or recurrent expressions"); + + // Create a new recurrent expression by adding the count to the + // coefficient of the old one. + new_add->AddChild(UpdateCoefficient(term->AsSERecurrentNode(), count)); + } + } + } + + // If there is only one term in the addition left just return that term. + if (new_add->GetChildren().size() == 1) { + return new_add->GetChild(0); + } + + // If there are no terms left in the addition just return 0. + if (new_add->GetChildren().size() == 0) { + return analysis_.CreateConstant(0); + } + + return analysis_.GetCachedOrAdd(std::move(new_add)); +} + +SENode* SENodeSimplifyImpl::FoldRecurrentAddExpressions(SENode* root) { + std::unique_ptr new_node{new SEAddNode(&analysis_)}; + + // A mapping of loops to the list of recurrent expressions which are with + // respect to those loops. + std::map>> + loops_to_recurrent{}; + + bool has_multiple_same_loop_recurrent_terms = false; + + for (SENode* child : *root) { + bool negation = false; + + if (child->GetType() == SENode::Negative) { + child = child->GetChild(0); + negation = true; + } + + if (child->GetType() == SENode::RecurrentAddExpr) { + const ir::Loop* loop = child->AsSERecurrentNode()->GetLoop(); + + SERecurrentNode* rec = child->AsSERecurrentNode(); + if (loops_to_recurrent.find(loop) == loops_to_recurrent.end()) { + loops_to_recurrent[loop] = {std::make_pair(rec, negation)}; + } else { + loops_to_recurrent[loop].push_back(std::make_pair(rec, negation)); + has_multiple_same_loop_recurrent_terms = true; + } + } else { + new_node->AddChild(child); + } + } + + if (!has_multiple_same_loop_recurrent_terms) return root; + + for (auto pair : loops_to_recurrent) { + std::vector>& recurrent_expressions = + pair.second; + const ir::Loop* loop = pair.first; + + std::unique_ptr new_coefficient{new SEAddNode(&analysis_)}; + std::unique_ptr new_offset{new SEAddNode(&analysis_)}; + + for (auto node_pair : recurrent_expressions) { + SERecurrentNode* node = node_pair.first; + bool negative = node_pair.second; + + if (!negative) { + new_coefficient->AddChild(node->GetCoefficient()); + new_offset->AddChild(node->GetOffset()); + } else { + new_coefficient->AddChild( + analysis_.CreateNegation(node->GetCoefficient())); + new_offset->AddChild(analysis_.CreateNegation(node->GetOffset())); + } + } + + std::unique_ptr new_recurrent{ + new SERecurrentNode(&analysis_, loop)}; + + SENode* new_coefficient_simplified = + analysis_.SimplifyExpression(new_coefficient.get()); + + SENode* new_offset_simplified = + analysis_.SimplifyExpression(new_offset.get()); + + if (new_coefficient_simplified->GetType() == SENode::Constant && + new_coefficient_simplified->AsSEConstantNode()->FoldToSingleValue() == + 0) { + return new_offset_simplified; + } + + new_recurrent->AddCoefficient(new_coefficient_simplified); + new_recurrent->AddOffset(new_offset_simplified); + + new_node->AddChild(analysis_.GetCachedOrAdd(std::move(new_recurrent))); + } + + // If we only have one child in the add just return that. + if (new_node->GetChildren().size() == 1) { + return new_node->GetChild(0); + } + + return analysis_.GetCachedOrAdd(std::move(new_node)); +} + +SENode* SENodeSimplifyImpl::EliminateZeroCoefficientRecurrents(SENode* node) { + if (node->GetType() != SENode::Add) return node; + + bool has_change = false; + + std::vector new_children{}; + for (SENode* child : *node) { + if (child->GetType() == SENode::RecurrentAddExpr) { + SENode* coefficient = child->AsSERecurrentNode()->GetCoefficient(); + // If coefficient is zero then we can eliminate the recurrent expression + // entirely and just return the offset as the recurrent expression is + // representing the equation coefficient*iterations + offset. + if (coefficient->GetType() == SENode::Constant && + coefficient->AsSEConstantNode()->FoldToSingleValue() == 0) { + new_children.push_back(child->AsSERecurrentNode()->GetOffset()); + has_change = true; + } else { + new_children.push_back(child); + } + } else { + new_children.push_back(child); + } + } + + if (!has_change) return node; + + std::unique_ptr new_add{new SEAddNode(node_->GetParentAnalysis())}; + + for (SENode* child : new_children) { + new_add->AddChild(child); + } + + return analysis_.GetCachedOrAdd(std::move(new_add)); +} + +SENode* SENodeSimplifyImpl::SimplifyRecurrentAddExpression( + SERecurrentNode* recurrent_expr) { + const std::vector& children = node_->GetChildren(); + + std::unique_ptr recurrent_node{new SERecurrentNode( + recurrent_expr->GetParentAnalysis(), recurrent_expr->GetLoop())}; + + // Create and simplify the new offset node. + std::unique_ptr new_offset{ + new SEAddNode(recurrent_expr->GetParentAnalysis())}; + new_offset->AddChild(recurrent_expr->GetOffset()); + + for (SENode* child : children) { + if (child->GetType() != SENode::RecurrentAddExpr) { + new_offset->AddChild(child); + } + } + + // Simplify the new offset. + SENode* simplified_child = analysis_.SimplifyExpression(new_offset.get()); + + // If the child can be simplified, add the simplified form otherwise, add it + // via the usual caching mechanism. + if (simplified_child->GetType() != SENode::CanNotCompute) { + recurrent_node->AddOffset(simplified_child); + } else { + recurrent_expr->AddOffset(analysis_.GetCachedOrAdd(std::move(new_offset))); + } + + recurrent_node->AddCoefficient(recurrent_expr->GetCoefficient()); + + return analysis_.GetCachedOrAdd(std::move(recurrent_node)); +} + +/* + * Scalar Analysis simplification public methods. + */ + +SENode* ScalarEvolutionAnalysis::SimplifyExpression(SENode* node) { + SENodeSimplifyImpl impl{this, node}; + + return impl.Simplify(); +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/scalar_replacement_pass.cpp b/3rdparty/spirv-tools/source/opt/scalar_replacement_pass.cpp new file mode 100644 index 000000000..8e96f70eb --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/scalar_replacement_pass.cpp @@ -0,0 +1,694 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "scalar_replacement_pass.h" + +#include "enum_string_mapping.h" +#include "extensions.h" +#include "make_unique.h" +#include "reflect.h" +#include "types.h" + +#include +#include + +namespace spvtools { +namespace opt { + +// Heuristic aggregate element limit. +const uint32_t MAX_NUM_ELEMENTS = 100u; + +Pass::Status ScalarReplacementPass::Process(ir::IRContext* c) { + InitializeProcessing(c); + + Status status = Status::SuccessWithoutChange; + for (auto& f : *get_module()) { + Status functionStatus = ProcessFunction(&f); + if (functionStatus == Status::Failure) + return functionStatus; + else if (functionStatus == Status::SuccessWithChange) + status = functionStatus; + } + + return status; +} + +Pass::Status ScalarReplacementPass::ProcessFunction(ir::Function* function) { + std::queue worklist; + ir::BasicBlock& entry = *function->begin(); + for (auto iter = entry.begin(); iter != entry.end(); ++iter) { + // Function storage class OpVariables must appear as the first instructions + // of the entry block. + if (iter->opcode() != SpvOpVariable) break; + + ir::Instruction* varInst = &*iter; + if (CanReplaceVariable(varInst)) { + worklist.push(varInst); + } + } + + Status status = Status::SuccessWithoutChange; + while (!worklist.empty()) { + ir::Instruction* varInst = worklist.front(); + worklist.pop(); + + if (!ReplaceVariable(varInst, &worklist)) + return Status::Failure; + else + status = Status::SuccessWithChange; + } + + return status; +} + +bool ScalarReplacementPass::ReplaceVariable( + ir::Instruction* inst, std::queue* worklist) { + std::vector replacements; + CreateReplacementVariables(inst, &replacements); + + std::vector dead; + dead.push_back(inst); + if (!get_def_use_mgr()->WhileEachUser( + inst, [this, &replacements, &dead](ir::Instruction* user) { + if (!ir::IsAnnotationInst(user->opcode())) { + switch (user->opcode()) { + case SpvOpLoad: + ReplaceWholeLoad(user, replacements); + dead.push_back(user); + break; + case SpvOpStore: + ReplaceWholeStore(user, replacements); + dead.push_back(user); + break; + case SpvOpAccessChain: + case SpvOpInBoundsAccessChain: + if (!ReplaceAccessChain(user, replacements)) return false; + dead.push_back(user); + break; + case SpvOpName: + case SpvOpMemberName: + break; + default: + assert(false && "Unexpected opcode"); + break; + } + } + return true; + })) + return false; + + // Clean up some dead code. + while (!dead.empty()) { + ir::Instruction* toKill = dead.back(); + dead.pop_back(); + + context()->KillInst(toKill); + } + + // Attempt to further scalarize. + for (auto var : replacements) { + if (get_def_use_mgr()->NumUsers(var) == 0) { + context()->KillInst(var); + } else if (CanReplaceVariable(var)) { + worklist->push(var); + } + } + + return true; +} + +void ScalarReplacementPass::ReplaceWholeLoad( + ir::Instruction* load, const std::vector& replacements) { + // Replaces the load of the entire composite with a load from each replacement + // variable followed by a composite construction. + ir::BasicBlock* block = context()->get_instr_block(load); + std::vector loads; + loads.reserve(replacements.size()); + ir::BasicBlock::iterator where(load); + for (auto var : replacements) { + // Create a load of each replacement variable. + ir::Instruction* type = GetStorageType(var); + uint32_t loadId = TakeNextId(); + std::unique_ptr newLoad( + new ir::Instruction(context(), SpvOpLoad, type->result_id(), loadId, + std::initializer_list{ + {SPV_OPERAND_TYPE_ID, {var->result_id()}}})); + // Copy memory access attributes which start at index 1. Index 0 is the + // pointer to load. + for (uint32_t i = 1; i < load->NumInOperands(); ++i) { + ir::Operand copy(load->GetInOperand(i)); + newLoad->AddOperand(std::move(copy)); + } + where = where.InsertBefore(std::move(newLoad)); + get_def_use_mgr()->AnalyzeInstDefUse(&*where); + context()->set_instr_block(&*where, block); + loads.push_back(&*where); + } + + // Construct a new composite. + uint32_t compositeId = TakeNextId(); + where = load; + std::unique_ptr compositeConstruct(new ir::Instruction( + context(), SpvOpCompositeConstruct, load->type_id(), compositeId, {})); + for (auto l : loads) { + ir::Operand op(SPV_OPERAND_TYPE_ID, + std::initializer_list{l->result_id()}); + compositeConstruct->AddOperand(std::move(op)); + } + where = where.InsertBefore(std::move(compositeConstruct)); + get_def_use_mgr()->AnalyzeInstDefUse(&*where); + context()->set_instr_block(&*where, block); + context()->ReplaceAllUsesWith(load->result_id(), compositeId); +} + +void ScalarReplacementPass::ReplaceWholeStore( + ir::Instruction* store, const std::vector& replacements) { + // Replaces a store to the whole composite with a series of extract and stores + // to each element. + uint32_t storeInput = store->GetSingleWordInOperand(1u); + ir::BasicBlock* block = context()->get_instr_block(store); + ir::BasicBlock::iterator where(store); + uint32_t elementIndex = 0; + for (auto var : replacements) { + // Create the extract. + ir::Instruction* type = GetStorageType(var); + uint32_t extractId = TakeNextId(); + std::unique_ptr extract(new ir::Instruction( + context(), SpvOpCompositeExtract, type->result_id(), extractId, + std::initializer_list{ + {SPV_OPERAND_TYPE_ID, {storeInput}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {elementIndex++}}})); + auto iter = where.InsertBefore(std::move(extract)); + get_def_use_mgr()->AnalyzeInstDefUse(&*iter); + context()->set_instr_block(&*iter, block); + + // Create the store. + std::unique_ptr newStore( + new ir::Instruction(context(), SpvOpStore, 0, 0, + std::initializer_list{ + {SPV_OPERAND_TYPE_ID, {var->result_id()}}, + {SPV_OPERAND_TYPE_ID, {extractId}}})); + // Copy memory access attributes which start at index 2. Index 0 is the + // pointer and index 1 is the data. + for (uint32_t i = 2; i < store->NumInOperands(); ++i) { + ir::Operand copy(store->GetInOperand(i)); + newStore->AddOperand(std::move(copy)); + } + iter = where.InsertBefore(std::move(newStore)); + get_def_use_mgr()->AnalyzeInstDefUse(&*iter); + context()->set_instr_block(&*iter, block); + } +} + +bool ScalarReplacementPass::ReplaceAccessChain( + ir::Instruction* chain, const std::vector& replacements) { + // Replaces the access chain with either another access chain (with one fewer + // indexes) or a direct use of the replacement variable. + uint32_t indexId = chain->GetSingleWordInOperand(1u); + const ir::Instruction* index = get_def_use_mgr()->GetDef(indexId); + size_t indexValue = GetConstantInteger(index); + if (indexValue > replacements.size()) { + // Out of bounds access, this is illegal IR. + return false; + } else { + const ir::Instruction* var = replacements[indexValue]; + if (chain->NumInOperands() > 2) { + // Replace input access chain with another access chain. + ir::BasicBlock::iterator chainIter(chain); + uint32_t replacementId = TakeNextId(); + std::unique_ptr replacementChain(new ir::Instruction( + context(), chain->opcode(), chain->type_id(), replacementId, + std::initializer_list{ + {SPV_OPERAND_TYPE_ID, {var->result_id()}}})); + // Add the remaining indexes. + for (uint32_t i = 2; i < chain->NumInOperands(); ++i) { + ir::Operand copy(chain->GetInOperand(i)); + replacementChain->AddOperand(std::move(copy)); + } + auto iter = chainIter.InsertBefore(std::move(replacementChain)); + get_def_use_mgr()->AnalyzeInstDefUse(&*iter); + context()->set_instr_block(&*iter, context()->get_instr_block(chain)); + context()->ReplaceAllUsesWith(chain->result_id(), replacementId); + } else { + // Replace with a use of the variable. + context()->ReplaceAllUsesWith(chain->result_id(), var->result_id()); + } + } + + return true; +} + +void ScalarReplacementPass::CreateReplacementVariables( + ir::Instruction* inst, std::vector* replacements) { + ir::Instruction* type = GetStorageType(inst); + uint32_t elem = 0; + switch (type->opcode()) { + case SpvOpTypeStruct: + type->ForEachInOperand([this, inst, &elem, replacements](uint32_t* id) { + CreateVariable(*id, inst, elem++, replacements); + }); + break; + case SpvOpTypeArray: + for (uint32_t i = 0; i != GetArrayLength(type); ++i) { + CreateVariable(type->GetSingleWordInOperand(0u), inst, i, replacements); + } + break; + + case SpvOpTypeMatrix: + case SpvOpTypeVector: + for (uint32_t i = 0; i != GetNumElements(type); ++i) { + CreateVariable(type->GetSingleWordInOperand(0u), inst, i, replacements); + } + break; + + default: + assert(false && "Unexpected type."); + break; + } + + TransferAnnotations(inst, replacements); +} + +void ScalarReplacementPass::TransferAnnotations( + const ir::Instruction* source, + std::vector* replacements) { + // Only transfer invariant and restrict decorations on the variable. There are + // no type or member decorations that are necessary to transfer. + for (auto inst : + get_decoration_mgr()->GetDecorationsFor(source->result_id(), false)) { + assert(inst->opcode() == SpvOpDecorate); + uint32_t decoration = inst->GetSingleWordInOperand(1u); + if (decoration == SpvDecorationInvariant || + decoration == SpvDecorationRestrict) { + for (auto var : *replacements) { + std::unique_ptr annotation(new ir::Instruction( + context(), SpvOpDecorate, 0, 0, + std::initializer_list{ + {SPV_OPERAND_TYPE_ID, {var->result_id()}}, + {SPV_OPERAND_TYPE_DECORATION, {decoration}}})); + for (uint32_t i = 2; i < inst->NumInOperands(); ++i) { + ir::Operand copy(inst->GetInOperand(i)); + annotation->AddOperand(std::move(copy)); + } + context()->AddAnnotationInst(std::move(annotation)); + get_def_use_mgr()->AnalyzeInstUse(&*--context()->annotation_end()); + } + } + } +} + +void ScalarReplacementPass::CreateVariable( + uint32_t typeId, ir::Instruction* varInst, uint32_t index, + std::vector* replacements) { + uint32_t ptrId = GetOrCreatePointerType(typeId); + uint32_t id = TakeNextId(); + std::unique_ptr variable(new ir::Instruction( + context(), SpvOpVariable, ptrId, id, + std::initializer_list{ + {SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}}})); + + ir::BasicBlock* block = context()->get_instr_block(varInst); + block->begin().InsertBefore(std::move(variable)); + ir::Instruction* inst = &*block->begin(); + + // If varInst was initialized, make sure to initialize its replacement. + GetOrCreateInitialValue(varInst, index, inst); + get_def_use_mgr()->AnalyzeInstDefUse(inst); + context()->set_instr_block(inst, block); + + replacements->push_back(inst); +} + +uint32_t ScalarReplacementPass::GetOrCreatePointerType(uint32_t id) { + auto iter = pointee_to_pointer_.find(id); + if (iter != pointee_to_pointer_.end()) return iter->second; + + analysis::Type* pointeeTy; + std::unique_ptr pointerTy; + std::tie(pointeeTy, pointerTy) = + context()->get_type_mgr()->GetTypeAndPointerType(id, + SpvStorageClassFunction); + uint32_t ptrId = 0; + if (id == context()->get_type_mgr()->GetId(pointeeTy)) { + // Non-ambiguous type, just ask the type manager for an id. + ptrId = context()->get_type_mgr()->GetTypeInstruction(pointerTy.get()); + pointee_to_pointer_[id] = ptrId; + return ptrId; + } + + // Ambiguous type. We must perform a linear search to try and find the right + // type. + for (auto global : context()->types_values()) { + if (global.opcode() == SpvOpTypePointer && + global.GetSingleWordInOperand(0u) == SpvStorageClassFunction && + global.GetSingleWordInOperand(1u) == id) { + if (!context()->get_feature_mgr()->HasExtension( + libspirv::Extension::kSPV_KHR_variable_pointers) || + get_decoration_mgr()->GetDecorationsFor(id, false).empty()) { + // If variable pointers is enabled, only reuse a decoration-less + // pointer of the correct type. + ptrId = global.result_id(); + break; + } + } + } + + if (ptrId != 0) { + pointee_to_pointer_[id] = ptrId; + return ptrId; + } + + ptrId = TakeNextId(); + context()->AddType(MakeUnique( + context(), SpvOpTypePointer, 0, ptrId, + std::initializer_list{ + {SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}}, + {SPV_OPERAND_TYPE_ID, {id}}})); + ir::Instruction* ptr = &*--context()->types_values_end(); + get_def_use_mgr()->AnalyzeInstDefUse(ptr); + pointee_to_pointer_[id] = ptrId; + // Register with the type manager if necessary. + context()->get_type_mgr()->RegisterType(ptrId, *pointerTy); + + return ptrId; +} + +void ScalarReplacementPass::GetOrCreateInitialValue(ir::Instruction* source, + uint32_t index, + ir::Instruction* newVar) { + assert(source->opcode() == SpvOpVariable); + if (source->NumInOperands() < 2) return; + + uint32_t initId = source->GetSingleWordInOperand(1u); + uint32_t storageId = GetStorageType(newVar)->result_id(); + ir::Instruction* init = get_def_use_mgr()->GetDef(initId); + uint32_t newInitId = 0; + // TODO(dnovillo): Refactor this with constant propagation. + if (init->opcode() == SpvOpConstantNull) { + // Initialize to appropriate NULL. + auto iter = type_to_null_.find(storageId); + if (iter == type_to_null_.end()) { + newInitId = TakeNextId(); + type_to_null_[storageId] = newInitId; + context()->AddGlobalValue(MakeUnique( + context(), SpvOpConstantNull, storageId, newInitId, + std::initializer_list{})); + ir::Instruction* newNull = &*--context()->types_values_end(); + get_def_use_mgr()->AnalyzeInstDefUse(newNull); + } else { + newInitId = iter->second; + } + } else if (ir::IsSpecConstantInst(init->opcode())) { + // Create a new constant extract. + newInitId = TakeNextId(); + context()->AddGlobalValue(MakeUnique( + context(), SpvOpSpecConstantOp, storageId, newInitId, + std::initializer_list{ + {SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER, {SpvOpCompositeExtract}}, + {SPV_OPERAND_TYPE_ID, {init->result_id()}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {index}}})); + ir::Instruction* newSpecConst = &*--context()->types_values_end(); + get_def_use_mgr()->AnalyzeInstDefUse(newSpecConst); + } else if (init->opcode() == SpvOpConstantComposite) { + // Get the appropriate index constant. + newInitId = init->GetSingleWordInOperand(index); + ir::Instruction* element = get_def_use_mgr()->GetDef(newInitId); + if (element->opcode() == SpvOpUndef) { + // Undef is not a valid initializer for a variable. + newInitId = 0; + } + } else { + assert(false); + } + + if (newInitId != 0) { + newVar->AddOperand({SPV_OPERAND_TYPE_ID, {newInitId}}); + } +} + +size_t ScalarReplacementPass::GetIntegerLiteral(const ir::Operand& op) const { + assert(op.words.size() <= 2); + size_t len = 0; + for (uint32_t i = 0; i != op.words.size(); ++i) { + len |= (op.words[i] << (32 * i)); + } + return len; +} + +size_t ScalarReplacementPass::GetConstantInteger( + const ir::Instruction* constant) const { + assert(get_def_use_mgr()->GetDef(constant->type_id())->opcode() == + SpvOpTypeInt); + assert(constant->opcode() == SpvOpConstant || + constant->opcode() == SpvOpConstantNull); + if (constant->opcode() == SpvOpConstantNull) { + return 0; + } + + const ir::Operand& op = constant->GetInOperand(0u); + return GetIntegerLiteral(op); +} + +size_t ScalarReplacementPass::GetArrayLength( + const ir::Instruction* arrayType) const { + assert(arrayType->opcode() == SpvOpTypeArray); + const ir::Instruction* length = + get_def_use_mgr()->GetDef(arrayType->GetSingleWordInOperand(1u)); + return GetConstantInteger(length); +} + +size_t ScalarReplacementPass::GetNumElements( + const ir::Instruction* type) const { + assert(type->opcode() == SpvOpTypeVector || + type->opcode() == SpvOpTypeMatrix); + const ir::Operand& op = type->GetInOperand(1u); + assert(op.words.size() <= 2); + size_t len = 0; + for (uint32_t i = 0; i != op.words.size(); ++i) { + len |= (op.words[i] << (32 * i)); + } + return len; +} + +ir::Instruction* ScalarReplacementPass::GetStorageType( + const ir::Instruction* inst) const { + assert(inst->opcode() == SpvOpVariable); + + uint32_t ptrTypeId = inst->type_id(); + uint32_t typeId = + get_def_use_mgr()->GetDef(ptrTypeId)->GetSingleWordInOperand(1u); + return get_def_use_mgr()->GetDef(typeId); +} + +bool ScalarReplacementPass::CanReplaceVariable( + const ir::Instruction* varInst) const { + assert(varInst->opcode() == SpvOpVariable); + + // Can only replace function scope variables. + if (varInst->GetSingleWordInOperand(0u) != SpvStorageClassFunction) + return false; + + if (!CheckTypeAnnotations(get_def_use_mgr()->GetDef(varInst->type_id()))) + return false; + + const ir::Instruction* typeInst = GetStorageType(varInst); + return CheckType(typeInst) && CheckAnnotations(varInst) && CheckUses(varInst); +} + +bool ScalarReplacementPass::CheckType(const ir::Instruction* typeInst) const { + if (!CheckTypeAnnotations(typeInst)) return false; + + switch (typeInst->opcode()) { + case SpvOpTypeStruct: + // Don't bother with empty structs or very large structs. + if (typeInst->NumInOperands() == 0 || + typeInst->NumInOperands() > MAX_NUM_ELEMENTS) + return false; + return true; + case SpvOpTypeArray: + if (GetArrayLength(typeInst) > MAX_NUM_ELEMENTS) return false; + return true; + // TODO(alanbaker): Develop some heuristics for when this should be + // re-enabled. + //// Specifically including matrix and vector in an attempt to reduce the + //// number of vector registers required. + // case SpvOpTypeMatrix: + // case SpvOpTypeVector: + // if (GetNumElements(typeInst) > MAX_NUM_ELEMENTS) return false; + // return true; + + case SpvOpTypeRuntimeArray: + default: + return false; + } +} + +bool ScalarReplacementPass::CheckTypeAnnotations( + const ir::Instruction* typeInst) const { + for (auto inst : + get_decoration_mgr()->GetDecorationsFor(typeInst->result_id(), false)) { + uint32_t decoration; + if (inst->opcode() == SpvOpDecorate) { + decoration = inst->GetSingleWordInOperand(1u); + } else { + assert(inst->opcode() == SpvOpMemberDecorate); + decoration = inst->GetSingleWordInOperand(2u); + } + + switch (decoration) { + case SpvDecorationRowMajor: + case SpvDecorationColMajor: + case SpvDecorationArrayStride: + case SpvDecorationMatrixStride: + case SpvDecorationCPacked: + case SpvDecorationInvariant: + case SpvDecorationRestrict: + case SpvDecorationOffset: + case SpvDecorationAlignment: + case SpvDecorationAlignmentId: + case SpvDecorationMaxByteOffset: + break; + default: + return false; + } + } + + return true; +} + +bool ScalarReplacementPass::CheckAnnotations( + const ir::Instruction* varInst) const { + for (auto inst : + get_decoration_mgr()->GetDecorationsFor(varInst->result_id(), false)) { + assert(inst->opcode() == SpvOpDecorate); + uint32_t decoration = inst->GetSingleWordInOperand(1u); + switch (decoration) { + case SpvDecorationInvariant: + case SpvDecorationRestrict: + case SpvDecorationAlignment: + case SpvDecorationAlignmentId: + case SpvDecorationMaxByteOffset: + break; + default: + return false; + } + } + + return true; +} + +bool ScalarReplacementPass::CheckUses(const ir::Instruction* inst) const { + VariableStats stats = {0, 0}; + bool ok = CheckUses(inst, &stats); + + // TODO(alanbaker/greg-lunarg): Add some meaningful heuristics about when + // SRoA is costly, such as when the structure has many (unaccessed?) + // members. + + return ok; +} + +bool ScalarReplacementPass::CheckUses(const ir::Instruction* inst, + VariableStats* stats) const { + bool ok = true; + get_def_use_mgr()->ForEachUse( + inst, [this, stats, &ok](const ir::Instruction* user, uint32_t index) { + // Annotations are check as a group separately. + if (!ir::IsAnnotationInst(user->opcode())) { + switch (user->opcode()) { + case SpvOpAccessChain: + case SpvOpInBoundsAccessChain: + if (index == 2u) { + uint32_t id = user->GetSingleWordOperand(3u); + const ir::Instruction* opInst = get_def_use_mgr()->GetDef(id); + if (!ir::IsCompileTimeConstantInst(opInst->opcode())) { + ok = false; + } else { + if (!CheckUsesRelaxed(user)) ok = false; + } + stats->num_partial_accesses++; + } else { + ok = false; + } + break; + case SpvOpLoad: + if (!CheckLoad(user, index)) ok = false; + stats->num_full_accesses++; + break; + case SpvOpStore: + if (!CheckStore(user, index)) ok = false; + stats->num_full_accesses++; + break; + case SpvOpName: + case SpvOpMemberName: + break; + default: + ok = false; + break; + } + } + }); + + return ok; +} + +bool ScalarReplacementPass::CheckUsesRelaxed( + const ir::Instruction* inst) const { + bool ok = true; + get_def_use_mgr()->ForEachUse( + inst, [this, &ok](const ir::Instruction* user, uint32_t index) { + switch (user->opcode()) { + case SpvOpAccessChain: + case SpvOpInBoundsAccessChain: + if (index != 2u) { + ok = false; + } else { + if (!CheckUsesRelaxed(user)) ok = false; + } + break; + case SpvOpLoad: + if (!CheckLoad(user, index)) ok = false; + break; + case SpvOpStore: + if (!CheckStore(user, index)) ok = false; + break; + default: + ok = false; + break; + } + }); + + return ok; +} + +bool ScalarReplacementPass::CheckLoad(const ir::Instruction* inst, + uint32_t index) const { + if (index != 2u) return false; + if (inst->NumInOperands() >= 2 && + inst->GetSingleWordInOperand(1u) & SpvMemoryAccessVolatileMask) + return false; + return true; +} + +bool ScalarReplacementPass::CheckStore(const ir::Instruction* inst, + uint32_t index) const { + if (index != 0u) return false; + if (inst->NumInOperands() >= 3 && + inst->GetSingleWordInOperand(2u) & SpvMemoryAccessVolatileMask) + return false; + return true; +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/scalar_replacement_pass.h b/3rdparty/spirv-tools/source/opt/scalar_replacement_pass.h new file mode 100644 index 000000000..a48174ff7 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/scalar_replacement_pass.h @@ -0,0 +1,205 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_SCALAR_REPLACEMENT_PASS_H_ +#define LIBSPIRV_OPT_SCALAR_REPLACEMENT_PASS_H_ + +#include "function.h" +#include "pass.h" +#include "type_manager.h" + +#include + +namespace spvtools { +namespace opt { + +// Documented in optimizer.hpp +class ScalarReplacementPass : public Pass { + public: + ScalarReplacementPass() = default; + + const char* name() const override { return "scalar-replacement"; } + + // Attempts to scalarize all appropriate function scope variables. Returns + // SuccessWithChange if any change is made. + Status Process(ir::IRContext* c) override; + + ir::IRContext::Analysis GetPreservedAnalyses() override { + return ir::IRContext::kAnalysisDefUse | + ir::IRContext::kAnalysisInstrToBlockMapping | + ir::IRContext::kAnalysisDecorations | + ir::IRContext::kAnalysisCombinators | ir::IRContext::kAnalysisCFG | + ir::IRContext::kAnalysisNameMap; + } + + private: + // Small container for tracking statistics about variables. + // + // TODO(alanbaker): Develop some useful heuristics to tune this pass. + struct VariableStats { + uint32_t num_partial_accesses; + uint32_t num_full_accesses; + }; + + // Attempts to scalarize all appropriate function scope variables in + // |function|. Returns SuccessWithChange if any changes are mode. + Status ProcessFunction(ir::Function* function); + + // Returns true if |varInst| can be scalarized. + // + // Examines the use chain of |varInst| to verify all uses are valid for + // scalarization. + bool CanReplaceVariable(const ir::Instruction* varInst) const; + + // Returns true if |typeInst| is an acceptable type to scalarize. + // + // Allows all aggregate types except runtime arrays. Additionally, checks the + // that the number of elements that would be scalarized is within bounds. + bool CheckType(const ir::Instruction* typeInst) const; + + // Returns true if all the decorations for |varInst| are acceptable for + // scalarization. + bool CheckAnnotations(const ir::Instruction* varInst) const; + + // Returns true if all the decorations for |typeInst| are acceptable for + // scalarization. + bool CheckTypeAnnotations(const ir::Instruction* typeInst) const; + + // Returns true if the uses of |inst| are acceptable for scalarization. + // + // Recursively checks all the uses of |inst|. For |inst| specifically, only + // allows SpvOpAccessChain, SpvOpInBoundsAccessChain, SpvOpLoad and + // SpvOpStore. Access chains must have the first index be a compile-time + // constant. Subsequent uses of access chains (including other access chains) + // are checked in a more relaxed manner. + bool CheckUses(const ir::Instruction* inst) const; + + // Helper function for the above |CheckUses|. + // + // This version tracks some stats about the current OpVariable. These stats + // are used to drive heuristics about when to scalarize. + bool CheckUses(const ir::Instruction* inst, VariableStats* stats) const; + + // Relaxed helper function for |CheckUses|. + bool CheckUsesRelaxed(const ir::Instruction* inst) const; + + // Transfers appropriate decorations from |source| to |replacements|. + void TransferAnnotations(const ir::Instruction* source, + std::vector* replacements); + + // Scalarizes |inst| and updates its uses. + // + // |inst| must be an OpVariable. It is replaced with an OpVariable for each + // for element of the composite type. Uses of |inst| are updated as + // appropriate. If the replacement variables are themselves scalarizable, they + // get added to |worklist| for further processing. If any replacement + // variable ends up with no uses it is erased. Returns false if any + // subsequent access chain is out of bounds. + bool ReplaceVariable(ir::Instruction* inst, + std::queue* worklist); + + // Returns the underlying storage type for |inst|. + // + // |inst| must be an OpVariable. Returns the type that is pointed to by + // |inst|. + ir::Instruction* GetStorageType(const ir::Instruction* inst) const; + + // Returns true if the load can be scalarized. + // + // |inst| must be an OpLoad. Returns true if |index| is the pointer operand of + // |inst| and the load is not from volatile memory. + bool CheckLoad(const ir::Instruction* inst, uint32_t index) const; + + // Returns true if the store can be scalarized. + // + // |inst| must be an OpStore. Returns true if |index| is the pointer operand + // of |inst| and the store is not to volatile memory. + bool CheckStore(const ir::Instruction* inst, uint32_t index) const; + + // Creates a variable of type |typeId| from the |index|'th element of + // |varInst|. The new variable is added to |replacements|. + void CreateVariable(uint32_t typeId, ir::Instruction* varInst, uint32_t index, + std::vector* replacements); + + // Populates |replacements| with a new OpVariable for each element of |inst|. + // + // |inst| must be an OpVariable of a composite type. New variables are + // initialized the same as the corresponding index in |inst|. |replacements| + // will contain a variable for each element of the composite with matching + // indexes (i.e. the 0'th element of |inst| is the 0'th entry of + // |replacements|). + void CreateReplacementVariables(ir::Instruction* inst, + std::vector* replacements); + + // Returns the value of an OpConstant of integer type. + // + // |constant| must use two or fewer words to generate the value. + size_t GetConstantInteger(const ir::Instruction* constant) const; + + // Returns the integer literal for |op|. + size_t GetIntegerLiteral(const ir::Operand& op) const; + + // Returns the array length for |arrayInst|. + size_t GetArrayLength(const ir::Instruction* arrayInst) const; + + // Returns the number of elements in |type|. + // + // |type| must be a vector or matrix type. + size_t GetNumElements(const ir::Instruction* type) const; + + // Returns an id for a pointer to |id|. + uint32_t GetOrCreatePointerType(uint32_t id); + + // Creates the initial value for the |index| element of |source| in |newVar|. + // + // If there is an initial value for |source| for element |index|, it is + // appended as an operand on |newVar|. If the initial value is OpUndef, no + // initial value is added to |newVar|. + void GetOrCreateInitialValue(ir::Instruction* source, uint32_t index, + ir::Instruction* newVar); + + // Replaces the load to the entire composite. + // + // Generates a load for each replacement variable and then creates a new + // composite by combining all of the loads. + // + // |load| must be a load. + void ReplaceWholeLoad(ir::Instruction* load, + const std::vector& replacements); + + // Replaces the store to the entire composite. + // + // Generates a composite extract and store for each element in the scalarized + // variable from the original store data input. + void ReplaceWholeStore(ir::Instruction* store, + const std::vector& replacements); + + // Replaces an access chain to the composite variable with either a direct use + // of the appropriate replacement variable or another access chain with the + // replacement variable as the base and one fewer indexes. Returns false if + // the chain has an out of bounds access. + bool ReplaceAccessChain(ir::Instruction* chain, + const std::vector& replacements); + + // Maps storage type to a pointer type enclosing that type. + std::unordered_map pointee_to_pointer_; + + // Maps type id to OpConstantNull for that type. + std::unordered_map type_to_null_; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_SCALAR_REPLACEMENT_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/set_spec_constant_default_value_pass.cpp b/3rdparty/spirv-tools/source/opt/set_spec_constant_default_value_pass.cpp new file mode 100644 index 000000000..bce78f9c6 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/set_spec_constant_default_value_pass.cpp @@ -0,0 +1,371 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "set_spec_constant_default_value_pass.h" + +#include +#include +#include +#include +#include + +#include "def_use_manager.h" +#include "ir_context.h" +#include "make_unique.h" +#include "spirv-tools/libspirv.h" +#include "type_manager.h" +#include "types.h" +#include "util/parse_number.h" + +namespace spvtools { +namespace opt { + +namespace { +using spvutils::EncodeNumberStatus; +using spvutils::NumberType; +using spvutils::ParseAndEncodeNumber; +using spvutils::ParseNumber; + +// Given a numeric value in a null-terminated c string and the expected type of +// the value, parses the string and encodes it in a vector of words. If the +// value is a scalar integer or floating point value, encodes the value in +// SPIR-V encoding format. If the value is 'false' or 'true', returns a vector +// with single word with value 0 or 1 respectively. Returns the vector +// containing the encoded value on success. Otherwise returns an empty vector. +std::vector ParseDefaultValueStr(const char* text, + const analysis::Type* type) { + std::vector result; + if (!strcmp(text, "true") && type->AsBool()) { + result.push_back(1u); + } else if (!strcmp(text, "false") && type->AsBool()) { + result.push_back(0u); + } else { + NumberType number_type = {32, SPV_NUMBER_UNSIGNED_INT}; + if (const auto* IT = type->AsInteger()) { + number_type.bitwidth = IT->width(); + number_type.kind = + IT->IsSigned() ? SPV_NUMBER_SIGNED_INT : SPV_NUMBER_UNSIGNED_INT; + } else if (const auto* FT = type->AsFloat()) { + number_type.bitwidth = FT->width(); + number_type.kind = SPV_NUMBER_FLOATING; + } else { + // Does not handle types other then boolean, integer or float. Returns + // empty vector. + result.clear(); + return result; + } + EncodeNumberStatus rc = ParseAndEncodeNumber( + text, number_type, [&result](uint32_t word) { result.push_back(word); }, + nullptr); + // Clear the result vector on failure. + if (rc != EncodeNumberStatus::kSuccess) { + result.clear(); + } + } + return result; +} + +// Given a bit pattern and a type, checks if the bit pattern is compatible +// with the type. If so, returns the bit pattern, otherwise returns an empty +// bit pattern. If the given bit pattern is empty, returns an empty bit +// pattern. If the given type represents a SPIR-V Boolean type, the bit pattern +// to be returned is determined with the following standard: +// If any words in the input bit pattern are non zero, returns a bit pattern +// with 0x1, which represents a 'true'. +// If all words in the bit pattern are zero, returns a bit pattern with 0x0, +// which represents a 'false'. +std::vector ParseDefaultValueBitPattern( + const std::vector& input_bit_pattern, + const analysis::Type* type) { + std::vector result; + if (type->AsBool()) { + if (std::any_of(input_bit_pattern.begin(), input_bit_pattern.end(), + [](uint32_t i) { return i != 0; })) { + result.push_back(1u); + } else { + result.push_back(0u); + } + return result; + } else if (const auto* IT = type->AsInteger()) { + if (IT->width() == input_bit_pattern.size() * sizeof(uint32_t) * 8) { + return std::vector(input_bit_pattern); + } + } else if (const auto* FT = type->AsFloat()) { + if (FT->width() == input_bit_pattern.size() * sizeof(uint32_t) * 8) { + return std::vector(input_bit_pattern); + } + } + result.clear(); + return result; +} + +// Returns true if the given instruction's result id could have a SpecId +// decoration. +bool CanHaveSpecIdDecoration(const ir::Instruction& inst) { + switch (inst.opcode()) { + case SpvOp::SpvOpSpecConstant: + case SpvOp::SpvOpSpecConstantFalse: + case SpvOp::SpvOpSpecConstantTrue: + return true; + default: + return false; + } +} + +// Given a decoration group defining instruction that is decorated with SpecId +// decoration, finds the spec constant defining instruction which is the real +// target of the SpecId decoration. Returns the spec constant defining +// instruction if such an instruction is found, otherwise returns a nullptr. +ir::Instruction* GetSpecIdTargetFromDecorationGroup( + const ir::Instruction& decoration_group_defining_inst, + analysis::DefUseManager* def_use_mgr) { + // Find the OpGroupDecorate instruction which consumes the given decoration + // group. Note that the given decoration group has SpecId decoration, which + // is unique for different spec constants. So the decoration group cannot be + // consumed by different OpGroupDecorate instructions. Therefore we only need + // the first OpGroupDecoration instruction that uses the given decoration + // group. + ir::Instruction* group_decorate_inst = nullptr; + if (def_use_mgr->WhileEachUser(&decoration_group_defining_inst, + [&group_decorate_inst](ir::Instruction* user) { + if (user->opcode() == + SpvOp::SpvOpGroupDecorate) { + group_decorate_inst = user; + return false; + } + return true; + })) + return nullptr; + + // Scan through the target ids of the OpGroupDecorate instruction. There + // should be only one spec constant target consumes the SpecId decoration. + // If multiple target ids are presented in the OpGroupDecorate instruction, + // they must be the same one that defined by an eligible spec constant + // instruction. If the OpGroupDecorate instruction has different target ids + // or a target id is not defined by an eligible spec cosntant instruction, + // returns a nullptr. + ir::Instruction* target_inst = nullptr; + for (uint32_t i = 1; i < group_decorate_inst->NumInOperands(); i++) { + // All the operands of a OpGroupDecorate instruction should be of type + // SPV_OPERAND_TYPE_ID. + uint32_t candidate_id = group_decorate_inst->GetSingleWordInOperand(i); + ir::Instruction* candidate_inst = def_use_mgr->GetDef(candidate_id); + + if (!candidate_inst) { + continue; + } + + if (!target_inst) { + // If the spec constant target has not been found yet, check if the + // candidate instruction is the target. + if (CanHaveSpecIdDecoration(*candidate_inst)) { + target_inst = candidate_inst; + } else { + // Spec id decoration should not be applied on other instructions. + // TODO(qining): Emit an error message in the invalid case once the + // error handling is done. + return nullptr; + } + } else { + // If the spec constant target has been found, check if the candidate + // instruction is the same one as the target. The module is invalid if + // the candidate instruction is different with the found target. + // TODO(qining): Emit an error messaage in the invalid case once the + // error handling is done. + if (candidate_inst != target_inst) return nullptr; + } + } + return target_inst; +} +} // namespace + +Pass::Status SetSpecConstantDefaultValuePass::Process( + ir::IRContext* irContext) { + InitializeProcessing(irContext); + + // The operand index of decoration target in an OpDecorate instruction. + const uint32_t kTargetIdOperandIndex = 0; + // The operand index of the decoration literal in an OpDecorate instruction. + const uint32_t kDecorationOperandIndex = 1; + // The operand index of Spec id literal value in an OpDecorate SpecId + // instruction. + const uint32_t kSpecIdLiteralOperandIndex = 2; + // The number of operands in an OpDecorate SpecId instruction. + const uint32_t kOpDecorateSpecIdNumOperands = 3; + // The in-operand index of the default value in a OpSpecConstant instruction. + const uint32_t kOpSpecConstantLiteralInOperandIndex = 0; + + bool modified = false; + // Scan through all the annotation instructions to find 'OpDecorate SpecId' + // instructions. Then extract the decoration target of those instructions. + // The decoration targets should be spec constant defining instructions with + // opcode: OpSpecConstant{|True|False}. The spec id of those spec constants + // will be used to look up their new default values in the mapping from + // spec id to new default value strings. Once a new default value string + // is found for a spec id, the string will be parsed according to the target + // spec constant type. The parsed value will be used to replace the original + // default value of the target spec constant. + for (ir::Instruction& inst : irContext->annotations()) { + // Only process 'OpDecorate SpecId' instructions + if (inst.opcode() != SpvOp::SpvOpDecorate) continue; + if (inst.NumOperands() != kOpDecorateSpecIdNumOperands) continue; + if (inst.GetSingleWordInOperand(kDecorationOperandIndex) != + uint32_t(SpvDecoration::SpvDecorationSpecId)) { + continue; + } + + // 'inst' is an OpDecorate SpecId instruction. + uint32_t spec_id = inst.GetSingleWordOperand(kSpecIdLiteralOperandIndex); + uint32_t target_id = inst.GetSingleWordOperand(kTargetIdOperandIndex); + + // Find the spec constant defining instruction. Note that the + // target_id might be a decoration group id. + ir::Instruction* spec_inst = nullptr; + if (ir::Instruction* target_inst = get_def_use_mgr()->GetDef(target_id)) { + if (target_inst->opcode() == SpvOp::SpvOpDecorationGroup) { + spec_inst = + GetSpecIdTargetFromDecorationGroup(*target_inst, get_def_use_mgr()); + } else { + spec_inst = target_inst; + } + } else { + continue; + } + if (!spec_inst) continue; + + // Get the default value bit pattern for this spec id. + std::vector bit_pattern; + + if (spec_id_to_value_str_.size() != 0) { + // Search for the new string-form default value for this spec id. + auto iter = spec_id_to_value_str_.find(spec_id); + if (iter == spec_id_to_value_str_.end()) { + continue; + } + + // Gets the string of the default value and parses it to bit pattern + // with the type of the spec constant. + const std::string& default_value_str = iter->second; + bit_pattern = ParseDefaultValueStr( + default_value_str.c_str(), + context()->get_type_mgr()->GetType(spec_inst->type_id())); + + } else { + // Search for the new bit-pattern-form default value for this spec id. + auto iter = spec_id_to_value_bit_pattern_.find(spec_id); + if (iter == spec_id_to_value_bit_pattern_.end()) { + continue; + } + + // Gets the bit-pattern of the default value from the map directly. + bit_pattern = ParseDefaultValueBitPattern( + iter->second, + context()->get_type_mgr()->GetType(spec_inst->type_id())); + } + + if (bit_pattern.empty()) continue; + + // Update the operand bit patterns of the spec constant defining + // instruction. + switch (spec_inst->opcode()) { + case SpvOp::SpvOpSpecConstant: + // If the new value is the same with the original value, no + // need to do anything. Otherwise update the operand words. + if (spec_inst->GetInOperand(kOpSpecConstantLiteralInOperandIndex) + .words != bit_pattern) { + spec_inst->SetInOperand(kOpSpecConstantLiteralInOperandIndex, + std::move(bit_pattern)); + modified = true; + } + break; + case SpvOp::SpvOpSpecConstantTrue: + // If the new value is also 'true', no need to change anything. + // Otherwise, set the opcode to OpSpecConstantFalse; + if (!static_cast(bit_pattern.front())) { + spec_inst->SetOpcode(SpvOp::SpvOpSpecConstantFalse); + modified = true; + } + break; + case SpvOp::SpvOpSpecConstantFalse: + // If the new value is also 'false', no need to change anything. + // Otherwise, set the opcode to OpSpecConstantTrue; + if (static_cast(bit_pattern.front())) { + spec_inst->SetOpcode(SpvOp::SpvOpSpecConstantTrue); + modified = true; + } + break; + default: + break; + } + // No need to update the DefUse manager, as this pass does not change any + // ids. + } + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; +} + +// Returns true if the given char is ':', '\0' or considered as blank space +// (i.e.: '\n', '\r', '\v', '\t', '\f' and ' '). +bool IsSeparator(char ch) { + return std::strchr(":\0", ch) || std::isspace(ch) != 0; +} + +std::unique_ptr +SetSpecConstantDefaultValuePass::ParseDefaultValuesString(const char* str) { + if (!str) return nullptr; + + auto spec_id_to_value = MakeUnique(); + + // The parsing loop, break when points to the end. + while (*str) { + // Find the spec id. + while (std::isspace(*str)) str++; // skip leading spaces. + const char* entry_begin = str; + while (!IsSeparator(*str)) str++; + const char* entry_end = str; + std::string spec_id_str(entry_begin, entry_end - entry_begin); + uint32_t spec_id = 0; + if (!ParseNumber(spec_id_str.c_str(), &spec_id)) { + // The spec id is not a valid uint32 number. + return nullptr; + } + auto iter = spec_id_to_value->find(spec_id); + if (iter != spec_id_to_value->end()) { + // Same spec id has been defined before + return nullptr; + } + // Find the ':', spaces between the spec id and the ':' are not allowed. + if (*str++ != ':') { + // ':' not found + return nullptr; + } + // Find the value string + const char* val_begin = str; + while (!IsSeparator(*str)) str++; + const char* val_end = str; + if (val_end == val_begin) { + // Value string is empty. + return nullptr; + } + // Update the mapping with spec id and value string. + (*spec_id_to_value)[spec_id] = std::string(val_begin, val_end - val_begin); + + // Skip trailing spaces. + while (std::isspace(*str)) str++; + } + + return spec_id_to_value; +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/set_spec_constant_default_value_pass.h b/3rdparty/spirv-tools/source/opt/set_spec_constant_default_value_pass.h new file mode 100644 index 000000000..95667bb89 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/set_spec_constant_default_value_pass.h @@ -0,0 +1,112 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_SET_SPEC_CONSTANT_DEFAULT_VALUE_PASS_H_ +#define LIBSPIRV_OPT_SET_SPEC_CONSTANT_DEFAULT_VALUE_PASS_H_ + +#include +#include +#include + +#include "ir_context.h" +#include "module.h" +#include "pass.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class SetSpecConstantDefaultValuePass : public Pass { + public: + using SpecIdToValueStrMap = std::unordered_map; + using SpecIdToValueBitPatternMap = + std::unordered_map>; + using SpecIdToInstMap = std::unordered_map; + + // Constructs a pass instance with a map from spec ids to default values + // in the form of string. + explicit SetSpecConstantDefaultValuePass( + const SpecIdToValueStrMap& default_values) + : spec_id_to_value_str_(default_values), + spec_id_to_value_bit_pattern_() {} + explicit SetSpecConstantDefaultValuePass(SpecIdToValueStrMap&& default_values) + : spec_id_to_value_str_(std::move(default_values)), + spec_id_to_value_bit_pattern_() {} + + // Constructs a pass instance with a map from spec ids to default values in + // the form of bit pattern. + explicit SetSpecConstantDefaultValuePass( + const SpecIdToValueBitPatternMap& default_values) + : spec_id_to_value_str_(), + spec_id_to_value_bit_pattern_(default_values) {} + explicit SetSpecConstantDefaultValuePass( + SpecIdToValueBitPatternMap&& default_values) + : spec_id_to_value_str_(), + spec_id_to_value_bit_pattern_(std::move(default_values)) {} + + const char* name() const override { return "set-spec-const-default-value"; } + Status Process(ir::IRContext*) override; + + // Parses the given null-terminated C string to get a mapping from Spec Id to + // default value strings. Returns a unique pointer of the mapping from spec + // ids to spec constant default value strings built from the given |str| on + // success. Returns a nullptr if the given string is not valid for building + // the mapping. + // A valid string for building the mapping should follow the rule below: + // + // ": : ..." + // Example: + // "200:0x11 201:3.14 202:1.4728" + // + // Entries are separated with blank spaces (i.e.:' ', '\n', '\r', '\t', + // '\f', '\v'). Each entry corresponds to a Spec Id and default value pair. + // Multiple spaces between, before or after entries are allowed. However, + // spaces are not allowed within spec id or the default value string because + // spaces are always considered as delimiter to separate entries. + // + // In each entry, the spec id and value string is separated by ':'. Missing + // ':' in any entry is invalid. And it is invalid to have blank spaces in + // between the spec id and ':' or the default value and ':'. + // + // : specifies the spec id value. + // The text must represent a valid uint32_t number. + // Hex format with '0x' prefix is allowed. + // Empty is not allowed. + // One spec id value can only be defined once, multiple default values + // defined for the same spec id is not allowed. Spec ids with same value + // but different formats (e.g. 0x100 and 256) are considered the same. + // + // : the default value string. + // Spaces before and after default value text is allowed. + // Spaces within the text is not allowed. + // Empty is not allowed. + static std::unique_ptr ParseDefaultValuesString( + const char* str); + + private: + // The mappings from spec ids to default values. Two maps are defined here, + // each to be used for one specific form of the default values. Only one of + // them will be populated in practice. + + // The mapping from spec ids to their string-form default values to be set. + const SpecIdToValueStrMap spec_id_to_value_str_; + // The mapping from spec ids to their bitpattern-form default values to be + // set. + const SpecIdToValueBitPatternMap spec_id_to_value_bit_pattern_; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_SET_SPEC_CONSTANT_DEFAULT_VALUE_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/simplification_pass.cpp b/3rdparty/spirv-tools/source/opt/simplification_pass.cpp new file mode 100644 index 000000000..356ab90be --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/simplification_pass.cpp @@ -0,0 +1,114 @@ +// Copyright (c) 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "simplification_pass.h" + +#include +#include +#include + +#include "fold.h" + +namespace spvtools { +namespace opt { + +Pass::Status SimplificationPass::Process(ir::IRContext* c) { + InitializeProcessing(c); + bool modified = false; + + for (ir::Function& function : *get_module()) { + modified |= SimplifyFunction(&function); + } + return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange); +} + +bool SimplificationPass::SimplifyFunction(ir::Function* function) { + bool modified = false; + // Phase 1: Traverse all instructions in dominance order. + // The second phase will only be on the instructions whose inputs have changed + // after being processed during phase 1. Since OpPhi instructions are the + // only instructions whose inputs do not necessarily dominate the use, we keep + // track of the OpPhi instructions already seen, and add them to the work list + // for phase 2 when needed. + std::vector work_list; + std::unordered_set process_phis; + std::unordered_set inst_to_kill; + std::unordered_set in_work_list; + + cfg()->ForEachBlockInReversePostOrder( + function->entry().get(), + [&modified, &process_phis, &work_list, &in_work_list, &inst_to_kill, + this](ir::BasicBlock* bb) { + for (ir::Instruction* inst = &*bb->begin(); inst; + inst = inst->NextNode()) { + if (inst->opcode() == SpvOpPhi) { + process_phis.insert(inst); + } + + if (inst->opcode() == SpvOpCopyObject || FoldInstruction(inst)) { + modified = true; + context()->AnalyzeUses(inst); + get_def_use_mgr()->ForEachUser(inst, [&work_list, &process_phis, + &in_work_list]( + ir::Instruction* use) { + if (process_phis.count(use) && in_work_list.insert(use).second) { + work_list.push_back(use); + } + }); + if (inst->opcode() == SpvOpCopyObject) { + context()->ReplaceAllUsesWith(inst->result_id(), + inst->GetSingleWordInOperand(0)); + inst_to_kill.insert(inst); + in_work_list.insert(inst); + } + } + } + }); + + // Phase 2: process the instructions in the work list until all of the work is + // done. This time we add all users to the work list because phase 1 + // has already finished. + for (size_t i = 0; i < work_list.size(); ++i) { + ir::Instruction* inst = work_list[i]; + in_work_list.erase(inst); + if (inst->opcode() == SpvOpCopyObject || FoldInstruction(inst)) { + modified = true; + context()->AnalyzeUses(inst); + get_def_use_mgr()->ForEachUser( + inst, [&work_list, &in_work_list](ir::Instruction* use) { + if (!use->IsDecoration() && use->opcode() != SpvOpName && + in_work_list.insert(use).second) { + work_list.push_back(use); + } + }); + + if (inst->opcode() == SpvOpCopyObject) { + context()->ReplaceAllUsesWith(inst->result_id(), + inst->GetSingleWordInOperand(0)); + inst_to_kill.insert(inst); + in_work_list.insert(inst); + } + } + } + + // Phase 3: Kill instructions we know are no longer needed. + for (ir::Instruction* inst : inst_to_kill) { + context()->KillInst(inst); + } + + return modified; +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/simplification_pass.h b/3rdparty/spirv-tools/source/opt/simplification_pass.h new file mode 100644 index 000000000..206d9dc8e --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/simplification_pass.h @@ -0,0 +1,49 @@ +// Copyright (c) 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_SIMPLIFICATION_PASS_H_ +#define LIBSPIRV_OPT_SIMPLIFICATION_PASS_H_ + +#include "function.h" +#include "ir_context.h" +#include "pass.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class SimplificationPass : public Pass { + public: + const char* name() const override { return "simplify-instructions"; } + Status Process(ir::IRContext*) override; + virtual ir::IRContext::Analysis GetPreservedAnalyses() override { + return ir::IRContext::kAnalysisDefUse | + ir::IRContext::kAnalysisInstrToBlockMapping | + ir::IRContext::kAnalysisDecorations | + ir::IRContext::kAnalysisCombinators | ir::IRContext::kAnalysisCFG | + ir::IRContext::kAnalysisDominatorAnalysis | + ir::IRContext::kAnalysisNameMap; + } + + private: + // Returns true if the module was changed. The simplifier is called on every + // instruction in |function| until nothing else in the function can be + // simplified. + bool SimplifyFunction(ir::Function* function); +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_SIMPLIFICATION_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/ssa_rewrite_pass.cpp b/3rdparty/spirv-tools/source/opt/ssa_rewrite_pass.cpp new file mode 100644 index 000000000..1e8be8836 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/ssa_rewrite_pass.cpp @@ -0,0 +1,582 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file implements the SSA rewriting algorithm proposed in +// +// Simple and Efficient Construction of Static Single Assignment Form. +// Braun M., Buchwald S., Hack S., Leißa R., Mallon C., Zwinkau A. (2013) +// In: Jhala R., De Bosschere K. (eds) +// Compiler Construction. CC 2013. +// Lecture Notes in Computer Science, vol 7791. +// Springer, Berlin, Heidelberg +// +// https://link.springer.com/chapter/10.1007/978-3-642-37051-9_6 +// +// In contrast to common eager algorithms based on dominance and dominance +// frontier information, this algorithm works backwards from load operations. +// +// When a target variable is loaded, it queries the variable's reaching +// definition. If the reaching definition is unknown at the current location, +// it searches backwards in the CFG, inserting Phi instructions at join points +// in the CFG along the way until it finds the desired store instruction. +// +// The algorithm avoids repeated lookups using memoization. +// +// For reducible CFGs, which are a superset of the structured CFGs in SPIRV, +// this algorithm is proven to produce minimal SSA. That is, it inserts the +// minimal number of Phi instructions required to ensure the SSA property, but +// some Phi instructions may be dead +// (https://en.wikipedia.org/wiki/Static_single_assignment_form). + +#include "ssa_rewrite_pass.h" +#include "cfg.h" +#include "make_unique.h" +#include "mem_pass.h" +#include "opcode.h" + +#include + +// Debug logging (0: Off, 1-N: Verbosity level). Replace this with the +// implementation done for +// https://github.com/KhronosGroup/SPIRV-Tools/issues/1351 +// #define SSA_REWRITE_DEBUGGING_LEVEL 3 + +#ifdef SSA_REWRITE_DEBUGGING_LEVEL +#include +#else +#define SSA_REWRITE_DEBUGGING_LEVEL 0 +#endif + +namespace spvtools { +namespace opt { + +namespace { +const uint32_t kStoreValIdInIdx = 1; +const uint32_t kVariableInitIdInIdx = 1; +} // namespace + +std::string SSARewriter::PhiCandidate::PrettyPrint(const ir::CFG* cfg) const { + std::ostringstream str; + str << "%" << result_id_ << " = Phi[%" << var_id_ << ", BB %" << bb_->id() + << "]("; + if (phi_args_.size() > 0) { + uint32_t arg_ix = 0; + for (uint32_t pred_label : cfg->preds(bb_->id())) { + uint32_t arg_id = phi_args_[arg_ix++]; + str << "[%" << arg_id << ", bb(%" << pred_label << ")] "; + } + } + str << ")"; + if (copy_of_ != 0) { + str << " [COPY OF " << copy_of_ << "]"; + } + str << ((is_complete_) ? " [COMPLETE]" : " [INCOMPLETE]"); + + return str.str(); +} + +SSARewriter::PhiCandidate& SSARewriter::CreatePhiCandidate(uint32_t var_id, + ir::BasicBlock* bb) { + uint32_t phi_result_id = pass_->context()->TakeNextId(); + auto result = phi_candidates_.emplace( + phi_result_id, PhiCandidate(var_id, phi_result_id, bb)); + PhiCandidate& phi_candidate = result.first->second; + return phi_candidate; +} + +void SSARewriter::ReplacePhiUsersWith(const PhiCandidate& phi_to_remove, + uint32_t repl_id) { + for (uint32_t user_id : phi_to_remove.users()) { + PhiCandidate* user_phi = GetPhiCandidate(user_id); + if (user_phi) { + // If the user is a Phi candidate, replace all arguments that refer to + // |phi_to_remove.result_id()| with |repl_id|. + for (uint32_t& arg : user_phi->phi_args()) { + if (arg == phi_to_remove.result_id()) { + arg = repl_id; + } + } + } else { + // For regular loads, traverse the |load_replacement_| table looking for + // instances of |phi_to_remove|. + for (auto& it : load_replacement_) { + if (it.second == phi_to_remove.result_id()) { + it.second = repl_id; + } + } + } + } +} + +uint32_t SSARewriter::TryRemoveTrivialPhi(PhiCandidate* phi_candidate) { + uint32_t same_id = 0; + for (uint32_t arg_id : phi_candidate->phi_args()) { + if (arg_id == same_id || arg_id == phi_candidate->result_id()) { + // This is a self-reference operand or a reference to the same value ID. + continue; + } + if (same_id != 0) { + // This Phi candidate merges at least two values. Therefore, it is not + // trivial. + assert(phi_candidate->copy_of() == 0 && + "Phi candidate transitioning from copy to non-copy."); + return phi_candidate->result_id(); + } + same_id = arg_id; + } + + // The previous logic has determined that this Phi candidate |phi_candidate| + // is trivial. It is essentially the copy operation phi_candidate->phi_result + // = Phi(same, same, same, ...). Since it is not necessary, we can re-route + // all the users of |phi_candidate->phi_result| to all its users, and remove + // |phi_candidate|. + + // Mark the Phi candidate as a trivial copy of |same_id|, so it won't be + // generated. + phi_candidate->MarkCopyOf(same_id); + + assert(same_id != 0 && "Completed Phis cannot have %0 in their arguments"); + + // Since |phi_candidate| always produces |same_id|, replace all the users of + // |phi_candidate| with |same_id|. + ReplacePhiUsersWith(*phi_candidate, same_id); + + return same_id; +} + +uint32_t SSARewriter::AddPhiOperands(PhiCandidate* phi_candidate) { + assert(phi_candidate->phi_args().size() == 0 && + "Phi candidate already has arguments"); + + bool found_0_arg = false; + for (uint32_t pred : pass_->cfg()->preds(phi_candidate->bb()->id())) { + ir::BasicBlock* pred_bb = pass_->cfg()->block(pred); + + // If |pred_bb| is not sealed, use %0 to indicate that + // |phi_candidate| needs to be completed after the whole CFG has + // been processed. + // + // Note that we cannot call GetReachingDef() in these cases + // because this would generate an empty Phi candidate in + // |pred_bb|. When |pred_bb| is later processed, a new definition + // for |phi_candidate->var_id_| will be lost because + // |phi_candidate| will still be reached by the empty Phi. + // + // Consider: + // + // BB %23: + // %38 = Phi[%i](%int_0[%1], %39[%25]) + // + // ... + // + // BB %25: [Starts unsealed] + // %39 = Phi[%i]() + // %34 = ... + // OpStore %i %34 -> Currdef(%i) at %25 is %34 + // OpBranch %23 + // + // When we first create the Phi in %38, we add an operandless Phi in + // %39 to hold the unknown reaching def for %i. + // + // But then, when we go to complete %39 at the end. The reaching def + // for %i in %25's predecessor is %38 itself. So we miss the fact + // that %25 has a def for %i that should be used. + // + // By making the argument %0, we make |phi_candidate| incomplete, + // which will cause it to be completed after the whole CFG has + // been scanned. + uint32_t arg_id = IsBlockSealed(pred_bb) + ? GetReachingDef(phi_candidate->var_id(), pred_bb) + : 0; + phi_candidate->phi_args().push_back(arg_id); + + if (arg_id == 0) { + found_0_arg = true; + } else { + // If this argument is another Phi candidate, add |phi_candidate| to the + // list of users for the defining Phi. + PhiCandidate* defining_phi = GetPhiCandidate(arg_id); + if (defining_phi && defining_phi != phi_candidate) { + defining_phi->AddUser(phi_candidate->result_id()); + } + } + } + + // If we could not fill-in all the arguments of this Phi, mark it incomplete + // so it gets completed after the whole CFG has been processed. + if (found_0_arg) { + phi_candidate->MarkIncomplete(); + incomplete_phis_.push(phi_candidate); + return phi_candidate->result_id(); + } + + // Try to remove |phi_candidate|, if it's trivial. + uint32_t repl_id = TryRemoveTrivialPhi(phi_candidate); + if (repl_id == phi_candidate->result_id()) { + // |phi_candidate| is complete and not trivial. Add it to the + // list of Phi candidates to generate. + phi_candidate->MarkComplete(); + phis_to_generate_.push_back(phi_candidate); + } + + return repl_id; +} + +uint32_t SSARewriter::GetReachingDef(uint32_t var_id, ir::BasicBlock* bb) { + // If |var_id| has a definition in |bb|, return it. + const auto& bb_it = defs_at_block_.find(bb); + if (bb_it != defs_at_block_.end()) { + const auto& current_defs = bb_it->second; + const auto& var_it = current_defs.find(var_id); + if (var_it != current_defs.end()) { + return var_it->second; + } + } + + // Otherwise, look up the value for |var_id| in |bb|'s predecessors. + uint32_t val_id = 0; + auto& predecessors = pass_->cfg()->preds(bb->id()); + if (predecessors.size() == 1) { + // If |bb| has exactly one predecessor, we look for |var_id|'s definition + // there. + val_id = GetReachingDef(var_id, pass_->cfg()->block(predecessors[0])); + } else if (predecessors.size() > 1) { + // If there is more than one predecessor, this is a join block which may + // require a Phi instruction. This will act as |var_id|'s current + // definition to break potential cycles. + PhiCandidate& phi_candidate = CreatePhiCandidate(var_id, bb); + WriteVariable(var_id, bb, phi_candidate.result_id()); + val_id = AddPhiOperands(&phi_candidate); + } + + // If we could not find a store for this variable in the path from the root + // of the CFG, the variable is not defined, so we use undef. + if (val_id == 0) { + val_id = pass_->GetUndefVal(var_id); + } + + WriteVariable(var_id, bb, val_id); + + return val_id; +} + +void SSARewriter::SealBlock(ir::BasicBlock* bb) { + auto result = sealed_blocks_.insert(bb); + (void)result; + assert(result.second == true && + "Tried to seal the same basic block more than once."); +} + +void SSARewriter::ProcessStore(ir::Instruction* inst, ir::BasicBlock* bb) { + auto opcode = inst->opcode(); + assert((opcode == SpvOpStore || opcode == SpvOpVariable) && + "Expecting a store or a variable definition instruction."); + + uint32_t var_id = 0; + uint32_t val_id = 0; + if (opcode == SpvOpStore) { + (void)pass_->GetPtr(inst, &var_id); + val_id = inst->GetSingleWordInOperand(kStoreValIdInIdx); + } else if (inst->NumInOperands() >= 2) { + var_id = inst->result_id(); + val_id = inst->GetSingleWordInOperand(kVariableInitIdInIdx); + } + if (pass_->IsTargetVar(var_id)) { + WriteVariable(var_id, bb, val_id); + +#if SSA_REWRITE_DEBUGGING_LEVEL > 1 + std::cerr << "\tFound store '%" << var_id << " = %" << val_id << "': " + << inst->PrettyPrint(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) + << "\n"; +#endif + } +} + +void SSARewriter::ProcessLoad(ir::Instruction* inst, ir::BasicBlock* bb) { + uint32_t var_id = 0; + (void)pass_->GetPtr(inst, &var_id); + if (pass_->IsTargetVar(var_id)) { + // Get the immediate reaching definition for |var_id|. + uint32_t val_id = GetReachingDef(var_id, bb); + + // Schedule a replacement for the result of this load instruction with + // |val_id|. After all the rewriting decisions are made, every use of + // this load will be replaced with |val_id|. + const uint32_t load_id = inst->result_id(); + assert(load_replacement_.count(load_id) == 0); + load_replacement_[load_id] = val_id; + PhiCandidate* defining_phi = GetPhiCandidate(val_id); + if (defining_phi) { + defining_phi->AddUser(load_id); + } + +#if SSA_REWRITE_DEBUGGING_LEVEL > 1 + std::cerr << "\tFound load: " + << inst->PrettyPrint(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) + << " (replacement for %" << load_id << " is %" << val_id << ")\n"; +#endif + } +} + +void SSARewriter::PrintPhiCandidates() const { + std::cerr << "\nPhi candidates:\n"; + for (const auto& phi_it : phi_candidates_) { + std::cerr << "\tBB %" << phi_it.second.bb()->id() << ": " + << phi_it.second.PrettyPrint(pass_->cfg()) << "\n"; + } + std::cerr << "\n"; +} + +void SSARewriter::PrintReplacementTable() const { + std::cerr << "\nLoad replacement table\n"; + for (const auto& it : load_replacement_) { + std::cerr << "\t%" << it.first << " -> %" << it.second << "\n"; + } + std::cerr << "\n"; +} + +void SSARewriter::GenerateSSAReplacements(ir::BasicBlock* bb) { +#if SSA_REWRITE_DEBUGGING_LEVEL > 1 + std::cerr << "Generating SSA replacements for block: " << bb->id() << "\n"; + std::cerr << bb->PrettyPrint(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) + << "\n"; +#endif + + for (auto& inst : *bb) { + auto opcode = inst.opcode(); + if (opcode == SpvOpStore || opcode == SpvOpVariable) { + ProcessStore(&inst, bb); + } else if (inst.opcode() == SpvOpLoad) { + ProcessLoad(&inst, bb); + } + } + + // Seal |bb|. This means that all the stores in it have been scanned and it's + // ready to feed them into its successors. + SealBlock(bb); + +#if SSA_REWRITE_DEBUGGING_LEVEL > 1 + PrintPhiCandidates(); + PrintReplacementTable(); + std::cerr << "\n\n"; +#endif +} + +uint32_t SSARewriter::GetReplacement(std::pair repl) { + uint32_t val_id = repl.second; + auto it = load_replacement_.find(val_id); + while (it != load_replacement_.end()) { + val_id = it->second; + it = load_replacement_.find(val_id); + } + return val_id; +} + +uint32_t SSARewriter::GetPhiArgument(const PhiCandidate* phi_candidate, + uint32_t ix) { + assert(phi_candidate->IsReady() && + "Tried to get the final argument from an incomplete/trivial Phi"); + + uint32_t arg_id = phi_candidate->phi_args()[ix]; + while (arg_id != 0) { + PhiCandidate* phi_user = GetPhiCandidate(arg_id); + if (phi_user == nullptr || phi_user->IsReady()) { + // If the argument is not a Phi or it's a Phi candidate ready to be + // emitted, return it. + return arg_id; + } + arg_id = phi_user->copy_of(); + } + + assert(false && + "No Phi candidates in the copy-of chain are ready to be generated"); + + return 0; +} + +bool SSARewriter::ApplyReplacements() { + bool modified = false; + +#if SSA_REWRITE_DEBUGGING_LEVEL > 2 + std::cerr << "\n\nApplying replacement decisions to IR\n\n"; + PrintPhiCandidates(); + PrintReplacementTable(); + std::cerr << "\n\n"; +#endif + + // Add Phi instructions from completed Phi candidates. + std::vector generated_phis; + for (const PhiCandidate* phi_candidate : phis_to_generate_) { +#if SSA_REWRITE_DEBUGGING_LEVEL > 2 + std::cerr << "Phi candidate: " << phi_candidate->PrettyPrint(pass_->cfg()) + << "\n"; +#endif + + assert(phi_candidate->is_complete() && + "Tried to instantiate a Phi instruction from an incomplete Phi " + "candidate"); + + // Build the vector of operands for the new OpPhi instruction. + uint32_t type_id = pass_->GetPointeeTypeId( + pass_->get_def_use_mgr()->GetDef(phi_candidate->var_id())); + std::vector phi_operands; + uint32_t arg_ix = 0; + for (uint32_t pred_label : pass_->cfg()->preds(phi_candidate->bb()->id())) { + uint32_t op_val_id = GetPhiArgument(phi_candidate, arg_ix++); + phi_operands.push_back( + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {op_val_id}}); + phi_operands.push_back( + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {pred_label}}); + } + + // Generate a new OpPhi instruction and insert it in its basic + // block. + std::unique_ptr phi_inst( + new ir::Instruction(pass_->context(), SpvOpPhi, type_id, + phi_candidate->result_id(), phi_operands)); + generated_phis.push_back(phi_inst.get()); + pass_->get_def_use_mgr()->AnalyzeInstDef(&*phi_inst); + pass_->context()->set_instr_block(&*phi_inst, phi_candidate->bb()); + auto insert_it = phi_candidate->bb()->begin(); + insert_it.InsertBefore(std::move(phi_inst)); + modified = true; + } + + // Scan uses for all inserted Phi instructions. Do this separately from the + // registration of the Phi instruction itself to avoid trying to analyze uses + // of Phi instructions that have not been registered yet. + for (ir::Instruction* phi_inst : generated_phis) { + pass_->get_def_use_mgr()->AnalyzeInstUse(&*phi_inst); + } + +#if SSA_REWRITE_DEBUGGING_LEVEL > 1 + std::cerr << "\n\nReplacing the result of load instructions with the " + "corresponding SSA id\n\n"; +#endif + + // Apply replacements from the load replacement table. + for (auto& repl : load_replacement_) { + uint32_t load_id = repl.first; + uint32_t val_id = GetReplacement(repl); + ir::Instruction* load_inst = + pass_->context()->get_def_use_mgr()->GetDef(load_id); + +#if SSA_REWRITE_DEBUGGING_LEVEL > 2 + std::cerr << "\t" + << load_inst->PrettyPrint( + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) + << " (%" << load_id << " -> %" << val_id << ")\n"; +#endif + + // Remove the load instruction and replace all the uses of this load's + // result with |val_id|. Kill any names or decorates using the load's + // result before replacing to prevent incorrect replacement in those + // instructions. + pass_->context()->KillNamesAndDecorates(load_id); + pass_->context()->ReplaceAllUsesWith(load_id, val_id); + pass_->context()->KillInst(load_inst); + modified = true; + } + + return modified; +} + +void SSARewriter::FinalizePhiCandidate(PhiCandidate* phi_candidate) { + assert(phi_candidate->phi_args().size() > 0 && + "Phi candidate should have arguments"); + + uint32_t ix = 0; + for (uint32_t pred : pass_->cfg()->preds(phi_candidate->bb()->id())) { + ir::BasicBlock* pred_bb = pass_->cfg()->block(pred); + uint32_t& arg_id = phi_candidate->phi_args()[ix++]; + if (arg_id == 0) { + // If |pred_bb| is still not sealed, it means it's unreachable. In this + // case, we just use Undef as an argument. + arg_id = IsBlockSealed(pred_bb) + ? GetReachingDef(phi_candidate->var_id(), pred_bb) + : pass_->GetUndefVal(phi_candidate->var_id()); + } + } + + // This candidate is now completed. + phi_candidate->MarkComplete(); + + // If |phi_candidate| is not trivial, add it to the list of Phis to generate. + if (TryRemoveTrivialPhi(phi_candidate) == phi_candidate->result_id()) { + // If we could not remove |phi_candidate|, it means that it is complete + // and not trivial. Add it to the list of Phis to generate. + assert(!phi_candidate->copy_of() && "A completed Phi cannot be trivial."); + phis_to_generate_.push_back(phi_candidate); + } +} + +void SSARewriter::FinalizePhiCandidates() { +#if SSA_REWRITE_DEBUGGING_LEVEL > 1 + std::cerr << "Finalizing Phi candidates:\n\n"; + PrintPhiCandidates(); + std::cerr << "\n"; +#endif + + // Now, complete the collected candidates. + while (incomplete_phis_.size() > 0) { + PhiCandidate* phi_candidate = incomplete_phis_.front(); + incomplete_phis_.pop(); + FinalizePhiCandidate(phi_candidate); + } +} + +bool SSARewriter::RewriteFunctionIntoSSA(ir::Function* fp) { +#if SSA_REWRITE_DEBUGGING_LEVEL > 0 + std::cerr << "Function before SSA rewrite:\n" + << fp->PrettyPrint(0) << "\n\n\n"; +#endif + + // Collect variables that can be converted into SSA IDs. + pass_->CollectTargetVars(fp); + + // Generate all the SSA replacements and Phi candidates. This will + // generate incomplete and trivial Phis. + pass_->cfg()->ForEachBlockInReversePostOrder( + fp->entry().get(), + [this](ir::BasicBlock* bb) { GenerateSSAReplacements(bb); }); + + // Remove trivial Phis and add arguments to incomplete Phis. + FinalizePhiCandidates(); + + // Finally, apply all the replacements in the IR. + bool modified = ApplyReplacements(); + +#if SSA_REWRITE_DEBUGGING_LEVEL > 0 + std::cerr << "\n\n\nFunction after SSA rewrite:\n" + << fp->PrettyPrint(0) << "\n"; +#endif + + return modified; +} + +void SSARewritePass::Initialize(ir::IRContext* c) { InitializeProcessing(c); } + +Pass::Status SSARewritePass::Process(ir::IRContext* c) { + Initialize(c); + + bool modified = false; + for (auto& fn : *get_module()) { + modified |= SSARewriter(this).RewriteFunctionIntoSSA(&fn); + } + return modified ? Pass::Status::SuccessWithChange + : Pass::Status::SuccessWithoutChange; +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/ssa_rewrite_pass.h b/3rdparty/spirv-tools/source/opt/ssa_rewrite_pass.h new file mode 100644 index 000000000..e58943635 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/ssa_rewrite_pass.h @@ -0,0 +1,304 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_SSA_REWRITE_PASS_H_ +#define LIBSPIRV_OPT_SSA_REWRITE_PASS_H_ + +#include "basic_block.h" +#include "ir_context.h" +#include "mem_pass.h" + +#include + +namespace spvtools { +namespace opt { + +// Utility class for passes that need to rewrite a function into SSA. This +// converts load/store operations on function-local variables into SSA IDs, +// which allows them to be the target of optimizing transformations. +// +// Store and load operations to these variables are converted into +// operations on SSA IDs. Phi instructions are added when needed. See the +// SSA construction paper for algorithmic details +// (https://link.springer.com/chapter/10.1007/978-3-642-37051-9_6) +class SSARewriter { + public: + SSARewriter(MemPass* pass) + : pass_(pass), first_phi_id_(pass_->get_module()->IdBound()) {} + + // Rewrites SSA-target variables in function |fp| into SSA. This is the + // entry point for the SSA rewrite algorithm. SSA-target variables are + // locally defined variables that meet the criteria set by IsSSATargetVar. + // + // It returns true if function |fp| was modified. Otherwise, it returns + // false. + bool RewriteFunctionIntoSSA(ir::Function* fp); + + private: + class PhiCandidate { + public: + explicit PhiCandidate(uint32_t var, uint32_t result, ir::BasicBlock* block) + : var_id_(var), + result_id_(result), + bb_(block), + phi_args_(), + copy_of_(0), + is_complete_(false), + users_() {} + + uint32_t var_id() const { return var_id_; } + uint32_t result_id() const { return result_id_; } + ir::BasicBlock* bb() const { return bb_; } + std::vector& phi_args() { return phi_args_; } + const std::vector& phi_args() const { return phi_args_; } + uint32_t copy_of() const { return copy_of_; } + bool is_complete() const { return is_complete_; } + std::vector& users() { return users_; } + const std::vector& users() const { return users_; } + + // Marks this phi candidate as a trivial copy of |orig_id|. + void MarkCopyOf(uint32_t orig_id) { copy_of_ = orig_id; } + + // Marks this phi candidate as incomplete. + void MarkIncomplete() { is_complete_ = false; } + + // Marks this phi candidate as complete. + void MarkComplete() { is_complete_ = true; } + + // Returns true if this Phi candidate is ready to be emitted. + bool IsReady() const { return is_complete() && copy_of() == 0; } + + // Pretty prints this Phi candidate into a string and returns it. |cfg| is + // needed to lookup basic block predecessors. + std::string PrettyPrint(const ir::CFG* cfg) const; + + // Registers |operand_id| as a user of this Phi candidate. + void AddUser(uint32_t operand_id) { users_.push_back(operand_id); } + + private: + // Variable ID that this Phi is merging. + uint32_t var_id_; + + // SSA ID generated by this Phi (i.e., this is the result ID of the eventual + // Phi instruction). + uint32_t result_id_; + + // Basic block to hold this Phi. + ir::BasicBlock* bb_; + + // Vector of operands for every predecessor block of |bb|. This vector is + // organized so that the Ith slot contains the argument coming from the Ith + // predecessor of |bb|. + std::vector phi_args_; + + // If this Phi is a trivial copy of another Phi, this is the ID of the + // original. If this is 0, it means that this is not a trivial Phi. + uint32_t copy_of_; + + // False, if this Phi candidate has no arguments or at least one argument is + // %0. + bool is_complete_; + + // List of all users for this Phi instruction. Each element is the result ID + // of the load instruction replaced by this Phi, or the result ID of a Phi + // candidate that has this Phi in its list of operands. + std::vector users_; + }; + + // Type used to keep track of store operations in each basic block. + typedef std::unordered_map> + BlockDefsMap; + + // Generates all the SSA rewriting decisions for basic block |bb|. This + // populates the Phi candidate table (|phi_candidate_|) and the load + // replacement table (|load_replacement_). + void GenerateSSAReplacements(ir::BasicBlock* bb); + + // Seals block |bb|. Sealing a basic block means |bb| and all its + // predecessors of |bb| have been scanned for loads/stores. + void SealBlock(ir::BasicBlock* bb); + + // Returns true if |bb| has been sealed. + bool IsBlockSealed(ir::BasicBlock* bb) { + return sealed_blocks_.count(bb) != 0; + } + + // Returns the Phi candidate with result ID |id| if it exists in the table + // |phi_candidates_|. If no such Phi candidate exists, it returns nullptr. + PhiCandidate* GetPhiCandidate(uint32_t id) { + auto it = phi_candidates_.find(id); + return (it != phi_candidates_.end()) ? &it->second : nullptr; + } + + // Replaces all the users of Phi candidate |phi_cand| to be users of + // |repl_id|. + void ReplacePhiUsersWith(const PhiCandidate& phi_cand, uint32_t repl_id); + + // Returns the value ID that should replace the load ID in the given + // replacement pair |repl|. The replacement is a pair (|load_id|, |val_id|). + // If |val_id| is itself replaced by another value in the table, this function + // will look the replacement for |val_id| until it finds one that is not + // itself replaced. For instance, given: + // + // %34 = OpLoad %float %f1 + // OpStore %t %34 + // %36 = OpLoad %float %t + // + // Assume that %f1 is reached by a Phi candidate %42, the load + // replacement table will have the following entries: + // + // %34 -> %42 + // %36 -> %34 + // + // So, when looking for the replacement for %36, we should not use + // %34. Rather, we should use %42. To do this, the chain of + // replacements must be followed until we reach an element that has + // no replacement. + uint32_t GetReplacement(std::pair repl); + + // Returns the argument at index |ix| from |phi_candidate|. If argument |ix| + // comes from a trivial Phi, it follows the copy-of chain from that trivial + // Phi until it finds the original Phi candidate. + // + // This is only valid after all Phi candidates have been completed. It can + // only be called when generating the IR for these Phis. + uint32_t GetPhiArgument(const PhiCandidate* phi_candidate, uint32_t ix); + + // Applies all the SSA replacement decisions. This replaces loads/stores to + // SSA target variables with their corresponding SSA IDs, and inserts Phi + // instructions for them. + bool ApplyReplacements(); + + // Registers a definition for variable |var_id| in basic block |bb| with + // value |val_id|. + void WriteVariable(uint32_t var_id, ir::BasicBlock* bb, uint32_t val_id) { + defs_at_block_[bb][var_id] = val_id; + } + + // Processes the store operation |inst| in basic block |bb|. This extracts + // the variable ID being stored into, determines whether the variable is an + // SSA-target variable, and, if it is, it stores its value in the + // |defs_at_block_| map. + void ProcessStore(ir::Instruction* inst, ir::BasicBlock* bb); + + // Processes the load operation |inst| in basic block |bb|. This extracts + // the variable ID being stored into, determines whether the variable is an + // SSA-target variable, and, if it is, it reads its reaching definition by + // calling |GetReachingDef|. + void ProcessLoad(ir::Instruction* inst, ir::BasicBlock* bb); + + // Reads the current definition for variable |var_id| in basic block |bb|. + // If |var_id| is not defined in block |bb| it walks up the predecessors of + // |bb|, creating new Phi candidates along the way, if needed. + // + // It returns the value for |var_id| from the RHS of the current reaching + // definition for |var_id|. + uint32_t GetReachingDef(uint32_t var_id, ir::BasicBlock* bb); + + // Adds arguments to |phi_candidate| by getting the reaching definition of + // |phi_candidate|'s variable on each of the predecessors of its basic + // block. After populating the argument list, it determines whether all its + // arguments are the same. If so, it returns the ID of the argument that + // this Phi copies. + uint32_t AddPhiOperands(PhiCandidate* phi_candidate); + + // Creates a Phi candidate instruction for variable |var_id| in basic block + // |bb|. + // + // Since the rewriting algorithm may remove Phi candidates when it finds + // them to be trivial, we avoid the expense of creating actual Phi + // instructions by keeping a pool of Phi candidates (|phi_candidates_|) + // during rewriting. + // + // Once the candidate Phi is created, it returns its ID. + PhiCandidate& CreatePhiCandidate(uint32_t var_id, ir::BasicBlock* bb); + + // Attempts to remove a trivial Phi candidate |phi_cand|. Trivial Phis are + // those that only reference themselves and one other value |val| any number + // of times. This will try to remove any other Phis that become trivial + // after |phi_cand| is removed. + // + // If |phi_cand| is trivial, it returns the SSA ID for the value that should + // replace it. Otherwise, it returns the SSA ID for |phi_cand|. + uint32_t TryRemoveTrivialPhi(PhiCandidate* phi_cand); + + // Finalizes |phi_candidate| by replacing every argument that is still %0 + // with its reaching definition. + void FinalizePhiCandidate(PhiCandidate* phi_candidate); + + // Finalizes processing of Phi candidates. Once the whole function has been + // scanned for loads and stores, the CFG will still have some incomplete and + // trivial Phis. This will add missing arguments and remove trivial Phi + // candidates. + void FinalizePhiCandidates(); + + // Prints the table of Phi candidates to std::cerr. + void PrintPhiCandidates() const; + + // Prints the load replacement table to std::cerr. + void PrintReplacementTable() const; + + // Map holding the value of every SSA-target variable at every basic block + // where the variable is stored. defs_at_block_[block][var_id] = val_id + // means that there is a store or Phi instruction for variable |var_id| at + // basic block |block| with value |val_id|. + BlockDefsMap defs_at_block_; + + // Map, indexed by Phi ID, holding all the Phi candidates created during SSA + // rewriting. |phi_candidates_[id]| returns the Phi candidate whose result + // is |id|. + std::unordered_map phi_candidates_; + + // Queue of incomplete Phi candidates. These are Phi candidates created at + // unsealed blocks. They need to be completed before they are instantiated + // in ApplyReplacements. + std::queue incomplete_phis_; + + // List of completed Phi candidates. These are the only candidates that + // will become real Phi instructions. + std::vector phis_to_generate_; + + // SSA replacement table. This maps variable IDs, resulting from a load + // operation, to the value IDs that will replace them after SSA rewriting. + // After all the rewriting decisions are made, a final scan through the IR + // is done to replace all uses of the original load ID with the value ID. + std::unordered_map load_replacement_; + + // Set of blocks that have been sealed already. + std::unordered_set sealed_blocks_; + + // Memory pass requesting the SSA rewriter. + MemPass* pass_; + + // ID of the first Phi created by the SSA rewriter. During rewriting, any + // ID bigger than this corresponds to a Phi candidate. + uint32_t first_phi_id_; +}; + +class SSARewritePass : public MemPass { + public: + SSARewritePass() = default; + const char* name() const override { return "ssa-rewrite"; } + Status Process(ir::IRContext* c) override; + + private: + // Initializes the pass. + void Initialize(ir::IRContext* c); +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_SSA_REWRITE_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/strength_reduction_pass.cpp b/3rdparty/spirv-tools/source/opt/strength_reduction_pass.cpp new file mode 100644 index 000000000..fd8ccf97c --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/strength_reduction_pass.cpp @@ -0,0 +1,199 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "strength_reduction_pass.h" + +#include +#include +#include +#include +#include + +#include "def_use_manager.h" +#include "ir_context.h" +#include "log.h" +#include "reflect.h" + +namespace { +// Count the number of trailing zeros in the binary representation of +// |constVal|. +uint32_t CountTrailingZeros(uint32_t constVal) { + // Faster if we use the hardware count trailing zeros instruction. + // If not available, we could create a table. + uint32_t shiftAmount = 0; + while ((constVal & 1) == 0) { + ++shiftAmount; + constVal = (constVal >> 1); + } + return shiftAmount; +} + +// Return true if |val| is a power of 2. +bool IsPowerOf2(uint32_t val) { + // The idea is that the & will clear out the least + // significant 1 bit. If it is a power of 2, then + // there is exactly 1 bit set, and the value becomes 0. + if (val == 0) return false; + return ((val - 1) & val) == 0; +} + +} // namespace + +namespace spvtools { +namespace opt { + +Pass::Status StrengthReductionPass::Process(ir::IRContext* c) { + InitializeProcessing(c); + + // Initialize the member variables on a per module basis. + bool modified = false; + int32_type_id_ = 0; + uint32_type_id_ = 0; + std::memset(constant_ids_, 0, sizeof(constant_ids_)); + + FindIntTypesAndConstants(); + modified = ScanFunctions(); + return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange); +} + +bool StrengthReductionPass::ReplaceMultiplyByPowerOf2( + ir::BasicBlock::iterator* inst) { + assert((*inst)->opcode() == SpvOp::SpvOpIMul && + "Only works for multiplication of integers."); + bool modified = false; + + // Currently only works on 32-bit integers. + if ((*inst)->type_id() != int32_type_id_ && + (*inst)->type_id() != uint32_type_id_) { + return modified; + } + + // Check the operands for a constant that is a power of 2. + for (int i = 0; i < 2; i++) { + uint32_t opId = (*inst)->GetSingleWordInOperand(i); + ir::Instruction* opInst = get_def_use_mgr()->GetDef(opId); + if (opInst->opcode() == SpvOp::SpvOpConstant) { + // We found a constant operand. + uint32_t constVal = opInst->GetSingleWordOperand(2); + + if (IsPowerOf2(constVal)) { + modified = true; + uint32_t shiftAmount = CountTrailingZeros(constVal); + uint32_t shiftConstResultId = GetConstantId(shiftAmount); + + // Create the new instruction. + uint32_t newResultId = TakeNextId(); + std::vector newOperands; + newOperands.push_back((*inst)->GetInOperand(1 - i)); + ir::Operand shiftOperand(spv_operand_type_t::SPV_OPERAND_TYPE_ID, + {shiftConstResultId}); + newOperands.push_back(shiftOperand); + std::unique_ptr newInstruction( + new ir::Instruction(context(), SpvOp::SpvOpShiftLeftLogical, + (*inst)->type_id(), newResultId, newOperands)); + + // Insert the new instruction and update the data structures. + (*inst) = (*inst).InsertBefore(std::move(newInstruction)); + get_def_use_mgr()->AnalyzeInstDefUse(&*(*inst)); + ++(*inst); + context()->ReplaceAllUsesWith((*inst)->result_id(), newResultId); + + // Remove the old instruction. + ir::Instruction* inst_to_delete = &*(*inst); + --(*inst); + context()->KillInst(inst_to_delete); + + // We do not want to replace the instruction twice if both operands + // are constants that are a power of 2. So we break here. + break; + } + } + } + + return modified; +} + +void StrengthReductionPass::FindIntTypesAndConstants() { + analysis::Integer int32(32, true); + int32_type_id_ = context()->get_type_mgr()->GetId(&int32); + analysis::Integer uint32(32, false); + uint32_type_id_ = context()->get_type_mgr()->GetId(&uint32); + for (auto iter = get_module()->types_values_begin(); + iter != get_module()->types_values_end(); ++iter) { + switch (iter->opcode()) { + case SpvOp::SpvOpConstant: + if (iter->type_id() == uint32_type_id_) { + uint32_t value = iter->GetSingleWordOperand(2); + if (value <= 32) constant_ids_[value] = iter->result_id(); + } + break; + default: + break; + } + } +} + +uint32_t StrengthReductionPass::GetConstantId(uint32_t val) { + assert(val <= 32 && + "This function does not handle constants larger than 32."); + + if (constant_ids_[val] == 0) { + if (uint32_type_id_ == 0) { + analysis::Integer uint(32, false); + uint32_type_id_ = context()->get_type_mgr()->GetTypeInstruction(&uint); + } + + // Construct the constant. + uint32_t resultId = TakeNextId(); + ir::Operand constant(spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, + {val}); + std::unique_ptr newConstant( + new ir::Instruction(context(), SpvOp::SpvOpConstant, uint32_type_id_, + resultId, {constant})); + get_module()->AddGlobalValue(std::move(newConstant)); + + // Notify the DefUseManager about this constant. + auto constantIter = --get_module()->types_values_end(); + get_def_use_mgr()->AnalyzeInstDef(&*constantIter); + + // Store the result id for next time. + constant_ids_[val] = resultId; + } + + return constant_ids_[val]; +} + +bool StrengthReductionPass::ScanFunctions() { + // I did not use |ForEachInst| in the module because the function that acts on + // the instruction gets a pointer to the instruction. We cannot use that to + // insert a new instruction. I want an iterator. + bool modified = false; + for (auto& func : *get_module()) { + for (auto& bb : func) { + for (auto inst = bb.begin(); inst != bb.end(); ++inst) { + switch (inst->opcode()) { + case SpvOp::SpvOpIMul: + if (ReplaceMultiplyByPowerOf2(&inst)) modified = true; + break; + default: + break; + } + } + } + } + return modified; +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/strength_reduction_pass.h b/3rdparty/spirv-tools/source/opt/strength_reduction_pass.h new file mode 100644 index 000000000..6c233e151 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/strength_reduction_pass.h @@ -0,0 +1,65 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_STRENGTH_REDUCTION_PASS_H_ +#define LIBSPIRV_OPT_STRENGTH_REDUCTION_PASS_H_ + +#include "def_use_manager.h" +#include "ir_context.h" +#include "module.h" +#include "pass.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class StrengthReductionPass : public Pass { + public: + const char* name() const override { return "strength-reduction"; } + Status Process(ir::IRContext*) override; + + private: + // Replaces multiple by power of 2 with an equivalent bit shift. + // Returns true if something changed. + bool ReplaceMultiplyByPowerOf2(ir::BasicBlock::iterator*); + + // Scan the types and constants in the module looking for the the integer + // types that we are + // interested in. The shift operation needs a small unsigned integer. We + // need to find + // them or create them. We do not want duplicates. + void FindIntTypesAndConstants(); + + // Get the id for the given constant. If it does not exist, it will be + // created. The parameter must be between 0 and 32 inclusive. + uint32_t GetConstantId(uint32_t); + + // Replaces certain instructions in function bodies with presumably cheaper + // ones. Returns true if something changed. + bool ScanFunctions(); + + // Type ids for the types of interest, or 0 if they do not exist. + uint32_t int32_type_id_; + uint32_t uint32_type_id_; + + // constant_ids[i] is the id for unsigned integer constant i. + // We set the limit at 32 because a bit shift of a 32-bit integer does not + // need a value larger than 32. + uint32_t constant_ids_[33]; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_STRENGTH_REDUCTION_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/strip_debug_info_pass.cpp b/3rdparty/spirv-tools/source/opt/strip_debug_info_pass.cpp new file mode 100644 index 000000000..ae35b1096 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/strip_debug_info_pass.cpp @@ -0,0 +1,36 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "strip_debug_info_pass.h" +#include "ir_context.h" + +namespace spvtools { +namespace opt { + +Pass::Status StripDebugInfoPass::Process(ir::IRContext* irContext) { + bool modified = !irContext->debugs1().empty() || + !irContext->debugs2().empty() || + !irContext->debugs3().empty(); + irContext->debug_clear(); + + irContext->module()->ForEachInst([&modified](ir::Instruction* inst) { + modified |= !inst->dbg_line_insts().empty(); + inst->dbg_line_insts().clear(); + }); + + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/strip_debug_info_pass.h b/3rdparty/spirv-tools/source/opt/strip_debug_info_pass.h new file mode 100644 index 000000000..52cbd680a --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/strip_debug_info_pass.h @@ -0,0 +1,35 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_STRIP_DEBUG_INFO_PASS_H_ +#define LIBSPIRV_OPT_STRIP_DEBUG_INFO_PASS_H_ + +#include "ir_context.h" +#include "module.h" +#include "pass.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class StripDebugInfoPass : public Pass { + public: + const char* name() const override { return "strip-debug"; } + Status Process(ir::IRContext* irContext) override; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_STRIP_DEBUG_INFO_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/strip_reflect_info_pass.cpp b/3rdparty/spirv-tools/source/opt/strip_reflect_info_pass.cpp new file mode 100644 index 000000000..d863e6600 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/strip_reflect_info_pass.cpp @@ -0,0 +1,75 @@ +// Copyright (c) 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "strip_reflect_info_pass.h" + +#include + +#include "instruction.h" +#include "ir_context.h" + +namespace spvtools { +namespace opt { + +using spvtools::ir::Instruction; + +Pass::Status StripReflectInfoPass::Process(ir::IRContext* irContext) { + bool modified = false; + + std::vector to_remove; + + bool other_uses_for_decorate_string = false; + for (auto& inst : irContext->module()->annotations()) { + switch (inst.opcode()) { + case SpvOpDecorateStringGOOGLE: + if (inst.GetSingleWordInOperand(1) == SpvDecorationHlslSemanticGOOGLE) { + to_remove.push_back(&inst); + } else { + other_uses_for_decorate_string = true; + } + break; + + case SpvOpDecorateId: + if (inst.GetSingleWordInOperand(1) == + SpvDecorationHlslCounterBufferGOOGLE) { + to_remove.push_back(&inst); + } + break; + + default: + break; + } + } + + for (auto& inst : irContext->module()->extensions()) { + const char* ext_name = + reinterpret_cast(&inst.GetInOperand(0).words[0]); + if (0 == std::strcmp(ext_name, "SPV_GOOGLE_hlsl_functionality1")) { + to_remove.push_back(&inst); + } else if (!other_uses_for_decorate_string && + 0 == std::strcmp(ext_name, "SPV_GOOGLE_decorate_string")) { + to_remove.push_back(&inst); + } + } + + for (auto* inst : to_remove) { + modified = true; + irContext->KillInst(inst); + } + + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/strip_reflect_info_pass.h b/3rdparty/spirv-tools/source/opt/strip_reflect_info_pass.h new file mode 100644 index 000000000..b6e9f33fb --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/strip_reflect_info_pass.h @@ -0,0 +1,44 @@ +// Copyright (c) 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_STRIP_REFLECT_INFO_PASS_H_ +#define LIBSPIRV_OPT_STRIP_REFLECT_INFO_PASS_H_ + +#include "ir_context.h" +#include "module.h" +#include "pass.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class StripReflectInfoPass : public Pass { + public: + const char* name() const override { return "strip-reflect"; } + Status Process(ir::IRContext* irContext) override; + + // Return the mask of preserved Analyses. + ir::IRContext::Analysis GetPreservedAnalyses() override { + return ir::IRContext::kAnalysisInstrToBlockMapping | + ir::IRContext::kAnalysisCombinators | ir::IRContext::kAnalysisCFG | + ir::IRContext::kAnalysisDominatorAnalysis | + ir::IRContext::kAnalysisLoopAnalysis | + ir::IRContext::kAnalysisNameMap; + } +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_STRIP_REFLECT_INFO_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/tree_iterator.h b/3rdparty/spirv-tools/source/opt/tree_iterator.h new file mode 100644 index 000000000..ba724dfa5 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/tree_iterator.h @@ -0,0 +1,246 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_TREE_ITERATOR_H_ +#define LIBSPIRV_OPT_TREE_ITERATOR_H_ + +#include +#include +#include + +namespace spvtools { +namespace opt { + +// Helper class to iterate over a tree in a depth first order. +// The class assumes the data structure is a tree, tree node type implements a +// forward iterator. +// At each step, the iterator holds the pointer to the current node and state of +// the walk. +// The state is recorded by stacking the iteration position of the node +// children. To move to the next node, the iterator: +// - Looks at the top of the stack; +// - Sets the node behind the iterator as the current node; +// - Increments the iterator if it has more children to visit, pops otherwise; +// - If the current node has children, the children iterator is pushed into the +// stack. +template +class TreeDFIterator { + static_assert(!std::is_pointer::value && + !std::is_reference::value, + "NodeTy should be a class"); + // Type alias to keep track of the const qualifier. + using NodeIterator = + typename std::conditional::value, + typename NodeTy::const_iterator, + typename NodeTy::iterator>::type; + + // Type alias to keep track of the const qualifier. + using NodePtr = NodeTy*; + + public: + // Standard iterator interface. + using reference = NodeTy&; + using value_type = NodeTy; + + explicit inline TreeDFIterator(NodePtr top_node) : current_(top_node) { + if (current_ && current_->begin() != current_->end()) + parent_iterators_.emplace(make_pair(current_, current_->begin())); + } + + // end() iterator. + inline TreeDFIterator() : TreeDFIterator(nullptr) {} + + bool operator==(const TreeDFIterator& x) const { + return current_ == x.current_; + } + + bool operator!=(const TreeDFIterator& x) const { return !(*this == x); } + + reference operator*() const { return *current_; } + + NodePtr operator->() const { return current_; } + + TreeDFIterator& operator++() { + MoveToNextNode(); + return *this; + } + + TreeDFIterator operator++(int) { + TreeDFIterator tmp = *this; + ++*this; + return tmp; + } + + private: + // Moves the iterator to the next node in the tree. + // If we are at the end, do nothing, otherwise + // if our current node has children, use the children iterator and push the + // current node into the stack. + // If we reach the end of the local iterator, pop it. + inline void MoveToNextNode() { + if (!current_) return; + if (parent_iterators_.empty()) { + current_ = nullptr; + return; + } + std::pair& next_it = parent_iterators_.top(); + // Set the new node. + current_ = *next_it.second; + // Update the iterator for the next child. + ++next_it.second; + // If we finished with node, pop it. + if (next_it.first->end() == next_it.second) parent_iterators_.pop(); + // If our current node is not a leaf, store the iteration state for later. + if (current_->begin() != current_->end()) + parent_iterators_.emplace(make_pair(current_, current_->begin())); + } + + // The current node of the tree. + NodePtr current_; + // State of the tree walk: each pair contains the parent node (which has been + // already visited) and the iterator of the next children to visit. + // When all the children has been visited, we pop the entry, get the next + // child and push back the pair if the children iterator is not end(). + std::stack> parent_iterators_; +}; + +// Helper class to iterate over a tree in a depth first post-order. +// The class assumes the data structure is a tree, tree node type implements a +// forward iterator. +// At each step, the iterator holds the pointer to the current node and state of +// the walk. +// The state is recorded by stacking the iteration position of the node +// children. To move to the next node, the iterator: +// - Looks at the top of the stack; +// - If the children iterator has reach the end, then the node become the +// current one and we pop the stack; +// - Otherwise, we save the child and increment the iterator; +// - We walk the child sub-tree until we find a leaf, stacking all non-leaves +// states (pair of node pointer and child iterator) as we walk it. +template +class PostOrderTreeDFIterator { + static_assert(!std::is_pointer::value && + !std::is_reference::value, + "NodeTy should be a class"); + // Type alias to keep track of the const qualifier. + using NodeIterator = + typename std::conditional::value, + typename NodeTy::const_iterator, + typename NodeTy::iterator>::type; + + // Type alias to keep track of the const qualifier. + using NodePtr = NodeTy*; + + public: + // Standard iterator interface. + using reference = NodeTy&; + using value_type = NodeTy; + + static inline PostOrderTreeDFIterator begin(NodePtr top_node) { + return PostOrderTreeDFIterator(top_node); + } + + static inline PostOrderTreeDFIterator end(NodePtr sentinel_node) { + return PostOrderTreeDFIterator(sentinel_node, false); + } + + bool operator==(const PostOrderTreeDFIterator& x) const { + return current_ == x.current_; + } + + bool operator!=(const PostOrderTreeDFIterator& x) const { + return !(*this == x); + } + + reference operator*() const { return *current_; } + + NodePtr operator->() const { return current_; } + + PostOrderTreeDFIterator& operator++() { + MoveToNextNode(); + return *this; + } + + PostOrderTreeDFIterator operator++(int) { + PostOrderTreeDFIterator tmp = *this; + ++*this; + return tmp; + } + + private: + explicit inline PostOrderTreeDFIterator(NodePtr top_node) + : current_(top_node) { + if (current_) WalkToLeaf(); + } + + // Constructor for the "end()" iterator. + // |end_sentinel| is the value that acts as end value (can be null). The bool + // parameters is to distinguish from the start() Ctor. + inline PostOrderTreeDFIterator(NodePtr sentinel_node, bool) + : current_(sentinel_node) {} + + // Moves the iterator to the next node in the tree. + // If we are at the end, do nothing, otherwise + // if our current node has children, use the children iterator and push the + // current node into the stack. + // If we reach the end of the local iterator, pop it. + inline void MoveToNextNode() { + if (!current_) return; + if (parent_iterators_.empty()) { + current_ = nullptr; + return; + } + std::pair& next_it = parent_iterators_.top(); + // If we visited all children, the current node is the top of the stack. + if (next_it.second == next_it.first->end()) { + // Set the new node. + current_ = next_it.first; + parent_iterators_.pop(); + return; + } + // We have more children to visit, set the current node to the first child + // and dive to leaf. + current_ = *next_it.second; + // Update the iterator for the next child (avoid unneeded pop). + ++next_it.second; + WalkToLeaf(); + } + + // Moves the iterator to the next node in the tree. + // If we are at the end, do nothing, otherwise + // if our current node has children, use the children iterator and push the + // current node into the stack. + // If we reach the end of the local iterator, pop it. + inline void WalkToLeaf() { + while (current_->begin() != current_->end()) { + NodeIterator next = ++current_->begin(); + parent_iterators_.emplace(make_pair(current_, next)); + // Set the first child as the new node. + current_ = *current_->begin(); + } + } + + // The current node of the tree. + NodePtr current_; + // State of the tree walk: each pair contains the parent node and the iterator + // of the next children to visit. + // When all the children has been visited, we pop the first entry and the + // parent node become the current node. + std::stack> parent_iterators_; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_TREE_ITERATOR_H_ diff --git a/3rdparty/spirv-tools/source/opt/type_manager.cpp b/3rdparty/spirv-tools/source/opt/type_manager.cpp new file mode 100644 index 000000000..9e2cd8652 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/type_manager.cpp @@ -0,0 +1,695 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "type_manager.h" + +#include +#include +#include + +#include "ir_context.h" +#include "log.h" +#include "make_unique.h" +#include "reflect.h" + +namespace { +const int kSpvTypePointerStorageClass = 1; +const int kSpvTypePointerTypeIdInIdx = 2; +} // namespace + +namespace spvtools { +namespace opt { +namespace analysis { + +TypeManager::TypeManager(const MessageConsumer& consumer, + spvtools::ir::IRContext* c) + : consumer_(consumer), context_(c) { + AnalyzeTypes(*c->module()); +} + +Type* TypeManager::GetType(uint32_t id) const { + auto iter = id_to_type_.find(id); + if (iter != id_to_type_.end()) return (*iter).second; + return nullptr; +} + +std::pair> TypeManager::GetTypeAndPointerType( + uint32_t id, SpvStorageClass sc) const { + Type* type = GetType(id); + if (type) { + return std::make_pair(type, MakeUnique(type, sc)); + } else { + return std::make_pair(type, std::unique_ptr()); + } +} + +uint32_t TypeManager::GetId(const Type* type) const { + auto iter = type_to_id_.find(type); + if (iter != type_to_id_.end()) return (*iter).second; + return 0; +} + +ForwardPointer* TypeManager::GetForwardPointer(uint32_t index) const { + if (index >= forward_pointers_.size()) return nullptr; + return forward_pointers_.at(index).get(); +} + +void TypeManager::AnalyzeTypes(const spvtools::ir::Module& module) { + for (const auto* inst : module.GetTypes()) RecordIfTypeDefinition(*inst); +} + +void TypeManager::RemoveId(uint32_t id) { + auto iter = id_to_type_.find(id); + if (iter == id_to_type_.end()) return; + + auto& type = iter->second; + if (!type->IsUniqueType(true)) { + auto tIter = type_to_id_.find(type); + if (tIter != type_to_id_.end() && tIter->second == id) { + // |type| currently maps to |id|. + // Search for an equivalent type to re-map. + bool found = false; + for (auto& pair : id_to_type_) { + if (pair.first != id && *pair.second == *type) { + // Equivalent ambiguous type, re-map type. + type_to_id_.erase(type); + type_to_id_[pair.second] = pair.first; + found = true; + break; + } + } + // No equivalent ambiguous type, remove mapping. + if (!found) type_to_id_.erase(tIter); + } + } else { + // Unique type, so just erase the entry. + type_to_id_.erase(type); + } + + // Erase the entry for |id|. + id_to_type_.erase(iter); +} + +uint32_t TypeManager::GetTypeInstruction(const Type* type) { + uint32_t id = GetId(type); + if (id != 0) return id; + + std::unique_ptr typeInst; + id = context()->TakeNextId(); + RegisterType(id, *type); + switch (type->kind()) { +#define DefineParameterlessCase(kind) \ + case Type::k##kind: \ + typeInst.reset(new ir::Instruction(context(), SpvOpType##kind, 0, id, \ + std::initializer_list{})); \ + break; + DefineParameterlessCase(Void); + DefineParameterlessCase(Bool); + DefineParameterlessCase(Sampler); + DefineParameterlessCase(Event); + DefineParameterlessCase(DeviceEvent); + DefineParameterlessCase(ReserveId); + DefineParameterlessCase(Queue); + DefineParameterlessCase(PipeStorage); + DefineParameterlessCase(NamedBarrier); +#undef DefineParameterlessCase + case Type::kInteger: + typeInst.reset(new ir::Instruction( + context(), SpvOpTypeInt, 0, id, + std::initializer_list{ + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {type->AsInteger()->width()}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, + {(type->AsInteger()->IsSigned() ? 1u : 0u)}}})); + break; + case Type::kFloat: + typeInst.reset(new ir::Instruction( + context(), SpvOpTypeFloat, 0, id, + std::initializer_list{ + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {type->AsFloat()->width()}}})); + break; + case Type::kVector: { + uint32_t subtype = GetTypeInstruction(type->AsVector()->element_type()); + typeInst.reset( + new ir::Instruction(context(), SpvOpTypeVector, 0, id, + std::initializer_list{ + {SPV_OPERAND_TYPE_ID, {subtype}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, + {type->AsVector()->element_count()}}})); + break; + } + case Type::kMatrix: { + uint32_t subtype = GetTypeInstruction(type->AsMatrix()->element_type()); + typeInst.reset( + new ir::Instruction(context(), SpvOpTypeMatrix, 0, id, + std::initializer_list{ + {SPV_OPERAND_TYPE_ID, {subtype}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, + {type->AsMatrix()->element_count()}}})); + break; + } + case Type::kImage: { + const Image* image = type->AsImage(); + uint32_t subtype = GetTypeInstruction(image->sampled_type()); + typeInst.reset(new ir::Instruction( + context(), SpvOpTypeImage, 0, id, + std::initializer_list{ + {SPV_OPERAND_TYPE_ID, {subtype}}, + {SPV_OPERAND_TYPE_DIMENSIONALITY, + {static_cast(image->dim())}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {image->depth()}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, + {(image->is_arrayed() ? 1u : 0u)}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, + {(image->is_multisampled() ? 1u : 0u)}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {image->sampled()}}, + {SPV_OPERAND_TYPE_SAMPLER_IMAGE_FORMAT, + {static_cast(image->format())}}, + {SPV_OPERAND_TYPE_ACCESS_QUALIFIER, + {static_cast(image->access_qualifier())}}})); + break; + } + case Type::kSampledImage: { + uint32_t subtype = + GetTypeInstruction(type->AsSampledImage()->image_type()); + typeInst.reset( + new ir::Instruction(context(), SpvOpTypeSampledImage, 0, id, + std::initializer_list{ + {SPV_OPERAND_TYPE_ID, {subtype}}})); + break; + } + case Type::kArray: { + uint32_t subtype = GetTypeInstruction(type->AsArray()->element_type()); + typeInst.reset(new ir::Instruction( + context(), SpvOpTypeArray, 0, id, + std::initializer_list{ + {SPV_OPERAND_TYPE_ID, {subtype}}, + {SPV_OPERAND_TYPE_ID, {type->AsArray()->LengthId()}}})); + break; + } + case Type::kRuntimeArray: { + uint32_t subtype = + GetTypeInstruction(type->AsRuntimeArray()->element_type()); + typeInst.reset( + new ir::Instruction(context(), SpvOpTypeRuntimeArray, 0, id, + std::initializer_list{ + {SPV_OPERAND_TYPE_ID, {subtype}}})); + break; + } + case Type::kStruct: { + std::vector ops; + const Struct* structTy = type->AsStruct(); + for (auto ty : structTy->element_types()) { + ops.push_back( + ir::Operand(SPV_OPERAND_TYPE_ID, {GetTypeInstruction(ty)})); + } + typeInst.reset( + new ir::Instruction(context(), SpvOpTypeStruct, 0, id, ops)); + break; + } + case Type::kOpaque: { + const Opaque* opaque = type->AsOpaque(); + size_t size = opaque->name().size(); + // Convert to null-terminated packed UTF-8 string. + std::vector words(size / 4 + 1, 0); + char* dst = reinterpret_cast(words.data()); + strncpy(dst, opaque->name().c_str(), size); + typeInst.reset( + new ir::Instruction(context(), SpvOpTypeOpaque, 0, id, + std::initializer_list{ + {SPV_OPERAND_TYPE_LITERAL_STRING, words}})); + break; + } + case Type::kPointer: { + const Pointer* pointer = type->AsPointer(); + uint32_t subtype = GetTypeInstruction(pointer->pointee_type()); + typeInst.reset(new ir::Instruction( + context(), SpvOpTypePointer, 0, id, + std::initializer_list{ + {SPV_OPERAND_TYPE_STORAGE_CLASS, + {static_cast(pointer->storage_class())}}, + {SPV_OPERAND_TYPE_ID, {subtype}}})); + break; + } + case Type::kFunction: { + std::vector ops; + const Function* function = type->AsFunction(); + ops.push_back(ir::Operand(SPV_OPERAND_TYPE_ID, + {GetTypeInstruction(function->return_type())})); + for (auto ty : function->param_types()) { + ops.push_back( + ir::Operand(SPV_OPERAND_TYPE_ID, {GetTypeInstruction(ty)})); + } + typeInst.reset( + new ir::Instruction(context(), SpvOpTypeFunction, 0, id, ops)); + break; + } + case Type::kPipe: + typeInst.reset(new ir::Instruction( + context(), SpvOpTypePipe, 0, id, + std::initializer_list{ + {SPV_OPERAND_TYPE_ACCESS_QUALIFIER, + {static_cast(type->AsPipe()->access_qualifier())}}})); + break; + case Type::kForwardPointer: + typeInst.reset(new ir::Instruction( + context(), SpvOpTypeForwardPointer, 0, 0, + std::initializer_list{ + {SPV_OPERAND_TYPE_ID, {type->AsForwardPointer()->target_id()}}, + {SPV_OPERAND_TYPE_STORAGE_CLASS, + {static_cast( + type->AsForwardPointer()->storage_class())}}})); + break; + default: + assert(false && "Unexpected type"); + break; + } + context()->AddType(std::move(typeInst)); + context()->AnalyzeDefUse(&*--context()->types_values_end()); + AttachDecorations(id, type); + return id; +} + +uint32_t TypeManager::FindPointerToType(uint32_t type_id, + SpvStorageClass storage_class) { + opt::analysis::Type* pointeeTy = context()->get_type_mgr()->GetType(type_id); + opt::analysis::Pointer pointerTy(pointeeTy, storage_class); + if (type_id == context()->get_type_mgr()->GetId(pointeeTy)) { + // Non-ambiguous type. Get the pointer type through the type manager. + return context()->get_type_mgr()->GetTypeInstruction(&pointerTy); + } + + // Ambiguous type, do a linear search. + ir::Module::inst_iterator type_itr = + context()->module()->types_values_begin(); + for (; type_itr != context()->module()->types_values_end(); ++type_itr) { + const ir::Instruction* type_inst = &*type_itr; + if (type_inst->opcode() == SpvOpTypePointer && + type_inst->GetSingleWordOperand(kSpvTypePointerTypeIdInIdx) == + type_id && + type_inst->GetSingleWordOperand(kSpvTypePointerStorageClass) == + storage_class) + return type_inst->result_id(); + } + + // Must create the pointer type. + uint32_t resultId = context()->TakeNextId(); + std::unique_ptr type_inst(new ir::Instruction( + context(), SpvOpTypePointer, 0, resultId, + {{spv_operand_type_t::SPV_OPERAND_TYPE_STORAGE_CLASS, + {uint32_t(storage_class)}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {type_id}}})); + context()->AnalyzeDefUse(type_inst.get()); + context()->AddType(std::move(type_inst)); + context()->get_type_mgr()->RegisterType(resultId, pointerTy); + return resultId; +} + +void TypeManager::AttachDecorations(uint32_t id, const Type* type) { + for (auto vec : type->decorations()) { + CreateDecoration(id, vec); + } + if (const Struct* structTy = type->AsStruct()) { + for (auto pair : structTy->element_decorations()) { + uint32_t element = pair.first; + for (auto vec : pair.second) { + CreateDecoration(id, vec, element); + } + } + } +} + +void TypeManager::CreateDecoration(uint32_t target, + const std::vector& decoration, + uint32_t element) { + std::vector ops; + ops.push_back(ir::Operand(SPV_OPERAND_TYPE_ID, {target})); + if (element != 0) { + ops.push_back(ir::Operand(SPV_OPERAND_TYPE_LITERAL_INTEGER, {element})); + } + ops.push_back(ir::Operand(SPV_OPERAND_TYPE_DECORATION, {decoration[0]})); + for (size_t i = 1; i < decoration.size(); ++i) { + ops.push_back( + ir::Operand(SPV_OPERAND_TYPE_LITERAL_INTEGER, {decoration[i]})); + } + context()->AddAnnotationInst(MakeUnique( + context(), (element == 0 ? SpvOpDecorate : SpvOpMemberDecorate), 0, 0, + ops)); + ir::Instruction* inst = &*--context()->annotation_end(); + context()->get_def_use_mgr()->AnalyzeInstUse(inst); +} + +Type* TypeManager::RebuildType(const Type& type) { + // The comparison and hash on the type pool will avoid inserting the rebuilt + // type if an equivalent type already exists. The rebuilt type will be deleted + // when it goes out of scope at the end of the function in that case. Repeated + // insertions of the same Type will, at most, keep one corresponding object in + // the type pool. + std::unique_ptr rebuilt_ty; + switch (type.kind()) { +#define DefineNoSubtypeCase(kind) \ + case Type::k##kind: \ + rebuilt_ty.reset(type.Clone().release()); \ + break; + DefineNoSubtypeCase(Void); + DefineNoSubtypeCase(Bool); + DefineNoSubtypeCase(Integer); + DefineNoSubtypeCase(Float); + DefineNoSubtypeCase(Sampler); + DefineNoSubtypeCase(Opaque); + DefineNoSubtypeCase(Event); + DefineNoSubtypeCase(DeviceEvent); + DefineNoSubtypeCase(ReserveId); + DefineNoSubtypeCase(Queue); + DefineNoSubtypeCase(Pipe); + DefineNoSubtypeCase(PipeStorage); + DefineNoSubtypeCase(NamedBarrier); +#undef DefineNoSubtypeCase + case Type::kVector: { + const Vector* vec_ty = type.AsVector(); + const Type* ele_ty = vec_ty->element_type(); + rebuilt_ty.reset( + new Vector(RebuildType(*ele_ty), vec_ty->element_count())); + break; + } + case Type::kMatrix: { + const Matrix* mat_ty = type.AsMatrix(); + const Type* ele_ty = mat_ty->element_type(); + rebuilt_ty.reset( + new Matrix(RebuildType(*ele_ty), mat_ty->element_count())); + break; + } + case Type::kImage: { + const Image* image_ty = type.AsImage(); + const Type* ele_ty = image_ty->sampled_type(); + rebuilt_ty.reset(new Image(RebuildType(*ele_ty), image_ty->dim(), + image_ty->depth(), image_ty->is_arrayed(), + image_ty->is_multisampled(), + image_ty->sampled(), image_ty->format(), + image_ty->access_qualifier())); + break; + } + case Type::kSampledImage: { + const SampledImage* image_ty = type.AsSampledImage(); + const Type* ele_ty = image_ty->image_type(); + rebuilt_ty.reset( + + new SampledImage(RebuildType(*ele_ty))); + break; + } + case Type::kArray: { + const Array* array_ty = type.AsArray(); + const Type* ele_ty = array_ty->element_type(); + rebuilt_ty.reset(new Array(RebuildType(*ele_ty), array_ty->LengthId())); + break; + } + case Type::kRuntimeArray: { + const RuntimeArray* array_ty = type.AsRuntimeArray(); + const Type* ele_ty = array_ty->element_type(); + rebuilt_ty.reset(new RuntimeArray(RebuildType(*ele_ty))); + break; + } + case Type::kStruct: { + const Struct* struct_ty = type.AsStruct(); + std::vector subtypes; + subtypes.reserve(struct_ty->element_types().size()); + for (const auto* ele_ty : struct_ty->element_types()) { + subtypes.push_back(RebuildType(*ele_ty)); + } + rebuilt_ty.reset(new Struct(subtypes)); + Struct* rebuilt_struct = rebuilt_ty->AsStruct(); + for (auto pair : struct_ty->element_decorations()) { + uint32_t index = pair.first; + for (const auto& dec : pair.second) { + // Explicit copy intended. + std::vector copy(dec); + rebuilt_struct->AddMemberDecoration(index, std::move(copy)); + } + } + break; + } + case Type::kPointer: { + const Pointer* pointer_ty = type.AsPointer(); + const Type* ele_ty = pointer_ty->pointee_type(); + rebuilt_ty.reset( + new Pointer(RebuildType(*ele_ty), pointer_ty->storage_class())); + break; + } + case Type::kFunction: { + const Function* function_ty = type.AsFunction(); + const Type* ret_ty = function_ty->return_type(); + std::vector param_types; + param_types.reserve(function_ty->param_types().size()); + for (const auto* param_ty : function_ty->param_types()) { + param_types.push_back(RebuildType(*param_ty)); + } + rebuilt_ty.reset(new Function(RebuildType(*ret_ty), param_types)); + break; + } + case Type::kForwardPointer: { + const ForwardPointer* forward_ptr_ty = type.AsForwardPointer(); + rebuilt_ty.reset(new ForwardPointer(forward_ptr_ty->target_id(), + forward_ptr_ty->storage_class())); + const Pointer* target_ptr = forward_ptr_ty->target_pointer(); + if (target_ptr) { + rebuilt_ty->AsForwardPointer()->SetTargetPointer( + RebuildType(*target_ptr)->AsPointer()); + } + break; + } + default: + assert(false && "Unhandled type"); + return nullptr; + } + for (const auto& dec : type.decorations()) { + // Explicit copy intended. + std::vector copy(dec); + rebuilt_ty->AddDecoration(std::move(copy)); + } + + return type_pool_.insert(std::move(rebuilt_ty)).first->get(); +} + +void TypeManager::RegisterType(uint32_t id, const Type& type) { + // Rebuild |type| so it and all its constituent types are owned by the type + // pool. + Type* rebuilt = RebuildType(type); + assert(rebuilt->IsSame(&type)); + id_to_type_[id] = rebuilt; + if (GetId(rebuilt) == 0) { + type_to_id_[rebuilt] = id; + } +} + +Type* TypeManager::GetRegisteredType(const Type* type) { + uint32_t id = GetTypeInstruction(type); + return GetType(id); +} + +Type* TypeManager::RecordIfTypeDefinition( + const spvtools::ir::Instruction& inst) { + if (!spvtools::ir::IsTypeInst(inst.opcode())) return nullptr; + + Type* type = nullptr; + switch (inst.opcode()) { + case SpvOpTypeVoid: + type = new Void(); + break; + case SpvOpTypeBool: + type = new Bool(); + break; + case SpvOpTypeInt: + type = new Integer(inst.GetSingleWordInOperand(0), + inst.GetSingleWordInOperand(1)); + break; + case SpvOpTypeFloat: + type = new Float(inst.GetSingleWordInOperand(0)); + break; + case SpvOpTypeVector: + type = new Vector(GetType(inst.GetSingleWordInOperand(0)), + inst.GetSingleWordInOperand(1)); + break; + case SpvOpTypeMatrix: + type = new Matrix(GetType(inst.GetSingleWordInOperand(0)), + inst.GetSingleWordInOperand(1)); + break; + case SpvOpTypeImage: { + const SpvAccessQualifier access = + inst.NumInOperands() < 8 + ? SpvAccessQualifierReadOnly + : static_cast(inst.GetSingleWordInOperand(7)); + type = new Image( + GetType(inst.GetSingleWordInOperand(0)), + static_cast(inst.GetSingleWordInOperand(1)), + inst.GetSingleWordInOperand(2), inst.GetSingleWordInOperand(3) == 1, + inst.GetSingleWordInOperand(4) == 1, inst.GetSingleWordInOperand(5), + static_cast(inst.GetSingleWordInOperand(6)), access); + } break; + case SpvOpTypeSampler: + type = new Sampler(); + break; + case SpvOpTypeSampledImage: + type = new SampledImage(GetType(inst.GetSingleWordInOperand(0))); + break; + case SpvOpTypeArray: + type = new Array(GetType(inst.GetSingleWordInOperand(0)), + inst.GetSingleWordInOperand(1)); + break; + case SpvOpTypeRuntimeArray: + type = new RuntimeArray(GetType(inst.GetSingleWordInOperand(0))); + break; + case SpvOpTypeStruct: { + std::vector element_types; + for (uint32_t i = 0; i < inst.NumInOperands(); ++i) { + element_types.push_back(GetType(inst.GetSingleWordInOperand(i))); + } + type = new Struct(element_types); + } break; + case SpvOpTypeOpaque: { + const uint32_t* data = inst.GetInOperand(0).words.data(); + type = new Opaque(reinterpret_cast(data)); + } break; + case SpvOpTypePointer: { + auto* ptr = new Pointer( + GetType(inst.GetSingleWordInOperand(1)), + static_cast(inst.GetSingleWordInOperand(0))); + // Let's see if somebody forward references this pointer. + for (auto* fp : unresolved_forward_pointers_) { + if (fp->target_id() == inst.result_id()) { + fp->SetTargetPointer(ptr); + unresolved_forward_pointers_.erase(fp); + break; + } + } + type = ptr; + } break; + case SpvOpTypeFunction: { + Type* return_type = GetType(inst.GetSingleWordInOperand(0)); + std::vector param_types; + for (uint32_t i = 1; i < inst.NumInOperands(); ++i) { + param_types.push_back(GetType(inst.GetSingleWordInOperand(i))); + } + type = new Function(return_type, param_types); + } break; + case SpvOpTypeEvent: + type = new Event(); + break; + case SpvOpTypeDeviceEvent: + type = new DeviceEvent(); + break; + case SpvOpTypeReserveId: + type = new ReserveId(); + break; + case SpvOpTypeQueue: + type = new Queue(); + break; + case SpvOpTypePipe: + type = new Pipe( + static_cast(inst.GetSingleWordInOperand(0))); + break; + case SpvOpTypeForwardPointer: { + // Handling of forward pointers is different from the other types. + auto* fp = new ForwardPointer( + inst.GetSingleWordInOperand(0), + static_cast(inst.GetSingleWordInOperand(1))); + forward_pointers_.emplace_back(fp); + unresolved_forward_pointers_.insert(fp); + return fp; + } + case SpvOpTypePipeStorage: + type = new PipeStorage(); + break; + case SpvOpTypeNamedBarrier: + type = new NamedBarrier(); + break; + default: + SPIRV_UNIMPLEMENTED(consumer_, "unhandled type"); + break; + } + + uint32_t id = inst.result_id(); + if (id == 0) { + SPIRV_ASSERT(consumer_, inst.opcode() == SpvOpTypeForwardPointer, + "instruction without result id found"); + } else { + SPIRV_ASSERT(consumer_, type != nullptr, + "type should not be nullptr at this point"); + std::vector decorations = + context()->get_decoration_mgr()->GetDecorationsFor(id, true); + for (auto dec : decorations) { + AttachDecoration(*dec, type); + } + std::unique_ptr unique(type); + auto pair = type_pool_.insert(std::move(unique)); + id_to_type_[id] = pair.first->get(); + type_to_id_[pair.first->get()] = id; + } + return type; +} + +void TypeManager::AttachDecoration(const ir::Instruction& inst, Type* type) { + const SpvOp opcode = inst.opcode(); + if (!ir::IsAnnotationInst(opcode)) return; + + switch (opcode) { + case SpvOpDecorate: { + const auto count = inst.NumOperands(); + std::vector data; + for (uint32_t i = 1; i < count; ++i) { + data.push_back(inst.GetSingleWordOperand(i)); + } + type->AddDecoration(std::move(data)); + } break; + case SpvOpMemberDecorate: { + const auto count = inst.NumOperands(); + const uint32_t index = inst.GetSingleWordOperand(1); + std::vector data; + for (uint32_t i = 2; i < count; ++i) { + data.push_back(inst.GetSingleWordOperand(i)); + } + if (Struct* st = type->AsStruct()) { + st->AddMemberDecoration(index, std::move(data)); + } else { + SPIRV_UNIMPLEMENTED(consumer_, "OpMemberDecorate non-struct type"); + } + } break; + default: + SPIRV_UNREACHABLE(consumer_); + break; + } +} + +const Type* TypeManager::GetMemberType( + const Type* parent_type, const std::vector& access_chain) { + for (uint32_t element_index : access_chain) { + if (const analysis::Struct* struct_type = parent_type->AsStruct()) { + parent_type = struct_type->element_types()[element_index]; + } else if (const analysis::Array* array_type = parent_type->AsArray()) { + parent_type = array_type->element_type(); + } else if (const analysis::RuntimeArray* runtime_array_type = + parent_type->AsRuntimeArray()) { + parent_type = runtime_array_type->element_type(); + } else if (const analysis::Vector* vector_type = parent_type->AsVector()) { + parent_type = vector_type->element_type(); + } else if (const analysis::Matrix* matrix_type = parent_type->AsMatrix()) { + parent_type = matrix_type->element_type(); + } else { + assert(false && "Trying to get a member of a type without members."); + } + } + return parent_type; +} + +} // namespace analysis +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/type_manager.h b/3rdparty/spirv-tools/source/opt/type_manager.h new file mode 100644 index 000000000..2020d8b9a --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/type_manager.h @@ -0,0 +1,195 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_TYPE_MANAGER_H_ +#define LIBSPIRV_OPT_TYPE_MANAGER_H_ + +#include +#include +#include +#include + +#include "module.h" +#include "spirv-tools/libspirv.hpp" +#include "types.h" + +namespace spvtools { +namespace ir { +class IRContext; +} // namespace ir +namespace opt { +namespace analysis { + +// Hashing functor. +// +// All type pointers must be non-null. +struct HashTypePointer { + size_t operator()(const Type* type) const { + assert(type); + return type->HashValue(); + } +}; +struct HashTypeUniquePointer { + size_t operator()(const std::unique_ptr& type) const { + assert(type); + return type->HashValue(); + } +}; + +// Equality functor. +// +// Checks if two types pointers are the same type. +// +// All type pointers must be non-null. +struct CompareTypePointers { + bool operator()(const Type* lhs, const Type* rhs) const { + assert(lhs && rhs); + return lhs->IsSame(rhs); + } +}; +struct CompareTypeUniquePointers { + bool operator()(const std::unique_ptr& lhs, + const std::unique_ptr& rhs) const { + assert(lhs && rhs); + return lhs->IsSame(rhs.get()); + } +}; + +// A class for managing the SPIR-V type hierarchy. +class TypeManager { + public: + using IdToTypeMap = std::unordered_map; + + // Constructs a type manager from the given |module|. All internal messages + // will be communicated to the outside via the given message |consumer|. + // This instance only keeps a reference to the |consumer|, so the |consumer| + // should outlive this instance. + TypeManager(const MessageConsumer& consumer, spvtools::ir::IRContext* c); + + TypeManager(const TypeManager&) = delete; + TypeManager(TypeManager&&) = delete; + TypeManager& operator=(const TypeManager&) = delete; + TypeManager& operator=(TypeManager&&) = delete; + + // Returns the type for the given type |id|. Returns nullptr if the given |id| + // does not define a type. + Type* GetType(uint32_t id) const; + // Returns the id for the given |type|. Returns 0 if can not find the given + // |type|. + uint32_t GetId(const Type* type) const; + // Returns the number of types hold in this manager. + size_t NumTypes() const { return id_to_type_.size(); } + // Iterators for all types contained in this manager. + IdToTypeMap::const_iterator begin() const { return id_to_type_.cbegin(); } + IdToTypeMap::const_iterator end() const { return id_to_type_.cend(); } + + // Returns the forward pointer type at the given |index|. + ForwardPointer* GetForwardPointer(uint32_t index) const; + // Returns the number of forward pointer types hold in this manager. + size_t NumForwardPointers() const { return forward_pointers_.size(); } + + // Returns a pair of the type and pointer to the type in |sc|. + // + // |id| must be a registered type. + std::pair> GetTypeAndPointerType( + uint32_t id, SpvStorageClass sc) const; + + // Returns an id for a declaration representing |type|. + // + // If |type| is registered, then the registered id is returned. Otherwise, + // this function recursively adds type and annotation instructions as + // necessary to fully define |type|. + uint32_t GetTypeInstruction(const Type* type); + + // Find pointer to type and storage in module, return its resultId. If it is + // not found, a new type is created, and its id is returned. + uint32_t FindPointerToType(uint32_t type_id, SpvStorageClass storage_class); + + // Registers |id| to |type|. + // + // If GetId(|type|) already returns a non-zero id, that mapping will be + // unchanged. + void RegisterType(uint32_t id, const Type& type); + + Type* GetRegisteredType(const Type* type); + + // Removes knowledge of |id| from the manager. + // + // If |id| is an ambiguous type the multiple ids may be registered to |id|'s + // type (e.g. %struct1 and %struct1 might hash to the same type). In that + // case, calling GetId() with |id|'s type will return another suitable id + // defining that type. + void RemoveId(uint32_t id); + + // Returns the type of the member of |parent_type| that is identified by + // |access_chain|. The vector |access_chain| is a series of integers that are + // used to pick members as in the |OpCompositeExtract| instructions. If you + // want a member of an array, vector, or matrix that does not have a constant + // index, you can use 0 in that position. All elements have the same type. + const Type* GetMemberType(const Type* parent_type, + const std::vector& access_chain); + + private: + using TypeToIdMap = std::unordered_map; + using TypePool = + std::unordered_set, HashTypeUniquePointer, + CompareTypeUniquePointers>; + using ForwardPointerVector = std::vector>; + + // Analyzes the types and decorations on types in the given |module|. + void AnalyzeTypes(const spvtools::ir::Module& module); + + spvtools::ir::IRContext* context() { return context_; } + + // Attachs the decorations on |type| to |id|. + void AttachDecorations(uint32_t id, const Type* type); + + // Create the annotation instruction. + // + // If |element| is zero, an OpDecorate is created, other an OpMemberDecorate + // is created. The annotation is registered with the DefUseManager and the + // DecorationManager. + void CreateDecoration(uint32_t id, const std::vector& decoration, + uint32_t element = 0); + + // Creates and returns a type from the given SPIR-V |inst|. Returns nullptr if + // the given instruction is not for defining a type. + Type* RecordIfTypeDefinition(const spvtools::ir::Instruction& inst); + // Attaches the decoration encoded in |inst| to |type|. Does nothing if the + // given instruction is not a decoration instruction. Assumes the target is + // |type| (e.g. should be called in loop of |type|'s decorations). + void AttachDecoration(const spvtools::ir::Instruction& inst, Type* type); + + // Returns an equivalent pointer to |type| built in terms of pointers owned by + // |type_pool_|. For example, if |type| is a vec3 of bool, it will be rebuilt + // replacing the bool subtype with one owned by |type_pool_|. + Type* RebuildType(const Type& type); + + const MessageConsumer& consumer_; // Message consumer. + spvtools::ir::IRContext* context_; + IdToTypeMap id_to_type_; // Mapping from ids to their type representations. + TypeToIdMap type_to_id_; // Mapping from types to their defining ids. + TypePool type_pool_; // Memory owner of type pointers. + ForwardPointerVector forward_pointers_; // All forward pointer declarations. + // All unresolved forward pointer declarations. + // Refers the contents in the above vector. + std::unordered_set unresolved_forward_pointers_; +}; + +} // namespace analysis +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_TYPE_MANAGER_H_ diff --git a/3rdparty/spirv-tools/source/opt/types.cpp b/3rdparty/spirv-tools/source/opt/types.cpp new file mode 100644 index 000000000..4838f8e50 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/types.cpp @@ -0,0 +1,588 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include "types.h" + +namespace spvtools { +namespace opt { +namespace analysis { + +using U32VecVec = std::vector>; + +namespace { + +// Returns true if the two vector of vectors are identical. +bool CompareTwoVectors(const U32VecVec a, const U32VecVec b) { + const auto size = a.size(); + if (size != b.size()) return false; + + if (size == 0) return true; + if (size == 1) return a.front() == b.front(); + + std::vector*> a_ptrs, b_ptrs; + a_ptrs.reserve(size); + a_ptrs.reserve(size); + for (uint32_t i = 0; i < size; ++i) { + a_ptrs.push_back(&a[i]); + b_ptrs.push_back(&b[i]); + } + + const auto cmp = [](const std::vector* m, + const std::vector* n) { + return m->front() < n->front(); + }; + + std::sort(a_ptrs.begin(), a_ptrs.end(), cmp); + std::sort(b_ptrs.begin(), b_ptrs.end(), cmp); + + for (uint32_t i = 0; i < size; ++i) { + if (*a_ptrs[i] != *b_ptrs[i]) return false; + } + return true; +} + +} // anonymous namespace + +std::string Type::GetDecorationStr() const { + std::ostringstream oss; + oss << "[["; + for (const auto& decoration : decorations_) { + oss << "("; + for (size_t i = 0; i < decoration.size(); ++i) { + oss << (i > 0 ? ", " : ""); + oss << decoration.at(i); + } + oss << ")"; + } + oss << "]]"; + return oss.str(); +} + +bool Type::HasSameDecorations(const Type* that) const { + return CompareTwoVectors(decorations_, that->decorations_); +} + +bool Type::IsUniqueType(bool allowVariablePointers) const { + switch (kind_) { + case kPointer: + return !allowVariablePointers; + case kStruct: + case kArray: + case kRuntimeArray: + return false; + default: + return true; + } +} + +std::unique_ptr Type::Clone() const { + std::unique_ptr type; + switch (kind_) { +#define DeclareKindCase(kind) \ + case k##kind: \ + type.reset(new kind(*this->As##kind())); \ + break; + DeclareKindCase(Void); + DeclareKindCase(Bool); + DeclareKindCase(Integer); + DeclareKindCase(Float); + DeclareKindCase(Vector); + DeclareKindCase(Matrix); + DeclareKindCase(Image); + DeclareKindCase(Sampler); + DeclareKindCase(SampledImage); + DeclareKindCase(Array); + DeclareKindCase(RuntimeArray); + DeclareKindCase(Struct); + DeclareKindCase(Opaque); + DeclareKindCase(Pointer); + DeclareKindCase(Function); + DeclareKindCase(Event); + DeclareKindCase(DeviceEvent); + DeclareKindCase(ReserveId); + DeclareKindCase(Queue); + DeclareKindCase(Pipe); + DeclareKindCase(ForwardPointer); + DeclareKindCase(PipeStorage); + DeclareKindCase(NamedBarrier); +#undef DeclareKindCase + default: + assert(false && "Unhandled type"); + } + return type; +} + +std::unique_ptr Type::RemoveDecorations() const { + std::unique_ptr type(Clone()); + type->ClearDecorations(); + return type; +} + +bool Type::operator==(const Type& other) const { + if (kind_ != other.kind_) return false; + + switch (kind_) { +#define DeclareKindCase(kind) \ + case k##kind: \ + return As##kind()->IsSame(&other); + DeclareKindCase(Void); + DeclareKindCase(Bool); + DeclareKindCase(Integer); + DeclareKindCase(Float); + DeclareKindCase(Vector); + DeclareKindCase(Matrix); + DeclareKindCase(Image); + DeclareKindCase(Sampler); + DeclareKindCase(SampledImage); + DeclareKindCase(Array); + DeclareKindCase(RuntimeArray); + DeclareKindCase(Struct); + DeclareKindCase(Opaque); + DeclareKindCase(Pointer); + DeclareKindCase(Function); + DeclareKindCase(Event); + DeclareKindCase(DeviceEvent); + DeclareKindCase(ReserveId); + DeclareKindCase(Queue); + DeclareKindCase(Pipe); + DeclareKindCase(ForwardPointer); + DeclareKindCase(PipeStorage); + DeclareKindCase(NamedBarrier); +#undef DeclareKindCase + default: + assert(false && "Unhandled type"); + return false; + } +} + +void Type::GetHashWords(std::vector* words) const { + words->push_back(kind_); + for (const auto& d : decorations_) { + for (auto w : d) { + words->push_back(w); + } + } + + switch (kind_) { +#define DeclareKindCase(type) \ + case k##type: \ + As##type()->GetExtraHashWords(words); \ + break; + DeclareKindCase(Void); + DeclareKindCase(Bool); + DeclareKindCase(Integer); + DeclareKindCase(Float); + DeclareKindCase(Vector); + DeclareKindCase(Matrix); + DeclareKindCase(Image); + DeclareKindCase(Sampler); + DeclareKindCase(SampledImage); + DeclareKindCase(Array); + DeclareKindCase(RuntimeArray); + DeclareKindCase(Struct); + DeclareKindCase(Opaque); + DeclareKindCase(Pointer); + DeclareKindCase(Function); + DeclareKindCase(Event); + DeclareKindCase(DeviceEvent); + DeclareKindCase(ReserveId); + DeclareKindCase(Queue); + DeclareKindCase(Pipe); + DeclareKindCase(ForwardPointer); + DeclareKindCase(PipeStorage); + DeclareKindCase(NamedBarrier); +#undef DeclareKindCase + default: + assert(false && "Unhandled type"); + break; + } +} + +size_t Type::HashValue() const { + std::u32string h; + std::vector words; + GetHashWords(&words); + for (auto w : words) { + h.push_back(w); + } + + return std::hash()(h); +} + +bool Integer::IsSame(const Type* that) const { + const Integer* it = that->AsInteger(); + return it && width_ == it->width_ && signed_ == it->signed_ && + HasSameDecorations(that); +} + +std::string Integer::str() const { + std::ostringstream oss; + oss << (signed_ ? "s" : "u") << "int" << width_; + return oss.str(); +} + +void Integer::GetExtraHashWords(std::vector* words) const { + words->push_back(width_); + words->push_back(signed_); +} + +bool Float::IsSame(const Type* that) const { + const Float* ft = that->AsFloat(); + return ft && width_ == ft->width_ && HasSameDecorations(that); +} + +std::string Float::str() const { + std::ostringstream oss; + oss << "float" << width_; + return oss.str(); +} + +void Float::GetExtraHashWords(std::vector* words) const { + words->push_back(width_); +} + +Vector::Vector(Type* type, uint32_t count) + : Type(kVector), element_type_(type), count_(count) { + assert(type->AsBool() || type->AsInteger() || type->AsFloat()); +} + +bool Vector::IsSame(const Type* that) const { + const Vector* vt = that->AsVector(); + if (!vt) return false; + return count_ == vt->count_ && element_type_->IsSame(vt->element_type_) && + HasSameDecorations(that); +} + +std::string Vector::str() const { + std::ostringstream oss; + oss << "<" << element_type_->str() << ", " << count_ << ">"; + return oss.str(); +} + +void Vector::GetExtraHashWords(std::vector* words) const { + element_type_->GetHashWords(words); + words->push_back(count_); +} + +Matrix::Matrix(Type* type, uint32_t count) + : Type(kMatrix), element_type_(type), count_(count) { + assert(type->AsVector()); +} + +bool Matrix::IsSame(const Type* that) const { + const Matrix* mt = that->AsMatrix(); + if (!mt) return false; + return count_ == mt->count_ && element_type_->IsSame(mt->element_type_) && + HasSameDecorations(that); +} + +std::string Matrix::str() const { + std::ostringstream oss; + oss << "<" << element_type_->str() << ", " << count_ << ">"; + return oss.str(); +} + +void Matrix::GetExtraHashWords(std::vector* words) const { + element_type_->GetHashWords(words); + words->push_back(count_); +} + +Image::Image(Type* type, SpvDim dimen, uint32_t d, bool array, bool multisample, + uint32_t sampling, SpvImageFormat f, SpvAccessQualifier qualifier) + : Type(kImage), + sampled_type_(type), + dim_(dimen), + depth_(d), + arrayed_(array), + ms_(multisample), + sampled_(sampling), + format_(f), + access_qualifier_(qualifier) { + // TODO(antiagainst): check sampled_type +} + +bool Image::IsSame(const Type* that) const { + const Image* it = that->AsImage(); + if (!it) return false; + return dim_ == it->dim_ && depth_ == it->depth_ && arrayed_ == it->arrayed_ && + ms_ == it->ms_ && sampled_ == it->sampled_ && format_ == it->format_ && + access_qualifier_ == it->access_qualifier_ && + sampled_type_->IsSame(it->sampled_type_) && HasSameDecorations(that); +} + +std::string Image::str() const { + std::ostringstream oss; + oss << "image(" << sampled_type_->str() << ", " << dim_ << ", " << depth_ + << ", " << arrayed_ << ", " << ms_ << ", " << sampled_ << ", " << format_ + << ", " << access_qualifier_ << ")"; + return oss.str(); +} + +void Image::GetExtraHashWords(std::vector* words) const { + sampled_type_->GetHashWords(words); + words->push_back(dim_); + words->push_back(depth_); + words->push_back(arrayed_); + words->push_back(ms_); + words->push_back(sampled_); + words->push_back(format_); + words->push_back(access_qualifier_); +} + +bool SampledImage::IsSame(const Type* that) const { + const SampledImage* sit = that->AsSampledImage(); + if (!sit) return false; + return image_type_->IsSame(sit->image_type_) && HasSameDecorations(that); +} + +std::string SampledImage::str() const { + std::ostringstream oss; + oss << "sampled_image(" << image_type_->str() << ")"; + return oss.str(); +} + +void SampledImage::GetExtraHashWords(std::vector* words) const { + image_type_->GetHashWords(words); +} + +Array::Array(Type* type, uint32_t length_id) + : Type(kArray), element_type_(type), length_id_(length_id) { + assert(!type->AsVoid()); +} + +bool Array::IsSame(const Type* that) const { + const Array* at = that->AsArray(); + if (!at) return false; + return length_id_ == at->length_id_ && + element_type_->IsSame(at->element_type_) && HasSameDecorations(that); +} + +std::string Array::str() const { + std::ostringstream oss; + oss << "[" << element_type_->str() << ", id(" << length_id_ << ")]"; + return oss.str(); +} + +void Array::GetExtraHashWords(std::vector* words) const { + element_type_->GetHashWords(words); + words->push_back(length_id_); +} + +RuntimeArray::RuntimeArray(Type* type) + : Type(kRuntimeArray), element_type_(type) { + assert(!type->AsVoid()); +} + +bool RuntimeArray::IsSame(const Type* that) const { + const RuntimeArray* rat = that->AsRuntimeArray(); + if (!rat) return false; + return element_type_->IsSame(rat->element_type_) && HasSameDecorations(that); +} + +std::string RuntimeArray::str() const { + std::ostringstream oss; + oss << "[" << element_type_->str() << "]"; + return oss.str(); +} + +void RuntimeArray::GetExtraHashWords(std::vector* words) const { + element_type_->GetHashWords(words); +} + +Struct::Struct(const std::vector& types) + : Type(kStruct), element_types_(types) { + for (const auto* t : types) { + (void)t; + assert(!t->AsVoid()); + } +} + +void Struct::AddMemberDecoration(uint32_t index, + std::vector&& decoration) { + if (index >= element_types_.size()) { + assert(0 && "index out of bound"); + return; + } + + element_decorations_[index].push_back(std::move(decoration)); +} + +bool Struct::IsSame(const Type* that) const { + const Struct* st = that->AsStruct(); + if (!st) return false; + if (element_types_.size() != st->element_types_.size()) return false; + const auto size = element_decorations_.size(); + if (size != st->element_decorations_.size()) return false; + if (!HasSameDecorations(that)) return false; + + for (size_t i = 0; i < element_types_.size(); ++i) { + if (!element_types_[i]->IsSame(st->element_types_[i])) return false; + } + for (const auto& p : element_decorations_) { + if (st->element_decorations_.count(p.first) == 0) return false; + if (!CompareTwoVectors(p.second, st->element_decorations_.at(p.first))) + return false; + } + return true; +} + +std::string Struct::str() const { + std::ostringstream oss; + oss << "{"; + const size_t count = element_types_.size(); + for (size_t i = 0; i < count; ++i) { + oss << element_types_[i]->str(); + if (i + 1 != count) oss << ", "; + } + oss << "}"; + return oss.str(); +} + +void Struct::GetExtraHashWords(std::vector* words) const { + for (auto* t : element_types_) { + t->GetHashWords(words); + } + for (const auto& pair : element_decorations_) { + words->push_back(pair.first); + for (const auto& d : pair.second) { + for (auto w : d) { + words->push_back(w); + } + } + } +} + +bool Opaque::IsSame(const Type* that) const { + const Opaque* ot = that->AsOpaque(); + if (!ot) return false; + return name_ == ot->name_ && HasSameDecorations(that); +} + +std::string Opaque::str() const { + std::ostringstream oss; + oss << "opaque('" << name_ << "')"; + return oss.str(); +} + +void Opaque::GetExtraHashWords(std::vector* words) const { + for (auto c : name_) { + words->push_back(static_cast(c)); + } +} + +Pointer::Pointer(const Type* type, SpvStorageClass sc) + : Type(kPointer), pointee_type_(type), storage_class_(sc) {} + +bool Pointer::IsSame(const Type* that) const { + const Pointer* pt = that->AsPointer(); + if (!pt) return false; + if (storage_class_ != pt->storage_class_) return false; + if (!pointee_type_->IsSame(pt->pointee_type_)) return false; + return HasSameDecorations(that); +} + +std::string Pointer::str() const { return pointee_type_->str() + "*"; } + +void Pointer::GetExtraHashWords(std::vector* words) const { + pointee_type_->GetHashWords(words); + words->push_back(storage_class_); +} + +Function::Function(Type* ret_type, const std::vector& params) + : Type(kFunction), return_type_(ret_type), param_types_(params) { + for (auto* t : params) { + (void)t; + assert(!t->AsVoid()); + } +} + +bool Function::IsSame(const Type* that) const { + const Function* ft = that->AsFunction(); + if (!ft) return false; + if (!return_type_->IsSame(ft->return_type_)) return false; + if (param_types_.size() != ft->param_types_.size()) return false; + for (size_t i = 0; i < param_types_.size(); ++i) { + if (!param_types_[i]->IsSame(ft->param_types_[i])) return false; + } + return HasSameDecorations(that); +} + +std::string Function::str() const { + std::ostringstream oss; + const size_t count = param_types_.size(); + oss << "("; + for (size_t i = 0; i < count; ++i) { + oss << param_types_[i]->str(); + if (i + 1 != count) oss << ", "; + } + oss << ") -> " << return_type_->str(); + return oss.str(); +} + +void Function::GetExtraHashWords(std::vector* words) const { + return_type_->GetHashWords(words); + for (const auto* t : param_types_) { + t->GetHashWords(words); + } +} + +bool Pipe::IsSame(const Type* that) const { + const Pipe* pt = that->AsPipe(); + if (!pt) return false; + return access_qualifier_ == pt->access_qualifier_ && HasSameDecorations(that); +} + +std::string Pipe::str() const { + std::ostringstream oss; + oss << "pipe(" << access_qualifier_ << ")"; + return oss.str(); +} + +void Pipe::GetExtraHashWords(std::vector* words) const { + words->push_back(access_qualifier_); +} + +bool ForwardPointer::IsSame(const Type* that) const { + const ForwardPointer* fpt = that->AsForwardPointer(); + if (!fpt) return false; + return target_id_ == fpt->target_id_ && + storage_class_ == fpt->storage_class_ && HasSameDecorations(that); +} + +std::string ForwardPointer::str() const { + std::ostringstream oss; + oss << "forward_pointer("; + if (pointer_ != nullptr) { + oss << pointer_->str(); + } else { + oss << target_id_; + } + oss << ")"; + return oss.str(); +} + +void ForwardPointer::GetExtraHashWords(std::vector* words) const { + words->push_back(target_id_); + words->push_back(storage_class_); + if (pointer_) pointer_->GetHashWords(words); +} + +} // namespace analysis +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/types.h b/3rdparty/spirv-tools/source/opt/types.h new file mode 100644 index 000000000..ee81b769b --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/types.h @@ -0,0 +1,538 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file provides a class hierarchy for representing SPIR-V types. + +#ifndef LIBSPIRV_OPT_TYPES_H_ +#define LIBSPIRV_OPT_TYPES_H_ + +#include +#include +#include +#include +#include + +#include "latest_version_spirv_header.h" +#include "spirv-tools/libspirv.h" + +namespace spvtools { +namespace opt { +namespace analysis { + +class Void; +class Bool; +class Integer; +class Float; +class Vector; +class Matrix; +class Image; +class Sampler; +class SampledImage; +class Array; +class RuntimeArray; +class Struct; +class Opaque; +class Pointer; +class Function; +class Event; +class DeviceEvent; +class ReserveId; +class Queue; +class Pipe; +class ForwardPointer; +class PipeStorage; +class NamedBarrier; + +// Abstract class for a SPIR-V type. It has a bunch of As() methods, +// which is used as a way to probe the actual . +class Type { + public: + // Available subtypes. + // + // When adding a new derived class of Type, please add an entry to the enum. + enum Kind { + kVoid, + kBool, + kInteger, + kFloat, + kVector, + kMatrix, + kImage, + kSampler, + kSampledImage, + kArray, + kRuntimeArray, + kStruct, + kOpaque, + kPointer, + kFunction, + kEvent, + kDeviceEvent, + kReserveId, + kQueue, + kPipe, + kForwardPointer, + kPipeStorage, + kNamedBarrier, + }; + + Type(Kind k) : kind_(k) {} + + virtual ~Type() {} + + // Attaches a decoration directly on this type. + void AddDecoration(std::vector&& d) { + decorations_.push_back(std::move(d)); + } + // Returns the decorations on this type as a string. + std::string GetDecorationStr() const; + // Returns true if this type has exactly the same decorations as |that| type. + bool HasSameDecorations(const Type* that) const; + // Returns true if this type is exactly the same as |that| type, including + // decorations. + virtual bool IsSame(const Type* that) const = 0; + // Returns a human-readable string to represent this type. + virtual std::string str() const = 0; + + Kind kind() const { return kind_; } + const std::vector>& decorations() const { + return decorations_; + } + + // Returns true if there is no decoration on this type. For struct types, + // returns true only when there is no decoration for both the struct type + // and the struct members. + virtual bool decoration_empty() const { return decorations_.empty(); } + + // Creates a clone of |this|. + std::unique_ptr Clone() const; + + // Returns a clone of |this| minus any decorations. + std::unique_ptr RemoveDecorations() const; + + // Returns true if this type must be unique. + // + // If variable pointers are allowed, then pointers are not required to be + // unique. + // 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); +#undef DeclareCastMethod + + bool operator==(const Type& other) const; + + // Returns the hash value of this type. + size_t HashValue() const; + + // Adds the necessary words to compute a hash value of this type to |words|. + void GetHashWords(std::vector* words) const; + + // Adds necessary extra words for a subtype to calculate a hash value into + // |words|. + virtual void GetExtraHashWords(std::vector* words) const = 0; + + 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, + // and the rest are the parameters to the decoration (if exists). + std::vector> decorations_; + + private: + // Removes decorations on this type. For struct types, also removes element + // decorations. + virtual void ClearDecorations() { decorations_.clear(); } + + Kind kind_; +}; + +class Integer : public Type { + public: + Integer(uint32_t w, bool is_signed) + : Type(kInteger), width_(w), signed_(is_signed) {} + Integer(const Integer&) = default; + + bool IsSame(const Type* that) const override; + std::string str() const override; + + Integer* AsInteger() override { return this; } + const Integer* AsInteger() const override { return this; } + uint32_t width() const { return width_; } + bool IsSigned() const { return signed_; } + + void GetExtraHashWords(std::vector* words) const override; + + private: + uint32_t width_; // bit width + bool signed_; // true if this integer is signed +}; + +class Float : public Type { + public: + Float(uint32_t w) : Type(kFloat), width_(w) {} + Float(const Float&) = default; + + bool IsSame(const Type* that) const override; + std::string str() const override; + + Float* AsFloat() override { return this; } + const Float* AsFloat() const override { return this; } + uint32_t width() const { return width_; } + + void GetExtraHashWords(std::vector* words) const override; + + private: + uint32_t width_; // bit width +}; + +class Vector : public Type { + public: + Vector(Type* element_type, uint32_t count); + Vector(const Vector&) = default; + + bool IsSame(const Type* that) const override; + std::string str() const override; + const Type* element_type() const { return element_type_; } + uint32_t element_count() const { return count_; } + + Vector* AsVector() override { return this; } + const Vector* AsVector() const override { return this; } + + void GetExtraHashWords(std::vector* words) const override; + + private: + Type* element_type_; + uint32_t count_; +}; + +class Matrix : public Type { + public: + Matrix(Type* element_type, uint32_t count); + Matrix(const Matrix&) = default; + + bool IsSame(const Type* that) const override; + std::string str() const override; + const Type* element_type() const { return element_type_; } + uint32_t element_count() const { return count_; } + + Matrix* AsMatrix() override { return this; } + const Matrix* AsMatrix() const override { return this; } + + void GetExtraHashWords(std::vector* words) const override; + + private: + Type* element_type_; + uint32_t count_; +}; + +class Image : public Type { + public: + Image(Type* type, SpvDim dimen, uint32_t d, bool array, bool multisample, + uint32_t sampling, SpvImageFormat f, + SpvAccessQualifier qualifier = SpvAccessQualifierReadOnly); + Image(const Image&) = default; + + bool IsSame(const Type* that) const override; + std::string str() const override; + + Image* AsImage() override { return this; } + const Image* AsImage() const override { return this; } + + const Type* sampled_type() const { return sampled_type_; } + SpvDim dim() const { return dim_; } + uint32_t depth() const { return depth_; } + bool is_arrayed() const { return arrayed_; } + bool is_multisampled() const { return ms_; } + uint32_t sampled() const { return sampled_; } + SpvImageFormat format() const { return format_; } + SpvAccessQualifier access_qualifier() const { return access_qualifier_; } + + void GetExtraHashWords(std::vector* words) const override; + + private: + Type* sampled_type_; + SpvDim dim_; + uint32_t depth_; + bool arrayed_; + bool ms_; + uint32_t sampled_; + SpvImageFormat format_; + SpvAccessQualifier access_qualifier_; +}; + +class SampledImage : public Type { + public: + SampledImage(Type* image) : Type(kSampledImage), image_type_(image) {} + SampledImage(const SampledImage&) = default; + + bool IsSame(const Type* that) const override; + std::string str() const override; + + SampledImage* AsSampledImage() override { return this; } + const SampledImage* AsSampledImage() const override { return this; } + + const Type* image_type() const { return image_type_; } + + void GetExtraHashWords(std::vector* words) const override; + + private: + Type* image_type_; +}; + +class Array : public Type { + public: + Array(Type* element_type, uint32_t length_id); + Array(const Array&) = default; + + bool IsSame(const Type* that) const override; + std::string str() const override; + const Type* element_type() const { return element_type_; } + uint32_t LengthId() const { return length_id_; } + + Array* AsArray() override { return this; } + const Array* AsArray() const override { return this; } + + void GetExtraHashWords(std::vector* words) const override; + + private: + Type* element_type_; + uint32_t length_id_; +}; + +class RuntimeArray : public Type { + public: + RuntimeArray(Type* element_type); + RuntimeArray(const RuntimeArray&) = default; + + bool IsSame(const Type* that) const override; + std::string str() const override; + const Type* element_type() const { return element_type_; } + + RuntimeArray* AsRuntimeArray() override { return this; } + const RuntimeArray* AsRuntimeArray() const override { return this; } + + void GetExtraHashWords(std::vector* words) const override; + + private: + Type* element_type_; +}; + +class Struct : public Type { + public: + Struct(const std::vector& element_types); + Struct(const Struct&) = default; + + // Adds a decoration to the member at the given index. The first word is the + // decoration enum, and the remaining words, if any, are its operands. + void AddMemberDecoration(uint32_t index, std::vector&& decoration); + + bool IsSame(const Type* that) const override; + std::string str() const override; + const std::vector& element_types() const { return element_types_; } + bool decoration_empty() const override { + return decorations_.empty() && element_decorations_.empty(); + } + + const std::map>>& + element_decorations() const { + return element_decorations_; + } + + Struct* AsStruct() override { return this; } + const Struct* AsStruct() const override { return this; } + + void GetExtraHashWords(std::vector* words) const override; + + private: + void ClearDecorations() override { + decorations_.clear(); + element_decorations_.clear(); + } + + std::vector element_types_; + // We can attach decorations to struct members and that should not affect the + // underlying element type. So we need an extra data structure here to keep + // track of element type decorations. They must be stored in an ordered map + // because |GetExtraHashWords| will traverse the structure. It must have a + // fixed order in order to hash to the same value every time. + std::map>> element_decorations_; +}; + +class Opaque : public Type { + public: + Opaque(std::string n) : Type(kOpaque), name_(std::move(n)) {} + Opaque(const Opaque&) = default; + + bool IsSame(const Type* that) const override; + std::string str() const override; + + Opaque* AsOpaque() override { return this; } + const Opaque* AsOpaque() const override { return this; } + + const std::string& name() const { return name_; } + + void GetExtraHashWords(std::vector* words) const override; + + private: + std::string name_; +}; + +class Pointer : public Type { + public: + Pointer(const Type* pointee, SpvStorageClass sc); + Pointer(const Pointer&) = default; + + bool IsSame(const Type* that) const override; + std::string str() const override; + const Type* pointee_type() const { return pointee_type_; } + SpvStorageClass storage_class() const { return storage_class_; } + + Pointer* AsPointer() override { return this; } + const Pointer* AsPointer() const override { return this; } + + void GetExtraHashWords(std::vector* words) const override; + + private: + const Type* pointee_type_; + SpvStorageClass storage_class_; +}; + +class Function : public Type { + public: + Function(Type* ret_type, const std::vector& params); + Function(const Function&) = default; + + bool IsSame(const Type* that) const override; + std::string str() const override; + + Function* AsFunction() override { return this; } + const Function* AsFunction() const override { return this; } + + const Type* return_type() const { return return_type_; } + const std::vector& param_types() const { return param_types_; } + + void GetExtraHashWords(std::vector* words) const override; + + private: + Type* return_type_; + std::vector param_types_; +}; + +class Pipe : public Type { + public: + Pipe(SpvAccessQualifier qualifier) + : Type(kPipe), access_qualifier_(qualifier) {} + Pipe(const Pipe&) = default; + + bool IsSame(const Type* that) const override; + std::string str() const override; + + Pipe* AsPipe() override { return this; } + const Pipe* AsPipe() const override { return this; } + + SpvAccessQualifier access_qualifier() const { return access_qualifier_; } + + void GetExtraHashWords(std::vector* words) const override; + + private: + SpvAccessQualifier access_qualifier_; +}; + +class ForwardPointer : public Type { + public: + ForwardPointer(uint32_t id, SpvStorageClass sc) + : Type(kForwardPointer), + target_id_(id), + storage_class_(sc), + pointer_(nullptr) {} + ForwardPointer(const ForwardPointer&) = default; + + uint32_t target_id() const { return target_id_; } + void SetTargetPointer(Pointer* pointer) { pointer_ = pointer; } + SpvStorageClass storage_class() const { return storage_class_; } + const Pointer* target_pointer() const { return pointer_; } + + bool IsSame(const Type* that) const override; + std::string str() const override; + + ForwardPointer* AsForwardPointer() override { return this; } + const ForwardPointer* AsForwardPointer() const override { return this; } + + void GetExtraHashWords(std::vector* words) const override; + + private: + uint32_t target_id_; + SpvStorageClass storage_class_; + Pointer* pointer_; +}; + +#define DefineParameterlessType(type, name) \ + class type : public Type { \ + public: \ + type() : Type(k##type) {} \ + type(const type&) = default; \ + \ + bool IsSame(const Type* that) const override { \ + return that->As##type() && HasSameDecorations(that); \ + } \ + std::string str() const override { return #name; } \ + \ + type* As##type() override { return this; } \ + const type* As##type() const override { return this; } \ + \ + void GetExtraHashWords(std::vector*) const override {} \ + } +DefineParameterlessType(Void, void); +DefineParameterlessType(Bool, bool); +DefineParameterlessType(Sampler, sampler); +DefineParameterlessType(Event, event); +DefineParameterlessType(DeviceEvent, device_event); +DefineParameterlessType(ReserveId, reserve_id); +DefineParameterlessType(Queue, queue); +DefineParameterlessType(PipeStorage, pipe_storage); +DefineParameterlessType(NamedBarrier, named_barrier); +#undef DefineParameterlessType + +} // namespace analysis +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_TYPES_H_ diff --git a/3rdparty/spirv-tools/source/opt/unify_const_pass.cpp b/3rdparty/spirv-tools/source/opt/unify_const_pass.cpp new file mode 100644 index 000000000..266757339 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/unify_const_pass.cpp @@ -0,0 +1,174 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "unify_const_pass.h" + +#include +#include + +#include "def_use_manager.h" +#include "ir_context.h" +#include "make_unique.h" + +namespace spvtools { +namespace opt { + +namespace { + +// The trie that stores a bunch of result ids and, for a given instruction, +// searches the result id that has been defined with the same opcode, type and +// operands. +class ResultIdTrie { + public: + ResultIdTrie() : root_(new Node) {} + + // For a given instruction, extracts its opcode, type id and operand words + // as an array of keys, looks up the trie to find a result id which is stored + // with the same opcode, type id and operand words. If none of such result id + // is found, creates a trie node with those keys, stores the instruction's + // result id and returns that result id. If an existing result id is found, + // returns the existing result id. + uint32_t LookupEquivalentResultFor(const ir::Instruction& inst) { + auto keys = GetLookUpKeys(inst); + auto* node = root_.get(); + for (uint32_t key : keys) { + node = node->GetOrCreateTrieNodeFor(key); + } + if (node->result_id() == 0) { + node->SetResultId(inst.result_id()); + } + return node->result_id(); + } + + private: + // The trie node to store result ids. + class Node { + public: + using TrieNodeMap = std::unordered_map>; + + Node() : result_id_(0), next_() {} + uint32_t result_id() const { return result_id_; } + + // Sets the result id stored in this node. + void SetResultId(uint32_t id) { result_id_ = id; } + + // Searches for the child trie node with the given key. If the node is + // found, returns that node. Otherwise creates an empty child node with + // that key and returns that newly created node. + Node* GetOrCreateTrieNodeFor(uint32_t key) { + auto iter = next_.find(key); + if (iter == next_.end()) { + // insert a new node and return the node. + return next_.insert(std::make_pair(key, MakeUnique())) + .first->second.get(); + } + return iter->second.get(); + } + + private: + // The result id stored in this node. 0 means this node is empty. + uint32_t result_id_; + // The mapping from the keys to the child nodes of this node. + TrieNodeMap next_; + }; + + // Returns a vector of the opcode followed by the words in the raw SPIR-V + // instruction encoding but without the result id. + std::vector GetLookUpKeys(const ir::Instruction& inst) { + std::vector keys; + // Need to use the opcode, otherwise there might be a conflict with the + // following case when 's binary value equals xx's id: + // OpSpecConstantOp tt yy zz + // OpSpecConstantComposite tt xx yy zz; + keys.push_back(static_cast(inst.opcode())); + for (const auto& operand : inst) { + if (operand.type == SPV_OPERAND_TYPE_RESULT_ID) continue; + keys.insert(keys.end(), operand.words.cbegin(), operand.words.cend()); + } + return keys; + } + + std::unique_ptr root_; // The root node of the trie. +}; +} // anonymous namespace + +Pass::Status UnifyConstantPass::Process(ir::IRContext* c) { + InitializeProcessing(c); + bool modified = false; + ResultIdTrie defined_constants; + + for( ir::Instruction* next_instruction, *inst = &*(context()->types_values_begin()); inst; inst = next_instruction) { + next_instruction = inst->NextNode(); + + // Do not handle the instruction when there are decorations upon the result + // id. + if (get_def_use_mgr()->GetAnnotations(inst->result_id()).size() != 0) { + continue; + } + + // The overall algorithm is to store the result ids of all the eligible + // constants encountered so far in a trie. For a constant defining + // instruction under consideration, use its opcode, result type id and + // words in operands as an array of keys to lookup the trie. If a result id + // can be found for that array of keys, a constant with exactly the same + // value must has been defined before, the constant under processing + // should be replaced by the constant previously defined. If no such result + // id can be found for that array of keys, this must be the first time a + // constant with its value be defined, we then create a new trie node to + // store the result id with the keys. When replacing a duplicated constant + // with a previously defined constant, all the uses of the duplicated + // constant, which must be placed after the duplicated constant defining + // instruction, will be updated. This way, the descendants of the + // previously defined constant and the duplicated constant will both refer + // to the previously defined constant. So that the operand ids which are + // used in key arrays will be the ids of the unified constants, when + // processing is up to a descendant. This makes comparing the key array + // always valid for judging duplication. + switch (inst->opcode()) { + case SpvOp::SpvOpConstantTrue: + case SpvOp::SpvOpConstantFalse: + case SpvOp::SpvOpConstant: + case SpvOp::SpvOpConstantNull: + case SpvOp::SpvOpConstantSampler: + case SpvOp::SpvOpConstantComposite: + // Only spec constants defined with OpSpecConstantOp and + // OpSpecConstantComposite should be processed in this pass. Spec + // constants defined with OpSpecConstant{|True|False} are decorated with + // 'SpecId' decoration and all of them should be treated as unique. + // 'SpecId' is not applicable to SpecConstants defined with + // OpSpecConstant{Op|Composite}, their values are not necessary to be + // unique. When all the operands/compoents are the same between two + // OpSpecConstant{Op|Composite} results, their result values must be the + // same so are unifiable. + case SpvOp::SpvOpSpecConstantOp: + case SpvOp::SpvOpSpecConstantComposite: { + uint32_t id = defined_constants.LookupEquivalentResultFor(*inst); + if (id != inst->result_id()) { + // The constant is a duplicated one, use the cached constant to + // replace the uses of this duplicated one, then turn it to nop. + context()->ReplaceAllUsesWith(inst->result_id(), id); + context()->KillInst(inst); + modified = true; + } + break; + } + default: + break; + } + } + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/unify_const_pass.h b/3rdparty/spirv-tools/source/opt/unify_const_pass.h new file mode 100644 index 000000000..648381765 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/unify_const_pass.h @@ -0,0 +1,35 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_UNIFY_CONSTANT_PASS_H_ +#define LIBSPIRV_OPT_UNIFY_CONSTANT_PASS_H_ + +#include "ir_context.h" +#include "module.h" +#include "pass.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class UnifyConstantPass : public Pass { + public: + const char* name() const override { return "unify-const"; } + Status Process(ir::IRContext*) override; +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_UNIFY_CONSTANT_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/value_number_table.cpp b/3rdparty/spirv-tools/source/opt/value_number_table.cpp new file mode 100644 index 000000000..7f5b7ce47 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/value_number_table.cpp @@ -0,0 +1,224 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "value_number_table.h" + +#include + +#include "cfg.h" + +namespace spvtools { +namespace opt { + +uint32_t ValueNumberTable::GetValueNumber( + spvtools::ir::Instruction* inst) const { + assert(inst->result_id() != 0 && + "inst must have a result id to get a value number."); + + // Check if this instruction already has a value. + auto result_id_to_val = id_to_value_.find(inst->result_id()); + if (result_id_to_val != id_to_value_.end()) { + return result_id_to_val->second; + } + return 0; +} + +uint32_t ValueNumberTable::AssignValueNumber(ir::Instruction* inst) { + // If it already has a value return that. + uint32_t value = GetValueNumber(inst); + if (value != 0) { + return value; + } + + // If the instruction has other side effects, then it must + // have its own value number. + // OpSampledImage and OpImage must remain in the same basic block in which + // they are used, because of this we will assign each one it own value number. + if (!context()->IsCombinatorInstruction(inst)) { + value = TakeNextValueNumber(); + id_to_value_[inst->result_id()] = value; + return value; + } + + switch (inst->opcode()) { + case SpvOpSampledImage: + case SpvOpImage: + case SpvOpVariable: + value = TakeNextValueNumber(); + id_to_value_[inst->result_id()] = value; + return value; + default: + break; + } + + // If it is a load from memory that can be modified, we have to assume the + // memory has been modified, so we give it a new value number. + // + // Note that this test will also handle volatile loads because they are not + // read only. However, if this is ever relaxed because we analyze stores, we + // will have to add a new case for volatile loads. + if (inst->IsLoad() && !inst->IsReadOnlyLoad()) { + value = TakeNextValueNumber(); + id_to_value_[inst->result_id()] = value; + return value; + } + + // When we copy an object, the value numbers should be the same. + if (inst->opcode() == SpvOpCopyObject) { + value = GetValueNumber(inst->GetSingleWordInOperand(0)); + if (value != 0) { + id_to_value_[inst->result_id()] = value; + return value; + } + } + + // 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) { + value = GetValueNumber(inst->GetSingleWordInOperand(0)); + if (value != 0) { + for (uint32_t op = 2; op < inst->NumInOperands(); op += 2) { + if (value != GetValueNumber(inst->GetSingleWordInOperand(op))) { + value = 0; + break; + } + } + if (value != 0) { + id_to_value_[inst->result_id()] = value; + return value; + } + } + } + + // Replace all of the operands by their value number. The sign bit will be + // set to distinguish between an id and a value number. + ir::Instruction value_ins(context(), inst->opcode(), inst->type_id(), + inst->result_id(), {}); + for (uint32_t o = 0; o < inst->NumInOperands(); ++o) { + const ir::Operand& op = inst->GetInOperand(o); + if (spvIsIdType(op.type)) { + uint32_t id_value = op.words[0]; + auto use_id_to_val = id_to_value_.find(id_value); + if (use_id_to_val != id_to_value_.end()) { + id_value = (1 << 31) | use_id_to_val->second; + } + value_ins.AddOperand(ir::Operand(op.type, {id_value})); + } else { + value_ins.AddOperand(ir::Operand(op.type, op.words)); + } + } + + // TODO: Implement a normal form for opcodes that commute like integer + // addition. This will let us know that a+b is the same value as b+a. + + // Otherwise, we check if this value has been computed before. + auto value_iterator = instruction_to_value_.find(value_ins); + if (value_iterator != instruction_to_value_.end()) { + value = id_to_value_[value_iterator->first.result_id()]; + id_to_value_[inst->result_id()] = value; + return value; + } + + // If not, assign it a new value number. + value = TakeNextValueNumber(); + id_to_value_[inst->result_id()] = value; + instruction_to_value_[value_ins] = value; + return value; +} + +void ValueNumberTable::BuildDominatorTreeValueNumberTable() { + // First value number the headers. + for (auto& inst : context()->annotations()) { + if (inst.result_id() != 0) { + AssignValueNumber(&inst); + } + } + + for (auto& inst : context()->capabilities()) { + if (inst.result_id() != 0) { + AssignValueNumber(&inst); + } + } + + for (auto& inst : context()->types_values()) { + if (inst.result_id() != 0) { + AssignValueNumber(&inst); + } + } + + for (auto& inst : context()->module()->ext_inst_imports()) { + if (inst.result_id() != 0) { + AssignValueNumber(&inst); + } + } + + for (ir::Function& func : *context()->module()) { + // For best results we want to traverse the code in reverse post order. + // This happens naturally because of the forward referencing rules. + for (ir::BasicBlock& block : func) { + for (ir::Instruction& inst : block) { + if (inst.result_id() != 0) { + AssignValueNumber(&inst); + } + } + } + } +} + +bool ComputeSameValue::operator()(const ir::Instruction& lhs, + const ir::Instruction& rhs) const { + if (lhs.result_id() == 0 || rhs.result_id() == 0) { + return false; + } + + if (lhs.opcode() != rhs.opcode()) { + return false; + } + + if (lhs.type_id() != rhs.type_id()) { + return false; + } + + if (lhs.NumInOperands() != rhs.NumInOperands()) { + return false; + } + + for (uint32_t i = 0; i < lhs.NumInOperands(); ++i) { + if (lhs.GetInOperand(i) != rhs.GetInOperand(i)) { + return false; + } + } + + return lhs.context()->get_decoration_mgr()->HaveTheSameDecorations( + lhs.result_id(), rhs.result_id()); +} + +std::size_t ValueTableHash::operator()( + const spvtools::ir::Instruction& inst) const { + // We hash the opcode and in-operands, not the result, because we want + // instructions that are the same except for the result to hash to the + // same value. + std::u32string h; + h.push_back(inst.opcode()); + h.push_back(inst.type_id()); + for (uint32_t i = 0; i < inst.NumInOperands(); ++i) { + const auto& opnd = inst.GetInOperand(i); + for (uint32_t word : opnd.words) { + h.push_back(word); + } + } + return std::hash()(h); +} +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/value_number_table.h b/3rdparty/spirv-tools/source/opt/value_number_table.h new file mode 100644 index 000000000..8ad20df34 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/value_number_table.h @@ -0,0 +1,94 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_VALUE_NUMBER_TABLE_H_ +#define LIBSPIRV_OPT_VALUE_NUMBER_TABLE_H_ + +#include +#include +#include "instruction.h" +#include "ir_context.h" + +namespace spvtools { +namespace opt { + +// Returns true if the two instructions compute the same value. Used by the +// value number table to compare two instructions. +class ComputeSameValue { + public: + bool operator()(const ir::Instruction& lhs, const ir::Instruction& rhs) const; +}; + +// The hash function used in the value number table. +class ValueTableHash { + public: + std::size_t operator()(const spvtools::ir::Instruction& inst) const; +}; + +// This class implements the value number analysis. It is using a hash-based +// approach to value numbering. It is essentially doing dominator-tree value +// numbering described in +// +// Preston Briggs, Keith D. Cooper, and L. Taylor Simpson. 1997. Value +// numbering. Softw. Pract. Exper. 27, 6 (June 1997), 701-724. +// https://www.cs.rice.edu/~keith/Promo/CRPC-TR94517.pdf.gz +// +// The main difference is that because we do not perform redundancy elimination +// as we build the value number table, we do not have to deal with cleaning up +// the scope. +class ValueNumberTable { + public: + ValueNumberTable(ir::IRContext* ctx) : context_(ctx), next_value_number_(1) { + BuildDominatorTreeValueNumberTable(); + } + + // Returns the value number of the value computed by |inst|. |inst| must have + // a result id that will hold the computed value. If no value number has been + // assigned to the result id, then the return value is 0. + uint32_t GetValueNumber(spvtools::ir::Instruction* inst) const; + + // Returns the value number of the value contain in |id|. Returns 0 if it + // has not been assigned a value number. + inline uint32_t GetValueNumber(uint32_t id) const; + + ir::IRContext* context() const { return context_; } + + private: + // Assigns a value number to every result id in the module. + void BuildDominatorTreeValueNumberTable(); + + // Returns the new value number. + uint32_t TakeNextValueNumber() { return next_value_number_++; } + + // Assigns a new value number to the result of |inst| if it does not already + // have one. Return the value number for |inst|. |inst| must have a result + // id. + uint32_t AssignValueNumber(ir::Instruction* inst); + + std::unordered_map + instruction_to_value_; + std::unordered_map id_to_value_; + ir::IRContext* context_; + uint32_t next_value_number_; +}; + +uint32_t ValueNumberTable::GetValueNumber(uint32_t id) const { + return GetValueNumber(context()->get_def_use_mgr()->GetDef(id)); +} + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_VALUE_NUMBER_TABLE_H_ diff --git a/3rdparty/spirv-tools/source/opt/workaround1209.cpp b/3rdparty/spirv-tools/source/opt/workaround1209.cpp new file mode 100644 index 000000000..ae05848ea --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/workaround1209.cpp @@ -0,0 +1,68 @@ +// Copyright (c) 2018 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "workaround1209.h" + +#include +#include + +namespace spvtools { +namespace opt { + +Pass::Status Workaround1209::Process(ir::IRContext* c) { + InitializeProcessing(c); + bool modified = false; + modified = RemoveOpUnreachableInLoops(); + return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange); +} + +bool Workaround1209::RemoveOpUnreachableInLoops() { + bool modified = false; + for (auto& func : *get_module()) { + std::list structured_order; + cfg()->ComputeStructuredOrder(&func, &*func.begin(), &structured_order); + + // Keep track of the loop merges. The top of the stack will always be the + // loop merge for the loop that immediately contains the basic block being + // processed. + std::stack loop_merges; + for (ir::BasicBlock* bb : structured_order) { + if (!loop_merges.empty() && bb->id() == loop_merges.top()) { + loop_merges.pop(); + } + + if (bb->tail()->opcode() == SpvOpUnreachable) { + if (!loop_merges.empty()) { + // We found an OpUnreachable inside a loop. + // Replace it with an unconditional branch to the loop merge. + context()->KillInst(&*bb->tail()); + std::unique_ptr new_branch( + new ir::Instruction(context(), SpvOpBranch, 0, 0, + {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, + {loop_merges.top()}}})); + context()->AnalyzeDefUse(&*new_branch); + bb->AddInstruction(std::move(new_branch)); + modified = true; + } + } else { + if (bb->GetLoopMergeInst()) { + loop_merges.push(bb->MergeBlockIdIfAny()); + } + } + } + } + return modified; +} +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/workaround1209.h b/3rdparty/spirv-tools/source/opt/workaround1209.h new file mode 100644 index 000000000..2265ac3f9 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/workaround1209.h @@ -0,0 +1,41 @@ +// Copyright (c) 2018 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_WORKAROUND1209_H_ +#define LIBSPIRV_OPT_WORKAROUND1209_H_ + +#include "pass.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class Workaround1209 : public Pass { + public: + const char* name() const override { return "workaround-1209"; } + Status Process(ir::IRContext*) override; + + private: + // There is at least one driver where an OpUnreachable found in a loop is not + // handled correctly. Workaround that by changing the OpUnreachable into a + // branch to the loop merge. + // + // Returns true if the code changed. + bool RemoveOpUnreachableInLoops(); +}; + +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_WORKAROUND1209_H_ diff --git a/3rdparty/spirv-tools/source/parsed_operand.cpp b/3rdparty/spirv-tools/source/parsed_operand.cpp new file mode 100644 index 000000000..6f3ffe8d5 --- /dev/null +++ b/3rdparty/spirv-tools/source/parsed_operand.cpp @@ -0,0 +1,75 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file contains utility functions for spv_parsed_operand_t. + +#include "parsed_operand.h" + +#include +#include "util/hex_float.h" + +namespace libspirv { + +void EmitNumericLiteral(std::ostream* out, const spv_parsed_instruction_t& inst, + const spv_parsed_operand_t& operand) { + assert(operand.type == SPV_OPERAND_TYPE_LITERAL_INTEGER || + operand.type == SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER); + assert(1 <= operand.num_words); + assert(operand.num_words <= 2); + + const uint32_t word = inst.words[operand.offset]; + if (operand.num_words == 1) { + switch (operand.number_kind) { + case SPV_NUMBER_SIGNED_INT: + *out << int32_t(word); + break; + case SPV_NUMBER_UNSIGNED_INT: + *out << word; + break; + case SPV_NUMBER_FLOATING: + if (operand.number_bit_width == 16) { + *out << spvutils::FloatProxy( + uint16_t(word & 0xFFFF)); + } else { + // Assume 32-bit floats. + *out << spvutils::FloatProxy(word); + } + break; + default: + assert(false && "Unreachable"); + } + } else if (operand.num_words == 2) { + // Multi-word numbers are presented with lower order words first. + uint64_t bits = + uint64_t(word) | (uint64_t(inst.words[operand.offset + 1]) << 32); + switch (operand.number_kind) { + case SPV_NUMBER_SIGNED_INT: + *out << int64_t(bits); + break; + case SPV_NUMBER_UNSIGNED_INT: + *out << bits; + break; + case SPV_NUMBER_FLOATING: + // Assume only 64-bit floats. + *out << spvutils::FloatProxy(bits); + break; + default: + assert(false && "Unreachable"); + } + } else { + // TODO(dneto): Support more than 64-bits at a time. + assert(false && "Unhandled"); + } +} +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/parsed_operand.h b/3rdparty/spirv-tools/source/parsed_operand.h new file mode 100644 index 000000000..8c2fd85ce --- /dev/null +++ b/3rdparty/spirv-tools/source/parsed_operand.h @@ -0,0 +1,32 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_PARSED_OPERAND_H_ +#define LIBSPIRV_PARSED_OPERAND_H_ + +#include +#include "spirv-tools/libspirv.h" + +namespace libspirv { + +// Emits the numeric literal representation of the given instruction operand +// to the stream. The operand must be of numeric type. If integral it may +// be up to 64 bits wide. If floating point, then it must be 16, 32, or 64 +// bits wide. +void EmitNumericLiteral(std::ostream* out, const spv_parsed_instruction_t& inst, + const spv_parsed_operand_t& operand); + +} // namespace libspirv + +#endif // LIBSPIRV_BINARY_H_ diff --git a/3rdparty/spirv-tools/source/print.cpp b/3rdparty/spirv-tools/source/print.cpp new file mode 100644 index 000000000..70d8f5965 --- /dev/null +++ b/3rdparty/spirv-tools/source/print.cpp @@ -0,0 +1,125 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "print.h" + +#if defined(SPIRV_ANDROID) || defined(SPIRV_LINUX) || defined(SPIRV_MAC) || \ + defined(SPIRV_FREEBSD) +namespace libspirv { + +clr::reset::operator const char*() { return "\x1b[0m"; } + +clr::grey::operator const char*() { return "\x1b[1;30m"; } + +clr::red::operator const char*() { return "\x1b[31m"; } + +clr::green::operator const char*() { return "\x1b[32m"; } + +clr::yellow::operator const char*() { return "\x1b[33m"; } + +clr::blue::operator const char*() { return "\x1b[34m"; } + +} // namespace libspirv +#elif defined(SPIRV_WINDOWS) +#include + +namespace libspirv { + +static void SetConsoleForegroundColorPrimary(HANDLE hConsole, WORD color) { + // Get screen buffer information from console handle + CONSOLE_SCREEN_BUFFER_INFO bufInfo; + GetConsoleScreenBufferInfo(hConsole, &bufInfo); + + // Get background color + color = WORD(color | (bufInfo.wAttributes & 0xfff0)); + + // Set foreground color + SetConsoleTextAttribute(hConsole, color); +} + +static void SetConsoleForegroundColor(WORD color) { + SetConsoleForegroundColorPrimary(GetStdHandle(STD_OUTPUT_HANDLE), color); + SetConsoleForegroundColorPrimary(GetStdHandle(STD_ERROR_HANDLE), color); +} + +clr::reset::operator const char*() { + if (isPrint) { + SetConsoleForegroundColor(0xf); + return ""; + } + return "\x1b[0m"; +} + +clr::grey::operator const char*() { + if (isPrint) { + SetConsoleForegroundColor(FOREGROUND_INTENSITY); + return ""; + } + return "\x1b[1;30m"; +} + +clr::red::operator const char*() { + if (isPrint) { + SetConsoleForegroundColor(FOREGROUND_RED); + return ""; + } + return "\x1b[31m"; +} + +clr::green::operator const char*() { + if (isPrint) { + SetConsoleForegroundColor(FOREGROUND_GREEN); + return ""; + } + return "\x1b[32m"; +} + +clr::yellow::operator const char*() { + if (isPrint) { + SetConsoleForegroundColor(FOREGROUND_RED | FOREGROUND_GREEN); + return ""; + } + return "\x1b[33m"; +} + +clr::blue::operator const char*() { + // Blue all by itself is hard to see against a black background (the + // default on command shell), or a medium blue background (the default + // on PowerShell). So increase its intensity. + + if (isPrint) { + SetConsoleForegroundColor(FOREGROUND_BLUE | FOREGROUND_INTENSITY); + return ""; + } + return "\x1b[94m"; +} + +} // namespace libspirv +#else +namespace libspirv { + +clr::reset::operator const char*() { return ""; } + +clr::grey::operator const char*() { return ""; } + +clr::red::operator const char*() { return ""; } + +clr::green::operator const char*() { return ""; } + +clr::yellow::operator const char*() { return ""; } + +clr::blue::operator const char*() { return ""; } + +} // namespace libspirv +#endif diff --git a/3rdparty/spirv-tools/source/print.h b/3rdparty/spirv-tools/source/print.h new file mode 100644 index 000000000..76d7c40fc --- /dev/null +++ b/3rdparty/spirv-tools/source/print.h @@ -0,0 +1,75 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_PRINT_H_ +#define LIBSPIRV_PRINT_H_ + +#include +#include + +namespace libspirv { + +// Wrapper for out stream selection. +class out_stream { + public: + out_stream() : pStream(nullptr) {} + explicit out_stream(std::stringstream& stream) : pStream(&stream) {} + + std::ostream& get() { + if (pStream) { + return *pStream; + } + return std::cout; + } + + private: + std::stringstream* pStream; +}; + +namespace clr { +// Resets console color. +struct reset { + operator const char*(); + bool isPrint; +}; +// Sets console color to grey. +struct grey { + operator const char*(); + bool isPrint; +}; +// Sets console color to red. +struct red { + operator const char*(); + bool isPrint; +}; +// Sets console color to green. +struct green { + operator const char*(); + bool isPrint; +}; +// Sets console color to yellow. +struct yellow { + operator const char*(); + bool isPrint; +}; +// Sets console color to blue. +struct blue { + operator const char*(); + bool isPrint; +}; +} // namespace clr + +} // namespace libspirv + +#endif // LIBSPIRV_PRINT_H_ diff --git a/3rdparty/spirv-tools/source/software_version.cpp b/3rdparty/spirv-tools/source/software_version.cpp new file mode 100644 index 000000000..b258ebe90 --- /dev/null +++ b/3rdparty/spirv-tools/source/software_version.cpp @@ -0,0 +1,27 @@ +// Copyright (c) 2015-2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "spirv-tools/libspirv.h" + +namespace { + +const char* kBuildVersions[] = { +#include "build-version.inc" +}; + +} // anonymous namespace + +const char* spvSoftwareVersionString(void) { return kBuildVersions[0]; } + +const char* spvSoftwareVersionDetailsString(void) { return kBuildVersions[1]; } diff --git a/3rdparty/spirv-tools/source/spirv_constant.h b/3rdparty/spirv-tools/source/spirv_constant.h new file mode 100644 index 000000000..8eb6572bc --- /dev/null +++ b/3rdparty/spirv-tools/source/spirv_constant.h @@ -0,0 +1,100 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_SPIRV_CONSTANT_H_ +#define LIBSPIRV_SPIRV_CONSTANT_H_ + +#include "latest_version_spirv_header.h" +#include "spirv-tools/libspirv.h" + +// Version number macros. + +// Evaluates to a well-formed version header word, given valid +// SPIR-V version major and minor version numbers. +#define SPV_SPIRV_VERSION_WORD(MAJOR, MINOR) \ + ((uint32_t(uint8_t(MAJOR)) << 16) | (uint32_t(uint8_t(MINOR)) << 8)) +// Returns the major version extracted from a version header word. +#define SPV_SPIRV_VERSION_MAJOR_PART(WORD) ((uint32_t(WORD) >> 16) & 0xff) +// Returns the minor version extracted from a version header word. +#define SPV_SPIRV_VERSION_MINOR_PART(WORD) ((uint32_t(WORD) >> 8) & 0xff) + +// Header indices + +#define SPV_INDEX_MAGIC_NUMBER 0u +#define SPV_INDEX_VERSION_NUMBER 1u +#define SPV_INDEX_GENERATOR_NUMBER 2u +#define SPV_INDEX_BOUND 3u +#define SPV_INDEX_SCHEMA 4u +#define SPV_INDEX_INSTRUCTION 5u + +// Universal limits + +// SPIR-V 1.0 limits +#define SPV_LIMIT_INSTRUCTION_WORD_COUNT_MAX 0xffff +#define SPV_LIMIT_LITERAL_STRING_UTF8_CHARS_MAX 0xffff + +// A single Unicode character in UTF-8 encoding can take +// up 4 bytes. +#define SPV_LIMIT_LITERAL_STRING_BYTES_MAX \ + (SPV_LIMIT_LITERAL_STRING_UTF8_CHARS_MAX * 4) + +// NOTE: These are set to the minimum maximum values +// TODO(dneto): Check these. + +// libspirv limits. +#define SPV_LIMIT_RESULT_ID_BOUND 0x00400000 +#define SPV_LIMIT_CONTROL_FLOW_NEST_DEPTH 0x00000400 +#define SPV_LIMIT_GLOBAL_VARIABLES_MAX 0x00010000 +#define SPV_LIMIT_LOCAL_VARIABLES_MAX 0x00080000 +// TODO: Decorations per target ID max, depends on decoration table size +#define SPV_LIMIT_EXECUTION_MODE_PER_ENTRY_POINT_MAX 0x00000100 +#define SPV_LIMIT_INDICIES_MAX_ACCESS_CHAIN_COMPOSITE_MAX 0x00000100 +#define SPV_LIMIT_FUNCTION_PARAMETERS_PER_FUNCTION_DECL 0x00000100 +#define SPV_LIMIT_FUNCTION_CALL_ARGUMENTS_MAX 0x00000100 +#define SPV_LIMIT_EXT_FUNCTION_CALL_ARGUMENTS_MAX 0x00000100 +#define SPV_LIMIT_SWITCH_LITERAL_LABEL_PAIRS_MAX 0x00004000 +#define SPV_LIMIT_STRUCT_MEMBERS_MAX 0x0000400 +#define SPV_LIMIT_STRUCT_NESTING_DEPTH_MAX 0x00000100 + +// Enumerations + +// Values mapping to registered tools. See the registry at +// https://www.khronos.org/registry/spir-v/api/spir-v.xml +// These values occupy the higher order 16 bits of the generator magic word. +typedef enum spv_generator_t { + // TODO(dneto) Values 0 through 5 were registered only as vendor. + SPV_GENERATOR_KHRONOS = 0, + SPV_GENERATOR_LUNARG = 1, + SPV_GENERATOR_VALVE = 2, + SPV_GENERATOR_CODEPLAY = 3, + SPV_GENERATOR_NVIDIA = 4, + SPV_GENERATOR_ARM = 5, + // These are vendor and tool. + SPV_GENERATOR_KHRONOS_LLVM_TRANSLATOR = 6, + SPV_GENERATOR_KHRONOS_ASSEMBLER = 7, + SPV_GENERATOR_KHRONOS_GLSLANG = 8, + SPV_GENERATOR_NUM_ENTRIES, + SPV_FORCE_16_BIT_ENUM(spv_generator_t) +} spv_generator_t; + +// Evaluates to a well-formed generator magic word from a tool value and +// miscellaneous 16-bit value. +#define SPV_GENERATOR_WORD(TOOL, MISC) \ + ((uint32_t(uint16_t(TOOL)) << 16) | uint16_t(MISC)) +// Returns the tool component of the generator word. +#define SPV_GENERATOR_TOOL_PART(WORD) (uint32_t(WORD) >> 16) +// Returns the misc part of the generator word. +#define SPV_GENERATOR_MISC_PART(WORD) (uint32_t(WORD) & 0xFFFF) + +#endif // LIBSPIRV_SPIRV_CONSTANT_H_ diff --git a/3rdparty/spirv-tools/source/spirv_definition.h b/3rdparty/spirv-tools/source/spirv_definition.h new file mode 100644 index 000000000..9e22108fa --- /dev/null +++ b/3rdparty/spirv-tools/source/spirv_definition.h @@ -0,0 +1,33 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_SPIRV_DEFINITION_H_ +#define LIBSPIRV_SPIRV_DEFINITION_H_ + +#include + +#include "latest_version_spirv_header.h" + +#define spvIsInBitfield(value, bitfield) ((value) == ((value)&bitfield)) + +typedef struct spv_header_t { + uint32_t magic; + uint32_t version; + uint32_t generator; + uint32_t bound; + uint32_t schema; // NOTE: Reserved + const uint32_t* instructions; // NOTE: Unfixed pointer to instruciton stream +} spv_header_t; + +#endif // LIBSPIRV_SPIRV_DEFINITION_H_ diff --git a/3rdparty/spirv-tools/source/spirv_endian.cpp b/3rdparty/spirv-tools/source/spirv_endian.cpp new file mode 100644 index 000000000..56eaac855 --- /dev/null +++ b/3rdparty/spirv-tools/source/spirv_endian.cpp @@ -0,0 +1,77 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "spirv_endian.h" + +#include + +enum { + I32_ENDIAN_LITTLE = 0x03020100ul, + I32_ENDIAN_BIG = 0x00010203ul, +}; + +// This constant value allows the detection of the host machine's endianness. +// Accessing it through the "value" member is valid due to C++11 section 3.10 +// paragraph 10. +static const union { + unsigned char bytes[4]; + uint32_t value; +} o32_host_order = {{0, 1, 2, 3}}; + +#define I32_ENDIAN_HOST (o32_host_order.value) + +uint32_t spvFixWord(const uint32_t word, const spv_endianness_t endian) { + if ((SPV_ENDIANNESS_LITTLE == endian && I32_ENDIAN_HOST == I32_ENDIAN_BIG) || + (SPV_ENDIANNESS_BIG == endian && I32_ENDIAN_HOST == I32_ENDIAN_LITTLE)) { + return (word & 0x000000ff) << 24 | (word & 0x0000ff00) << 8 | + (word & 0x00ff0000) >> 8 | (word & 0xff000000) >> 24; + } + + return word; +} + +uint64_t spvFixDoubleWord(const uint32_t low, const uint32_t high, + const spv_endianness_t endian) { + return (uint64_t(spvFixWord(high, endian)) << 32) | spvFixWord(low, endian); +} + +spv_result_t spvBinaryEndianness(spv_const_binary binary, + spv_endianness_t* pEndian) { + if (!binary->code || !binary->wordCount) return SPV_ERROR_INVALID_BINARY; + if (!pEndian) return SPV_ERROR_INVALID_POINTER; + + uint8_t bytes[4]; + memcpy(bytes, binary->code, sizeof(uint32_t)); + + if (0x03 == bytes[0] && 0x02 == bytes[1] && 0x23 == bytes[2] && + 0x07 == bytes[3]) { + *pEndian = SPV_ENDIANNESS_LITTLE; + return SPV_SUCCESS; + } + + if (0x07 == bytes[0] && 0x23 == bytes[1] && 0x02 == bytes[2] && + 0x03 == bytes[3]) { + *pEndian = SPV_ENDIANNESS_BIG; + return SPV_SUCCESS; + } + + return SPV_ERROR_INVALID_BINARY; +} + +bool spvIsHostEndian(spv_endianness_t endian) { + return ((SPV_ENDIANNESS_LITTLE == endian) && + (I32_ENDIAN_LITTLE == I32_ENDIAN_HOST)) || + ((SPV_ENDIANNESS_BIG == endian) && + (I32_ENDIAN_BIG == I32_ENDIAN_HOST)); +} diff --git a/3rdparty/spirv-tools/source/spirv_endian.h b/3rdparty/spirv-tools/source/spirv_endian.h new file mode 100644 index 000000000..c64b33819 --- /dev/null +++ b/3rdparty/spirv-tools/source/spirv_endian.h @@ -0,0 +1,37 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_SPIRV_ENDIAN_H_ +#define LIBSPIRV_SPIRV_ENDIAN_H_ + +#include "spirv-tools/libspirv.h" + +// Converts a word in the specified endianness to the host native endianness. +uint32_t spvFixWord(const uint32_t word, const spv_endianness_t endianness); + +// Converts a pair of words in the specified endianness to the host native +// endianness. +uint64_t spvFixDoubleWord(const uint32_t low, const uint32_t high, + const spv_endianness_t endianness); + +// Gets the endianness of the SPIR-V module given in the binary parameter. +// Returns SPV_ENDIANNESS_UNKNOWN if the SPIR-V magic number is invalid, +// otherwise writes the determined endianness into *endian. +spv_result_t spvBinaryEndianness(const spv_const_binary binary, + spv_endianness_t* endian); + +// Returns true if the given endianness matches the host's native endiannes. +bool spvIsHostEndian(spv_endianness_t endian); + +#endif // LIBSPIRV_SPIRV_ENDIAN_H_ diff --git a/3rdparty/spirv-tools/source/spirv_stats.cpp b/3rdparty/spirv-tools/source/spirv_stats.cpp new file mode 100644 index 000000000..ff4b3c67a --- /dev/null +++ b/3rdparty/spirv-tools/source/spirv_stats.cpp @@ -0,0 +1,325 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "spirv_stats.h" + +#include + +#include +#include +#include +#include + +#include "binary.h" +#include "diagnostic.h" +#include "enum_string_mapping.h" +#include "extensions.h" +#include "id_descriptor.h" +#include "instruction.h" +#include "opcode.h" +#include "operand.h" +#include "spirv-tools/libspirv.h" +#include "spirv_endian.h" +#include "spirv_validator_options.h" +#include "val/instruction.h" +#include "val/validation_state.h" +#include "validate.h" + +using libspirv::IdDescriptorCollection; +using libspirv::Instruction; +using libspirv::SpirvStats; +using libspirv::ValidationState_t; + +namespace { + +// Helper class for stats aggregation. Receives as in/out parameter. +// Constructs ValidationState and updates it by running validator for each +// instruction. +class StatsAggregator { + public: + StatsAggregator(SpirvStats* in_out_stats, const spv_const_context context) { + stats_ = in_out_stats; + vstate_.reset(new ValidationState_t(context, &validator_options_)); + } + + // Collects header statistics and sets correct id_bound. + spv_result_t ProcessHeader(spv_endianness_t /* endian */, + uint32_t /* magic */, uint32_t version, + uint32_t generator, uint32_t id_bound, + uint32_t /* schema */) { + vstate_->setIdBound(id_bound); + ++stats_->version_hist[version]; + ++stats_->generator_hist[generator]; + return SPV_SUCCESS; + } + + // Runs validator to validate the instruction and update vstate_, + // then procession the instruction to collect stats. + spv_result_t ProcessInstruction(const spv_parsed_instruction_t* inst) { + const spv_result_t validation_result = + spvtools::ValidateInstructionAndUpdateValidationState(vstate_.get(), + inst); + if (validation_result != SPV_SUCCESS) return validation_result; + + ProcessOpcode(); + ProcessCapability(); + ProcessExtension(); + ProcessConstant(); + ProcessEnums(); + ProcessLiteralStrings(); + ProcessNonIdWords(); + ProcessIdDescriptors(); + + return SPV_SUCCESS; + } + + // Collects statistics of descriptors generated by IdDescriptorCollection. + void ProcessIdDescriptors() { + const Instruction& inst = GetCurrentInstruction(); + const uint32_t new_descriptor = + id_descriptors_.ProcessInstruction(inst.c_inst()); + + if (new_descriptor) { + std::stringstream ss; + ss << spvOpcodeString(inst.opcode()); + for (size_t i = 1; i < inst.words().size(); ++i) { + ss << " " << inst.word(i); + } + stats_->id_descriptor_labels.emplace(new_descriptor, ss.str()); + } + + uint32_t index = 0; + for (const auto& operand : inst.operands()) { + if (spvIsIdType(operand.type)) { + const uint32_t descriptor = + id_descriptors_.GetDescriptor(inst.word(operand.offset)); + if (descriptor) { + ++stats_->id_descriptor_hist[descriptor]; + ++stats_ + ->operand_slot_id_descriptor_hist[std::pair( + inst.opcode(), index)][descriptor]; + } + } + ++index; + } + } + + // Collects statistics of enum words for operands of specific types. + void ProcessEnums() { + const Instruction& inst = GetCurrentInstruction(); + for (const auto& operand : inst.operands()) { + switch (operand.type) { + case SPV_OPERAND_TYPE_SOURCE_LANGUAGE: + case SPV_OPERAND_TYPE_EXECUTION_MODEL: + case SPV_OPERAND_TYPE_ADDRESSING_MODEL: + case SPV_OPERAND_TYPE_MEMORY_MODEL: + case SPV_OPERAND_TYPE_EXECUTION_MODE: + case SPV_OPERAND_TYPE_STORAGE_CLASS: + case SPV_OPERAND_TYPE_DIMENSIONALITY: + case SPV_OPERAND_TYPE_SAMPLER_ADDRESSING_MODE: + case SPV_OPERAND_TYPE_SAMPLER_FILTER_MODE: + case SPV_OPERAND_TYPE_SAMPLER_IMAGE_FORMAT: + case SPV_OPERAND_TYPE_IMAGE_CHANNEL_ORDER: + case SPV_OPERAND_TYPE_IMAGE_CHANNEL_DATA_TYPE: + case SPV_OPERAND_TYPE_FP_ROUNDING_MODE: + case SPV_OPERAND_TYPE_LINKAGE_TYPE: + case SPV_OPERAND_TYPE_ACCESS_QUALIFIER: + case SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE: + case SPV_OPERAND_TYPE_DECORATION: + case SPV_OPERAND_TYPE_BUILT_IN: + case SPV_OPERAND_TYPE_GROUP_OPERATION: + case SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS: + case SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO: + case SPV_OPERAND_TYPE_CAPABILITY: { + ++stats_->enum_hist[operand.type][inst.word(operand.offset)]; + break; + } + default: + break; + } + } + } + + // Collects statistics of literal strings used by opcodes. + void ProcessLiteralStrings() { + const Instruction& inst = GetCurrentInstruction(); + for (const auto& operand : inst.operands()) { + if (operand.type == SPV_OPERAND_TYPE_LITERAL_STRING) { + const std::string str = + reinterpret_cast(&inst.words()[operand.offset]); + ++stats_->literal_strings_hist[inst.opcode()][str]; + } + } + } + + // Collects statistics of all single word non-id operand slots. + void ProcessNonIdWords() { + const Instruction& inst = GetCurrentInstruction(); + uint32_t index = 0; + for (const auto& operand : inst.operands()) { + if (operand.num_words == 1 && !spvIsIdType(operand.type)) { + ++stats_->operand_slot_non_id_words_hist[std::pair( + inst.opcode(), index)][inst.word(operand.offset)]; + } + ++index; + } + } + + // Collects OpCapability statistics. + void ProcessCapability() { + const Instruction& inst = GetCurrentInstruction(); + if (inst.opcode() != SpvOpCapability) return; + const uint32_t capability = inst.word(inst.operands()[0].offset); + ++stats_->capability_hist[capability]; + } + + // Collects OpExtension statistics. + void ProcessExtension() { + const Instruction& inst = GetCurrentInstruction(); + if (inst.opcode() != SpvOpExtension) return; + const std::string extension = libspirv::GetExtensionString(&inst.c_inst()); + ++stats_->extension_hist[extension]; + } + + // Collects OpCode statistics. + void ProcessOpcode() { + auto inst_it = vstate_->ordered_instructions().rbegin(); + const SpvOp opcode = inst_it->opcode(); + ++stats_->opcode_hist[opcode]; + + const uint32_t opcode_and_num_operands = + (uint32_t(inst_it->operands().size()) << 16) | uint32_t(opcode); + ++stats_->opcode_and_num_operands_hist[opcode_and_num_operands]; + + ++inst_it; + + if (inst_it != vstate_->ordered_instructions().rend()) { + const SpvOp prev_opcode = inst_it->opcode(); + ++stats_->opcode_and_num_operands_markov_hist[prev_opcode] + [opcode_and_num_operands]; + } + + auto step_it = stats_->opcode_markov_hist.begin(); + for (; inst_it != vstate_->ordered_instructions().rend() && + step_it != stats_->opcode_markov_hist.end(); + ++inst_it, ++step_it) { + auto& hist = (*step_it)[inst_it->opcode()]; + ++hist[opcode]; + } + } + + // Collects OpConstant statistics. + void ProcessConstant() { + const Instruction& inst = GetCurrentInstruction(); + if (inst.opcode() != SpvOpConstant) return; + const uint32_t type_id = inst.GetOperandAs(0); + const auto type_decl_it = vstate_->all_definitions().find(type_id); + assert(type_decl_it != vstate_->all_definitions().end()); + const Instruction& type_decl_inst = *type_decl_it->second; + const SpvOp type_op = type_decl_inst.opcode(); + if (type_op == SpvOpTypeInt) { + const uint32_t bit_width = type_decl_inst.GetOperandAs(1); + const uint32_t is_signed = type_decl_inst.GetOperandAs(2); + assert(is_signed == 0 || is_signed == 1); + if (bit_width == 16) { + if (is_signed) + ++stats_->s16_constant_hist[inst.GetOperandAs(2)]; + else + ++stats_->u16_constant_hist[inst.GetOperandAs(2)]; + } else if (bit_width == 32) { + if (is_signed) + ++stats_->s32_constant_hist[inst.GetOperandAs(2)]; + else + ++stats_->u32_constant_hist[inst.GetOperandAs(2)]; + } else if (bit_width == 64) { + if (is_signed) + ++stats_->s64_constant_hist[inst.GetOperandAs(2)]; + else + ++stats_->u64_constant_hist[inst.GetOperandAs(2)]; + } else { + assert(false && "TypeInt bit width is not 16, 32 or 64"); + } + } else if (type_op == SpvOpTypeFloat) { + const uint32_t bit_width = type_decl_inst.GetOperandAs(1); + if (bit_width == 32) { + ++stats_->f32_constant_hist[inst.GetOperandAs(2)]; + } else if (bit_width == 64) { + ++stats_->f64_constant_hist[inst.GetOperandAs(2)]; + } else { + assert(bit_width == 16); + } + } + } + + SpirvStats* stats() { return stats_; } + + private: + // Returns the current instruction (the one last processed by the validator). + const Instruction& GetCurrentInstruction() const { + return vstate_->ordered_instructions().back(); + } + + SpirvStats* stats_; + spv_validator_options_t validator_options_; + std::unique_ptr vstate_; + IdDescriptorCollection id_descriptors_; +}; + +spv_result_t ProcessHeader(void* user_data, spv_endianness_t endian, + uint32_t magic, uint32_t version, uint32_t generator, + uint32_t id_bound, uint32_t schema) { + StatsAggregator* stats_aggregator = + reinterpret_cast(user_data); + return stats_aggregator->ProcessHeader(endian, magic, version, generator, + id_bound, schema); +} + +spv_result_t ProcessInstruction(void* user_data, + const spv_parsed_instruction_t* inst) { + StatsAggregator* stats_aggregator = + reinterpret_cast(user_data); + return stats_aggregator->ProcessInstruction(inst); +} + +} // namespace + +namespace libspirv { + +spv_result_t AggregateStats(const spv_context_t& context, const uint32_t* words, + const size_t num_words, spv_diagnostic* pDiagnostic, + SpirvStats* stats) { + spv_const_binary_t binary = {words, num_words}; + + spv_endianness_t endian; + spv_position_t position = {}; + if (spvBinaryEndianness(&binary, &endian)) { + return libspirv::DiagnosticStream(position, context.consumer, + SPV_ERROR_INVALID_BINARY) + << "Invalid SPIR-V magic number."; + } + + spv_header_t header; + if (spvBinaryHeaderGet(&binary, endian, &header)) { + return libspirv::DiagnosticStream(position, context.consumer, + SPV_ERROR_INVALID_BINARY) + << "Invalid SPIR-V header."; + } + + StatsAggregator stats_aggregator(stats, &context); + + return spvBinaryParse(&context, &stats_aggregator, words, num_words, + ProcessHeader, ProcessInstruction, pDiagnostic); +} + +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/spirv_stats.h b/3rdparty/spirv-tools/source/spirv_stats.h new file mode 100644 index 000000000..cc6c23914 --- /dev/null +++ b/3rdparty/spirv-tools/source/spirv_stats.h @@ -0,0 +1,130 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_SPIRV_STATS_H_ +#define LIBSPIRV_SPIRV_STATS_H_ + +#include +#include +#include +#include + +#include "spirv-tools/libspirv.hpp" + +namespace libspirv { + +struct SpirvStats { + // Version histogram, version_word -> count. + std::unordered_map version_hist; + + // Generator histogram, generator_word -> count. + std::unordered_map generator_hist; + + // Capability histogram, SpvCapabilityXXX -> count. + std::unordered_map capability_hist; + + // Extension histogram, extension_string -> count. + std::unordered_map extension_hist; + + // Opcode histogram, SpvOpXXX -> count. + std::unordered_map opcode_hist; + + // Histogram of words combining opcode and number of operands, + // opcode | (num_operands << 16) -> count. + std::unordered_map opcode_and_num_operands_hist; + + // OpConstant u16 histogram, value -> count. + std::unordered_map u16_constant_hist; + + // OpConstant u32 histogram, value -> count. + std::unordered_map u32_constant_hist; + + // OpConstant u64 histogram, value -> count. + std::unordered_map u64_constant_hist; + + // OpConstant s16 histogram, value -> count. + std::unordered_map s16_constant_hist; + + // OpConstant s32 histogram, value -> count. + std::unordered_map s32_constant_hist; + + // OpConstant s64 histogram, value -> count. + std::unordered_map s64_constant_hist; + + // OpConstant f32 histogram, value -> count. + std::unordered_map f32_constant_hist; + + // OpConstant f64 histogram, value -> count. + std::unordered_map f64_constant_hist; + + // Enum histogram, operand type -> operand value -> count. + std::unordered_map> + enum_hist; + + // Histogram of all non-id single words. + // pair -> value -> count. + // This is a generalization of enum_hist, also includes literal integers and + // masks. + std::map, std::map> + operand_slot_non_id_words_hist; + + // Historgam of descriptors generated by IdDescriptorCollection. + // Descriptor -> count. + std::unordered_map id_descriptor_hist; + + // Debut labels for id descriptors, descriptor -> label. + std::unordered_map id_descriptor_labels; + + // Historgam of descriptors generated by IdDescriptorCollection for every + // operand slot. pair -> descriptor -> count. + std::map, std::map> + operand_slot_id_descriptor_hist; + + // Histogram of literal strings, sharded by opcodes, opcode -> string -> + // count. + // This is suboptimal if an opcode has multiple literal string operands, + // as it wouldn't differentiate between operands. + std::unordered_map> + literal_strings_hist; + + // Markov chain histograms: + // opcode -> next(opcode | (num_operands << 16)) -> count. + // See also opcode_and_num_operands_hist, which collects global statistics. + std::unordered_map> + opcode_and_num_operands_markov_hist; + + // Used to collect statistics on opcodes triggering other opcodes. + // Container scheme: gap between instructions -> cue opcode -> later opcode + // -> count. + // For example opcode_markov_hist[2][OpFMul][OpFAdd] corresponds to + // the number of times an OpMul appears, followed by 2 other instructions, + // followed by OpFAdd. + // opcode_markov_hist[0][OpFMul][OpFAdd] corresponds to how many times + // OpFMul appears, directly followed by OpFAdd. + // The size of the outer std::vector also serves as an input parameter, + // determining how many steps will be collected. + // I.e. do opcode_markov_hist.resize(1) to collect data for one step only. + std::vector< + std::unordered_map>> + opcode_markov_hist; +}; + +// Aggregates existing |stats| with new stats extracted from |binary|. +spv_result_t AggregateStats(const spv_context_t& context, const uint32_t* words, + const size_t num_words, spv_diagnostic* pDiagnostic, + SpirvStats* stats); + +} // namespace libspirv + +#endif // LIBSPIRV_SPIRV_STATS_H_ diff --git a/3rdparty/spirv-tools/source/spirv_target_env.cpp b/3rdparty/spirv-tools/source/spirv_target_env.cpp new file mode 100644 index 000000000..cc99228e2 --- /dev/null +++ b/3rdparty/spirv-tools/source/spirv_target_env.cpp @@ -0,0 +1,188 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "spirv-tools/libspirv.h" +#include "spirv_constant.h" + +const char* spvTargetEnvDescription(spv_target_env env) { + switch (env) { + case SPV_ENV_UNIVERSAL_1_0: + return "SPIR-V 1.0"; + case SPV_ENV_VULKAN_1_0: + return "SPIR-V 1.0 (under Vulkan 1.0 semantics)"; + case SPV_ENV_UNIVERSAL_1_1: + return "SPIR-V 1.1"; + case SPV_ENV_OPENCL_1_2: + return "SPIR-V 1.0 (under OpenCL 1.2 Full Profile semantics)"; + case SPV_ENV_OPENCL_EMBEDDED_1_2: + return "SPIR-V 1.0 (under OpenCL 1.2 Embedded Profile semantics)"; + case SPV_ENV_OPENCL_2_0: + return "SPIR-V 1.0 (under OpenCL 2.0 Full Profile semantics)"; + case SPV_ENV_OPENCL_EMBEDDED_2_0: + return "SPIR-V 1.0 (under OpenCL 2.0 Embedded Profile semantics)"; + case SPV_ENV_OPENCL_2_1: + return "SPIR-V 1.0 (under OpenCL 2.1 Full Profile semantics)"; + case SPV_ENV_OPENCL_EMBEDDED_2_1: + return "SPIR-V 1.0 (under OpenCL 2.1 Embedded Profile semantics)"; + case SPV_ENV_OPENCL_2_2: + return "SPIR-V 1.2 (under OpenCL 2.2 Full Profile semantics)"; + case SPV_ENV_OPENCL_EMBEDDED_2_2: + return "SPIR-V 1.2 (under OpenCL 2.2 Embedded Profile semantics)"; + case SPV_ENV_OPENGL_4_0: + return "SPIR-V 1.0 (under OpenGL 4.0 semantics)"; + case SPV_ENV_OPENGL_4_1: + return "SPIR-V 1.0 (under OpenGL 4.1 semantics)"; + case SPV_ENV_OPENGL_4_2: + return "SPIR-V 1.0 (under OpenGL 4.2 semantics)"; + case SPV_ENV_OPENGL_4_3: + return "SPIR-V 1.0 (under OpenGL 4.3 semantics)"; + case SPV_ENV_OPENGL_4_5: + return "SPIR-V 1.0 (under OpenGL 4.5 semantics)"; + case SPV_ENV_UNIVERSAL_1_2: + return "SPIR-V 1.2"; + case SPV_ENV_UNIVERSAL_1_3: + return "SPIR-V 1.3"; + case SPV_ENV_VULKAN_1_1: + return "SPIR-V 1.3 (under Vulkan 1.1 semantics)"; + } + assert(0 && "Unhandled SPIR-V target environment"); + return ""; +} + +uint32_t spvVersionForTargetEnv(spv_target_env env) { + switch (env) { + case SPV_ENV_UNIVERSAL_1_0: + case SPV_ENV_VULKAN_1_0: + case SPV_ENV_OPENCL_1_2: + case SPV_ENV_OPENCL_EMBEDDED_1_2: + case SPV_ENV_OPENCL_2_0: + case SPV_ENV_OPENCL_EMBEDDED_2_0: + case SPV_ENV_OPENCL_2_1: + case SPV_ENV_OPENCL_EMBEDDED_2_1: + case SPV_ENV_OPENGL_4_0: + case SPV_ENV_OPENGL_4_1: + case SPV_ENV_OPENGL_4_2: + case SPV_ENV_OPENGL_4_3: + case SPV_ENV_OPENGL_4_5: + return SPV_SPIRV_VERSION_WORD(1, 0); + case SPV_ENV_UNIVERSAL_1_1: + return SPV_SPIRV_VERSION_WORD(1, 1); + case SPV_ENV_UNIVERSAL_1_2: + case SPV_ENV_OPENCL_2_2: + case SPV_ENV_OPENCL_EMBEDDED_2_2: + return SPV_SPIRV_VERSION_WORD(1, 2); + case SPV_ENV_UNIVERSAL_1_3: + case SPV_ENV_VULKAN_1_1: + return SPV_SPIRV_VERSION_WORD(1, 3); + } + assert(0 && "Unhandled SPIR-V target environment"); + return SPV_SPIRV_VERSION_WORD(0, 0); +} + +bool spvParseTargetEnv(const char* s, spv_target_env* env) { + auto match = [s](const char* b) { + return s && (0 == strncmp(s, b, strlen(b))); + }; + if (match("vulkan1.0")) { + if (env) *env = SPV_ENV_VULKAN_1_0; + return true; + } else if (match("vulkan1.1")) { + if (env) *env = SPV_ENV_VULKAN_1_1; + return true; + } else if (match("spv1.0")) { + if (env) *env = SPV_ENV_UNIVERSAL_1_0; + return true; + } else if (match("spv1.1")) { + if (env) *env = SPV_ENV_UNIVERSAL_1_1; + return true; + } else if (match("spv1.2")) { + if (env) *env = SPV_ENV_UNIVERSAL_1_2; + return true; + } else if (match("spv1.3")) { + if (env) *env = SPV_ENV_UNIVERSAL_1_3; + return true; + } else if (match("opencl1.2embedded")) { + if (env) *env = SPV_ENV_OPENCL_EMBEDDED_1_2; + return true; + } else if (match("opencl1.2")) { + if (env) *env = SPV_ENV_OPENCL_1_2; + return true; + } else if (match("opencl2.0embedded")) { + if (env) *env = SPV_ENV_OPENCL_EMBEDDED_2_0; + return true; + } else if (match("opencl2.0")) { + if (env) *env = SPV_ENV_OPENCL_2_0; + return true; + } else if (match("opencl2.1embedded")) { + if (env) *env = SPV_ENV_OPENCL_EMBEDDED_2_1; + return true; + } else if (match("opencl2.1")) { + if (env) *env = SPV_ENV_OPENCL_2_1; + return true; + } else if (match("opencl2.2embedded")) { + if (env) *env = SPV_ENV_OPENCL_EMBEDDED_2_2; + return true; + } else if (match("opencl2.2")) { + if (env) *env = SPV_ENV_OPENCL_2_2; + return true; + } else if (match("opengl4.0")) { + if (env) *env = SPV_ENV_OPENGL_4_0; + return true; + } else if (match("opengl4.1")) { + if (env) *env = SPV_ENV_OPENGL_4_1; + return true; + } else if (match("opengl4.2")) { + if (env) *env = SPV_ENV_OPENGL_4_2; + return true; + } else if (match("opengl4.3")) { + if (env) *env = SPV_ENV_OPENGL_4_3; + return true; + } else if (match("opengl4.5")) { + if (env) *env = SPV_ENV_OPENGL_4_5; + return true; + } else { + if (env) *env = SPV_ENV_UNIVERSAL_1_0; + return false; + } +} + +bool spvIsVulkanEnv(spv_target_env env) { + switch (env) { + case SPV_ENV_UNIVERSAL_1_0: + case SPV_ENV_OPENCL_1_2: + case SPV_ENV_OPENCL_EMBEDDED_1_2: + case SPV_ENV_OPENCL_2_0: + case SPV_ENV_OPENCL_EMBEDDED_2_0: + case SPV_ENV_OPENCL_2_1: + case SPV_ENV_OPENCL_EMBEDDED_2_1: + case SPV_ENV_OPENGL_4_0: + case SPV_ENV_OPENGL_4_1: + case SPV_ENV_OPENGL_4_2: + case SPV_ENV_OPENGL_4_3: + case SPV_ENV_OPENGL_4_5: + case SPV_ENV_UNIVERSAL_1_1: + case SPV_ENV_UNIVERSAL_1_2: + case SPV_ENV_OPENCL_2_2: + case SPV_ENV_OPENCL_EMBEDDED_2_2: + case SPV_ENV_UNIVERSAL_1_3: + return false; + case SPV_ENV_VULKAN_1_0: + case SPV_ENV_VULKAN_1_1: + return true; + } + return false; +} diff --git a/3rdparty/spirv-tools/source/spirv_target_env.h b/3rdparty/spirv-tools/source/spirv_target_env.h new file mode 100644 index 000000000..315dbbe91 --- /dev/null +++ b/3rdparty/spirv-tools/source/spirv_target_env.h @@ -0,0 +1,30 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_SPIRV_TARGET_ENV_H_ +#define LIBSPIRV_SPIRV_TARGET_ENV_H_ + +#include "spirv-tools/libspirv.h" + +// Parses s into *env and returns true if successful. If unparsable, returns +// false and sets *env to SPV_ENV_UNIVERSAL_1_0. +bool spvParseTargetEnv(const char* s, spv_target_env* env); + +// Returns true if |env| is a VULKAN environment, false otherwise. +bool spvIsVulkanEnv(spv_target_env env); + +// Returns the version number for the given SPIR-V target environment. +uint32_t spvVersionForTargetEnv(spv_target_env env); + +#endif // LIBSPIRV_SPIRV_TARGET_ENV_H_ diff --git a/3rdparty/spirv-tools/source/spirv_validator_options.cpp b/3rdparty/spirv-tools/source/spirv_validator_options.cpp new file mode 100644 index 000000000..fe522da48 --- /dev/null +++ b/3rdparty/spirv-tools/source/spirv_validator_options.cpp @@ -0,0 +1,88 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "spirv_validator_options.h" + +bool spvParseUniversalLimitsOptions(const char* s, spv_validator_limit* type) { + auto match = [s](const char* b) { + return s && (0 == strncmp(s, b, strlen(b))); + }; + if (match("--max-struct-members")) { + *type = spv_validator_limit_max_struct_members; + } else if (match("--max-struct_depth")) { + *type = spv_validator_limit_max_struct_depth; + } else if (match("--max-local-variables")) { + *type = spv_validator_limit_max_local_variables; + } else if (match("--max-global-variables")) { + *type = spv_validator_limit_max_global_variables; + } else if (match("--max-switch-branches")) { + *type = spv_validator_limit_max_global_variables; + } else if (match("--max-function-args")) { + *type = spv_validator_limit_max_function_args; + } else if (match("--max-control-flow-nesting-depth")) { + *type = spv_validator_limit_max_control_flow_nesting_depth; + } else if (match("--max-access-chain-indexes")) { + *type = spv_validator_limit_max_access_chain_indexes; + } else { + // The command line option for this validator limit has not been added. + // Therefore we return false. + return false; + } + + return true; +} + +spv_validator_options spvValidatorOptionsCreate(void) { + return new spv_validator_options_t; +} + +void spvValidatorOptionsDestroy(spv_validator_options options) { + delete options; +} + +void spvValidatorOptionsSetUniversalLimit(spv_validator_options options, + spv_validator_limit limit_type, + uint32_t limit) { + assert(options && "Validator options object may not be Null"); + switch (limit_type) { +#define LIMIT(TYPE, FIELD) \ + case TYPE: \ + options->universal_limits_.FIELD = limit; \ + break; + LIMIT(spv_validator_limit_max_struct_members, max_struct_members) + LIMIT(spv_validator_limit_max_struct_depth, max_struct_depth) + LIMIT(spv_validator_limit_max_local_variables, max_local_variables) + LIMIT(spv_validator_limit_max_global_variables, max_global_variables) + LIMIT(spv_validator_limit_max_switch_branches, max_switch_branches) + LIMIT(spv_validator_limit_max_function_args, max_function_args) + LIMIT(spv_validator_limit_max_control_flow_nesting_depth, + max_control_flow_nesting_depth) + LIMIT(spv_validator_limit_max_access_chain_indexes, + max_access_chain_indexes) +#undef LIMIT + } +} + +void spvValidatorOptionsSetRelaxStoreStruct(spv_validator_options options, + bool val) { + options->relax_struct_store = val; +} + +void spvValidatorOptionsSetRelaxLogicalPointer(spv_validator_options options, + bool val) { + options->relax_logcial_pointer = val; +} diff --git a/3rdparty/spirv-tools/source/spirv_validator_options.h b/3rdparty/spirv-tools/source/spirv_validator_options.h new file mode 100644 index 000000000..d15b63bb0 --- /dev/null +++ b/3rdparty/spirv-tools/source/spirv_validator_options.h @@ -0,0 +1,50 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_SPIRV_VALIDATOR_OPTIONS_H_ +#define LIBSPIRV_SPIRV_VALIDATOR_OPTIONS_H_ + +#include "spirv-tools/libspirv.h" + +// Return true if the command line option for the validator limit is valid (Also +// returns the Enum for option in this case). Returns false otherwise. +bool spvParseUniversalLimitsOptions(const char* s, spv_validator_limit* limit); + +// Default initialization of this structure is to the default Universal Limits +// described in the SPIR-V Spec. +struct validator_universal_limits_t { + uint32_t max_struct_members{16383}; + uint32_t max_struct_depth{255}; + uint32_t max_local_variables{524287}; + uint32_t max_global_variables{65535}; + uint32_t max_switch_branches{16383}; + uint32_t max_function_args{255}; + uint32_t max_control_flow_nesting_depth{1023}; + uint32_t max_access_chain_indexes{255}; +}; + +// Manages command line options passed to the SPIR-V Validator. New struct +// members may be added for any new option. +struct spv_validator_options_t { + spv_validator_options_t() + : universal_limits_(), + relax_struct_store(false), + relax_logcial_pointer(false) {} + + validator_universal_limits_t universal_limits_; + bool relax_struct_store; + bool relax_logcial_pointer; +}; + +#endif // LIBSPIRV_SPIRV_VALIDATOR_OPTIONS_H_ diff --git a/3rdparty/spirv-tools/source/table.cpp b/3rdparty/spirv-tools/source/table.cpp new file mode 100644 index 000000000..1a40e27c6 --- /dev/null +++ b/3rdparty/spirv-tools/source/table.cpp @@ -0,0 +1,62 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "table.h" + +#include + +spv_context spvContextCreate(spv_target_env env) { + switch (env) { + case SPV_ENV_UNIVERSAL_1_0: + case SPV_ENV_VULKAN_1_0: + case SPV_ENV_UNIVERSAL_1_1: + case SPV_ENV_OPENCL_1_2: + case SPV_ENV_OPENCL_EMBEDDED_1_2: + case SPV_ENV_OPENCL_2_0: + case SPV_ENV_OPENCL_EMBEDDED_2_0: + case SPV_ENV_OPENCL_2_1: + case SPV_ENV_OPENCL_EMBEDDED_2_1: + case SPV_ENV_OPENCL_2_2: + case SPV_ENV_OPENCL_EMBEDDED_2_2: + case SPV_ENV_OPENGL_4_0: + case SPV_ENV_OPENGL_4_1: + case SPV_ENV_OPENGL_4_2: + case SPV_ENV_OPENGL_4_3: + case SPV_ENV_OPENGL_4_5: + case SPV_ENV_UNIVERSAL_1_2: + case SPV_ENV_UNIVERSAL_1_3: + case SPV_ENV_VULKAN_1_1: + break; + default: + return nullptr; + } + + spv_opcode_table opcode_table; + spv_operand_table operand_table; + spv_ext_inst_table ext_inst_table; + + spvOpcodeTableGet(&opcode_table, env); + spvOperandTableGet(&operand_table, env); + spvExtInstTableGet(&ext_inst_table, env); + + return new spv_context_t{env, opcode_table, operand_table, ext_inst_table, + nullptr /* a null default consumer */}; +} + +void spvContextDestroy(spv_context context) { delete context; } + +void libspirv::SetContextMessageConsumer(spv_context context, + spvtools::MessageConsumer consumer) { + context->consumer = std::move(consumer); +} diff --git a/3rdparty/spirv-tools/source/table.h b/3rdparty/spirv-tools/source/table.h new file mode 100644 index 000000000..d4e58348c --- /dev/null +++ b/3rdparty/spirv-tools/source/table.h @@ -0,0 +1,133 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_TABLE_H_ +#define LIBSPIRV_TABLE_H_ + +#include "latest_version_spirv_header.h" + +#include "extensions.h" +#include "message.h" +#include "spirv-tools/libspirv.hpp" + +typedef struct spv_opcode_desc_t { + const char* name; + const SpvOp opcode; + const uint32_t numCapabilities; + const SpvCapability* capabilities; + // operandTypes[0..numTypes-1] describe logical operands for the instruction. + // The operand types include result id and result-type id, followed by + // the types of arguments. + const uint16_t numTypes; + spv_operand_type_t operandTypes[16]; // TODO: Smaller/larger? + const bool hasResult; // Does the instruction have a result ID operand? + const bool hasType; // Does the instruction have a type ID operand? + // A set of extensions that enable this feature. If empty then this operand + // value is in core and its availability is subject to minVersion. The + // assembler, binary parser, and disassembler ignore this rule, so you can + // freely process invalid modules. + const uint32_t numExtensions; + const libspirv::Extension* extensions; + // Minimal core SPIR-V version required for this feature, if without + // extensions. ~0u means reserved for future use. ~0u and non-empty extension + // lists means only available in extensions. + const uint32_t minVersion; +} spv_opcode_desc_t; + +typedef struct spv_operand_desc_t { + const char* name; + const uint32_t value; + const uint32_t numCapabilities; + const SpvCapability* capabilities; + // A set of extensions that enable this feature. If empty then this operand + // value is in core and its availability is subject to minVersion. The + // assembler, binary parser, and disassembler ignore this rule, so you can + // freely process invalid modules. + const uint32_t numExtensions; + const libspirv::Extension* extensions; + const spv_operand_type_t operandTypes[16]; // TODO: Smaller/larger? + // Minimal core SPIR-V version required for this feature, if without + // extensions. ~0u means reserved for future use. ~0u and non-empty extension + // lists means only available in extensions. + const uint32_t minVersion; +} spv_operand_desc_t; + +typedef struct spv_operand_desc_group_t { + const spv_operand_type_t type; + const uint32_t count; + const spv_operand_desc_t* entries; +} spv_operand_desc_group_t; + +typedef struct spv_ext_inst_desc_t { + const char* name; + const uint32_t ext_inst; + const uint32_t numCapabilities; + const SpvCapability* capabilities; + const spv_operand_type_t operandTypes[16]; // TODO: Smaller/larger? +} spv_ext_inst_desc_t; + +typedef struct spv_ext_inst_group_t { + const spv_ext_inst_type_t type; + const uint32_t count; + const spv_ext_inst_desc_t* entries; +} spv_ext_inst_group_t; + +typedef struct spv_opcode_table_t { + const uint32_t count; + const spv_opcode_desc_t* entries; +} spv_opcode_table_t; + +typedef struct spv_operand_table_t { + const uint32_t count; + const spv_operand_desc_group_t* types; +} spv_operand_table_t; + +typedef struct spv_ext_inst_table_t { + const uint32_t count; + const spv_ext_inst_group_t* groups; +} spv_ext_inst_table_t; + +typedef const spv_opcode_desc_t* spv_opcode_desc; +typedef const spv_operand_desc_t* spv_operand_desc; +typedef const spv_ext_inst_desc_t* spv_ext_inst_desc; + +typedef const spv_opcode_table_t* spv_opcode_table; +typedef const spv_operand_table_t* spv_operand_table; +typedef const spv_ext_inst_table_t* spv_ext_inst_table; + +struct spv_context_t { + const spv_target_env target_env; + const spv_opcode_table opcode_table; + const spv_operand_table operand_table; + const spv_ext_inst_table ext_inst_table; + spvtools::MessageConsumer consumer; +}; + +namespace libspirv { +// Sets the message consumer to |consumer| in the given |context|. The original +// message consumer will be overwritten. +void SetContextMessageConsumer(spv_context context, + spvtools::MessageConsumer consumer); +} // namespace libspirv + +// Populates *table with entries for env. +spv_result_t spvOpcodeTableGet(spv_opcode_table* table, spv_target_env env); + +// Populates *table with entries for env. +spv_result_t spvOperandTableGet(spv_operand_table* table, spv_target_env env); + +// Populates *table with entries for env. +spv_result_t spvExtInstTableGet(spv_ext_inst_table* table, spv_target_env env); + +#endif // LIBSPIRV_TABLE_H_ diff --git a/3rdparty/spirv-tools/source/text.cpp b/3rdparty/spirv-tools/source/text.cpp new file mode 100644 index 000000000..ac4f8a1f0 --- /dev/null +++ b/3rdparty/spirv-tools/source/text.cpp @@ -0,0 +1,810 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "text.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "assembly_grammar.h" +#include "binary.h" +#include "diagnostic.h" +#include "ext_inst.h" +#include "instruction.h" +#include "message.h" +#include "opcode.h" +#include "operand.h" +#include "spirv-tools/libspirv.h" +#include "spirv_constant.h" +#include "spirv_target_env.h" +#include "table.h" +#include "text_handler.h" +#include "util/bitutils.h" +#include "util/parse_number.h" + +bool spvIsValidIDCharacter(const char value) { + return value == '_' || 0 != ::isalnum(value); +} + +// Returns true if the given string represents a valid ID name. +bool spvIsValidID(const char* textValue) { + const char* c = textValue; + for (; *c != '\0'; ++c) { + if (!spvIsValidIDCharacter(*c)) { + return false; + } + } + // If the string was empty, then the ID also is not valid. + return c != textValue; +} + +// Text API + +spv_result_t spvTextToLiteral(const char* textValue, spv_literal_t* pLiteral) { + bool isSigned = false; + int numPeriods = 0; + bool isString = false; + + const size_t len = strlen(textValue); + if (len == 0) return SPV_FAILED_MATCH; + + for (uint64_t index = 0; index < len; ++index) { + switch (textValue[index]) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + break; + case '.': + numPeriods++; + break; + case '-': + if (index == 0) { + isSigned = true; + } else { + isString = true; + } + break; + default: + isString = true; + index = len; // break out of the loop too. + break; + } + } + + pLiteral->type = spv_literal_type_t(99); + + if (isString || numPeriods > 1 || (isSigned && len == 1)) { + if (len < 2 || textValue[0] != '"' || textValue[len - 1] != '"') + return SPV_FAILED_MATCH; + bool escaping = false; + for (const char* val = textValue + 1; val != textValue + len - 1; ++val) { + if ((*val == '\\') && (!escaping)) { + escaping = true; + } else { + // Have to save space for the null-terminator + if (pLiteral->str.size() >= SPV_LIMIT_LITERAL_STRING_BYTES_MAX) + return SPV_ERROR_OUT_OF_MEMORY; + pLiteral->str.push_back(*val); + escaping = false; + } + } + + pLiteral->type = SPV_LITERAL_TYPE_STRING; + } else if (numPeriods == 1) { + double d = std::strtod(textValue, nullptr); + float f = (float)d; + if (d == (double)f) { + pLiteral->type = SPV_LITERAL_TYPE_FLOAT_32; + pLiteral->value.f = f; + } else { + pLiteral->type = SPV_LITERAL_TYPE_FLOAT_64; + pLiteral->value.d = d; + } + } else if (isSigned) { + int64_t i64 = strtoll(textValue, nullptr, 10); + int32_t i32 = (int32_t)i64; + if (i64 == (int64_t)i32) { + pLiteral->type = SPV_LITERAL_TYPE_INT_32; + pLiteral->value.i32 = i32; + } else { + pLiteral->type = SPV_LITERAL_TYPE_INT_64; + pLiteral->value.i64 = i64; + } + } else { + uint64_t u64 = strtoull(textValue, nullptr, 10); + uint32_t u32 = (uint32_t)u64; + if (u64 == (uint64_t)u32) { + pLiteral->type = SPV_LITERAL_TYPE_UINT_32; + pLiteral->value.u32 = u32; + } else { + pLiteral->type = SPV_LITERAL_TYPE_UINT_64; + pLiteral->value.u64 = u64; + } + } + + return SPV_SUCCESS; +} + +namespace { + +/// Parses an immediate integer from text, guarding against overflow. If +/// successful, adds the parsed value to pInst, advances the context past it, +/// and returns SPV_SUCCESS. Otherwise, leaves pInst alone, emits diagnostics, +/// and returns SPV_ERROR_INVALID_TEXT. +spv_result_t encodeImmediate(libspirv::AssemblyContext* context, + const char* text, spv_instruction_t* pInst) { + assert(*text == '!'); + uint32_t parse_result; + if (!spvutils::ParseNumber(text + 1, &parse_result)) { + return context->diagnostic(SPV_ERROR_INVALID_TEXT) + << "Invalid immediate integer: !" << text + 1; + } + context->binaryEncodeU32(parse_result, pInst); + context->seekForward(static_cast(strlen(text))); + return SPV_SUCCESS; +} + +} // anonymous namespace + +/// @brief Translate an Opcode operand to binary form +/// +/// @param[in] grammar the grammar to use for compilation +/// @param[in, out] context the dynamic compilation info +/// @param[in] type of the operand +/// @param[in] textValue word of text to be parsed +/// @param[out] pInst return binary Opcode +/// @param[in,out] pExpectedOperands the operand types expected +/// +/// @return result code +spv_result_t spvTextEncodeOperand(const libspirv::AssemblyGrammar& grammar, + libspirv::AssemblyContext* context, + const spv_operand_type_t type, + const char* textValue, + spv_instruction_t* pInst, + spv_operand_pattern_t* pExpectedOperands) { + // NOTE: Handle immediate int in the stream + if ('!' == textValue[0]) { + if (auto error = encodeImmediate(context, textValue, pInst)) { + return error; + } + *pExpectedOperands = + spvAlternatePatternFollowingImmediate(*pExpectedOperands); + return SPV_SUCCESS; + } + + // Optional literal operands can fail to parse. In that case use + // SPV_FAILED_MATCH to avoid emitting a diagostic. Use the following + // for those situations. + spv_result_t error_code_for_literals = + spvOperandIsOptional(type) ? SPV_FAILED_MATCH : SPV_ERROR_INVALID_TEXT; + + switch (type) { + case SPV_OPERAND_TYPE_ID: + case SPV_OPERAND_TYPE_TYPE_ID: + case SPV_OPERAND_TYPE_RESULT_ID: + case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID: + case SPV_OPERAND_TYPE_SCOPE_ID: + case SPV_OPERAND_TYPE_OPTIONAL_ID: { + if ('%' == textValue[0]) { + textValue++; + } else { + return context->diagnostic() << "Expected id to start with %."; + } + if (!spvIsValidID(textValue)) { + return context->diagnostic() << "Invalid ID " << textValue; + } + const uint32_t id = context->spvNamedIdAssignOrGet(textValue); + if (type == SPV_OPERAND_TYPE_TYPE_ID) pInst->resultTypeId = id; + spvInstructionAddWord(pInst, id); + + // Set the extended instruction type. + // The import set id is the 3rd operand of OpExtInst. + if (pInst->opcode == SpvOpExtInst && pInst->words.size() == 4) { + auto ext_inst_type = context->getExtInstTypeForId(pInst->words[3]); + if (ext_inst_type == SPV_EXT_INST_TYPE_NONE) { + return context->diagnostic() + << "Invalid extended instruction import Id " + << pInst->words[2]; + } + pInst->extInstType = ext_inst_type; + } + } break; + + case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER: { + // The assembler accepts the symbolic name for an extended instruction, + // and emits its corresponding number. + spv_ext_inst_desc extInst; + if (grammar.lookupExtInst(pInst->extInstType, textValue, &extInst)) { + return context->diagnostic() + << "Invalid extended instruction name '" << textValue << "'."; + } + spvInstructionAddWord(pInst, extInst->ext_inst); + + // Prepare to parse the operands for the extended instructions. + spvPushOperandTypes(extInst->operandTypes, pExpectedOperands); + } break; + + case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: { + // The assembler accepts the symbolic name for the opcode, but without + // the "Op" prefix. For example, "IAdd" is accepted. The number + // of the opcode is emitted. + SpvOp opcode; + if (grammar.lookupSpecConstantOpcode(textValue, &opcode)) { + return context->diagnostic() << "Invalid " << spvOperandTypeStr(type) + << " '" << textValue << "'."; + } + spv_opcode_desc opcodeEntry = nullptr; + if (grammar.lookupOpcode(opcode, &opcodeEntry)) { + return context->diagnostic(SPV_ERROR_INTERNAL) + << "OpSpecConstant opcode table out of sync"; + } + spvInstructionAddWord(pInst, uint32_t(opcodeEntry->opcode)); + + // Prepare to parse the operands for the opcode. Except skip the + // type Id and result Id, since they've already been processed. + assert(opcodeEntry->hasType); + assert(opcodeEntry->hasResult); + assert(opcodeEntry->numTypes >= 2); + spvPushOperandTypes(opcodeEntry->operandTypes + 2, pExpectedOperands); + } break; + + case SPV_OPERAND_TYPE_LITERAL_INTEGER: + case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_INTEGER: { + // The current operand is an *unsigned* 32-bit integer. + // That's just how the grammar works. + libspirv::IdType expected_type = { + 32, false, libspirv::IdTypeClass::kScalarIntegerType}; + if (auto error = context->binaryEncodeNumericLiteral( + textValue, error_code_for_literals, expected_type, pInst)) { + return error; + } + } break; + + case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_NUMBER: + // This is a context-independent literal number which can be a 32-bit + // number of floating point value. + if (auto error = context->binaryEncodeNumericLiteral( + textValue, error_code_for_literals, libspirv::kUnknownType, + pInst)) { + return error; + } + break; + + case SPV_OPERAND_TYPE_OPTIONAL_TYPED_LITERAL_INTEGER: + case SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER: { + libspirv::IdType expected_type = libspirv::kUnknownType; + // The encoding for OpConstant, OpSpecConstant and OpSwitch all + // depend on either their own result-id or the result-id of + // one of their parameters. + if (SpvOpConstant == pInst->opcode || + SpvOpSpecConstant == pInst->opcode) { + // The type of the literal is determined by the type Id of the + // instruction. + expected_type = + context->getTypeOfTypeGeneratingValue(pInst->resultTypeId); + if (!libspirv::isScalarFloating(expected_type) && + !libspirv::isScalarIntegral(expected_type)) { + spv_opcode_desc d; + const char* opcode_name = "opcode"; + if (SPV_SUCCESS == grammar.lookupOpcode(pInst->opcode, &d)) { + opcode_name = d->name; + } + return context->diagnostic() + << "Type for " << opcode_name + << " must be a scalar floating point or integer type"; + } + } else if (pInst->opcode == SpvOpSwitch) { + // The type of the literal is the same as the type of the selector. + expected_type = context->getTypeOfValueInstruction(pInst->words[1]); + if (!libspirv::isScalarIntegral(expected_type)) { + return context->diagnostic() + << "The selector operand for OpSwitch must be the result" + " of an instruction that generates an integer scalar"; + } + } + if (auto error = context->binaryEncodeNumericLiteral( + textValue, error_code_for_literals, expected_type, pInst)) { + return error; + } + } break; + + case SPV_OPERAND_TYPE_LITERAL_STRING: + case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_STRING: { + spv_literal_t literal = {}; + spv_result_t error = spvTextToLiteral(textValue, &literal); + if (error != SPV_SUCCESS) { + if (error == SPV_ERROR_OUT_OF_MEMORY) return error; + return context->diagnostic(error_code_for_literals) + << "Invalid literal string '" << textValue << "'."; + } + if (literal.type != SPV_LITERAL_TYPE_STRING) { + return context->diagnostic() + << "Expected literal string, found literal number '" << textValue + << "'."; + } + + // NOTE: Special case for extended instruction library import + if (SpvOpExtInstImport == pInst->opcode) { + const spv_ext_inst_type_t ext_inst_type = + spvExtInstImportTypeGet(literal.str.c_str()); + if (SPV_EXT_INST_TYPE_NONE == ext_inst_type) { + return context->diagnostic() + << "Invalid extended instruction import '" << literal.str + << "'"; + } + if ((error = context->recordIdAsExtInstImport(pInst->words[1], + ext_inst_type))) + return error; + } + + if (context->binaryEncodeString(literal.str.c_str(), pInst)) + return SPV_ERROR_INVALID_TEXT; + } break; + + // Masks. + case SPV_OPERAND_TYPE_FP_FAST_MATH_MODE: + case SPV_OPERAND_TYPE_FUNCTION_CONTROL: + case SPV_OPERAND_TYPE_LOOP_CONTROL: + case SPV_OPERAND_TYPE_IMAGE: + case SPV_OPERAND_TYPE_OPTIONAL_IMAGE: + case SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS: + case SPV_OPERAND_TYPE_SELECTION_CONTROL: + case SPV_OPERAND_TYPE_DEBUG_INFO_FLAGS: { + uint32_t value; + if (grammar.parseMaskOperand(type, textValue, &value)) { + return context->diagnostic() << "Invalid " << spvOperandTypeStr(type) + << " operand '" << textValue << "'."; + } + if (auto error = context->binaryEncodeU32(value, pInst)) return error; + // Prepare to parse the operands for this logical operand. + grammar.pushOperandTypesForMask(type, value, pExpectedOperands); + } break; + case SPV_OPERAND_TYPE_OPTIONAL_CIV: { + auto error = spvTextEncodeOperand( + grammar, context, SPV_OPERAND_TYPE_OPTIONAL_LITERAL_NUMBER, textValue, + pInst, pExpectedOperands); + if (error == SPV_FAILED_MATCH) { + // It's not a literal number -- is it a literal string? + error = spvTextEncodeOperand(grammar, context, + SPV_OPERAND_TYPE_OPTIONAL_LITERAL_STRING, + textValue, pInst, pExpectedOperands); + } + if (error == SPV_FAILED_MATCH) { + // It's not a literal -- is it an ID? + error = + spvTextEncodeOperand(grammar, context, SPV_OPERAND_TYPE_OPTIONAL_ID, + textValue, pInst, pExpectedOperands); + } + if (error) { + return context->diagnostic(error) + << "Invalid word following !: " << textValue; + } + if (pExpectedOperands->empty()) { + pExpectedOperands->push_back(SPV_OPERAND_TYPE_OPTIONAL_CIV); + } + } break; + default: { + // NOTE: All non literal operands are handled here using the operand + // table. + spv_operand_desc entry; + if (grammar.lookupOperand(type, textValue, strlen(textValue), &entry)) { + return context->diagnostic() << "Invalid " << spvOperandTypeStr(type) + << " '" << textValue << "'."; + } + if (context->binaryEncodeU32(entry->value, pInst)) { + return context->diagnostic() << "Invalid " << spvOperandTypeStr(type) + << " '" << textValue << "'."; + } + + // Prepare to parse the operands for this logical operand. + spvPushOperandTypes(entry->operandTypes, pExpectedOperands); + } break; + } + return SPV_SUCCESS; +} + +namespace { + +/// Encodes an instruction started by ! at the given position in text. +/// +/// Puts the encoded words into *pInst. If successful, moves position past the +/// instruction and returns SPV_SUCCESS. Otherwise, returns an error code and +/// leaves position pointing to the error in text. +spv_result_t encodeInstructionStartingWithImmediate( + const libspirv::AssemblyGrammar& grammar, + libspirv::AssemblyContext* context, spv_instruction_t* pInst) { + std::string firstWord; + spv_position_t nextPosition = {}; + auto error = context->getWord(&firstWord, &nextPosition); + if (error) return context->diagnostic(error) << "Internal Error"; + + if ((error = encodeImmediate(context, firstWord.c_str(), pInst))) { + return error; + } + while (context->advance() != SPV_END_OF_STREAM) { + // A beginning of a new instruction means we're done. + if (context->isStartOfNewInst()) return SPV_SUCCESS; + + // Otherwise, there must be an operand that's either a literal, an ID, or + // an immediate. + std::string operandValue; + if ((error = context->getWord(&operandValue, &nextPosition))) + return context->diagnostic(error) << "Internal Error"; + + if (operandValue == "=") + return context->diagnostic() << firstWord << " not allowed before =."; + + // Needed to pass to spvTextEncodeOpcode(), but it shouldn't ever be + // expanded. + spv_operand_pattern_t dummyExpectedOperands; + error = spvTextEncodeOperand( + grammar, context, SPV_OPERAND_TYPE_OPTIONAL_CIV, operandValue.c_str(), + pInst, &dummyExpectedOperands); + if (error) return error; + context->setPosition(nextPosition); + } + return SPV_SUCCESS; +} + +/// @brief Translate single Opcode and operands to binary form +/// +/// @param[in] grammar the grammar to use for compilation +/// @param[in, out] context the dynamic compilation info +/// @param[in] text stream to translate +/// @param[out] pInst returned binary Opcode +/// @param[in,out] pPosition in the text stream +/// +/// @return result code +spv_result_t spvTextEncodeOpcode(const libspirv::AssemblyGrammar& grammar, + libspirv::AssemblyContext* context, + spv_instruction_t* pInst) { + // Check for ! first. + if ('!' == context->peek()) { + return encodeInstructionStartingWithImmediate(grammar, context, pInst); + } + + std::string firstWord; + spv_position_t nextPosition = {}; + spv_result_t error = context->getWord(&firstWord, &nextPosition); + if (error) return context->diagnostic() << "Internal Error"; + + std::string opcodeName; + std::string result_id; + spv_position_t result_id_position = {}; + if (context->startsWithOp()) { + opcodeName = firstWord; + } else { + result_id = firstWord; + if ('%' != result_id.front()) { + return context->diagnostic() + << "Expected or at the beginning " + "of an instruction, found '" + << result_id << "'."; + } + result_id_position = context->position(); + + // The '=' sign. + context->setPosition(nextPosition); + if (context->advance()) + return context->diagnostic() << "Expected '=', found end of stream."; + std::string equal_sign; + error = context->getWord(&equal_sign, &nextPosition); + if ("=" != equal_sign) + return context->diagnostic() << "'=' expected after result id."; + + // The after the '=' sign. + context->setPosition(nextPosition); + if (context->advance()) + return context->diagnostic() << "Expected opcode, found end of stream."; + error = context->getWord(&opcodeName, &nextPosition); + if (error) return context->diagnostic(error) << "Internal Error"; + if (!context->startsWithOp()) { + return context->diagnostic() + << "Invalid Opcode prefix '" << opcodeName << "'."; + } + } + + // NOTE: The table contains Opcode names without the "Op" prefix. + const char* pInstName = opcodeName.data() + 2; + + spv_opcode_desc opcodeEntry; + error = grammar.lookupOpcode(pInstName, &opcodeEntry); + if (error) { + return context->diagnostic(error) + << "Invalid Opcode name '" << opcodeName << "'"; + } + if (opcodeEntry->hasResult && result_id.empty()) { + return context->diagnostic() + << "Expected at the beginning of an instruction, found '" + << firstWord << "'."; + } + pInst->opcode = opcodeEntry->opcode; + context->setPosition(nextPosition); + // Reserve the first word for the instruction. + spvInstructionAddWord(pInst, 0); + + // Maintains the ordered list of expected operand types. + // For many instructions we only need the {numTypes, operandTypes} + // entries in opcodeEntry. However, sometimes we need to modify + // the list as we parse the operands. This occurs when an operand + // has its own logical operands (such as the LocalSize operand for + // ExecutionMode), or for extended instructions that may have their + // own operands depending on the selected extended instruction. + spv_operand_pattern_t expectedOperands; + expectedOperands.reserve(opcodeEntry->numTypes); + for (auto i = 0; i < opcodeEntry->numTypes; i++) + expectedOperands.push_back( + opcodeEntry->operandTypes[opcodeEntry->numTypes - i - 1]); + + while (!expectedOperands.empty()) { + const spv_operand_type_t type = expectedOperands.back(); + expectedOperands.pop_back(); + + // Expand optional tuples lazily. + if (spvExpandOperandSequenceOnce(type, &expectedOperands)) continue; + + if (type == SPV_OPERAND_TYPE_RESULT_ID && !result_id.empty()) { + // Handle the for value generating instructions. + // We've already consumed it from the text stream. Here + // we inject its words into the instruction. + spv_position_t temp_pos = context->position(); + error = spvTextEncodeOperand(grammar, context, SPV_OPERAND_TYPE_RESULT_ID, + result_id.c_str(), pInst, nullptr); + result_id_position = context->position(); + // Because we are injecting we have to reset the position afterwards. + context->setPosition(temp_pos); + if (error) return error; + } else { + // Find the next word. + error = context->advance(); + if (error == SPV_END_OF_STREAM) { + if (spvOperandIsOptional(type)) { + // This would have been the last potential operand for the + // instruction, + // and we didn't find one. We're finished parsing this instruction. + break; + } else { + return context->diagnostic() + << "Expected operand, found end of stream."; + } + } + assert(error == SPV_SUCCESS && "Somebody added another way to fail"); + + if (context->isStartOfNewInst()) { + if (spvOperandIsOptional(type)) { + break; + } else { + return context->diagnostic() + << "Expected operand, found next instruction instead."; + } + } + + std::string operandValue; + error = context->getWord(&operandValue, &nextPosition); + if (error) return context->diagnostic(error) << "Internal Error"; + + error = spvTextEncodeOperand(grammar, context, type, operandValue.c_str(), + pInst, &expectedOperands); + + if (error == SPV_FAILED_MATCH && spvOperandIsOptional(type)) + return SPV_SUCCESS; + + if (error) return error; + + context->setPosition(nextPosition); + } + } + + if (spvOpcodeGeneratesType(pInst->opcode)) { + if (context->recordTypeDefinition(pInst) != SPV_SUCCESS) { + return SPV_ERROR_INVALID_TEXT; + } + } else if (opcodeEntry->hasType) { + // SPIR-V dictates that if an instruction has both a return value and a + // type ID then the type id is first, and the return value is second. + assert(opcodeEntry->hasResult && + "Unknown opcode: has a type but no result."); + context->recordTypeIdForValue(pInst->words[2], pInst->words[1]); + } + + if (pInst->words.size() > SPV_LIMIT_INSTRUCTION_WORD_COUNT_MAX) { + return context->diagnostic() + << "Instruction too long: " << pInst->words.size() + << " words, but the limit is " + << SPV_LIMIT_INSTRUCTION_WORD_COUNT_MAX; + } + + pInst->words[0] = + spvOpcodeMake(uint16_t(pInst->words.size()), opcodeEntry->opcode); + + return SPV_SUCCESS; +} + +enum { kAssemblerVersion = 0 }; + +// Populates a binary stream's |header|. The target environment is specified via +// |env| and Id bound is via |bound|. +spv_result_t SetHeader(spv_target_env env, const uint32_t bound, + uint32_t* header) { + if (!header) return SPV_ERROR_INVALID_BINARY; + + header[SPV_INDEX_MAGIC_NUMBER] = SpvMagicNumber; + header[SPV_INDEX_VERSION_NUMBER] = spvVersionForTargetEnv(env); + header[SPV_INDEX_GENERATOR_NUMBER] = + SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_ASSEMBLER, kAssemblerVersion); + header[SPV_INDEX_BOUND] = bound; + header[SPV_INDEX_SCHEMA] = 0; // NOTE: Reserved + + return SPV_SUCCESS; +} + +// Collects all numeric ids in the module source into |numeric_ids|. +// This function is essentially a dry-run of spvTextToBinary. +spv_result_t GetNumericIds(const libspirv::AssemblyGrammar& grammar, + const spvtools::MessageConsumer& consumer, + const spv_text text, + std::set* numeric_ids) { + libspirv::AssemblyContext context(text, consumer); + + if (!text->str) return context.diagnostic() << "Missing assembly text."; + + if (!grammar.isValid()) { + return SPV_ERROR_INVALID_TABLE; + } + + // Skip past whitespace and comments. + context.advance(); + + while (context.hasText()) { + spv_instruction_t inst; + + if (spvTextEncodeOpcode(grammar, &context, &inst)) { + return SPV_ERROR_INVALID_TEXT; + } + + if (context.advance()) break; + } + + *numeric_ids = context.GetNumericIds(); + return SPV_SUCCESS; +} + +// Translates a given assembly language module into binary form. +// If a diagnostic is generated, it is not yet marked as being +// for a text-based input. +spv_result_t spvTextToBinaryInternal(const libspirv::AssemblyGrammar& grammar, + const spvtools::MessageConsumer& consumer, + const spv_text text, + const uint32_t options, + spv_binary* pBinary) { + // The ids in this set will have the same values both in source and binary. + // All other ids will be generated by filling in the gaps. + std::set ids_to_preserve; + + if (options & SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS) { + // Collect all numeric ids from the source into ids_to_preserve. + const spv_result_t result = + GetNumericIds(grammar, consumer, text, &ids_to_preserve); + if (result != SPV_SUCCESS) return result; + } + + libspirv::AssemblyContext context(text, consumer, std::move(ids_to_preserve)); + + if (!text->str) return context.diagnostic() << "Missing assembly text."; + + if (!grammar.isValid()) { + return SPV_ERROR_INVALID_TABLE; + } + if (!pBinary) return SPV_ERROR_INVALID_POINTER; + + std::vector instructions; + + // Skip past whitespace and comments. + context.advance(); + + while (context.hasText()) { + instructions.push_back({}); + spv_instruction_t& inst = instructions.back(); + + if (spvTextEncodeOpcode(grammar, &context, &inst)) { + return SPV_ERROR_INVALID_TEXT; + } + + if (context.advance()) break; + } + + size_t totalSize = SPV_INDEX_INSTRUCTION; + for (auto& inst : instructions) { + totalSize += inst.words.size(); + } + + uint32_t* data = new uint32_t[totalSize]; + if (!data) return SPV_ERROR_OUT_OF_MEMORY; + uint64_t currentIndex = SPV_INDEX_INSTRUCTION; + for (auto& inst : instructions) { + memcpy(data + currentIndex, inst.words.data(), + sizeof(uint32_t) * inst.words.size()); + currentIndex += inst.words.size(); + } + + if (auto error = SetHeader(grammar.target_env(), context.getBound(), data)) + return error; + + spv_binary binary = new spv_binary_t(); + if (!binary) { + delete[] data; + return SPV_ERROR_OUT_OF_MEMORY; + } + binary->code = data; + binary->wordCount = totalSize; + + *pBinary = binary; + + return SPV_SUCCESS; +} + +} // anonymous namespace + +spv_result_t spvTextToBinary(const spv_const_context context, + const char* input_text, + const size_t input_text_size, spv_binary* pBinary, + spv_diagnostic* pDiagnostic) { + return spvTextToBinaryWithOptions(context, input_text, input_text_size, + SPV_BINARY_TO_TEXT_OPTION_NONE, pBinary, + pDiagnostic); +} + +spv_result_t spvTextToBinaryWithOptions(const spv_const_context context, + const char* input_text, + const size_t input_text_size, + const uint32_t options, + spv_binary* pBinary, + spv_diagnostic* pDiagnostic) { + spv_context_t hijack_context = *context; + if (pDiagnostic) { + *pDiagnostic = nullptr; + libspirv::UseDiagnosticAsMessageConsumer(&hijack_context, pDiagnostic); + } + + spv_text_t text = {input_text, input_text_size}; + libspirv::AssemblyGrammar grammar(&hijack_context); + + spv_result_t result = spvTextToBinaryInternal( + grammar, hijack_context.consumer, &text, options, pBinary); + if (pDiagnostic && *pDiagnostic) (*pDiagnostic)->isTextSource = true; + + return result; +} + +void spvTextDestroy(spv_text text) { + if (!text) return; + delete[] text->str; + delete text; +} diff --git a/3rdparty/spirv-tools/source/text.h b/3rdparty/spirv-tools/source/text.h new file mode 100644 index 000000000..19990cc93 --- /dev/null +++ b/3rdparty/spirv-tools/source/text.h @@ -0,0 +1,53 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_TEXT_H_ +#define LIBSPIRV_TEXT_H_ + +#include + +#include "operand.h" +#include "spirv-tools/libspirv.h" +#include "spirv_constant.h" + +typedef enum spv_literal_type_t { + SPV_LITERAL_TYPE_INT_32, + SPV_LITERAL_TYPE_INT_64, + SPV_LITERAL_TYPE_UINT_32, + SPV_LITERAL_TYPE_UINT_64, + SPV_LITERAL_TYPE_FLOAT_32, + SPV_LITERAL_TYPE_FLOAT_64, + SPV_LITERAL_TYPE_STRING, + SPV_FORCE_32_BIT_ENUM(spv_literal_type_t) +} spv_literal_type_t; + +typedef struct spv_literal_t { + spv_literal_type_t type; + union value_t { + int32_t i32; + int64_t i64; + uint32_t u32; + uint64_t u64; + float f; + double d; + } value; + std::string str; // Special field for literal string. +} spv_literal_t; + +// Converts the given text string to a number/string literal and writes the +// result to *literal. String literals must be surrounded by double-quotes ("), +// which are then stripped. +spv_result_t spvTextToLiteral(const char* text, spv_literal_t* literal); + +#endif // LIBSPIRV_TEXT_H_ diff --git a/3rdparty/spirv-tools/source/text_handler.cpp b/3rdparty/spirv-tools/source/text_handler.cpp new file mode 100644 index 000000000..1a1b48d04 --- /dev/null +++ b/3rdparty/spirv-tools/source/text_handler.cpp @@ -0,0 +1,397 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "text_handler.h" + +#include +#include +#include +#include +#include + +#include "assembly_grammar.h" +#include "binary.h" +#include "ext_inst.h" +#include "instruction.h" +#include "opcode.h" +#include "text.h" +#include "util/bitutils.h" +#include "util/hex_float.h" +#include "util/parse_number.h" + +namespace { +// Advances |text| to the start of the next line and writes the new position to +// |position|. +spv_result_t advanceLine(spv_text text, spv_position position) { + while (true) { + if (position->index >= text->length) return SPV_END_OF_STREAM; + switch (text->str[position->index]) { + case '\0': + return SPV_END_OF_STREAM; + case '\n': + position->column = 0; + position->line++; + position->index++; + return SPV_SUCCESS; + default: + position->column++; + position->index++; + break; + } + } +} + +// Advances |text| to first non white space character and writes the new +// position to |position|. +// If a null terminator is found during the text advance, SPV_END_OF_STREAM is +// returned, SPV_SUCCESS otherwise. No error checking is performed on the +// parameters, its the users responsibility to ensure these are non null. +spv_result_t advance(spv_text text, spv_position position) { + // NOTE: Consume white space, otherwise don't advance. + if (position->index >= text->length) return SPV_END_OF_STREAM; + switch (text->str[position->index]) { + case '\0': + return SPV_END_OF_STREAM; + case ';': + if (spv_result_t error = advanceLine(text, position)) return error; + return advance(text, position); + case ' ': + case '\t': + case '\r': + position->column++; + position->index++; + return advance(text, position); + case '\n': + position->column = 0; + position->line++; + position->index++; + return advance(text, position); + default: + break; + } + return SPV_SUCCESS; +} + +// Fetches the next word from the given text stream starting from the given +// *position. On success, writes the decoded word into *word and updates +// *position to the location past the returned word. +// +// A word ends at the next comment or whitespace. However, double-quoted +// strings remain intact, and a backslash always escapes the next character. +spv_result_t getWord(spv_text text, spv_position position, std::string* word) { + if (!text->str || !text->length) return SPV_ERROR_INVALID_TEXT; + if (!position) return SPV_ERROR_INVALID_POINTER; + + const size_t start_index = position->index; + + bool quoting = false; + bool escaping = false; + + // NOTE: Assumes first character is not white space! + while (true) { + if (position->index >= text->length) { + word->assign(text->str + start_index, text->str + position->index); + return SPV_SUCCESS; + } + const char ch = text->str[position->index]; + if (ch == '\\') + escaping = !escaping; + else { + switch (ch) { + case '"': + if (!escaping) quoting = !quoting; + break; + case ' ': + case ';': + case '\t': + case '\n': + case '\r': + if (escaping || quoting) break; + // Fall through. + case '\0': { // NOTE: End of word found! + word->assign(text->str + start_index, text->str + position->index); + return SPV_SUCCESS; + } + default: + break; + } + escaping = false; + } + + position->column++; + position->index++; + } +} + +// Returns true if the characters in the text as position represent +// the start of an Opcode. +bool startsWithOp(spv_text text, spv_position position) { + if (text->length < position->index + 3) return false; + char ch0 = text->str[position->index]; + char ch1 = text->str[position->index + 1]; + char ch2 = text->str[position->index + 2]; + return ('O' == ch0 && 'p' == ch1 && ('A' <= ch2 && ch2 <= 'Z')); +} + +} // anonymous namespace + +namespace libspirv { + +const IdType kUnknownType = {0, false, IdTypeClass::kBottom}; + +// TODO(dneto): Reorder AssemblyContext definitions to match declaration order. + +// This represents all of the data that is only valid for the duration of +// a single compilation. +uint32_t AssemblyContext::spvNamedIdAssignOrGet(const char* textValue) { + if (!ids_to_preserve_.empty()) { + uint32_t id = 0; + if (spvutils::ParseNumber(textValue, &id)) { + if (ids_to_preserve_.find(id) != ids_to_preserve_.end()) { + bound_ = std::max(bound_, id + 1); + return id; + } + } + } + + const auto it = named_ids_.find(textValue); + if (it == named_ids_.end()) { + uint32_t id = next_id_++; + if (!ids_to_preserve_.empty()) { + while (ids_to_preserve_.find(id) != ids_to_preserve_.end()) { + id = next_id_++; + } + } + + named_ids_.emplace(textValue, id); + bound_ = std::max(bound_, id + 1); + return id; + } + + return it->second; +} + +uint32_t AssemblyContext::getBound() const { return bound_; } + +spv_result_t AssemblyContext::advance() { + return ::advance(text_, ¤t_position_); +} + +spv_result_t AssemblyContext::getWord(std::string* word, + spv_position next_position) { + *next_position = current_position_; + return ::getWord(text_, next_position, word); +} + +bool AssemblyContext::startsWithOp() { + return ::startsWithOp(text_, ¤t_position_); +} + +bool AssemblyContext::isStartOfNewInst() { + spv_position_t pos = current_position_; + if (::advance(text_, &pos)) return false; + if (::startsWithOp(text_, &pos)) return true; + + std::string word; + pos = current_position_; + if (::getWord(text_, &pos, &word)) return false; + if ('%' != word.front()) return false; + + if (::advance(text_, &pos)) return false; + if (::getWord(text_, &pos, &word)) return false; + if ("=" != word) return false; + + if (::advance(text_, &pos)) return false; + if (::startsWithOp(text_, &pos)) return true; + return false; +} + +char AssemblyContext::peek() const { + return text_->str[current_position_.index]; +} + +bool AssemblyContext::hasText() const { + return text_->length > current_position_.index; +} + +void AssemblyContext::seekForward(uint32_t size) { + current_position_.index += size; + current_position_.column += size; +} + +spv_result_t AssemblyContext::binaryEncodeU32(const uint32_t value, + spv_instruction_t* pInst) { + pInst->words.insert(pInst->words.end(), value); + return SPV_SUCCESS; +} + +spv_result_t AssemblyContext::binaryEncodeNumericLiteral( + const char* val, spv_result_t error_code, const IdType& type, + spv_instruction_t* pInst) { + using spvutils::EncodeNumberStatus; + // Populate the NumberType from the IdType for parsing. + spvutils::NumberType number_type; + switch (type.type_class) { + case IdTypeClass::kOtherType: + return diagnostic(SPV_ERROR_INTERNAL) + << "Unexpected numeric literal type"; + case IdTypeClass::kScalarIntegerType: + if (type.isSigned) { + number_type = {type.bitwidth, SPV_NUMBER_SIGNED_INT}; + } else { + number_type = {type.bitwidth, SPV_NUMBER_UNSIGNED_INT}; + } + break; + case IdTypeClass::kScalarFloatType: + number_type = {type.bitwidth, SPV_NUMBER_FLOATING}; + break; + case IdTypeClass::kBottom: + // kBottom means the type is unknown and we need to infer the type before + // parsing the number. The rule is: If there is a decimal point, treat + // the value as a floating point value, otherwise a integer value, then + // if the first char of the integer text is '-', treat the integer as a + // signed integer, otherwise an unsigned integer. + uint32_t bitwidth = static_cast(assumedBitWidth(type)); + if (strchr(val, '.')) { + number_type = {bitwidth, SPV_NUMBER_FLOATING}; + } else if (type.isSigned || val[0] == '-') { + number_type = {bitwidth, SPV_NUMBER_SIGNED_INT}; + } else { + number_type = {bitwidth, SPV_NUMBER_UNSIGNED_INT}; + } + break; + } + + std::string error_msg; + EncodeNumberStatus parse_status = ParseAndEncodeNumber( + val, number_type, + [this, pInst](uint32_t d) { this->binaryEncodeU32(d, pInst); }, + &error_msg); + switch (parse_status) { + case EncodeNumberStatus::kSuccess: + return SPV_SUCCESS; + case EncodeNumberStatus::kInvalidText: + return diagnostic(error_code) << error_msg; + case EncodeNumberStatus::kUnsupported: + return diagnostic(SPV_ERROR_INTERNAL) << error_msg; + case EncodeNumberStatus::kInvalidUsage: + return diagnostic(SPV_ERROR_INVALID_TEXT) << error_msg; + } + // This line is not reachable, only added to satisfy the compiler. + return diagnostic(SPV_ERROR_INTERNAL) + << "Unexpected result code from ParseAndEncodeNumber()"; +} + +spv_result_t AssemblyContext::binaryEncodeString(const char* value, + spv_instruction_t* pInst) { + const size_t length = strlen(value); + const size_t wordCount = (length / 4) + 1; + const size_t oldWordCount = pInst->words.size(); + const size_t newWordCount = oldWordCount + wordCount; + + // TODO(dneto): We can just defer this check until later. + if (newWordCount > SPV_LIMIT_INSTRUCTION_WORD_COUNT_MAX) { + return diagnostic() << "Instruction too long: more than " + << SPV_LIMIT_INSTRUCTION_WORD_COUNT_MAX << " words."; + } + + pInst->words.resize(newWordCount); + + // Make sure all the bytes in the last word are 0, in case we only + // write a partial word at the end. + pInst->words.back() = 0; + + char* dest = (char*)&pInst->words[oldWordCount]; + strncpy(dest, value, length); + + return SPV_SUCCESS; +} + +spv_result_t AssemblyContext::recordTypeDefinition( + const spv_instruction_t* pInst) { + uint32_t value = pInst->words[1]; + if (types_.find(value) != types_.end()) { + return diagnostic() << "Value " << value + << " has already been used to generate a type"; + } + + if (pInst->opcode == SpvOpTypeInt) { + if (pInst->words.size() != 4) + return diagnostic() << "Invalid OpTypeInt instruction"; + types_[value] = {pInst->words[2], pInst->words[3] != 0, + IdTypeClass::kScalarIntegerType}; + } else if (pInst->opcode == SpvOpTypeFloat) { + if (pInst->words.size() != 3) + return diagnostic() << "Invalid OpTypeFloat instruction"; + types_[value] = {pInst->words[2], false, IdTypeClass::kScalarFloatType}; + } else { + types_[value] = {0, false, IdTypeClass::kOtherType}; + } + return SPV_SUCCESS; +} + +IdType AssemblyContext::getTypeOfTypeGeneratingValue(uint32_t value) const { + auto type = types_.find(value); + if (type == types_.end()) { + return kUnknownType; + } + return std::get<1>(*type); +} + +IdType AssemblyContext::getTypeOfValueInstruction(uint32_t value) const { + auto type_value = value_types_.find(value); + if (type_value == value_types_.end()) { + return {0, false, IdTypeClass::kBottom}; + } + return getTypeOfTypeGeneratingValue(std::get<1>(*type_value)); +} + +spv_result_t AssemblyContext::recordTypeIdForValue(uint32_t value, + uint32_t type) { + bool successfully_inserted = false; + std::tie(std::ignore, successfully_inserted) = + value_types_.insert(std::make_pair(value, type)); + if (!successfully_inserted) + return diagnostic() << "Value is being defined a second time"; + return SPV_SUCCESS; +} + +spv_result_t AssemblyContext::recordIdAsExtInstImport( + uint32_t id, spv_ext_inst_type_t type) { + bool successfully_inserted = false; + std::tie(std::ignore, successfully_inserted) = + import_id_to_ext_inst_type_.insert(std::make_pair(id, type)); + if (!successfully_inserted) + return diagnostic() << "Import Id is being defined a second time"; + return SPV_SUCCESS; +} + +spv_ext_inst_type_t AssemblyContext::getExtInstTypeForId(uint32_t id) const { + auto type = import_id_to_ext_inst_type_.find(id); + if (type == import_id_to_ext_inst_type_.end()) { + return SPV_EXT_INST_TYPE_NONE; + } + return std::get<1>(*type); +} + +std::set AssemblyContext::GetNumericIds() const { + std::set ids; + for (const auto& kv : named_ids_) { + uint32_t id; + if (spvutils::ParseNumber(kv.first.c_str(), &id)) ids.insert(id); + } + return ids; +} + +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/text_handler.h b/3rdparty/spirv-tools/source/text_handler.h new file mode 100644 index 000000000..e49b51bbd --- /dev/null +++ b/3rdparty/spirv-tools/source/text_handler.h @@ -0,0 +1,259 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_TEXT_HANDLER_H_ +#define LIBSPIRV_TEXT_HANDLER_H_ + +#include +#include +#include +#include + +#include "diagnostic.h" +#include "instruction.h" +#include "message.h" +#include "spirv-tools/libspirv.h" +#include "text.h" + +namespace libspirv { +// Structures + +// This is a lattice for tracking types. +enum class IdTypeClass { + kBottom = 0, // We have no information yet. + kScalarIntegerType, + kScalarFloatType, + kOtherType +}; + +// Contains ID type information that needs to be tracked across all Ids. +// Bitwidth is only valid when type_class is kScalarIntegerType or +// kScalarFloatType. +struct IdType { + uint32_t bitwidth; // Safe to assume that we will not have > 2^32 bits. + bool isSigned; // This is only significant if type_class is integral. + IdTypeClass type_class; +}; + +// Default equality operator for IdType. Tests if all members are the same. +inline bool operator==(const IdType& first, const IdType& second) { + return (first.bitwidth == second.bitwidth) && + (first.isSigned == second.isSigned) && + (first.type_class == second.type_class); +} + +// Tests whether any member of the IdTypes do not match. +inline bool operator!=(const IdType& first, const IdType& second) { + return !(first == second); +} + +// A value representing an unknown type. +extern const IdType kUnknownType; + +// Returns true if the type is a scalar integer type. +inline bool isScalarIntegral(const IdType& type) { + return type.type_class == IdTypeClass::kScalarIntegerType; +} + +// Returns true if the type is a scalar floating point type. +inline bool isScalarFloating(const IdType& type) { + return type.type_class == IdTypeClass::kScalarFloatType; +} + +// Returns the number of bits in the type. +// This is only valid for bottom, scalar integer, and scalar floating +// classes. For bottom, assume 32 bits. +inline int assumedBitWidth(const IdType& type) { + switch (type.type_class) { + case IdTypeClass::kBottom: + return 32; + case IdTypeClass::kScalarIntegerType: + case IdTypeClass::kScalarFloatType: + return type.bitwidth; + default: + break; + } + // We don't care about this case. + return 0; +} + +// A templated class with a static member function Clamp, where Clamp +// sets a referenced value of type T to 0 if T is an unsigned +// integer type, and returns true if it modified the referenced +// value. +template +class ClampToZeroIfUnsignedType { + public: + // The default specialization does not clamp the value. + static bool Clamp(T*) { return false; } +}; + +// The specialization of ClampToZeroIfUnsignedType for unsigned integer +// types. +template +class ClampToZeroIfUnsignedType< + T, typename std::enable_if::value>::type> { + public: + static bool Clamp(T* value_pointer) { + if (*value_pointer) { + *value_pointer = 0; + return true; + } + return false; + } +}; + +// Encapsulates the data used during the assembly of a SPIR-V module. +class AssemblyContext { + public: + AssemblyContext(spv_text text, const spvtools::MessageConsumer& consumer, + std::set&& ids_to_preserve = std::set()) + : current_position_({}), + consumer_(consumer), + text_(text), + bound_(1), + next_id_(1), + ids_to_preserve_(std::move(ids_to_preserve)) {} + + // Assigns a new integer value to the given text ID, or returns the previously + // assigned integer value if the ID has been seen before. + uint32_t spvNamedIdAssignOrGet(const char* textValue); + + // Returns the largest largest numeric ID that has been assigned. + uint32_t getBound() const; + + // Advances position to point to the next word in the input stream. + // Returns SPV_SUCCESS on success. + spv_result_t advance(); + + // Sets word to the next word in the input text. Fills next_position with + // the next location past the end of the word. + spv_result_t getWord(std::string* word, spv_position next_position); + + // Returns true if the next word in the input is the start of a new Opcode. + bool startsWithOp(); + + // Returns true if the next word in the input is the start of a new + // instruction. + bool isStartOfNewInst(); + + // Returns a diagnostic object initialized with current position in the input + // stream, and for the given error code. Any data written to this object will + // show up in pDiagnsotic on destruction. + DiagnosticStream diagnostic(spv_result_t error) { + return DiagnosticStream(current_position_, consumer_, error); + } + + // Returns a diagnostic object with the default assembly error code. + DiagnosticStream diagnostic() { + // The default failure for assembly is invalid text. + return diagnostic(SPV_ERROR_INVALID_TEXT); + } + + // Returns then next character in the input stream. + char peek() const; + + // Returns true if there is more text in the input stream. + bool hasText() const; + + // Seeks the input stream forward by 'size' characters. + void seekForward(uint32_t size); + + // Sets the current position in the input stream to the given position. + void setPosition(const spv_position_t& newPosition) { + current_position_ = newPosition; + } + + // Returns the current position in the input stream. + const spv_position_t& position() const { return current_position_; } + + // Appends the given 32-bit value to the given instruction. + // Returns SPV_SUCCESS if the value could be correctly inserted in the + // instruction. + spv_result_t binaryEncodeU32(const uint32_t value, spv_instruction_t* pInst); + + // Appends the given string to the given instruction. + // Returns SPV_SUCCESS if the value could be correctly inserted in the + // instruction. + spv_result_t binaryEncodeString(const char* value, spv_instruction_t* pInst); + + // Appends the given numeric literal to the given instruction. + // Validates and respects the bitwidth supplied in the IdType argument. + // If the type is of class kBottom the value will be encoded as a + // 32-bit integer. + // Returns SPV_SUCCESS if the value could be correctly added to the + // instruction. Returns the given error code on failure, and emits + // a diagnostic if that error code is not SPV_FAILED_MATCH. + spv_result_t binaryEncodeNumericLiteral(const char* numeric_literal, + spv_result_t error_code, + const IdType& type, + spv_instruction_t* pInst); + + // Returns the IdType associated with this type-generating value. + // If the type has not been previously recorded with recordTypeDefinition, + // kUnknownType will be returned. + IdType getTypeOfTypeGeneratingValue(uint32_t value) const; + + // Returns the IdType that represents the return value of this Value + // generating instruction. + // If the value has not been recorded with recordTypeIdForValue, or the type + // could not be determined kUnknownType will be returned. + IdType getTypeOfValueInstruction(uint32_t value) const; + + // Tracks the type-defining instruction. The result of the tracking can + // later be queried using getValueType. + // pInst is expected to be completely filled in by the time this instruction + // is called. + // Returns SPV_SUCCESS on success, or SPV_ERROR_INVALID_VALUE on error. + spv_result_t recordTypeDefinition(const spv_instruction_t* pInst); + + // Tracks the relationship between the value and its type. + spv_result_t recordTypeIdForValue(uint32_t value, uint32_t type); + + // Records the given Id as being the import of the given extended instruction + // type. + spv_result_t recordIdAsExtInstImport(uint32_t id, spv_ext_inst_type_t type); + + // Returns the extended instruction type corresponding to the import with + // the given Id, if it exists. Returns SPV_EXT_INST_TYPE_NONE if the + // id is not the id for an extended instruction type. + spv_ext_inst_type_t getExtInstTypeForId(uint32_t id) const; + + // Returns a set consisting of each ID generated by spvNamedIdAssignOrGet from + // a numeric ID text representation. For example, generated from "%12" but not + // from "%foo". + std::set GetNumericIds() const; + + private: + // Maps ID names to their corresponding numerical ids. + using spv_named_id_table = std::unordered_map; + // Maps type-defining IDs to their IdType. + using spv_id_to_type_map = std::unordered_map; + // Maps Ids to the id of their type. + using spv_id_to_type_id = std::unordered_map; + + spv_named_id_table named_ids_; + spv_id_to_type_map types_; + spv_id_to_type_id value_types_; + // Maps an extended instruction import Id to the extended instruction type. + std::unordered_map import_id_to_ext_inst_type_; + spv_position_t current_position_; + spvtools::MessageConsumer consumer_; + spv_text text_; + uint32_t bound_; + uint32_t next_id_; + std::set ids_to_preserve_; +}; +} // namespace libspirv +#endif // _LIBSPIRV_TEXT_HANDLER_H_ diff --git a/3rdparty/spirv-tools/source/util/bit_stream.cpp b/3rdparty/spirv-tools/source/util/bit_stream.cpp new file mode 100644 index 000000000..77e2bc17d --- /dev/null +++ b/3rdparty/spirv-tools/source/util/bit_stream.cpp @@ -0,0 +1,437 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include + +#include "util/bit_stream.h" + +namespace spvutils { + +namespace { + +// Returns if the system is little-endian. Unfortunately only works during +// runtime. +bool IsLittleEndian() { + // This constant value allows the detection of the host machine's endianness. + // Accessing it as an array of bytes is valid due to C++11 section 3.10 + // paragraph 10. + static const uint16_t kFF00 = 0xff00; + return reinterpret_cast(&kFF00)[0] == 0; +} + +// Copies bytes from the given buffer to a uint64_t buffer. +// Motivation: casting uint64_t* to uint8_t* is ok. Casting in the other +// direction is only advisable if uint8_t* is aligned to 64-bit word boundary. +std::vector ToBuffer64(const void* buffer, size_t num_bytes) { + std::vector out; + out.resize((num_bytes + 7) / 8, 0); + memcpy(out.data(), buffer, num_bytes); + return out; +} + +// Copies uint8_t buffer to a uint64_t buffer. +std::vector ToBuffer64(const std::vector& in) { + return ToBuffer64(in.data(), in.size()); +} + +// Returns uint64_t containing the same bits as |val|. +// Type size must be less than 8 bytes. +template +uint64_t ToU64(T val) { + static_assert(sizeof(T) <= 8, "Type size too big"); + uint64_t val64 = 0; + std::memcpy(&val64, &val, sizeof(T)); + return val64; +} + +// Returns value of type T containing the same bits as |val64|. +// Type size must be less than 8 bytes. Upper (unused) bits of |val64| must be +// zero (irrelevant, but is checked with assertion). +template +T FromU64(uint64_t val64) { + assert(sizeof(T) == 8 || (val64 >> (sizeof(T) * 8)) == 0); + static_assert(sizeof(T) <= 8, "Type size too big"); + T val = 0; + std::memcpy(&val, &val64, sizeof(T)); + return val; +} + +// Writes bits from |val| to |writer| in chunks of size |chunk_length|. +// Signal bit is used to signal if the reader should expect another chunk: +// 0 - no more chunks to follow +// 1 - more chunks to follow +// If number of written bits reaches |max_payload| last chunk is truncated. +void WriteVariableWidthInternal(BitWriterInterface* writer, uint64_t val, + size_t chunk_length, size_t max_payload) { + assert(chunk_length > 0); + assert(chunk_length < max_payload); + assert(max_payload == 64 || (val >> max_payload) == 0); + + if (val == 0) { + // Split in two writes for more readable logging. + writer->WriteBits(0, chunk_length); + writer->WriteBits(0, 1); + return; + } + + size_t payload_written = 0; + + while (val) { + if (payload_written + chunk_length >= max_payload) { + // This has to be the last chunk. + // There is no need for the signal bit and the chunk can be truncated. + const size_t left_to_write = max_payload - payload_written; + assert((val >> left_to_write) == 0); + writer->WriteBits(val, left_to_write); + break; + } + + writer->WriteBits(val, chunk_length); + payload_written += chunk_length; + val = val >> chunk_length; + + // Write a single bit to signal if there is more to come. + writer->WriteBits(val ? 1 : 0, 1); + } +} + +// Reads data written with WriteVariableWidthInternal. |chunk_length| and +// |max_payload| should be identical to those used to write the data. +// Returns false if the stream ends prematurely. +bool ReadVariableWidthInternal(BitReaderInterface* reader, uint64_t* val, + size_t chunk_length, size_t max_payload) { + assert(chunk_length > 0); + assert(chunk_length <= max_payload); + size_t payload_read = 0; + + while (payload_read + chunk_length < max_payload) { + uint64_t bits = 0; + if (reader->ReadBits(&bits, chunk_length) != chunk_length) return false; + + *val |= bits << payload_read; + payload_read += chunk_length; + + uint64_t more_to_come = 0; + if (reader->ReadBits(&more_to_come, 1) != 1) return false; + + if (!more_to_come) { + return true; + } + } + + // Need to read the last chunk which may be truncated. No signal bit follows. + uint64_t bits = 0; + const size_t left_to_read = max_payload - payload_read; + if (reader->ReadBits(&bits, left_to_read) != left_to_read) return false; + + *val |= bits << payload_read; + return true; +} + +// Calls WriteVariableWidthInternal with the right max_payload argument. +template +void WriteVariableWidthUnsigned(BitWriterInterface* writer, T val, + size_t chunk_length) { + static_assert(std::is_unsigned::value, "Type must be unsigned"); + static_assert(std::is_integral::value, "Type must be integral"); + WriteVariableWidthInternal(writer, val, chunk_length, sizeof(T) * 8); +} + +// Calls ReadVariableWidthInternal with the right max_payload argument. +template +bool ReadVariableWidthUnsigned(BitReaderInterface* reader, T* val, + size_t chunk_length) { + static_assert(std::is_unsigned::value, "Type must be unsigned"); + static_assert(std::is_integral::value, "Type must be integral"); + uint64_t val64 = 0; + if (!ReadVariableWidthInternal(reader, &val64, chunk_length, sizeof(T) * 8)) + return false; + *val = static_cast(val64); + assert(*val == val64); + return true; +} + +// Encodes signed |val| to an unsigned value and calls +// WriteVariableWidthInternal with the right max_payload argument. +template +void WriteVariableWidthSigned(BitWriterInterface* writer, T val, + size_t chunk_length, size_t zigzag_exponent) { + static_assert(std::is_signed::value, "Type must be signed"); + static_assert(std::is_integral::value, "Type must be integral"); + WriteVariableWidthInternal(writer, EncodeZigZag(val, zigzag_exponent), + chunk_length, sizeof(T) * 8); +} + +// Calls ReadVariableWidthInternal with the right max_payload argument +// and decodes the value. +template +bool ReadVariableWidthSigned(BitReaderInterface* reader, T* val, + size_t chunk_length, size_t zigzag_exponent) { + static_assert(std::is_signed::value, "Type must be signed"); + static_assert(std::is_integral::value, "Type must be integral"); + uint64_t encoded = 0; + if (!ReadVariableWidthInternal(reader, &encoded, chunk_length, sizeof(T) * 8)) + return false; + + const int64_t decoded = DecodeZigZag(encoded, zigzag_exponent); + + *val = static_cast(decoded); + assert(*val == decoded); + return true; +} + +} // namespace + +size_t Log2U64(uint64_t val) { + size_t res = 0; + + if (val & 0xFFFFFFFF00000000) { + val >>= 32; + res |= 32; + } + + if (val & 0xFFFF0000) { + val >>= 16; + res |= 16; + } + + if (val & 0xFF00) { + val >>= 8; + res |= 8; + } + + if (val & 0xF0) { + val >>= 4; + res |= 4; + } + + if (val & 0xC) { + val >>= 2; + res |= 2; + } + + if (val & 0x2) { + res |= 1; + } + + return res; +} + +void BitWriterInterface::WriteVariableWidthU64(uint64_t val, + size_t chunk_length) { + WriteVariableWidthUnsigned(this, val, chunk_length); +} + +void BitWriterInterface::WriteVariableWidthU32(uint32_t val, + size_t chunk_length) { + WriteVariableWidthUnsigned(this, val, chunk_length); +} + +void BitWriterInterface::WriteVariableWidthU16(uint16_t val, + size_t chunk_length) { + WriteVariableWidthUnsigned(this, val, chunk_length); +} + +void BitWriterInterface::WriteVariableWidthU8(uint8_t val, + size_t chunk_length) { + WriteVariableWidthUnsigned(this, val, chunk_length); +} + +void BitWriterInterface::WriteVariableWidthS64(int64_t val, size_t chunk_length, + size_t zigzag_exponent) { + WriteVariableWidthSigned(this, val, chunk_length, zigzag_exponent); +} + +void BitWriterInterface::WriteVariableWidthS32(int32_t val, size_t chunk_length, + size_t zigzag_exponent) { + WriteVariableWidthSigned(this, val, chunk_length, zigzag_exponent); +} + +void BitWriterInterface::WriteVariableWidthS16(int16_t val, size_t chunk_length, + size_t zigzag_exponent) { + WriteVariableWidthSigned(this, val, chunk_length, zigzag_exponent); +} + +void BitWriterInterface::WriteVariableWidthS8(int8_t val, size_t chunk_length, + size_t zigzag_exponent) { + WriteVariableWidthSigned(this, val, chunk_length, zigzag_exponent); +} + +void BitWriterInterface::WriteFixedWidth(uint64_t val, uint64_t max_val) { + if (val > max_val) { + assert(0 && "WriteFixedWidth: value too wide"); + return; + } + + const size_t num_bits = 1 + Log2U64(max_val); + WriteBits(val, num_bits); +} + +BitWriterWord64::BitWriterWord64(size_t reserve_bits) : end_(0) { + buffer_.reserve(NumBitsToNumWords<64>(reserve_bits)); +} + +void BitWriterWord64::WriteBits(uint64_t bits, size_t num_bits) { + // Check that |bits| and |num_bits| are valid and consistent. + assert(num_bits <= 64); + const bool is_little_endian = IsLittleEndian(); + assert(is_little_endian && "Big-endian architecture support not implemented"); + if (!is_little_endian) return; + + if (num_bits == 0) return; + + bits = GetLowerBits(bits, num_bits); + + EmitSequence(bits, num_bits); + + // Offset from the start of the current word. + const size_t offset = end_ % 64; + + if (offset == 0) { + // If no offset, simply add |bits| as a new word to the buffer_. + buffer_.push_back(bits); + } else { + // Shift bits and add them to the current word after offset. + const uint64_t first_word = bits << offset; + buffer_.back() |= first_word; + + // If we don't overflow to the next word, there is nothing more to do. + + if (offset + num_bits > 64) { + // We overflow to the next word. + const uint64_t second_word = bits >> (64 - offset); + // Add remaining bits as a new word to buffer_. + buffer_.push_back(second_word); + } + } + + // Move end_ into position for next write. + end_ += num_bits; + assert(buffer_.size() * 64 >= end_); +} + +bool BitReaderInterface::ReadVariableWidthU64(uint64_t* val, + size_t chunk_length) { + return ReadVariableWidthUnsigned(this, val, chunk_length); +} + +bool BitReaderInterface::ReadVariableWidthU32(uint32_t* val, + size_t chunk_length) { + return ReadVariableWidthUnsigned(this, val, chunk_length); +} + +bool BitReaderInterface::ReadVariableWidthU16(uint16_t* val, + size_t chunk_length) { + return ReadVariableWidthUnsigned(this, val, chunk_length); +} + +bool BitReaderInterface::ReadVariableWidthU8(uint8_t* val, + size_t chunk_length) { + return ReadVariableWidthUnsigned(this, val, chunk_length); +} + +bool BitReaderInterface::ReadVariableWidthS64(int64_t* val, size_t chunk_length, + size_t zigzag_exponent) { + return ReadVariableWidthSigned(this, val, chunk_length, zigzag_exponent); +} + +bool BitReaderInterface::ReadVariableWidthS32(int32_t* val, size_t chunk_length, + size_t zigzag_exponent) { + return ReadVariableWidthSigned(this, val, chunk_length, zigzag_exponent); +} + +bool BitReaderInterface::ReadVariableWidthS16(int16_t* val, size_t chunk_length, + size_t zigzag_exponent) { + return ReadVariableWidthSigned(this, val, chunk_length, zigzag_exponent); +} + +bool BitReaderInterface::ReadVariableWidthS8(int8_t* val, size_t chunk_length, + size_t zigzag_exponent) { + return ReadVariableWidthSigned(this, val, chunk_length, zigzag_exponent); +} + +bool BitReaderInterface::ReadFixedWidth(uint64_t* val, uint64_t max_val) { + const size_t num_bits = 1 + Log2U64(max_val); + return ReadBits(val, num_bits) == num_bits; +} + +BitReaderWord64::BitReaderWord64(std::vector&& buffer) + : buffer_(std::move(buffer)), pos_(0) {} + +BitReaderWord64::BitReaderWord64(const std::vector& buffer) + : buffer_(ToBuffer64(buffer)), pos_(0) {} + +BitReaderWord64::BitReaderWord64(const void* buffer, size_t num_bytes) + : buffer_(ToBuffer64(buffer, num_bytes)), pos_(0) {} + +size_t BitReaderWord64::ReadBits(uint64_t* bits, size_t num_bits) { + assert(num_bits <= 64); + const bool is_little_endian = IsLittleEndian(); + assert(is_little_endian && "Big-endian architecture support not implemented"); + if (!is_little_endian) return 0; + + if (ReachedEnd()) return 0; + + // Index of the current word. + const size_t index = pos_ / 64; + + // Bit position in the current word where we start reading. + const size_t offset = pos_ % 64; + + // Read all bits from the current word (it might be too much, but + // excessive bits will be removed later). + *bits = buffer_[index] >> offset; + + const size_t num_read_from_first_word = std::min(64 - offset, num_bits); + pos_ += num_read_from_first_word; + + if (pos_ >= buffer_.size() * 64) { + // Reached end of buffer_. + EmitSequence(*bits, num_read_from_first_word); + return num_read_from_first_word; + } + + if (offset + num_bits > 64) { + // Requested |num_bits| overflows to next word. + // Write all bits from the beginning of next word to *bits after offset. + *bits |= buffer_[index + 1] << (64 - offset); + pos_ += offset + num_bits - 64; + } + + // We likely have written more bits than requested. Clear excessive bits. + *bits = GetLowerBits(*bits, num_bits); + EmitSequence(*bits, num_bits); + return num_bits; +} + +bool BitReaderWord64::ReachedEnd() const { return pos_ >= buffer_.size() * 64; } + +bool BitReaderWord64::OnlyZeroesLeft() const { + if (ReachedEnd()) return true; + + const size_t index = pos_ / 64; + if (index < buffer_.size() - 1) return false; + + assert(index == buffer_.size() - 1); + + const size_t offset = pos_ % 64; + const uint64_t remaining_bits = buffer_[index] >> offset; + return !remaining_bits; +} + +} // namespace spvutils diff --git a/3rdparty/spirv-tools/source/util/bit_stream.h b/3rdparty/spirv-tools/source/util/bit_stream.h new file mode 100644 index 000000000..247ae3545 --- /dev/null +++ b/3rdparty/spirv-tools/source/util/bit_stream.h @@ -0,0 +1,447 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Contains utils for reading, writing and debug printing bit streams. + +#ifndef LIBSPIRV_UTIL_BIT_STREAM_H_ +#define LIBSPIRV_UTIL_BIT_STREAM_H_ + +#include +#include +#include +#include +#include +#include +#include + +namespace spvutils { + +// Returns rounded down log2(val). log2(0) is considered 0. +size_t Log2U64(uint64_t val); + +// Terminology: +// Bits - usually used for a uint64 word, first bit is the lowest. +// Stream - std::string of '0' and '1', read left-to-right, +// i.e. first bit is at the front and not at the end as in +// std::bitset::to_string(). +// Bitset - std::bitset corresponding to uint64 bits and to reverse(stream). + +// Converts number of bits to a respective number of chunks of size N. +// For example NumBitsToNumWords<8> returns how many bytes are needed to store +// |num_bits|. +template +inline size_t NumBitsToNumWords(size_t num_bits) { + return (num_bits + (N - 1)) / N; +} + +// Returns value of the same type as |in|, where all but the first |num_bits| +// are set to zero. +template +inline T GetLowerBits(T in, size_t num_bits) { + return sizeof(T) * 8 == num_bits ? in : in & T((T(1) << num_bits) - T(1)); +} + +// Encodes signed integer as unsigned in zigzag order: +// 0 -> 0 +// -1 -> 1 +// 1 -> 2 +// -2 -> 3 +// 2 -> 4 +// Motivation: -1 is 0xFF...FF what doesn't work very well with +// WriteVariableWidth which prefers to have as many 0 bits as possible. +inline uint64_t EncodeZigZag(int64_t val) { return (val << 1) ^ (val >> 63); } + +// Decodes signed integer encoded with EncodeZigZag. +inline int64_t DecodeZigZag(uint64_t val) { + if (val & 1) { + // Negative. + // 1 -> -1 + // 3 -> -2 + // 5 -> -3 + return -1 - (val >> 1); + } else { + // Non-negative. + // 0 -> 0 + // 2 -> 1 + // 4 -> 2 + return val >> 1; + } +} + +// Encodes signed integer as unsigned. This is a generalized version of +// EncodeZigZag, designed to favor small positive numbers. +// Values are transformed in blocks of 2^|block_exponent|. +// If |block_exponent| is zero, then this degenerates into normal EncodeZigZag. +// Example when |block_exponent| is 1 (return value is the index): +// 0, 1, -1, -2, 2, 3, -3, -4, 4, 5, -5, -6, 6, 7, -7, -8 +// Example when |block_exponent| is 2: +// 0, 1, 2, 3, -1, -2, -3, -4, 4, 5, 6, 7, -5, -6, -7, -8 +inline uint64_t EncodeZigZag(int64_t val, size_t block_exponent) { + assert(block_exponent < 64); + const uint64_t uval = static_cast(val >= 0 ? val : -val - 1); + const uint64_t block_num = + ((uval >> block_exponent) << 1) + (val >= 0 ? 0 : 1); + const uint64_t pos = GetLowerBits(uval, block_exponent); + return (block_num << block_exponent) + pos; +} + +// Decodes signed integer encoded with EncodeZigZag. |block_exponent| must be +// the same. +inline int64_t DecodeZigZag(uint64_t val, size_t block_exponent) { + assert(block_exponent < 64); + const uint64_t block_num = val >> block_exponent; + const uint64_t pos = GetLowerBits(val, block_exponent); + if (block_num & 1) { + // Negative. + return -1LL - ((block_num >> 1) << block_exponent) - pos; + } else { + // Positive. + return ((block_num >> 1) << block_exponent) + pos; + } +} + +// Converts |buffer| to a stream of '0' and '1'. +template +std::string BufferToStream(const std::vector& buffer) { + std::stringstream ss; + for (auto it = buffer.begin(); it != buffer.end(); ++it) { + std::string str = std::bitset(*it).to_string(); + // Strings generated by std::bitset::to_string are read right to left. + // Reversing to left to right. + std::reverse(str.begin(), str.end()); + ss << str; + } + return ss.str(); +} + +// Converts a left-to-right input string of '0' and '1' to a buffer of |T| +// words. +template +std::vector StreamToBuffer(std::string str) { + // The input string is left-to-right, the input argument of std::bitset needs + // to right-to-left. Instead of reversing tokens, reverse the entire string + // and iterate tokens from end to begin. + std::reverse(str.begin(), str.end()); + const int word_size = static_cast(sizeof(T) * 8); + const int str_length = static_cast(str.length()); + std::vector buffer; + buffer.reserve(NumBitsToNumWords(str.length())); + for (int index = str_length - word_size; index >= 0; index -= word_size) { + buffer.push_back(static_cast( + std::bitset(str, index, word_size).to_ullong())); + } + const size_t suffix_length = str.length() % word_size; + if (suffix_length != 0) { + buffer.push_back(static_cast( + std::bitset(str, 0, suffix_length).to_ullong())); + } + return buffer; +} + +// Adds '0' chars at the end of the string until the size is a multiple of N. +template +inline std::string PadToWord(std::string&& str) { + const size_t tail_length = str.size() % N; + if (tail_length != 0) str += std::string(N - tail_length, '0'); + return str; +} + +// Adds '0' chars at the end of the string until the size is a multiple of N. +template +inline std::string PadToWord(const std::string& str) { + return PadToWord(std::string(str)); +} + +// Converts a left-to-right stream of bits to std::bitset. +template +inline std::bitset StreamToBitset(std::string str) { + std::reverse(str.begin(), str.end()); + return std::bitset(str); +} + +// Converts first |num_bits| of std::bitset to a left-to-right stream of bits. +template +inline std::string BitsetToStream(const std::bitset& bits, + size_t num_bits = N) { + std::string str = bits.to_string().substr(N - num_bits); + std::reverse(str.begin(), str.end()); + return str; +} + +// Converts a left-to-right stream of bits to uint64. +inline uint64_t StreamToBits(std::string str) { + std::reverse(str.begin(), str.end()); + return std::bitset<64>(str).to_ullong(); +} + +// Converts first |num_bits| stored in uint64 to a left-to-right stream of bits. +inline std::string BitsToStream(uint64_t bits, size_t num_bits = 64) { + std::bitset<64> bitset(bits); + return BitsetToStream(bitset, num_bits); +} + +// Base class for writing sequences of bits. +class BitWriterInterface { + public: + BitWriterInterface() {} + virtual ~BitWriterInterface() {} + + // Writes lower |num_bits| in |bits| to the stream. + // |num_bits| must be no greater than 64. + virtual void WriteBits(uint64_t bits, size_t num_bits) = 0; + + // Writes left-to-right string of '0' and '1' to stream. + // String length must be no greater than 64. + // Note: "01" will be writen as 0x2, not 0x1. The string doesn't represent + // numbers but a stream of bits in the order they come from encoder. + virtual void WriteStream(const std::string& bits) { + WriteBits(StreamToBits(bits), bits.length()); + } + + // Writes lower |num_bits| in |bits| to the stream. + // |num_bits| must be no greater than 64. + template + void WriteBitset(const std::bitset& bits, size_t num_bits = N) { + WriteBits(bits.to_ullong(), num_bits); + } + + // Writes bits from value of type |T| to the stream. No encoding is done. + // Always writes 8 * sizeof(T) bits. + template + void WriteUnencoded(T val) { + static_assert(sizeof(T) <= 64, "Type size too large"); + uint64_t bits = 0; + memcpy(&bits, &val, sizeof(T)); + WriteBits(bits, sizeof(T) * 8); + } + + // Writes |val| in chunks of size |chunk_length| followed by a signal bit: + // 0 - no more chunks to follow + // 1 - more chunks to follow + // for example 255 is encoded into 1111 1 1111 0 for chunk length 4. + // The last chunk can be truncated and signal bit omitted, if the entire + // payload (for example 16 bit for uint16_t has already been written). + void WriteVariableWidthU64(uint64_t val, size_t chunk_length); + void WriteVariableWidthU32(uint32_t val, size_t chunk_length); + void WriteVariableWidthU16(uint16_t val, size_t chunk_length); + void WriteVariableWidthU8(uint8_t val, size_t chunk_length); + void WriteVariableWidthS64(int64_t val, size_t chunk_length, + size_t zigzag_exponent); + void WriteVariableWidthS32(int32_t val, size_t chunk_length, + size_t zigzag_exponent); + void WriteVariableWidthS16(int16_t val, size_t chunk_length, + size_t zigzag_exponent); + void WriteVariableWidthS8(int8_t val, size_t chunk_length, + size_t zigzag_exponent); + + // Writes |val| using fixed bit width. Bit width is determined by |max_val|: + // max_val 0 -> bit width 1 + // max_val 1 -> bit width 1 + // max_val 2 -> bit width 2 + // max_val 3 -> bit width 2 + // max_val 4 -> bit width 3 + // max_val 5 -> bit width 3 + // max_val 8 -> bit width 4 + // max_val n -> bit width 1 + floor(log2(n)) + // |val| needs to be <= |max_val|. + void WriteFixedWidth(uint64_t val, uint64_t max_val); + + // Returns number of bits written. + virtual size_t GetNumBits() const = 0; + + // Provides direct access to the buffer data if implemented. + virtual const uint8_t* GetData() const { return nullptr; } + + // Returns buffer size in bytes. + size_t GetDataSizeBytes() const { return NumBitsToNumWords<8>(GetNumBits()); } + + // Generates and returns byte array containing written bits. + virtual std::vector GetDataCopy() const = 0; + + BitWriterInterface(const BitWriterInterface&) = delete; + BitWriterInterface& operator=(const BitWriterInterface&) = delete; +}; + +// This class is an implementation of BitWriterInterface, using +// std::vector to store written bits. +class BitWriterWord64 : public BitWriterInterface { + public: + explicit BitWriterWord64(size_t reserve_bits = 64); + + void WriteBits(uint64_t bits, size_t num_bits) override; + + size_t GetNumBits() const override { return end_; } + + const uint8_t* GetData() const override { + return reinterpret_cast(buffer_.data()); + } + + std::vector GetDataCopy() const override { + return std::vector(GetData(), GetData() + GetDataSizeBytes()); + } + + // Returns written stream as std::string, padded with zeroes so that the + // length is a multiple of 64. + std::string GetStreamPadded64() const { return BufferToStream(buffer_); } + + // Sets callback to emit bit sequences after every write. + void SetCallback(std::function callback) { + callback_ = callback; + } + + protected: + // Sends string generated from arguments to callback_ if defined. + void EmitSequence(uint64_t bits, size_t num_bits) const { + if (callback_) callback_(BitsToStream(bits, num_bits)); + } + + private: + std::vector buffer_; + // Total number of bits written so far. Named 'end' as analogy to std::end(). + size_t end_; + + // If not null, the writer will use the callback to emit the written bit + // sequence as a string of '0' and '1'. + std::function callback_; +}; + +// Base class for reading sequences of bits. +class BitReaderInterface { + public: + BitReaderInterface() {} + virtual ~BitReaderInterface() {} + + // Reads |num_bits| from the stream, stores them in |bits|. + // Returns number of read bits. |num_bits| must be no greater than 64. + virtual size_t ReadBits(uint64_t* bits, size_t num_bits) = 0; + + // Reads |num_bits| from the stream, stores them in |bits|. + // Returns number of read bits. |num_bits| must be no greater than 64. + template + size_t ReadBitset(std::bitset* bits, size_t num_bits = N) { + uint64_t val = 0; + size_t num_read = ReadBits(&val, num_bits); + if (num_read) { + *bits = std::bitset(val); + } + return num_read; + } + + // Reads |num_bits| from the stream, returns string in left-to-right order. + // The length of the returned string may be less than |num_bits| if end was + // reached. + std::string ReadStream(size_t num_bits) { + uint64_t bits = 0; + size_t num_read = ReadBits(&bits, num_bits); + return BitsToStream(bits, num_read); + } + + // Reads 8 * sizeof(T) bits and stores them in |val|. + template + bool ReadUnencoded(T* val) { + static_assert(sizeof(T) <= 64, "Type size too large"); + uint64_t bits = 0; + const size_t num_read = ReadBits(&bits, sizeof(T) * 8); + if (num_read != sizeof(T) * 8) return false; + memcpy(val, &bits, sizeof(T)); + return true; + } + + // Returns number of bits already read. + virtual size_t GetNumReadBits() const = 0; + + // These two functions define 'hard' and 'soft' EOF. + // + // Returns true if the end of the buffer was reached. + virtual bool ReachedEnd() const = 0; + // Returns true if we reached the end of the buffer or are nearing it and only + // zero bits are left to read. Implementations of this function are allowed to + // commit a "false negative" error if the end of the buffer was not reached, + // i.e. it can return false even if indeed only zeroes are left. + // It is assumed that the consumer expects that + // the buffer stream ends with padding zeroes, and would accept this as a + // 'soft' EOF. Implementations of this class do not necessarily need to + // implement this, default behavior can simply delegate to ReachedEnd(). + virtual bool OnlyZeroesLeft() const { return ReachedEnd(); } + + // Reads value encoded with WriteVariableWidthXXX (see BitWriterInterface). + // Reader and writer must use the same |chunk_length| and variable type. + // Returns true on success, false if the bit stream ends prematurely. + bool ReadVariableWidthU64(uint64_t* val, size_t chunk_length); + bool ReadVariableWidthU32(uint32_t* val, size_t chunk_length); + bool ReadVariableWidthU16(uint16_t* val, size_t chunk_length); + bool ReadVariableWidthU8(uint8_t* val, size_t chunk_length); + bool ReadVariableWidthS64(int64_t* val, size_t chunk_length, + size_t zigzag_exponent); + bool ReadVariableWidthS32(int32_t* val, size_t chunk_length, + size_t zigzag_exponent); + bool ReadVariableWidthS16(int16_t* val, size_t chunk_length, + size_t zigzag_exponent); + bool ReadVariableWidthS8(int8_t* val, size_t chunk_length, + size_t zigzag_exponent); + + // Reads value written by WriteFixedWidth (|max_val| needs to be the same). + // Returns true on success, false if the bit stream ends prematurely. + bool ReadFixedWidth(uint64_t* val, uint64_t max_val); + + BitReaderInterface(const BitReaderInterface&) = delete; + BitReaderInterface& operator=(const BitReaderInterface&) = delete; +}; + +// This class is an implementation of BitReaderInterface which accepts both +// uint8_t and uint64_t buffers as input. uint64_t buffers are consumed and +// owned. uint8_t buffers are copied. +class BitReaderWord64 : public BitReaderInterface { + public: + // Consumes and owns the buffer. + explicit BitReaderWord64(std::vector&& buffer); + + // Copies the buffer and casts it to uint64. + // Consuming the original buffer and casting it to uint64 is difficult, + // as it would potentially cause data misalignment and poor performance. + explicit BitReaderWord64(const std::vector& buffer); + BitReaderWord64(const void* buffer, size_t num_bytes); + + size_t ReadBits(uint64_t* bits, size_t num_bits) override; + + size_t GetNumReadBits() const override { return pos_; } + + bool ReachedEnd() const override; + bool OnlyZeroesLeft() const override; + + BitReaderWord64() = delete; + + // Sets callback to emit bit sequences after every read. + void SetCallback(std::function callback) { + callback_ = callback; + } + + protected: + // Sends string generated from arguments to callback_ if defined. + void EmitSequence(uint64_t bits, size_t num_bits) const { + if (callback_) callback_(BitsToStream(bits, num_bits)); + } + + private: + const std::vector buffer_; + size_t pos_; + + // If not null, the reader will use the callback to emit the read bit + // sequence as a string of '0' and '1'. + std::function callback_; +}; + +} // namespace spvutils + +#endif // LIBSPIRV_UTIL_BIT_STREAM_H_ diff --git a/3rdparty/spirv-tools/source/util/bitutils.h b/3rdparty/spirv-tools/source/util/bitutils.h new file mode 100644 index 000000000..9b53d3b2d --- /dev/null +++ b/3rdparty/spirv-tools/source/util/bitutils.h @@ -0,0 +1,94 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_UTIL_BITUTILS_H_ +#define LIBSPIRV_UTIL_BITUTILS_H_ + +#include +#include + +namespace spvutils { + +// Performs a bitwise copy of source to the destination type Dest. +template +Dest BitwiseCast(Src source) { + Dest dest; + static_assert(sizeof(source) == sizeof(dest), + "BitwiseCast: Source and destination must have the same size"); + std::memcpy(&dest, &source, sizeof(dest)); + return dest; +} + +// SetBits returns an integer of type with bits set +// for position through , counting from the least +// significant bit. In particular when Num == 0, no positions are set to 1. +// A static assert will be triggered if First + Num > sizeof(T) * 8, that is, +// a bit that will not fit in the underlying type is set. +template +struct SetBits { + static_assert(First < sizeof(T) * 8, + "Tried to set a bit that is shifted too far."); + const static T get = (T(1) << First) | SetBits::get; +}; + +template +struct SetBits { + const static T get = T(0); +}; + +// This is all compile-time so we can put our tests right here. +static_assert(SetBits::get == uint32_t(0x00000000), + "SetBits failed"); +static_assert(SetBits::get == uint32_t(0x00000001), + "SetBits failed"); +static_assert(SetBits::get == uint32_t(0x80000000), + "SetBits failed"); +static_assert(SetBits::get == uint32_t(0x00000006), + "SetBits failed"); +static_assert(SetBits::get == uint32_t(0xc0000000), + "SetBits failed"); +static_assert(SetBits::get == uint32_t(0x7FFFFFFF), + "SetBits failed"); +static_assert(SetBits::get == uint32_t(0xFFFFFFFF), + "SetBits failed"); +static_assert(SetBits::get == uint32_t(0xFFFF0000), + "SetBits failed"); + +static_assert(SetBits::get == uint64_t(0x0000000000000001LL), + "SetBits failed"); +static_assert(SetBits::get == uint64_t(0x8000000000000000LL), + "SetBits failed"); +static_assert(SetBits::get == uint64_t(0xc000000000000000LL), + "SetBits failed"); +static_assert(SetBits::get == uint64_t(0x0000000080000000LL), + "SetBits failed"); +static_assert(SetBits::get == uint64_t(0x00000000FFFF0000LL), + "SetBits failed"); + +// Returns number of '1' bits in a word. +template +size_t CountSetBits(T word) { + static_assert(std::is_integral::value, + "CountSetBits requires integer type"); + size_t count = 0; + while (word) { + word &= word - 1; + ++count; + } + return count; +} + +} // namespace spvutils + +#endif // LIBSPIRV_UTIL_BITUTILS_H_ diff --git a/3rdparty/spirv-tools/source/util/hex_float.h b/3rdparty/spirv-tools/source/util/hex_float.h new file mode 100644 index 000000000..de99cc356 --- /dev/null +++ b/3rdparty/spirv-tools/source/util/hex_float.h @@ -0,0 +1,1148 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_UTIL_HEX_FLOAT_H_ +#define LIBSPIRV_UTIL_HEX_FLOAT_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bitutils.h" + +#ifndef __GNUC__ +#define GCC_VERSION 0 +#else +#define GCC_VERSION \ + (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) +#endif + +namespace spvutils { + +class Float16 { + public: + Float16(uint16_t v) : val(v) {} + Float16() = default; + static bool isNan(const Float16& val) { + return ((val.val & 0x7C00) == 0x7C00) && ((val.val & 0x3FF) != 0); + } + // Returns true if the given value is any kind of infinity. + static bool isInfinity(const Float16& val) { + return ((val.val & 0x7C00) == 0x7C00) && ((val.val & 0x3FF) == 0); + } + Float16(const Float16& other) { val = other.val; } + uint16_t get_value() const { return val; } + + // Returns the maximum normal value. + static Float16 max() { return Float16(0x7bff); } + // Returns the lowest normal value. + static Float16 lowest() { return Float16(0xfbff); } + + private: + uint16_t val; +}; + +// To specialize this type, you must override uint_type to define +// an unsigned integer that can fit your floating point type. +// You must also add a isNan function that returns true if +// a value is Nan. +template +struct FloatProxyTraits { + using uint_type = void; +}; + +template <> +struct FloatProxyTraits { + using uint_type = uint32_t; + static bool isNan(float f) { return std::isnan(f); } + // Returns true if the given value is any kind of infinity. + static bool isInfinity(float f) { return std::isinf(f); } + // Returns the maximum normal value. + static float max() { return std::numeric_limits::max(); } + // Returns the lowest normal value. + static float lowest() { return std::numeric_limits::lowest(); } + // Returns the value as the native floating point format. + static float getAsFloat(const uint_type& t) { return BitwiseCast(t); } + // Returns the bits from the given floating pointer number. + static uint_type getBitsFromFloat(const float& t) { + return BitwiseCast(t); + } + // Returns the bitwidth. + static uint32_t width() { return 32u; } +}; + +template <> +struct FloatProxyTraits { + using uint_type = uint64_t; + static bool isNan(double f) { return std::isnan(f); } + // Returns true if the given value is any kind of infinity. + static bool isInfinity(double f) { return std::isinf(f); } + // Returns the maximum normal value. + static double max() { return std::numeric_limits::max(); } + // Returns the lowest normal value. + static double lowest() { return std::numeric_limits::lowest(); } + // Returns the value as the native floating point format. + static double getAsFloat(const uint_type& t) { + return BitwiseCast(t); + } + // Returns the bits from the given floating pointer number. + static uint_type getBitsFromFloat(const double& t) { + return BitwiseCast(t); + } + // Returns the bitwidth. + static uint32_t width() { return 64u; } +}; + +template <> +struct FloatProxyTraits { + using uint_type = uint16_t; + static bool isNan(Float16 f) { return Float16::isNan(f); } + // Returns true if the given value is any kind of infinity. + static bool isInfinity(Float16 f) { return Float16::isInfinity(f); } + // Returns the maximum normal value. + static Float16 max() { return Float16::max(); } + // Returns the lowest normal value. + static Float16 lowest() { return Float16::lowest(); } + // Returns the value as the native floating point format. + static Float16 getAsFloat(const uint_type& t) { return Float16(t); } + // Returns the bits from the given floating pointer number. + static uint_type getBitsFromFloat(const Float16& t) { return t.get_value(); } + // Returns the bitwidth. + static uint32_t width() { return 16u; } +}; + +// Since copying a floating point number (especially if it is NaN) +// does not guarantee that bits are preserved, this class lets us +// store the type and use it as a float when necessary. +template +class FloatProxy { + public: + using uint_type = typename FloatProxyTraits::uint_type; + + // Since this is to act similar to the normal floats, + // do not initialize the data by default. + FloatProxy() = default; + + // Intentionally non-explicit. This is a proxy type so + // implicit conversions allow us to use it more transparently. + FloatProxy(T val) { data_ = FloatProxyTraits::getBitsFromFloat(val); } + + // Intentionally non-explicit. This is a proxy type so + // implicit conversions allow us to use it more transparently. + FloatProxy(uint_type val) { data_ = val; } + + // This is helpful to have and is guaranteed not to stomp bits. + FloatProxy operator-() const { + return static_cast(data_ ^ + (uint_type(0x1) << (sizeof(T) * 8 - 1))); + } + + // Returns the data as a floating point value. + T getAsFloat() const { return FloatProxyTraits::getAsFloat(data_); } + + // Returns the raw data. + uint_type data() const { return data_; } + + // Returns a vector of words suitable for use in an Operand. + std::vector GetWords() const { + std::vector words; + if (FloatProxyTraits::width() == 64) { + FloatProxyTraits::uint_type d = data(); + words.push_back(static_cast(d)); + words.push_back(static_cast(d >> 32)); + } else { + words.push_back(static_cast(data())); + } + return words; + } + + // Returns true if the value represents any type of NaN. + bool isNan() { return FloatProxyTraits::isNan(getAsFloat()); } + // Returns true if the value represents any type of infinity. + bool isInfinity() { return FloatProxyTraits::isInfinity(getAsFloat()); } + + // Returns the maximum normal value. + static FloatProxy max() { + return FloatProxy(FloatProxyTraits::max()); + } + // Returns the lowest normal value. + static FloatProxy lowest() { + return FloatProxy(FloatProxyTraits::lowest()); + } + + private: + uint_type data_; +}; + +template +bool operator==(const FloatProxy& first, const FloatProxy& second) { + return first.data() == second.data(); +} + +// Reads a FloatProxy value as a normal float from a stream. +template +std::istream& operator>>(std::istream& is, FloatProxy& value) { + T float_val; + is >> float_val; + value = FloatProxy(float_val); + return is; +} + +// This is an example traits. It is not meant to be used in practice, but will +// be the default for any non-specialized type. +template +struct HexFloatTraits { + // Integer type that can store this hex-float. + using uint_type = void; + // Signed integer type that can store this hex-float. + using int_type = void; + // The numerical type that this HexFloat represents. + using underlying_type = void; + // The type needed to construct the underlying type. + using native_type = void; + // The number of bits that are actually relevant in the uint_type. + // This allows us to deal with, for example, 24-bit values in a 32-bit + // integer. + static const uint32_t num_used_bits = 0; + // Number of bits that represent the exponent. + static const uint32_t num_exponent_bits = 0; + // Number of bits that represent the fractional part. + static const uint32_t num_fraction_bits = 0; + // The bias of the exponent. (How much we need to subtract from the stored + // value to get the correct value.) + static const uint32_t exponent_bias = 0; +}; + +// Traits for IEEE float. +// 1 sign bit, 8 exponent bits, 23 fractional bits. +template <> +struct HexFloatTraits> { + using uint_type = uint32_t; + using int_type = int32_t; + using underlying_type = FloatProxy; + using native_type = float; + static const uint_type num_used_bits = 32; + static const uint_type num_exponent_bits = 8; + static const uint_type num_fraction_bits = 23; + static const uint_type exponent_bias = 127; +}; + +// Traits for IEEE double. +// 1 sign bit, 11 exponent bits, 52 fractional bits. +template <> +struct HexFloatTraits> { + using uint_type = uint64_t; + using int_type = int64_t; + using underlying_type = FloatProxy; + using native_type = double; + static const uint_type num_used_bits = 64; + static const uint_type num_exponent_bits = 11; + static const uint_type num_fraction_bits = 52; + static const uint_type exponent_bias = 1023; +}; + +// Traits for IEEE half. +// 1 sign bit, 5 exponent bits, 10 fractional bits. +template <> +struct HexFloatTraits> { + using uint_type = uint16_t; + using int_type = int16_t; + using underlying_type = uint16_t; + using native_type = uint16_t; + static const uint_type num_used_bits = 16; + static const uint_type num_exponent_bits = 5; + static const uint_type num_fraction_bits = 10; + static const uint_type exponent_bias = 15; +}; + +enum class round_direction { + kToZero, + kToNearestEven, + kToPositiveInfinity, + kToNegativeInfinity, + max = kToNegativeInfinity +}; + +// Template class that houses a floating pointer number. +// It exposes a number of constants based on the provided traits to +// assist in interpreting the bits of the value. +template > +class HexFloat { + public: + using uint_type = typename Traits::uint_type; + using int_type = typename Traits::int_type; + using underlying_type = typename Traits::underlying_type; + using native_type = typename Traits::native_type; + + explicit HexFloat(T f) : value_(f) {} + + T value() const { return value_; } + void set_value(T f) { value_ = f; } + + // These are all written like this because it is convenient to have + // compile-time constants for all of these values. + + // Pass-through values to save typing. + static const uint32_t num_used_bits = Traits::num_used_bits; + static const uint32_t exponent_bias = Traits::exponent_bias; + static const uint32_t num_exponent_bits = Traits::num_exponent_bits; + static const uint32_t num_fraction_bits = Traits::num_fraction_bits; + + // Number of bits to shift left to set the highest relevant bit. + static const uint32_t top_bit_left_shift = num_used_bits - 1; + // How many nibbles (hex characters) the fractional part takes up. + static const uint32_t fraction_nibbles = (num_fraction_bits + 3) / 4; + // If the fractional part does not fit evenly into a hex character (4-bits) + // then we have to left-shift to get rid of leading 0s. This is the amount + // we have to shift (might be 0). + static const uint32_t num_overflow_bits = + fraction_nibbles * 4 - num_fraction_bits; + + // The representation of the fraction, not the actual bits. This + // includes the leading bit that is usually implicit. + static const uint_type fraction_represent_mask = + spvutils::SetBits::get; + + // The topmost bit in the nibble-aligned fraction. + static const uint_type fraction_top_bit = + uint_type(1) << (num_fraction_bits + num_overflow_bits - 1); + + // The least significant bit in the exponent, which is also the bit + // immediately to the left of the significand. + static const uint_type first_exponent_bit = uint_type(1) + << (num_fraction_bits); + + // The mask for the encoded fraction. It does not include the + // implicit bit. + static const uint_type fraction_encode_mask = + spvutils::SetBits::get; + + // The bit that is used as a sign. + static const uint_type sign_mask = uint_type(1) << top_bit_left_shift; + + // The bits that represent the exponent. + static const uint_type exponent_mask = + spvutils::SetBits::get; + + // How far left the exponent is shifted. + static const uint32_t exponent_left_shift = num_fraction_bits; + + // How far from the right edge the fraction is shifted. + static const uint32_t fraction_right_shift = + static_cast(sizeof(uint_type) * 8) - num_fraction_bits; + + // The maximum representable unbiased exponent. + static const int_type max_exponent = + (exponent_mask >> num_fraction_bits) - exponent_bias; + // The minimum representable exponent for normalized numbers. + static const int_type min_exponent = -static_cast(exponent_bias); + + // Returns the bits associated with the value. + uint_type getBits() const { return value_.data(); } + + // Returns the bits associated with the value, without the leading sign bit. + uint_type getUnsignedBits() const { + return static_cast(value_.data() & ~sign_mask); + } + + // Returns the bits associated with the exponent, shifted to start at the + // lsb of the type. + const uint_type getExponentBits() const { + return static_cast((getBits() & exponent_mask) >> + num_fraction_bits); + } + + // Returns the exponent in unbiased form. This is the exponent in the + // human-friendly form. + const int_type getUnbiasedExponent() const { + return static_cast(getExponentBits() - exponent_bias); + } + + // Returns just the significand bits from the value. + const uint_type getSignificandBits() const { + return getBits() & fraction_encode_mask; + } + + // If the number was normalized, returns the unbiased exponent. + // If the number was denormal, normalize the exponent first. + const int_type getUnbiasedNormalizedExponent() const { + if ((getBits() & ~sign_mask) == 0) { // special case if everything is 0 + return 0; + } + int_type exp = getUnbiasedExponent(); + if (exp == min_exponent) { // We are in denorm land. + uint_type significand_bits = getSignificandBits(); + while ((significand_bits & (first_exponent_bit >> 1)) == 0) { + significand_bits = static_cast(significand_bits << 1); + exp = static_cast(exp - 1); + } + significand_bits &= fraction_encode_mask; + } + return exp; + } + + // Returns the signficand after it has been normalized. + const uint_type getNormalizedSignificand() const { + int_type unbiased_exponent = getUnbiasedNormalizedExponent(); + uint_type significand = getSignificandBits(); + for (int_type i = unbiased_exponent; i <= min_exponent; ++i) { + significand = static_cast(significand << 1); + } + significand &= fraction_encode_mask; + return significand; + } + + // Returns true if this number represents a negative value. + bool isNegative() const { return (getBits() & sign_mask) != 0; } + + // Sets this HexFloat from the individual components. + // Note this assumes EVERY significand is normalized, and has an implicit + // leading one. This means that the only way that this method will set 0, + // is if you set a number so denormalized that it underflows. + // Do not use this method with raw bits extracted from a subnormal number, + // since subnormals do not have an implicit leading 1 in the significand. + // The significand is also expected to be in the + // lowest-most num_fraction_bits of the uint_type. + // The exponent is expected to be unbiased, meaning an exponent of + // 0 actually means 0. + // If underflow_round_up is set, then on underflow, if a number is non-0 + // and would underflow, we round up to the smallest denorm. + void setFromSignUnbiasedExponentAndNormalizedSignificand( + bool negative, int_type exponent, uint_type significand, + bool round_denorm_up) { + bool significand_is_zero = significand == 0; + + if (exponent <= min_exponent) { + // If this was denormalized, then we have to shift the bit on, meaning + // the significand is not zero. + significand_is_zero = false; + significand |= first_exponent_bit; + significand = static_cast(significand >> 1); + } + + while (exponent < min_exponent) { + significand = static_cast(significand >> 1); + ++exponent; + } + + if (exponent == min_exponent) { + if (significand == 0 && !significand_is_zero && round_denorm_up) { + significand = static_cast(0x1); + } + } + + uint_type new_value = 0; + if (negative) { + new_value = static_cast(new_value | sign_mask); + } + exponent = static_cast(exponent + exponent_bias); + assert(exponent >= 0); + + // put it all together + exponent = static_cast((exponent << exponent_left_shift) & + exponent_mask); + significand = static_cast(significand & fraction_encode_mask); + new_value = static_cast(new_value | (exponent | significand)); + value_ = T(new_value); + } + + // Increments the significand of this number by the given amount. + // If this would spill the significand into the implicit bit, + // carry is set to true and the significand is shifted to fit into + // the correct location, otherwise carry is set to false. + // All significands and to_increment are assumed to be within the bounds + // for a valid significand. + static uint_type incrementSignificand(uint_type significand, + uint_type to_increment, bool* carry) { + significand = static_cast(significand + to_increment); + *carry = false; + if (significand & first_exponent_bit) { + *carry = true; + // The implicit 1-bit will have carried, so we should zero-out the + // top bit and shift back. + significand = static_cast(significand & ~first_exponent_bit); + significand = static_cast(significand >> 1); + } + return significand; + } + +#if GCC_VERSION == 40801 + // These exist because MSVC throws warnings on negative right-shifts + // even if they are not going to be executed. Eg: + // constant_number < 0? 0: constant_number + // These convert the negative left-shifts into right shifts. + template + struct negatable_left_shift { + static uint_type val(uint_type val) { + if (N > 0) { + return static_cast(val << N); + } else { + return static_cast(val >> N); + } + } + }; + + template + struct negatable_right_shift { + static uint_type val(uint_type val) { + if (N > 0) { + return static_cast(val >> N); + } else { + return static_cast(val << N); + } + } + }; + +#else + // These exist because MSVC throws warnings on negative right-shifts + // even if they are not going to be executed. Eg: + // constant_number < 0? 0: constant_number + // These convert the negative left-shifts into right shifts. + template + struct negatable_left_shift { + static uint_type val(uint_type val) { + return static_cast(val >> -N); + } + }; + + template + struct negatable_left_shift= 0>::type> { + static uint_type val(uint_type val) { + return static_cast(val << N); + } + }; + + template + struct negatable_right_shift { + static uint_type val(uint_type val) { + return static_cast(val << -N); + } + }; + + template + struct negatable_right_shift= 0>::type> { + static uint_type val(uint_type val) { + return static_cast(val >> N); + } + }; +#endif + + // Returns the significand, rounded to fit in a significand in + // other_T. This is shifted so that the most significant + // bit of the rounded number lines up with the most significant bit + // of the returned significand. + template + typename other_T::uint_type getRoundedNormalizedSignificand( + round_direction dir, bool* carry_bit) { + using other_uint_type = typename other_T::uint_type; + static const int_type num_throwaway_bits = + static_cast(num_fraction_bits) - + static_cast(other_T::num_fraction_bits); + + static const uint_type last_significant_bit = + (num_throwaway_bits < 0) + ? 0 + : negatable_left_shift::val(1u); + static const uint_type first_rounded_bit = + (num_throwaway_bits < 1) + ? 0 + : negatable_left_shift::val(1u); + + static const uint_type throwaway_mask_bits = + num_throwaway_bits > 0 ? num_throwaway_bits : 0; + static const uint_type throwaway_mask = + spvutils::SetBits::get; + + *carry_bit = false; + other_uint_type out_val = 0; + uint_type significand = getNormalizedSignificand(); + // If we are up-casting, then we just have to shift to the right location. + if (num_throwaway_bits <= 0) { + out_val = static_cast(significand); + uint_type shift_amount = static_cast(-num_throwaway_bits); + out_val = static_cast(out_val << shift_amount); + return out_val; + } + + // If every non-representable bit is 0, then we don't have any casting to + // do. + if ((significand & throwaway_mask) == 0) { + return static_cast( + negatable_right_shift::val(significand)); + } + + bool round_away_from_zero = false; + // We actually have to narrow the significand here, so we have to follow the + // rounding rules. + switch (dir) { + case round_direction::kToZero: + break; + case round_direction::kToPositiveInfinity: + round_away_from_zero = !isNegative(); + break; + case round_direction::kToNegativeInfinity: + round_away_from_zero = isNegative(); + break; + case round_direction::kToNearestEven: + // Have to round down, round bit is 0 + if ((first_rounded_bit & significand) == 0) { + break; + } + if (((significand & throwaway_mask) & ~first_rounded_bit) != 0) { + // If any subsequent bit of the rounded portion is non-0 then we round + // up. + round_away_from_zero = true; + break; + } + // We are exactly half-way between 2 numbers, pick even. + if ((significand & last_significant_bit) != 0) { + // 1 for our last bit, round up. + round_away_from_zero = true; + break; + } + break; + } + + if (round_away_from_zero) { + return static_cast( + negatable_right_shift::val(incrementSignificand( + significand, last_significant_bit, carry_bit))); + } else { + return static_cast( + negatable_right_shift::val(significand)); + } + } + + // Casts this value to another HexFloat. If the cast is widening, + // then round_dir is ignored. If the cast is narrowing, then + // the result is rounded in the direction specified. + // This number will retain Nan and Inf values. + // It will also saturate to Inf if the number overflows, and + // underflow to (0 or min depending on rounding) if the number underflows. + template + void castTo(other_T& other, round_direction round_dir) { + other = other_T(static_cast(0)); + bool negate = isNegative(); + if (getUnsignedBits() == 0) { + if (negate) { + other.set_value(-other.value()); + } + return; + } + uint_type significand = getSignificandBits(); + bool carried = false; + typename other_T::uint_type rounded_significand = + getRoundedNormalizedSignificand(round_dir, &carried); + + int_type exponent = getUnbiasedExponent(); + if (exponent == min_exponent) { + // If we are denormal, normalize the exponent, so that we can encode + // easily. + exponent = static_cast(exponent + 1); + for (uint_type check_bit = first_exponent_bit >> 1; check_bit != 0; + check_bit = static_cast(check_bit >> 1)) { + exponent = static_cast(exponent - 1); + if (check_bit & significand) break; + } + } + + bool is_nan = + (getBits() & exponent_mask) == exponent_mask && significand != 0; + bool is_inf = + !is_nan && + ((exponent + carried) > static_cast(other_T::exponent_bias) || + (significand == 0 && (getBits() & exponent_mask) == exponent_mask)); + + // If we are Nan or Inf we should pass that through. + if (is_inf) { + other.set_value(BitwiseCast( + static_cast( + (negate ? other_T::sign_mask : 0) | other_T::exponent_mask))); + return; + } + if (is_nan) { + typename other_T::uint_type shifted_significand; + shifted_significand = static_cast( + negatable_left_shift< + static_cast(other_T::num_fraction_bits) - + static_cast(num_fraction_bits)>::val(significand)); + + // We are some sort of Nan. We try to keep the bit-pattern of the Nan + // as close as possible. If we had to shift off bits so we are 0, then we + // just set the last bit. + other.set_value(BitwiseCast( + static_cast( + (negate ? other_T::sign_mask : 0) | other_T::exponent_mask | + (shifted_significand == 0 ? 0x1 : shifted_significand)))); + return; + } + + bool round_underflow_up = + isNegative() ? round_dir == round_direction::kToNegativeInfinity + : round_dir == round_direction::kToPositiveInfinity; + using other_int_type = typename other_T::int_type; + // setFromSignUnbiasedExponentAndNormalizedSignificand will + // zero out any underflowing value (but retain the sign). + other.setFromSignUnbiasedExponentAndNormalizedSignificand( + negate, static_cast(exponent), rounded_significand, + round_underflow_up); + return; + } + + private: + T value_; + + static_assert(num_used_bits == + Traits::num_exponent_bits + Traits::num_fraction_bits + 1, + "The number of bits do not fit"); + static_assert(sizeof(T) == sizeof(uint_type), "The type sizes do not match"); +}; + +// Returns 4 bits represented by the hex character. +inline uint8_t get_nibble_from_character(int character) { + const char* dec = "0123456789"; + const char* lower = "abcdef"; + const char* upper = "ABCDEF"; + const char* p = nullptr; + if ((p = strchr(dec, character))) { + return static_cast(p - dec); + } else if ((p = strchr(lower, character))) { + return static_cast(p - lower + 0xa); + } else if ((p = strchr(upper, character))) { + return static_cast(p - upper + 0xa); + } + + assert(false && "This was called with a non-hex character"); + return 0; +} + +// Outputs the given HexFloat to the stream. +template +std::ostream& operator<<(std::ostream& os, const HexFloat& value) { + using HF = HexFloat; + using uint_type = typename HF::uint_type; + using int_type = typename HF::int_type; + + static_assert(HF::num_used_bits != 0, + "num_used_bits must be non-zero for a valid float"); + static_assert(HF::num_exponent_bits != 0, + "num_exponent_bits must be non-zero for a valid float"); + static_assert(HF::num_fraction_bits != 0, + "num_fractin_bits must be non-zero for a valid float"); + + const uint_type bits = value.value().data(); + const char* const sign = (bits & HF::sign_mask) ? "-" : ""; + const uint_type exponent = static_cast( + (bits & HF::exponent_mask) >> HF::num_fraction_bits); + + uint_type fraction = static_cast((bits & HF::fraction_encode_mask) + << HF::num_overflow_bits); + + const bool is_zero = exponent == 0 && fraction == 0; + const bool is_denorm = exponent == 0 && !is_zero; + + // exponent contains the biased exponent we have to convert it back into + // the normal range. + int_type int_exponent = static_cast(exponent - HF::exponent_bias); + // If the number is all zeros, then we actually have to NOT shift the + // exponent. + int_exponent = is_zero ? 0 : int_exponent; + + // If we are denorm, then start shifting, and decreasing the exponent until + // our leading bit is 1. + + if (is_denorm) { + while ((fraction & HF::fraction_top_bit) == 0) { + fraction = static_cast(fraction << 1); + int_exponent = static_cast(int_exponent - 1); + } + // Since this is denormalized, we have to consume the leading 1 since it + // will end up being implicit. + fraction = static_cast(fraction << 1); // eat the leading 1 + fraction &= HF::fraction_represent_mask; + } + + uint_type fraction_nibbles = HF::fraction_nibbles; + // We do not have to display any trailing 0s, since this represents the + // fractional part. + while (fraction_nibbles > 0 && (fraction & 0xF) == 0) { + // Shift off any trailing values; + fraction = static_cast(fraction >> 4); + --fraction_nibbles; + } + + const auto saved_flags = os.flags(); + const auto saved_fill = os.fill(); + + os << sign << "0x" << (is_zero ? '0' : '1'); + if (fraction_nibbles) { + // Make sure to keep the leading 0s in place, since this is the fractional + // part. + os << "." << std::setw(static_cast(fraction_nibbles)) + << std::setfill('0') << std::hex << fraction; + } + os << "p" << std::dec << (int_exponent >= 0 ? "+" : "") << int_exponent; + + os.flags(saved_flags); + os.fill(saved_fill); + + return os; +} + +// Returns true if negate_value is true and the next character on the +// input stream is a plus or minus sign. In that case we also set the fail bit +// on the stream and set the value to the zero value for its type. +template +inline bool RejectParseDueToLeadingSign(std::istream& is, bool negate_value, + HexFloat& value) { + if (negate_value) { + auto next_char = is.peek(); + if (next_char == '-' || next_char == '+') { + // Fail the parse. Emulate standard behaviour by setting the value to + // the zero value, and set the fail bit on the stream. + value = HexFloat(typename HexFloat::uint_type{0}); + is.setstate(std::ios_base::failbit); + return true; + } + } + return false; +} + +// Parses a floating point number from the given stream and stores it into the +// value parameter. +// If negate_value is true then the number may not have a leading minus or +// plus, and if it successfully parses, then the number is negated before +// being stored into the value parameter. +// If the value cannot be correctly parsed or overflows the target floating +// point type, then set the fail bit on the stream. +// TODO(dneto): Promise C++11 standard behavior in how the value is set in +// the error case, but only after all target platforms implement it correctly. +// In particular, the Microsoft C++ runtime appears to be out of spec. +template +inline std::istream& ParseNormalFloat(std::istream& is, bool negate_value, + HexFloat& value) { + if (RejectParseDueToLeadingSign(is, negate_value, value)) { + return is; + } + T val; + is >> val; + if (negate_value) { + val = -val; + } + value.set_value(val); + // In the failure case, map -0.0 to 0.0. + if (is.fail() && value.getUnsignedBits() == 0u) { + value = HexFloat(typename HexFloat::uint_type{0}); + } + if (val.isInfinity()) { + // Fail the parse. Emulate standard behaviour by setting the value to + // the closest normal value, and set the fail bit on the stream. + value.set_value((value.isNegative() | negate_value) ? T::lowest() + : T::max()); + is.setstate(std::ios_base::failbit); + } + return is; +} + +// Specialization of ParseNormalFloat for FloatProxy values. +// This will parse the float as it were a 32-bit floating point number, +// and then round it down to fit into a Float16 value. +// The number is rounded towards zero. +// If negate_value is true then the number may not have a leading minus or +// plus, and if it successfully parses, then the number is negated before +// being stored into the value parameter. +// If the value cannot be correctly parsed or overflows the target floating +// point type, then set the fail bit on the stream. +// TODO(dneto): Promise C++11 standard behavior in how the value is set in +// the error case, but only after all target platforms implement it correctly. +// In particular, the Microsoft C++ runtime appears to be out of spec. +template <> +inline std::istream& +ParseNormalFloat, HexFloatTraits>>( + std::istream& is, bool negate_value, + HexFloat, HexFloatTraits>>& value) { + // First parse as a 32-bit float. + HexFloat> float_val(0.0f); + ParseNormalFloat(is, negate_value, float_val); + + // Then convert to 16-bit float, saturating at infinities, and + // rounding toward zero. + float_val.castTo(value, round_direction::kToZero); + + // Overflow on 16-bit behaves the same as for 32- and 64-bit: set the + // fail bit and set the lowest or highest value. + if (Float16::isInfinity(value.value().getAsFloat())) { + value.set_value(value.isNegative() ? Float16::lowest() : Float16::max()); + is.setstate(std::ios_base::failbit); + } + return is; +} + +// Reads a HexFloat from the given stream. +// If the float is not encoded as a hex-float then it will be parsed +// as a regular float. +// This may fail if your stream does not support at least one unget. +// Nan values can be encoded with "0x1.p+exponent_bias". +// This would normally overflow a float and round to +// infinity but this special pattern is the exact representation for a NaN, +// and therefore is actually encoded as the correct NaN. To encode inf, +// either 0x0p+exponent_bias can be specified or any exponent greater than +// exponent_bias. +// Examples using IEEE 32-bit float encoding. +// 0x1.0p+128 (+inf) +// -0x1.0p-128 (-inf) +// +// 0x1.1p+128 (+Nan) +// -0x1.1p+128 (-Nan) +// +// 0x1p+129 (+inf) +// -0x1p+129 (-inf) +template +std::istream& operator>>(std::istream& is, HexFloat& value) { + using HF = HexFloat; + using uint_type = typename HF::uint_type; + using int_type = typename HF::int_type; + + value.set_value(static_cast(0.f)); + + if (is.flags() & std::ios::skipws) { + // If the user wants to skip whitespace , then we should obey that. + while (std::isspace(is.peek())) { + is.get(); + } + } + + auto next_char = is.peek(); + bool negate_value = false; + + if (next_char != '-' && next_char != '0') { + return ParseNormalFloat(is, negate_value, value); + } + + if (next_char == '-') { + negate_value = true; + is.get(); + next_char = is.peek(); + } + + if (next_char == '0') { + is.get(); // We may have to unget this. + auto maybe_hex_start = is.peek(); + if (maybe_hex_start != 'x' && maybe_hex_start != 'X') { + is.unget(); + return ParseNormalFloat(is, negate_value, value); + } else { + is.get(); // Throw away the 'x'; + } + } else { + return ParseNormalFloat(is, negate_value, value); + } + + // This "looks" like a hex-float so treat it as one. + bool seen_p = false; + bool seen_dot = false; + uint_type fraction_index = 0; + + uint_type fraction = 0; + int_type exponent = HF::exponent_bias; + + // Strip off leading zeros so we don't have to special-case them later. + while ((next_char = is.peek()) == '0') { + is.get(); + } + + bool is_denorm = + true; // Assume denorm "representation" until we hear otherwise. + // NB: This does not mean the value is actually denorm, + // it just means that it was written 0. + bool bits_written = false; // Stays false until we write a bit. + while (!seen_p && !seen_dot) { + // Handle characters that are left of the fractional part. + if (next_char == '.') { + seen_dot = true; + } else if (next_char == 'p') { + seen_p = true; + } else if (::isxdigit(next_char)) { + // We know this is not denormalized since we have stripped all leading + // zeroes and we are not a ".". + is_denorm = false; + int number = get_nibble_from_character(next_char); + for (int i = 0; i < 4; ++i, number <<= 1) { + uint_type write_bit = (number & 0x8) ? 0x1 : 0x0; + if (bits_written) { + // If we are here the bits represented belong in the fractional + // part of the float, and we have to adjust the exponent accordingly. + fraction = static_cast( + fraction | + static_cast( + write_bit << (HF::top_bit_left_shift - fraction_index++))); + exponent = static_cast(exponent + 1); + } + bits_written |= write_bit != 0; + } + } else { + // We have not found our exponent yet, so we have to fail. + is.setstate(std::ios::failbit); + return is; + } + is.get(); + next_char = is.peek(); + } + bits_written = false; + while (seen_dot && !seen_p) { + // Handle only fractional parts now. + if (next_char == 'p') { + seen_p = true; + } else if (::isxdigit(next_char)) { + int number = get_nibble_from_character(next_char); + for (int i = 0; i < 4; ++i, number <<= 1) { + uint_type write_bit = (number & 0x8) ? 0x01 : 0x00; + bits_written |= write_bit != 0; + if (is_denorm && !bits_written) { + // Handle modifying the exponent here this way we can handle + // an arbitrary number of hex values without overflowing our + // integer. + exponent = static_cast(exponent - 1); + } else { + fraction = static_cast( + fraction | + static_cast( + write_bit << (HF::top_bit_left_shift - fraction_index++))); + } + } + } else { + // We still have not found our 'p' exponent yet, so this is not a valid + // hex-float. + is.setstate(std::ios::failbit); + return is; + } + is.get(); + next_char = is.peek(); + } + + bool seen_sign = false; + int8_t exponent_sign = 1; + int_type written_exponent = 0; + while (true) { + if ((next_char == '-' || next_char == '+')) { + if (seen_sign) { + is.setstate(std::ios::failbit); + return is; + } + seen_sign = true; + exponent_sign = (next_char == '-') ? -1 : 1; + } else if (::isdigit(next_char)) { + // Hex-floats express their exponent as decimal. + written_exponent = static_cast(written_exponent * 10); + written_exponent = + static_cast(written_exponent + (next_char - '0')); + } else { + break; + } + is.get(); + next_char = is.peek(); + } + + written_exponent = static_cast(written_exponent * exponent_sign); + exponent = static_cast(exponent + written_exponent); + + bool is_zero = is_denorm && (fraction == 0); + if (is_denorm && !is_zero) { + fraction = static_cast(fraction << 1); + exponent = static_cast(exponent - 1); + } else if (is_zero) { + exponent = 0; + } + + if (exponent <= 0 && !is_zero) { + fraction = static_cast(fraction >> 1); + fraction |= static_cast(1) << HF::top_bit_left_shift; + } + + fraction = (fraction >> HF::fraction_right_shift) & HF::fraction_encode_mask; + + const int_type max_exponent = + SetBits::get; + + // Handle actual denorm numbers + while (exponent < 0 && !is_zero) { + fraction = static_cast(fraction >> 1); + exponent = static_cast(exponent + 1); + + fraction &= HF::fraction_encode_mask; + if (fraction == 0) { + // We have underflowed our fraction. We should clamp to zero. + is_zero = true; + exponent = 0; + } + } + + // We have overflowed so we should be inf/-inf. + if (exponent > max_exponent) { + exponent = max_exponent; + fraction = 0; + } + + uint_type output_bits = static_cast( + static_cast(negate_value ? 1 : 0) << HF::top_bit_left_shift); + output_bits |= fraction; + + uint_type shifted_exponent = static_cast( + static_cast(exponent << HF::exponent_left_shift) & + HF::exponent_mask); + output_bits |= shifted_exponent; + + T output_float(output_bits); + value.set_value(output_float); + + return is; +} + +// Writes a FloatProxy value to a stream. +// Zero and normal numbers are printed in the usual notation, but with +// enough digits to fully reproduce the value. Other values (subnormal, +// NaN, and infinity) are printed as a hex float. +template +std::ostream& operator<<(std::ostream& os, const FloatProxy& value) { + auto float_val = value.getAsFloat(); + switch (std::fpclassify(float_val)) { + case FP_ZERO: + case FP_NORMAL: { + auto saved_precision = os.precision(); + os.precision(std::numeric_limits::max_digits10); + os << float_val; + os.precision(saved_precision); + } break; + default: + os << HexFloat>(value); + break; + } + return os; +} + +template <> +inline std::ostream& operator<<(std::ostream& os, + const FloatProxy& value) { + os << HexFloat>(value); + return os; +} +} // namespace spvutils + +#endif // LIBSPIRV_UTIL_HEX_FLOAT_H_ diff --git a/3rdparty/spirv-tools/source/util/huffman_codec.h b/3rdparty/spirv-tools/source/util/huffman_codec.h new file mode 100644 index 000000000..c2f7b1a98 --- /dev/null +++ b/3rdparty/spirv-tools/source/util/huffman_codec.h @@ -0,0 +1,385 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Contains utils for reading, writing and debug printing bit streams. + +#ifndef LIBSPIRV_UTIL_HUFFMAN_CODEC_H_ +#define LIBSPIRV_UTIL_HUFFMAN_CODEC_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace spvutils { + +// Used to generate and apply a Huffman coding scheme. +// |Val| is the type of variable being encoded (for example a string or a +// literal). +template +class HuffmanCodec { + public: + // Huffman tree node. + struct Node { + Node() {} + + // Creates Node from serialization leaving weight and id undefined. + Node(const Val& in_value, uint32_t in_left, uint32_t in_right) + : value(in_value), left(in_left), right(in_right) {} + + Val value = Val(); + uint32_t weight = 0; + // Ids are issued sequentially starting from 1. Ids are used as an ordering + // tie-breaker, to make sure that the ordering (and resulting coding scheme) + // are consistent accross multiple platforms. + uint32_t id = 0; + // Handles of children. + uint32_t left = 0; + uint32_t right = 0; + }; + + // Creates Huffman codec from a histogramm. + // Histogramm counts must not be zero. + explicit HuffmanCodec(const std::map& hist) { + if (hist.empty()) return; + + // Heuristic estimate. + nodes_.reserve(3 * hist.size()); + + // Create NIL. + CreateNode(); + + // The queue is sorted in ascending order by weight (or by node id if + // weights are equal). + std::vector queue_vector; + queue_vector.reserve(hist.size()); + std::priority_queue, + std::function> + queue(std::bind(&HuffmanCodec::LeftIsBigger, this, + std::placeholders::_1, std::placeholders::_2), + std::move(queue_vector)); + + // Put all leaves in the queue. + for (const auto& pair : hist) { + const uint32_t node = CreateNode(); + MutableValueOf(node) = pair.first; + MutableWeightOf(node) = pair.second; + assert(WeightOf(node)); + queue.push(node); + } + + // Form the tree by combining two subtrees with the least weight, + // and pushing the root of the new tree in the queue. + while (true) { + // We push a node at the end of each iteration, so the queue is never + // supposed to be empty at this point, unless there are no leaves, but + // that case was already handled. + assert(!queue.empty()); + const uint32_t right = queue.top(); + queue.pop(); + + // If the queue is empty at this point, then the last node is + // the root of the complete Huffman tree. + if (queue.empty()) { + root_ = right; + break; + } + + const uint32_t left = queue.top(); + queue.pop(); + + // Combine left and right into a new tree and push it into the queue. + const uint32_t parent = CreateNode(); + MutableWeightOf(parent) = WeightOf(right) + WeightOf(left); + MutableLeftOf(parent) = left; + MutableRightOf(parent) = right; + queue.push(parent); + } + + // Traverse the tree and form encoding table. + CreateEncodingTable(); + } + + // Creates Huffman codec from saved tree structure. + // |nodes| is the list of nodes of the tree, nodes[0] being NIL. + // |root_handle| is the index of the root node. + HuffmanCodec(uint32_t root_handle, std::vector&& nodes) { + nodes_ = std::move(nodes); + assert(!nodes_.empty()); + assert(root_handle > 0 && root_handle < nodes_.size()); + assert(!LeftOf(0) && !RightOf(0)); + + root_ = root_handle; + + // Traverse the tree and form encoding table. + CreateEncodingTable(); + } + + // Serializes the codec in the following text format: + // (, { + // {0, 0, 0}, + // {val1, left1, right1}, + // {val2, left2, right2}, + // ... + // }) + std::string SerializeToText(int indent_num_whitespaces) const { + const bool value_is_text = std::is_same::value; + + const std::string indent1 = std::string(indent_num_whitespaces, ' '); + const std::string indent2 = std::string(indent_num_whitespaces + 2, ' '); + + std::stringstream code; + code << "(" << root_ << ", {\n"; + + for (const Node& node : nodes_) { + code << indent2 << "{"; + if (value_is_text) code << "\""; + code << node.value; + if (value_is_text) code << "\""; + code << ", " << node.left << ", " << node.right << "},\n"; + } + + code << indent1 << "})"; + + return code.str(); + } + + // Prints the Huffman tree in the following format: + // w------w------'x' + // w------'y' + // Where w stands for the weight of the node. + // Right tree branches appear above left branches. Taking the right path + // adds 1 to the code, taking the left adds 0. + void PrintTree(std::ostream& out) const { PrintTreeInternal(out, root_, 0); } + + // Traverses the tree and prints the Huffman table: value, code + // and optionally node weight for every leaf. + void PrintTable(std::ostream& out, bool print_weights = true) { + std::queue> queue; + queue.emplace(root_, ""); + + while (!queue.empty()) { + const uint32_t node = queue.front().first; + const std::string code = queue.front().second; + queue.pop(); + if (!RightOf(node) && !LeftOf(node)) { + out << ValueOf(node); + if (print_weights) out << " " << WeightOf(node); + out << " " << code << std::endl; + } else { + if (LeftOf(node)) queue.emplace(LeftOf(node), code + "0"); + + if (RightOf(node)) queue.emplace(RightOf(node), code + "1"); + } + } + } + + // Returns the Huffman table. The table was built at at construction time, + // this function just returns a const reference. + const std::unordered_map>& GetEncodingTable() + const { + return encoding_table_; + } + + // Encodes |val| and stores its Huffman code in the lower |num_bits| of + // |bits|. Returns false of |val| is not in the Huffman table. + bool Encode(const Val& val, uint64_t* bits, size_t* num_bits) const { + auto it = encoding_table_.find(val); + if (it == encoding_table_.end()) return false; + *bits = it->second.first; + *num_bits = it->second.second; + return true; + } + + // Reads bits one-by-one using callback |read_bit| until a match is found. + // Matching value is stored in |val|. Returns false if |read_bit| terminates + // before a code was mathced. + // |read_bit| has type bool func(bool* bit). When called, the next bit is + // stored in |bit|. |read_bit| returns false if the stream terminates + // prematurely. + bool DecodeFromStream(const std::function& read_bit, + Val* val) const { + uint32_t node = root_; + while (true) { + assert(node); + + if (!RightOf(node) && !LeftOf(node)) { + *val = ValueOf(node); + return true; + } + + bool go_right; + if (!read_bit(&go_right)) return false; + + if (go_right) + node = RightOf(node); + else + node = LeftOf(node); + } + + assert(0); + return false; + } + + private: + // Returns value of the node referenced by |handle|. + Val ValueOf(uint32_t node) const { return nodes_.at(node).value; } + + // Returns left child of |node|. + uint32_t LeftOf(uint32_t node) const { return nodes_.at(node).left; } + + // Returns right child of |node|. + uint32_t RightOf(uint32_t node) const { return nodes_.at(node).right; } + + // Returns weight of |node|. + uint32_t WeightOf(uint32_t node) const { return nodes_.at(node).weight; } + + // Returns id of |node|. + uint32_t IdOf(uint32_t node) const { return nodes_.at(node).id; } + + // Returns mutable reference to value of |node|. + Val& MutableValueOf(uint32_t node) { + assert(node); + return nodes_.at(node).value; + } + + // Returns mutable reference to handle of left child of |node|. + uint32_t& MutableLeftOf(uint32_t node) { + assert(node); + return nodes_.at(node).left; + } + + // Returns mutable reference to handle of right child of |node|. + uint32_t& MutableRightOf(uint32_t node) { + assert(node); + return nodes_.at(node).right; + } + + // Returns mutable reference to weight of |node|. + uint32_t& MutableWeightOf(uint32_t node) { return nodes_.at(node).weight; } + + // Returns mutable reference to id of |node|. + uint32_t& MutableIdOf(uint32_t node) { return nodes_.at(node).id; } + + // Returns true if |left| has bigger weight than |right|. Node ids are + // used as tie-breaker. + bool LeftIsBigger(uint32_t left, uint32_t right) const { + if (WeightOf(left) == WeightOf(right)) { + assert(IdOf(left) != IdOf(right)); + return IdOf(left) > IdOf(right); + } + return WeightOf(left) > WeightOf(right); + } + + // Prints subtree (helper function used by PrintTree). + void PrintTreeInternal(std::ostream& out, uint32_t node, size_t depth) const { + if (!node) return; + + const size_t kTextFieldWidth = 7; + + if (!RightOf(node) && !LeftOf(node)) { + out << ValueOf(node) << std::endl; + } else { + if (RightOf(node)) { + std::stringstream label; + label << std::setfill('-') << std::left << std::setw(kTextFieldWidth) + << WeightOf(RightOf(node)); + out << label.str(); + PrintTreeInternal(out, RightOf(node), depth + 1); + } + + if (LeftOf(node)) { + out << std::string(depth * kTextFieldWidth, ' '); + std::stringstream label; + label << std::setfill('-') << std::left << std::setw(kTextFieldWidth) + << WeightOf(LeftOf(node)); + out << label.str(); + PrintTreeInternal(out, LeftOf(node), depth + 1); + } + } + } + + // Traverses the Huffman tree and saves paths to the leaves as bit + // sequences to encoding_table_. + void CreateEncodingTable() { + struct Context { + Context(uint32_t in_node, uint64_t in_bits, size_t in_depth) + : node(in_node), bits(in_bits), depth(in_depth) {} + uint32_t node; + // Huffman tree depth cannot exceed 64 as histogramm counts are expected + // to be positive and limited by numeric_limits::max(). + // For practical applications tree depth would be much smaller than 64. + uint64_t bits; + size_t depth; + }; + + std::queue queue; + queue.emplace(root_, 0, 0); + + while (!queue.empty()) { + const Context& context = queue.front(); + const uint32_t node = context.node; + const uint64_t bits = context.bits; + const size_t depth = context.depth; + queue.pop(); + + if (!RightOf(node) && !LeftOf(node)) { + auto insertion_result = encoding_table_.emplace( + ValueOf(node), std::pair(bits, depth)); + assert(insertion_result.second); + (void)insertion_result; + } else { + if (LeftOf(node)) queue.emplace(LeftOf(node), bits, depth + 1); + + if (RightOf(node)) + queue.emplace(RightOf(node), bits | (1ULL << depth), depth + 1); + } + } + } + + // Creates new Huffman tree node and stores it in the deleter array. + uint32_t CreateNode() { + const uint32_t handle = static_cast(nodes_.size()); + nodes_.emplace_back(Node()); + nodes_.back().id = next_node_id_++; + return handle; + } + + // Huffman tree root handle. + uint32_t root_ = 0; + + // Huffman tree deleter. + std::vector nodes_; + + // Encoding table value -> {bits, num_bits}. + // Huffman codes are expected to never exceed 64 bit length (this is in fact + // impossible if frequencies are stored as uint32_t). + std::unordered_map> encoding_table_; + + // Next node id issued by CreateNode(); + uint32_t next_node_id_ = 1; +}; + +} // namespace spvutils + +#endif // LIBSPIRV_UTIL_HUFFMAN_CODEC_H_ diff --git a/3rdparty/spirv-tools/source/util/ilist.h b/3rdparty/spirv-tools/source/util/ilist.h new file mode 100644 index 000000000..c4287d248 --- /dev/null +++ b/3rdparty/spirv-tools/source/util/ilist.h @@ -0,0 +1,365 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_ILIST_H_ +#define LIBSPIRV_OPT_ILIST_H_ + +#include +#include +#include +#include + +#include "ilist_node.h" + +namespace spvtools { +namespace utils { + +// An IntrusiveList is a generic implementation of a doubly-linked list. The +// intended convention for using this container is: +// +// class Node : public IntrusiveNodeBase { +// // Note that "Node", the class being defined is the template. +// // Must have a default constructor accessible to List. +// // Add whatever data is needed in the node +// }; +// +// using List = IntrusiveList; +// +// You can also inherit from IntrusiveList instead of a typedef if you want to +// add more functionality. +// +// The condition on the template for IntrusiveNodeBase is there to add some type +// checking to the container. The compiler will still allow inserting elements +// of type IntrusiveNodeBase, but that would be an error. This assumption +// allows NextNode and PreviousNode to return pointers to Node, and casting will +// not be required by the user. + +template +class IntrusiveList { + public: + static_assert( + std::is_base_of, NodeType>::value, + "The type from the node must be derived from IntrusiveNodeBase, with " + "itself in the template."); + + // Creates an empty list. + inline IntrusiveList(); + + // Moves the contents of the given list to the list being constructed. + IntrusiveList(IntrusiveList&&); + + // Destorys the list. Note that the elements of the list will not be deleted, + // but they will be removed from the list. + virtual ~IntrusiveList(); + + // Moves all of the elements in the list on the RHS to the list on the LHS. + IntrusiveList& operator=(IntrusiveList&&); + + // Basetype for iterators so an IntrusiveList can be traversed like STL + // containers. + template + class iterator_template { + public: + iterator_template(const iterator_template& i) : node_(i.node_) {} + + iterator_template& operator++() { + node_ = node_->next_node_; + return *this; + } + + iterator_template& operator--() { + node_ = node_->previous_node_; + return *this; + } + + iterator_template& operator=(const iterator_template& i) { + node_ = i.node_; + return *this; + } + + T& operator*() const { return *node_; } + T* operator->() const { return node_; } + + friend inline bool operator==(const iterator_template& lhs, + const iterator_template& rhs) { + return lhs.node_ == rhs.node_; + } + friend inline bool operator!=(const iterator_template& lhs, + const iterator_template& rhs) { + return !(lhs == rhs); + } + + // Moves the nodes in |list| to the list that |this| points to. The + // positions of the nodes will be immediately before the element pointed to + // by the iterator. The return value will be an iterator pointing to the + // first of the newly inserted elements. + iterator_template MoveBefore(IntrusiveList* list) { + if (list->empty()) return *this; + + NodeType* first_node = list->sentinel_.next_node_; + NodeType* last_node = list->sentinel_.previous_node_; + + this->node_->previous_node_->next_node_ = first_node; + first_node->previous_node_ = this->node_->previous_node_; + + last_node->next_node_ = this->node_; + this->node_->previous_node_ = last_node; + + list->sentinel_.next_node_ = &list->sentinel_; + list->sentinel_.previous_node_ = &list->sentinel_; + + return iterator(first_node); + } + + // Define standard iterator types needs so this class can be + // used with . + using iterator_category = std::bidirectional_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = T; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + using size_type = size_t; + + protected: + iterator_template() = delete; + inline iterator_template(T* node) { node_ = node; } + T* node_; + + friend IntrusiveList; + }; + + using iterator = iterator_template; + using const_iterator = iterator_template; + + // Various types of iterators for the start (begin) and one past the end (end) + // of the list. + // + // Decrementing |end()| iterator will give and iterator pointing to the last + // element in the list, if one exists. + // + // Incrementing |end()| iterator will give |begin()|. + // + // Decrementing |begin()| will give |end()|. + // + // TODO: Not marking these functions as noexcept because Visual Studio 2013 + // does not support it. When we no longer care about that compiler, we should + // mark these as noexcept. + iterator begin(); + iterator end(); + const_iterator begin() const; + const_iterator end() const; + const_iterator cbegin() const; + const_iterator cend() const; + + // Appends |node| to the end of the list. If |node| is already in a list, it + // will be removed from that list first. + void push_back(NodeType* node); + + // Returns true if the list is empty. + bool empty() const; + + // Makes the current list empty. + inline void clear(); + + // Returns references to the first or last element in the list. It is an + // error to call these functions on an empty list. + NodeType& front(); + NodeType& back(); + const NodeType& front() const; + const NodeType& back() const; + + // Transfers [|first|, |last|) from |other| into the list at |where|. + // + // If |other| is |this|, no change is made. + void Splice(iterator where, IntrusiveList* other, iterator first, + iterator last); + + protected: + // Doing a deep copy of the list does not make sense if the list does not own + // the data. It is not clear who will own the newly created data. Making + // copies illegal for that reason. + IntrusiveList(const IntrusiveList&) = delete; + IntrusiveList& operator=(const IntrusiveList&) = delete; + + // This function will assert if it finds the list containing |node| is not in + // a valid state. + static void Check(NodeType* node); + + // A special node used to represent both the start and end of the list, + // without being part of the list. + NodeType sentinel_; +}; + +// Implementation of IntrusiveList + +template +inline IntrusiveList::IntrusiveList() : sentinel_() { + sentinel_.next_node_ = &sentinel_; + sentinel_.previous_node_ = &sentinel_; + sentinel_.is_sentinel_ = true; +} + +template +IntrusiveList::IntrusiveList(IntrusiveList&& list) : sentinel_() { + sentinel_.next_node_ = &sentinel_; + sentinel_.previous_node_ = &sentinel_; + sentinel_.is_sentinel_ = true; + list.sentinel_.ReplaceWith(&sentinel_); +} + +template +IntrusiveList::~IntrusiveList() { + clear(); +} + +template +IntrusiveList& IntrusiveList::operator=( + IntrusiveList&& list) { + list.sentinel_.ReplaceWith(&sentinel_); + return *this; +} + +template +inline typename IntrusiveList::iterator +IntrusiveList::begin() { + return iterator(sentinel_.next_node_); +} + +template +inline typename IntrusiveList::iterator +IntrusiveList::end() { + return iterator(&sentinel_); +} + +template +inline typename IntrusiveList::const_iterator +IntrusiveList::begin() const { + return const_iterator(sentinel_.next_node_); +} + +template +inline typename IntrusiveList::const_iterator +IntrusiveList::end() const { + return const_iterator(&sentinel_); +} + +template +inline typename IntrusiveList::const_iterator +IntrusiveList::cbegin() const { + return const_iterator(sentinel_.next_node_); +} + +template +inline typename IntrusiveList::const_iterator +IntrusiveList::cend() const { + return const_iterator(&sentinel_); +} + +template +void IntrusiveList::push_back(NodeType* node) { + node->InsertBefore(&sentinel_); +} + +template +bool IntrusiveList::empty() const { + return sentinel_.NextNode() == nullptr; +} + +template +void IntrusiveList::clear() { + while (!empty()) { + front().RemoveFromList(); + } +} + +template +NodeType& IntrusiveList::front() { + NodeType* node = sentinel_.NextNode(); + assert(node != nullptr && "Can't get the front of an empty list."); + return *node; +} + +template +NodeType& IntrusiveList::back() { + NodeType* node = sentinel_.PreviousNode(); + assert(node != nullptr && "Can't get the back of an empty list."); + return *node; +} + +template +const NodeType& IntrusiveList::front() const { + NodeType* node = sentinel_.NextNode(); + assert(node != nullptr && "Can't get the front of an empty list."); + return *node; +} + +template +const NodeType& IntrusiveList::back() const { + NodeType* node = sentinel_.PreviousNode(); + assert(node != nullptr && "Can't get the back of an empty list."); + return *node; +} + +template +void IntrusiveList::Splice(iterator where, + IntrusiveList* other, + iterator first, iterator last) { + if (first == last) return; + if (other == this) return; + + NodeType* first_prev = first.node_->previous_node_; + NodeType* where_next = where.node_->next_node_; + + // Attach first. + where.node_->next_node_ = first.node_; + first.node_->previous_node_ = where.node_; + + // Attach last. + where_next->previous_node_ = last.node_->previous_node_; + last.node_->previous_node_->next_node_ = where_next; + + // Fixup other. + first_prev->next_node_ = last.node_; + last.node_->previous_node_ = first_prev; +} + +template +void IntrusiveList::Check(NodeType* start) { + int sentinel_count = 0; + NodeType* p = start; + do { + assert(p != nullptr); + assert(p->next_node_->previous_node_ == p); + assert(p->previous_node_->next_node_ == p); + if (p->is_sentinel_) sentinel_count++; + p = p->next_node_; + } while (p != start); + assert(sentinel_count == 1 && "List should have exactly 1 sentinel node."); + + p = start; + do { + assert(p != nullptr); + assert(p->previous_node_->next_node_ == p); + assert(p->next_node_->previous_node_ == p); + if (p->is_sentinel_) sentinel_count++; + p = p->previous_node_; + } while (p != start); +} + +} // namespace utils +} // namespace spvtools + +#endif // LIBSPIRV_OPT_ILIST_H_ diff --git a/3rdparty/spirv-tools/source/util/ilist_node.h b/3rdparty/spirv-tools/source/util/ilist_node.h new file mode 100644 index 000000000..76ea302d7 --- /dev/null +++ b/3rdparty/spirv-tools/source/util/ilist_node.h @@ -0,0 +1,263 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_ILIST_NODE_H_ +#define LIBSPIRV_OPT_ILIST_NODE_H_ + +#include + +namespace spvtools { +namespace utils { + +template +class IntrusiveList; + +// IntrusiveNodeBase is the base class for nodes in an IntrusiveList. +// See the comments in ilist.h on how to use the class. + +template +class IntrusiveNodeBase { + public: + // Creates a new node that is not in a list. + inline IntrusiveNodeBase(); + inline IntrusiveNodeBase(const IntrusiveNodeBase&); + inline IntrusiveNodeBase& operator=(const IntrusiveNodeBase&); + inline IntrusiveNodeBase(IntrusiveNodeBase&& that); + + // Destroys a node. It is an error to destroy a node that is part of a + // list, unless it is a sentinel. + virtual ~IntrusiveNodeBase(); + + IntrusiveNodeBase& operator=(IntrusiveNodeBase&& that); + + // Returns true if |this| is in a list. + inline bool IsInAList() const; + + // Returns the node that comes after the given node in the list, if one + // exists. If the given node is not in a list or is at the end of the list, + // the return value is nullptr. + inline NodeType* NextNode() const; + + // Returns the node that comes before the given node in the list, if one + // exists. If the given node is not in a list or is at the start of the + // list, the return value is nullptr. + inline NodeType* PreviousNode() const; + + // Inserts the given node immediately before |pos| in the list. + // If the given node is already in a list, it will first be removed + // from that list. + // + // It is assumed that the given node is of type NodeType. It is an error if + // |pos| is not already in a list. + inline void InsertBefore(NodeType* pos); + + // Inserts the given node immediately after |pos| in the list. + // If the given node is already in a list, it will first be removed + // from that list. + // + // It is assumed that the given node is of type NodeType. It is an error if + // |pos| is not already in a list. + inline void InsertAfter(NodeType* pos); + + // Removes the given node from the list. It is assumed that the node is + // in a list. Note that this does not free any storage related to the node, + // it becomes the caller's responsibility to free the storage. + inline void RemoveFromList(); + + protected: + // Replaces |this| with |target|. |this| is a sentinel if and only if + // |target| is also a sentinel. + // + // If neither node is a sentinel, |target| takes + // the place of |this|. It is assumed that |target| is not in a list. + // + // If both are sentinels, then it will cause all of the + // nodes in the list containing |this| to be moved to the list containing + // |target|. In this case, it is assumed that |target| is an empty list. + // + // No storage will be deleted. + void ReplaceWith(NodeType* target); + + // Returns true if |this| is the sentinel node of an empty list. + bool IsEmptyList(); + + // The pointers to the next and previous nodes in the list. + // If the current node is not part of a list, then |next_node_| and + // |previous_node_| are equal to |nullptr|. + NodeType* next_node_; + NodeType* previous_node_; + + // Only true for the sentinel node stored in the list itself. + bool is_sentinel_; + + friend IntrusiveList; +}; + +// Implementation of IntrusiveNodeBase + +template +inline IntrusiveNodeBase::IntrusiveNodeBase() + : next_node_(nullptr), previous_node_(nullptr), is_sentinel_(false) {} + +template +inline IntrusiveNodeBase::IntrusiveNodeBase( + const IntrusiveNodeBase&) { + next_node_ = nullptr; + previous_node_ = nullptr; + is_sentinel_ = false; +} + +template +inline IntrusiveNodeBase& IntrusiveNodeBase::operator=( + const IntrusiveNodeBase&) { + assert(!is_sentinel_); + if (IsInAList()) { + RemoveFromList(); + } + return *this; +} + +template +inline IntrusiveNodeBase::IntrusiveNodeBase(IntrusiveNodeBase&& that) + : next_node_(nullptr), + previous_node_(nullptr), + is_sentinel_(that.is_sentinel_) { + if (is_sentinel_) { + next_node_ = this; + previous_node_ = this; + } + that.ReplaceWith(this); +} + +template +IntrusiveNodeBase::~IntrusiveNodeBase() { + assert(is_sentinel_ || !IsInAList()); +} + +template +IntrusiveNodeBase& IntrusiveNodeBase::operator=( + IntrusiveNodeBase&& that) { + that.ReplaceWith(this); + return *this; +} + +template +inline bool IntrusiveNodeBase::IsInAList() const { + return next_node_ != nullptr; +} + +template +inline NodeType* IntrusiveNodeBase::NextNode() const { + if (!next_node_->is_sentinel_) return next_node_; + return nullptr; +} + +template +inline NodeType* IntrusiveNodeBase::PreviousNode() const { + if (!previous_node_->is_sentinel_) return previous_node_; + return nullptr; +} + +template +inline void IntrusiveNodeBase::InsertBefore(NodeType* pos) { + assert(!this->is_sentinel_ && "Sentinel nodes cannot be moved around."); + assert(pos->IsInAList() && "Pos should already be in a list."); + if (this->IsInAList()) this->RemoveFromList(); + + this->next_node_ = pos; + this->previous_node_ = pos->previous_node_; + pos->previous_node_ = static_cast(this); + this->previous_node_->next_node_ = static_cast(this); +} + +template +inline void IntrusiveNodeBase::InsertAfter(NodeType* pos) { + assert(!this->is_sentinel_ && "Sentinel nodes cannot be moved around."); + assert(pos->IsInAList() && "Pos should already be in a list."); + if (this->IsInAList()) { + this->RemoveFromList(); + } + + this->previous_node_ = pos; + this->next_node_ = pos->next_node_; + pos->next_node_ = static_cast(this); + this->next_node_->previous_node_ = static_cast(this); +} + +template +inline void IntrusiveNodeBase::RemoveFromList() { + assert(!this->is_sentinel_ && "Sentinel nodes cannot be moved around."); + assert(this->IsInAList() && + "Cannot remove a node from a list if it is not in a list."); + + this->next_node_->previous_node_ = this->previous_node_; + this->previous_node_->next_node_ = this->next_node_; + this->next_node_ = nullptr; + this->previous_node_ = nullptr; +} + +template +void IntrusiveNodeBase::ReplaceWith(NodeType* target) { + if (this->is_sentinel_) { + assert(target->IsEmptyList() && + "If target is not an empty list, the nodes in that list would not " + "be linked to a sentinel."); + } else { + assert(IsInAList() && "The node being replaced must be in a list."); + assert(!target->is_sentinel_ && + "Cannot turn a sentinel node into one that is not."); + } + + if (!this->IsEmptyList()) { + // Link target into the same position that |this| was in. + target->next_node_ = this->next_node_; + target->previous_node_ = this->previous_node_; + target->next_node_->previous_node_ = target; + target->previous_node_->next_node_ = target; + + // Reset |this| to itself default value. + if (!this->is_sentinel_) { + // Reset |this| so that it is not in a list. + this->next_node_ = nullptr; + this->previous_node_ = nullptr; + } else { + // Set |this| so that it is the head of an empty list. + // We cannot treat sentinel nodes like others because it is invalid for + // a sentinel node to not be in a list. + this->next_node_ = static_cast(this); + this->previous_node_ = static_cast(this); + } + } else { + // If |this| points to itself, it must be a sentinel node with an empty + // list. Reset |this| so that it is the head of an empty list. We want + // |target| to be the same. The asserts above guarantee that. + } +} + +template +bool IntrusiveNodeBase::IsEmptyList() { + if (next_node_ == this) { + assert(is_sentinel_ && + "None sentinel nodes should never point to themselves."); + assert(previous_node_ == this && + "Inconsistency with the previous and next nodes."); + return true; + } + return false; +} + +} // namespace utils +} // namespace spvtools + +#endif // LIBSPIRV_OPT_ILIST_NODE_H_ diff --git a/3rdparty/spirv-tools/source/util/move_to_front.h b/3rdparty/spirv-tools/source/util/move_to_front.h new file mode 100644 index 000000000..de405dde4 --- /dev/null +++ b/3rdparty/spirv-tools/source/util/move_to_front.h @@ -0,0 +1,825 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_UTIL_MOVE_TO_FRONT_H_ +#define LIBSPIRV_UTIL_MOVE_TO_FRONT_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace spvutils { + +// Log(n) move-to-front implementation. Implements the following functions: +// Insert - pushes value to the front of the mtf sequence +// (only unique values allowed). +// Remove - remove value from the sequence. +// ValueFromRank - access value by its 1-indexed rank in the sequence. +// RankFromValue - get the rank of the given value in the sequence. +// Accessing a value with ValueFromRank or RankFromValue moves the value to the +// front of the sequence (rank of 1). +// +// The implementation is based on an AVL-based order statistic tree. The tree +// is ordered by timestamps issued when values are inserted or accessed (recent +// values go to the left side of the tree, old values are gradually rotated to +// the right side). +// +// Terminology +// rank: 1-indexed rank showing how recently the value was inserted or accessed. +// node: handle used internally to access node data. +// size: size of the subtree of a node (including the node). +// height: distance from a node to the farthest leaf. +template +class MoveToFront { + public: + explicit MoveToFront(size_t reserve_capacity = 4) { + nodes_.reserve(reserve_capacity); + + // Create NIL node. + nodes_.emplace_back(Node()); + } + + virtual ~MoveToFront() {} + + // Inserts value in the move-to-front sequence. Does nothing if the value is + // already in the sequence. Returns true if insertion was successful. + // The inserted value is placed at the front of the sequence (rank 1). + bool Insert(const Val& value); + + // Removes value from move-to-front sequence. Returns false iff the value + // was not found. + bool Remove(const Val& value); + + // Computes 1-indexed rank of value in the move-to-front sequence and moves + // the value to the front. Example: + // Before the call: 4 8 2 1 7 + // RankFromValue(8) returns 2 + // After the call: 8 4 2 1 7 + // Returns true iff the value was found in the sequence. + bool RankFromValue(const Val& value, uint32_t* rank); + + // Returns value corresponding to a 1-indexed rank in the move-to-front + // sequence and moves the value to the front. Example: + // Before the call: 4 8 2 1 7 + // ValueFromRank(2) returns 8 + // After the call: 8 4 2 1 7 + // Returns true iff the rank is within bounds [1, GetSize()]. + bool ValueFromRank(uint32_t rank, Val* value); + + // Moves the value to the front of the sequence. + // Returns false iff value is not in the sequence. + bool Promote(const Val& value); + + // Returns true iff the move-to-front sequence contains the value. + bool HasValue(const Val& value) const; + + // Returns the number of elements in the move-to-front sequence. + uint32_t GetSize() const { return SizeOf(root_); } + + protected: + // Internal tree data structure uses handles instead of pointers. Leaves and + // root parent reference a singleton under handle 0. Although dereferencing + // a null pointer is not possible, inappropriate access to handle 0 would + // cause an assertion. Handles are not garbage collected if value was + // deprecated + // with DeprecateValue(). But handles are recycled when a node is + // repositioned. + + // Internal tree data structure node. + struct Node { + // Timestamp from a logical clock which updates every time the element is + // accessed through ValueFromRank or RankFromValue. + uint32_t timestamp = 0; + // The size of the node's subtree, including the node. + // SizeOf(LeftOf(node)) + SizeOf(RightOf(node)) + 1. + uint32_t size = 0; + // Handles to connected nodes. + uint32_t left = 0; + uint32_t right = 0; + uint32_t parent = 0; + // Distance to the farthest leaf. + // Leaves have height 0, real nodes at least 1. + uint32_t height = 0; + // Stored value. + Val value = Val(); + }; + + // Creates node and sets correct values. Non-NIL nodes should be created only + // through this function. If the node with this value has been created + // previously + // and since orphaned, reuses the old node instead of creating a new one. + uint32_t CreateNode(uint32_t timestamp, const Val& value) { + uint32_t handle = static_cast(nodes_.size()); + const auto result = value_to_node_.emplace(value, handle); + if (result.second) { + // Create new node. + nodes_.emplace_back(Node()); + Node& node = nodes_.back(); + node.timestamp = timestamp; + node.value = value; + node.size = 1; + // Non-NIL nodes start with height 1 because their NIL children are + // leaves. + node.height = 1; + } else { + // Reuse old node. + handle = result.first->second; + assert(!IsInTree(handle)); + assert(ValueOf(handle) == value); + assert(SizeOf(handle) == 1); + assert(HeightOf(handle) == 1); + MutableTimestampOf(handle) = timestamp; + } + + return handle; + } + + // Node accessor methods. Naming is designed to be similar to natural + // language as these functions tend to be used in sequences, for example: + // ParentOf(LeftestDescendentOf(RightOf(node))) + + // Returns value of the node referenced by |handle|. + Val ValueOf(uint32_t node) const { return nodes_.at(node).value; } + + // Returns left child of |node|. + uint32_t LeftOf(uint32_t node) const { return nodes_.at(node).left; } + + // Returns right child of |node|. + uint32_t RightOf(uint32_t node) const { return nodes_.at(node).right; } + + // Returns parent of |node|. + uint32_t ParentOf(uint32_t node) const { return nodes_.at(node).parent; } + + // Returns timestamp of |node|. + uint32_t TimestampOf(uint32_t node) const { + assert(node); + return nodes_.at(node).timestamp; + } + + // Returns size of |node|. + uint32_t SizeOf(uint32_t node) const { return nodes_.at(node).size; } + + // Returns height of |node|. + uint32_t HeightOf(uint32_t node) const { return nodes_.at(node).height; } + + // Returns mutable reference to value of |node|. + Val& MutableValueOf(uint32_t node) { + assert(node); + return nodes_.at(node).value; + } + + // Returns mutable reference to handle of left child of |node|. + uint32_t& MutableLeftOf(uint32_t node) { + assert(node); + return nodes_.at(node).left; + } + + // Returns mutable reference to handle of right child of |node|. + uint32_t& MutableRightOf(uint32_t node) { + assert(node); + return nodes_.at(node).right; + } + + // Returns mutable reference to handle of parent of |node|. + uint32_t& MutableParentOf(uint32_t node) { + assert(node); + return nodes_.at(node).parent; + } + + // Returns mutable reference to timestamp of |node|. + uint32_t& MutableTimestampOf(uint32_t node) { + assert(node); + return nodes_.at(node).timestamp; + } + + // Returns mutable reference to size of |node|. + uint32_t& MutableSizeOf(uint32_t node) { + assert(node); + return nodes_.at(node).size; + } + + // Returns mutable reference to height of |node|. + uint32_t& MutableHeightOf(uint32_t node) { + assert(node); + return nodes_.at(node).height; + } + + // Returns true iff |node| is left child of its parent. + bool IsLeftChild(uint32_t node) const { + assert(node); + return LeftOf(ParentOf(node)) == node; + } + + // Returns true iff |node| is right child of its parent. + bool IsRightChild(uint32_t node) const { + assert(node); + return RightOf(ParentOf(node)) == node; + } + + // Returns true iff |node| has no relatives. + bool IsOrphan(uint32_t node) const { + assert(node); + return !ParentOf(node) && !LeftOf(node) && !RightOf(node); + } + + // Returns true iff |node| is in the tree. + bool IsInTree(uint32_t node) const { + assert(node); + return node == root_ || !IsOrphan(node); + } + + // Returns the height difference between right and left subtrees. + int BalanceOf(uint32_t node) const { + return int(HeightOf(RightOf(node))) - int(HeightOf(LeftOf(node))); + } + + // Updates size and height of the node, assuming that the children have + // correct values. + void UpdateNode(uint32_t node); + + // Returns the most LeftOf(LeftOf(... descendent which is not leaf. + uint32_t LeftestDescendantOf(uint32_t node) const { + uint32_t parent = 0; + while (node) { + parent = node; + node = LeftOf(node); + } + return parent; + } + + // Returns the most RightOf(RightOf(... descendent which is not leaf. + uint32_t RightestDescendantOf(uint32_t node) const { + uint32_t parent = 0; + while (node) { + parent = node; + node = RightOf(node); + } + return parent; + } + + // Inserts node in the tree. The node must be an orphan. + void InsertNode(uint32_t node); + + // Removes node from the tree. May change value_to_node_ if removal uses a + // scapegoat. Returns the removed (orphaned) handle for recycling. The + // returned handle may not be equal to |node| if scapegoat was used. + uint32_t RemoveNode(uint32_t node); + + // Rotates |node| left, reassigns all connections and returns the node + // which takes place of the |node|. + uint32_t RotateLeft(const uint32_t node); + + // Rotates |node| right, reassigns all connections and returns the node + // which takes place of the |node|. + uint32_t RotateRight(const uint32_t node); + + // Root node handle. The tree is empty if root_ is 0. + uint32_t root_ = 0; + + // Incremented counters for next timestamp and value. + uint32_t next_timestamp_ = 1; + + // Holds all tree nodes. Indices of this vector are node handles. + std::vector nodes_; + + // Maps ids to node handles. + std::unordered_map value_to_node_; + + // Cache for the last accessed value in the sequence. + Val last_accessed_value_ = Val(); + bool last_accessed_value_valid_ = false; +}; + +template +class MultiMoveToFront { + public: + // Inserts |value| to sequence with handle |mtf|. + // Returns false if |mtf| already has |value|. + bool Insert(uint64_t mtf, const Val& value) { + if (GetMtf(mtf).Insert(value)) { + val_to_mtfs_[value].insert(mtf); + return true; + } + return false; + } + + // Removes |value| from sequence with handle |mtf|. + // Returns false if |mtf| doesn't have |value|. + bool Remove(uint64_t mtf, const Val& value) { + if (GetMtf(mtf).Remove(value)) { + val_to_mtfs_[value].erase(mtf); + return true; + } + assert(val_to_mtfs_[value].count(mtf) == 0); + return false; + } + + // Removes |value| from all sequences which have it. + void RemoveFromAll(const Val& value) { + auto it = val_to_mtfs_.find(value); + if (it == val_to_mtfs_.end()) return; + + auto& mtfs_containing_value = it->second; + for (uint64_t mtf : mtfs_containing_value) { + GetMtf(mtf).Remove(value); + } + + val_to_mtfs_.erase(value); + } + + // Computes rank of |value| in sequence |mtf|. + // Returns false if |mtf| doesn't have |value|. + bool RankFromValue(uint64_t mtf, const Val& value, uint32_t* rank) { + return GetMtf(mtf).RankFromValue(value, rank); + } + + // Finds |value| with |rank| in sequence |mtf|. + // Returns false if |rank| is out of bounds. + bool ValueFromRank(uint64_t mtf, uint32_t rank, Val* value) { + return GetMtf(mtf).ValueFromRank(rank, value); + } + + // Returns size of |mtf| sequence. + uint32_t GetSize(uint64_t mtf) { return GetMtf(mtf).GetSize(); } + + // Promotes |value| in all sequences which have it. + void Promote(const Val& value) { + const auto it = val_to_mtfs_.find(value); + if (it == val_to_mtfs_.end()) return; + + const auto& mtfs_containing_value = it->second; + for (uint64_t mtf : mtfs_containing_value) { + GetMtf(mtf).Promote(value); + } + } + + // Inserts |value| in sequence |mtf| or promotes if it's already there. + void InsertOrPromote(uint64_t mtf, const Val& value) { + if (!Insert(mtf, value)) { + GetMtf(mtf).Promote(value); + } + } + + // Returns if |mtf| sequence has |value|. + bool HasValue(uint64_t mtf, const Val& value) { + return GetMtf(mtf).HasValue(value); + } + + private: + // Returns actual MoveToFront object corresponding to |handle|. + // As multiple operations are often performed consecutively for the same + // sequence, the last returned value is cached. + MoveToFront& GetMtf(uint64_t handle) { + if (!cached_mtf_ || cached_handle_ != handle) { + cached_handle_ = handle; + cached_mtf_ = &mtfs_[handle]; + } + + return *cached_mtf_; + } + + // Container holding MoveToFront objects. Map key is sequence handle. + std::map> mtfs_; + + // Container mapping value to sequences which contain that value. + std::unordered_map> val_to_mtfs_; + + // Cache for the last accessed sequence. + uint64_t cached_handle_ = 0; + MoveToFront* cached_mtf_ = nullptr; +}; + +template +bool MoveToFront::Insert(const Val& value) { + auto it = value_to_node_.find(value); + if (it != value_to_node_.end() && IsInTree(it->second)) return false; + + const uint32_t old_size = GetSize(); + (void)old_size; + + InsertNode(CreateNode(next_timestamp_++, value)); + + last_accessed_value_ = value; + last_accessed_value_valid_ = true; + + assert(value_to_node_.count(value)); + assert(old_size + 1 == GetSize()); + return true; +} + +template +bool MoveToFront::Remove(const Val& value) { + auto it = value_to_node_.find(value); + if (it == value_to_node_.end()) return false; + + if (!IsInTree(it->second)) return false; + + if (last_accessed_value_ == value) last_accessed_value_valid_ = false; + + const uint32_t orphan = RemoveNode(it->second); + (void)orphan; + // The node of |value| is still alive but it's orphaned now. Can still be + // reused later. + assert(!IsInTree(orphan)); + assert(ValueOf(orphan) == value); + return true; +} + +template +bool MoveToFront::RankFromValue(const Val& value, uint32_t* rank) { + if (last_accessed_value_valid_ && last_accessed_value_ == value) { + *rank = 1; + return true; + } + + const uint32_t old_size = GetSize(); + if (old_size == 1) { + if (ValueOf(root_) == value) { + *rank = 1; + return true; + } else { + return false; + } + } + + const auto it = value_to_node_.find(value); + if (it == value_to_node_.end()) { + return false; + } + + uint32_t target = it->second; + + if (!IsInTree(target)) { + return false; + } + + uint32_t node = target; + *rank = 1 + SizeOf(LeftOf(node)); + while (node) { + if (IsRightChild(node)) *rank += 1 + SizeOf(LeftOf(ParentOf(node))); + node = ParentOf(node); + } + + // Don't update timestamp if the node has rank 1. + if (*rank != 1) { + // Update timestamp and reposition the node. + target = RemoveNode(target); + assert(ValueOf(target) == value); + assert(old_size == GetSize() + 1); + MutableTimestampOf(target) = next_timestamp_++; + InsertNode(target); + assert(old_size == GetSize()); + } + + last_accessed_value_ = value; + last_accessed_value_valid_ = true; + return true; +} + +template +bool MoveToFront::HasValue(const Val& value) const { + const auto it = value_to_node_.find(value); + if (it == value_to_node_.end()) { + return false; + } + + return IsInTree(it->second); +} + +template +bool MoveToFront::Promote(const Val& value) { + if (last_accessed_value_valid_ && last_accessed_value_ == value) { + return true; + } + + const uint32_t old_size = GetSize(); + if (old_size == 1) return ValueOf(root_) == value; + + const auto it = value_to_node_.find(value); + if (it == value_to_node_.end()) { + return false; + } + + uint32_t target = it->second; + + if (!IsInTree(target)) { + return false; + } + + // Update timestamp and reposition the node. + target = RemoveNode(target); + assert(ValueOf(target) == value); + assert(old_size == GetSize() + 1); + MutableTimestampOf(target) = next_timestamp_++; + InsertNode(target); + assert(old_size == GetSize()); + + last_accessed_value_ = value; + last_accessed_value_valid_ = true; + return true; +} + +template +bool MoveToFront::ValueFromRank(uint32_t rank, Val* value) { + if (last_accessed_value_valid_ && rank == 1) { + *value = last_accessed_value_; + return true; + } + + const uint32_t old_size = GetSize(); + if (rank <= 0 || rank > old_size) { + return false; + } + + if (old_size == 1) { + *value = ValueOf(root_); + return true; + } + + const bool update_timestamp = (rank != 1); + + uint32_t node = root_; + while (node) { + const uint32_t left_subtree_num_nodes = SizeOf(LeftOf(node)); + if (rank == left_subtree_num_nodes + 1) { + // This is the node we are looking for. + // Don't update timestamp if the node has rank 1. + if (update_timestamp) { + node = RemoveNode(node); + assert(old_size == GetSize() + 1); + MutableTimestampOf(node) = next_timestamp_++; + InsertNode(node); + assert(old_size == GetSize()); + } + *value = ValueOf(node); + last_accessed_value_ = *value; + last_accessed_value_valid_ = true; + return true; + } + + if (rank < left_subtree_num_nodes + 1) { + // Descend into the left subtree. The rank is still valid. + node = LeftOf(node); + } else { + // Descend into the right subtree. We leave behind the left subtree and + // the current node, adjust the |rank| accordingly. + rank -= left_subtree_num_nodes + 1; + node = RightOf(node); + } + } + + assert(0); + return false; +} + +template +void MoveToFront::InsertNode(uint32_t node) { + assert(!IsInTree(node)); + assert(SizeOf(node) == 1); + assert(HeightOf(node) == 1); + assert(TimestampOf(node)); + + if (!root_) { + root_ = node; + return; + } + + uint32_t iter = root_; + uint32_t parent = 0; + + // Will determine if |node| will become the right or left child after + // insertion (but before balancing). + bool right_child = true; + + // Find the node which will become |node|'s parent after insertion + // (but before balancing). + while (iter) { + parent = iter; + assert(TimestampOf(iter) != TimestampOf(node)); + right_child = TimestampOf(iter) > TimestampOf(node); + iter = right_child ? RightOf(iter) : LeftOf(iter); + } + + assert(parent); + + // Connect node and parent. + MutableParentOf(node) = parent; + if (right_child) + MutableRightOf(parent) = node; + else + MutableLeftOf(parent) = node; + + // Insertion is finished. Start the balancing process. + bool needs_rebalancing = true; + parent = ParentOf(node); + + while (parent) { + UpdateNode(parent); + + if (needs_rebalancing) { + const int parent_balance = BalanceOf(parent); + + if (RightOf(parent) == node) { + // Added node to the right subtree. + if (parent_balance > 1) { + // Parent is right heavy, rotate left. + if (BalanceOf(node) < 0) RotateRight(node); + parent = RotateLeft(parent); + } else if (parent_balance == 0 || parent_balance == -1) { + // Parent is balanced or left heavy, no need to balance further. + needs_rebalancing = false; + } + } else { + // Added node to the left subtree. + if (parent_balance < -1) { + // Parent is left heavy, rotate right. + if (BalanceOf(node) > 0) RotateLeft(node); + parent = RotateRight(parent); + } else if (parent_balance == 0 || parent_balance == 1) { + // Parent is balanced or right heavy, no need to balance further. + needs_rebalancing = false; + } + } + } + + assert(BalanceOf(parent) >= -1 && (BalanceOf(parent) <= 1)); + + node = parent; + parent = ParentOf(parent); + } +} + +template +uint32_t MoveToFront::RemoveNode(uint32_t node) { + if (LeftOf(node) && RightOf(node)) { + // If |node| has two children, then use another node as scapegoat and swap + // their contents. We pick the scapegoat on the side of the tree which has + // more nodes. + const uint32_t scapegoat = SizeOf(LeftOf(node)) >= SizeOf(RightOf(node)) + ? RightestDescendantOf(LeftOf(node)) + : LeftestDescendantOf(RightOf(node)); + assert(scapegoat); + std::swap(MutableValueOf(node), MutableValueOf(scapegoat)); + std::swap(MutableTimestampOf(node), MutableTimestampOf(scapegoat)); + value_to_node_[ValueOf(node)] = node; + value_to_node_[ValueOf(scapegoat)] = scapegoat; + node = scapegoat; + } + + // |node| may have only one child at this point. + assert(!RightOf(node) || !LeftOf(node)); + + uint32_t parent = ParentOf(node); + uint32_t child = RightOf(node) ? RightOf(node) : LeftOf(node); + + // Orphan |node| and reconnect parent and child. + if (child) MutableParentOf(child) = parent; + + if (parent) { + if (LeftOf(parent) == node) + MutableLeftOf(parent) = child; + else + MutableRightOf(parent) = child; + } + + MutableParentOf(node) = 0; + MutableLeftOf(node) = 0; + MutableRightOf(node) = 0; + UpdateNode(node); + const uint32_t orphan = node; + + if (root_ == node) root_ = child; + + // Removal is finished. Start the balancing process. + bool needs_rebalancing = true; + node = child; + + while (parent) { + UpdateNode(parent); + + if (needs_rebalancing) { + const int parent_balance = BalanceOf(parent); + + if (parent_balance == 1 || parent_balance == -1) { + // The height of the subtree was not changed. + needs_rebalancing = false; + } else { + if (RightOf(parent) == node) { + // Removed node from the right subtree. + if (parent_balance < -1) { + // Parent is left heavy, rotate right. + const uint32_t sibling = LeftOf(parent); + if (BalanceOf(sibling) > 0) RotateLeft(sibling); + parent = RotateRight(parent); + } + } else { + // Removed node from the left subtree. + if (parent_balance > 1) { + // Parent is right heavy, rotate left. + const uint32_t sibling = RightOf(parent); + if (BalanceOf(sibling) < 0) RotateRight(sibling); + parent = RotateLeft(parent); + } + } + } + } + + assert(BalanceOf(parent) >= -1 && (BalanceOf(parent) <= 1)); + + node = parent; + parent = ParentOf(parent); + } + + return orphan; +} + +template +uint32_t MoveToFront::RotateLeft(const uint32_t node) { + const uint32_t pivot = RightOf(node); + assert(pivot); + + // LeftOf(pivot) gets attached to node in place of pivot. + MutableRightOf(node) = LeftOf(pivot); + if (RightOf(node)) MutableParentOf(RightOf(node)) = node; + + // Pivot gets attached to ParentOf(node) in place of node. + MutableParentOf(pivot) = ParentOf(node); + if (!ParentOf(node)) + root_ = pivot; + else if (IsLeftChild(node)) + MutableLeftOf(ParentOf(node)) = pivot; + else + MutableRightOf(ParentOf(node)) = pivot; + + // Node is child of pivot. + MutableLeftOf(pivot) = node; + MutableParentOf(node) = pivot; + + // Update both node and pivot. Pivot is the new parent of node, so node should + // be updated first. + UpdateNode(node); + UpdateNode(pivot); + + return pivot; +} + +template +uint32_t MoveToFront::RotateRight(const uint32_t node) { + const uint32_t pivot = LeftOf(node); + assert(pivot); + + // RightOf(pivot) gets attached to node in place of pivot. + MutableLeftOf(node) = RightOf(pivot); + if (LeftOf(node)) MutableParentOf(LeftOf(node)) = node; + + // Pivot gets attached to ParentOf(node) in place of node. + MutableParentOf(pivot) = ParentOf(node); + if (!ParentOf(node)) + root_ = pivot; + else if (IsLeftChild(node)) + MutableLeftOf(ParentOf(node)) = pivot; + else + MutableRightOf(ParentOf(node)) = pivot; + + // Node is child of pivot. + MutableRightOf(pivot) = node; + MutableParentOf(node) = pivot; + + // Update both node and pivot. Pivot is the new parent of node, so node should + // be updated first. + UpdateNode(node); + UpdateNode(pivot); + + return pivot; +} + +template +void MoveToFront::UpdateNode(uint32_t node) { + MutableSizeOf(node) = 1 + SizeOf(LeftOf(node)) + SizeOf(RightOf(node)); + MutableHeightOf(node) = + 1 + std::max(HeightOf(LeftOf(node)), HeightOf(RightOf(node))); +} + +} // namespace spvutils + +#endif // LIBSPIRV_UTIL_MOVE_TO_FRONT_H_ diff --git a/3rdparty/spirv-tools/source/util/parse_number.cpp b/3rdparty/spirv-tools/source/util/parse_number.cpp new file mode 100644 index 000000000..bb87b3dad --- /dev/null +++ b/3rdparty/spirv-tools/source/util/parse_number.cpp @@ -0,0 +1,214 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/parse_number.h" + +#include +#include +#include +#include +#include +#include + +#include "util/hex_float.h" + +namespace spvutils { + +namespace { +// A helper class that temporarily stores error messages and dump the messages +// to a string which given as as pointer when it is destructed. If the given +// pointer is a nullptr, this class does not store error message. +class ErrorMsgStream { + public: + explicit ErrorMsgStream(std::string* error_msg_sink) + : error_msg_sink_(error_msg_sink) { + if (error_msg_sink_) stream_.reset(new std::ostringstream()); + } + ~ErrorMsgStream() { + if (error_msg_sink_ && stream_) *error_msg_sink_ = stream_->str(); + } + template + ErrorMsgStream& operator<<(T val) { + if (stream_) *stream_ << val; + return *this; + } + + private: + std::unique_ptr stream_; + // The destination string to which this class dump the error message when + // destructor is called. + std::string* error_msg_sink_; +}; +} // namespace + +EncodeNumberStatus ParseAndEncodeIntegerNumber( + const char* text, const NumberType& type, + std::function emit, std::string* error_msg) { + if (!text) { + ErrorMsgStream(error_msg) << "The given text is a nullptr"; + return EncodeNumberStatus::kInvalidText; + } + + if (!IsIntegral(type)) { + ErrorMsgStream(error_msg) << "The expected type is not a integer type"; + return EncodeNumberStatus::kInvalidUsage; + } + + const uint32_t bit_width = AssumedBitWidth(type); + + if (bit_width > 64) { + ErrorMsgStream(error_msg) + << "Unsupported " << bit_width << "-bit integer literals"; + return EncodeNumberStatus::kUnsupported; + } + + // Either we are expecting anything or integer. + bool is_negative = text[0] == '-'; + bool can_be_signed = IsSigned(type); + + if (is_negative && !can_be_signed) { + ErrorMsgStream(error_msg) + << "Cannot put a negative number in an unsigned literal"; + return EncodeNumberStatus::kInvalidUsage; + } + + const bool is_hex = text[0] == '0' && (text[1] == 'x' || text[1] == 'X'); + + uint64_t decoded_bits; + if (is_negative) { + int64_t decoded_signed = 0; + + if (!ParseNumber(text, &decoded_signed)) { + ErrorMsgStream(error_msg) << "Invalid signed integer literal: " << text; + return EncodeNumberStatus::kInvalidText; + } + + if (!CheckRangeAndIfHexThenSignExtend(decoded_signed, type, is_hex, + &decoded_signed)) { + ErrorMsgStream(error_msg) + << "Integer " << (is_hex ? std::hex : std::dec) << std::showbase + << decoded_signed << " does not fit in a " << std::dec << bit_width + << "-bit " << (IsSigned(type) ? "signed" : "unsigned") << " integer"; + return EncodeNumberStatus::kInvalidText; + } + decoded_bits = decoded_signed; + } else { + // There's no leading minus sign, so parse it as an unsigned integer. + if (!ParseNumber(text, &decoded_bits)) { + ErrorMsgStream(error_msg) << "Invalid unsigned integer literal: " << text; + return EncodeNumberStatus::kInvalidText; + } + if (!CheckRangeAndIfHexThenSignExtend(decoded_bits, type, is_hex, + &decoded_bits)) { + ErrorMsgStream(error_msg) + << "Integer " << (is_hex ? std::hex : std::dec) << std::showbase + << decoded_bits << " does not fit in a " << std::dec << bit_width + << "-bit " << (IsSigned(type) ? "signed" : "unsigned") << " integer"; + return EncodeNumberStatus::kInvalidText; + } + } + if (bit_width > 32) { + uint32_t low = uint32_t(0x00000000ffffffff & decoded_bits); + uint32_t high = uint32_t((0xffffffff00000000 & decoded_bits) >> 32); + emit(low); + emit(high); + } else { + emit(uint32_t(decoded_bits)); + } + return EncodeNumberStatus::kSuccess; +} + +EncodeNumberStatus ParseAndEncodeFloatingPointNumber( + const char* text, const NumberType& type, + std::function emit, std::string* error_msg) { + if (!text) { + ErrorMsgStream(error_msg) << "The given text is a nullptr"; + return EncodeNumberStatus::kInvalidText; + } + + if (!IsFloating(type)) { + ErrorMsgStream(error_msg) << "The expected type is not a float type"; + return EncodeNumberStatus::kInvalidUsage; + } + + const auto bit_width = AssumedBitWidth(type); + switch (bit_width) { + case 16: { + HexFloat> hVal(0); + if (!ParseNumber(text, &hVal)) { + ErrorMsgStream(error_msg) << "Invalid 16-bit float literal: " << text; + return EncodeNumberStatus::kInvalidText; + } + // getAsFloat will return the spvutils::Float16 value, and get_value + // will return a uint16_t representing the bits of the float. + // The encoding is therefore correct from the perspective of the SPIR-V + // spec since the top 16 bits will be 0. + emit(static_cast(hVal.value().getAsFloat().get_value())); + return EncodeNumberStatus::kSuccess; + } break; + case 32: { + HexFloat> fVal(0.0f); + if (!ParseNumber(text, &fVal)) { + ErrorMsgStream(error_msg) << "Invalid 32-bit float literal: " << text; + return EncodeNumberStatus::kInvalidText; + } + emit(BitwiseCast(fVal)); + return EncodeNumberStatus::kSuccess; + } break; + case 64: { + HexFloat> dVal(0.0); + if (!ParseNumber(text, &dVal)) { + ErrorMsgStream(error_msg) << "Invalid 64-bit float literal: " << text; + return EncodeNumberStatus::kInvalidText; + } + uint64_t decoded_val = BitwiseCast(dVal); + uint32_t low = uint32_t(0x00000000ffffffff & decoded_val); + uint32_t high = uint32_t((0xffffffff00000000 & decoded_val) >> 32); + emit(low); + emit(high); + return EncodeNumberStatus::kSuccess; + } break; + default: + break; + } + ErrorMsgStream(error_msg) + << "Unsupported " << bit_width << "-bit float literals"; + return EncodeNumberStatus::kUnsupported; +} + +EncodeNumberStatus ParseAndEncodeNumber(const char* text, + const NumberType& type, + std::function emit, + std::string* error_msg) { + if (!text) { + ErrorMsgStream(error_msg) << "The given text is a nullptr"; + return EncodeNumberStatus::kInvalidText; + } + + if (IsUnknown(type)) { + ErrorMsgStream(error_msg) + << "The expected type is not a integer or float type"; + return EncodeNumberStatus::kInvalidUsage; + } + + // If we explicitly expect a floating-point number, we should handle that + // first. + if (IsFloating(type)) { + return ParseAndEncodeFloatingPointNumber(text, type, emit, error_msg); + } + + return ParseAndEncodeIntegerNumber(text, type, emit, error_msg); +} + +} // namespace spvutils diff --git a/3rdparty/spirv-tools/source/util/parse_number.h b/3rdparty/spirv-tools/source/util/parse_number.h new file mode 100644 index 000000000..2a9bd6d46 --- /dev/null +++ b/3rdparty/spirv-tools/source/util/parse_number.h @@ -0,0 +1,250 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_UTIL_PARSE_NUMBER_H_ +#define LIBSPIRV_UTIL_PARSE_NUMBER_H_ + +#include +#include +#include + +#include "spirv-tools/libspirv.h" +#include "util/hex_float.h" + +namespace spvutils { + +// A struct to hold the expected type information for the number in text to be +// parsed. +struct NumberType { + uint32_t bitwidth; + // SPV_NUMBER_NONE means the type is unknown and is invalid to be used with + // ParseAndEncode{|Integer|Floating}Number(). + spv_number_kind_t kind; +}; + +// Returns true if the type is a scalar integer type. +inline bool IsIntegral(const NumberType& type) { + return type.kind == SPV_NUMBER_UNSIGNED_INT || + type.kind == SPV_NUMBER_SIGNED_INT; +} + +// Returns true if the type is a scalar floating point type. +inline bool IsFloating(const NumberType& type) { + return type.kind == SPV_NUMBER_FLOATING; +} + +// Returns true if the type is a signed value. +inline bool IsSigned(const NumberType& type) { + return type.kind == SPV_NUMBER_FLOATING || type.kind == SPV_NUMBER_SIGNED_INT; +} + +// Returns true if the type is unknown. +inline bool IsUnknown(const NumberType& type) { + return type.kind == SPV_NUMBER_NONE; +} + +// Returns the number of bits in the type. This is only valid for integer and +// floating types. +inline int AssumedBitWidth(const NumberType& type) { + switch (type.kind) { + case SPV_NUMBER_SIGNED_INT: + case SPV_NUMBER_UNSIGNED_INT: + case SPV_NUMBER_FLOATING: + return type.bitwidth; + default: + break; + } + // We don't care about this case. + return 0; +} + +// A templated class with a static member function Clamp, where Clamp sets a +// referenced value of type T to 0 if T is an unsigned integer type, and +// returns true if it modified the referenced value. +template +class ClampToZeroIfUnsignedType { + public: + // The default specialization does not clamp the value. + static bool Clamp(T*) { return false; } +}; + +// The specialization of ClampToZeroIfUnsignedType for unsigned integer types. +template +class ClampToZeroIfUnsignedType< + T, typename std::enable_if::value>::type> { + public: + static bool Clamp(T* value_pointer) { + if (*value_pointer) { + *value_pointer = 0; + return true; + } + return false; + } +}; + +// Returns true if the given value fits within the target scalar integral type. +// The target type may have an unusual bit width. If the value was originally +// specified as a hexadecimal number, then the overflow bits should be zero. +// If it was hex and the target type is signed, then return the sign-extended +// value through the updated_value_for_hex pointer argument. On failure, +// returns false. +template +bool CheckRangeAndIfHexThenSignExtend(T value, const NumberType& type, + bool is_hex, T* updated_value_for_hex) { + // The encoded result has three regions of bits that are of interest, from + // least to most significant: + // - magnitude bits, where the magnitude of the number would be stored if + // we were using a signed-magnitude representation. + // - an optional sign bit + // - overflow bits, up to bit 63 of a 64-bit number + // For example: + // Type Overflow Sign Magnitude + // --------------- -------- ---- --------- + // unsigned 8 bit 8-63 n/a 0-7 + // signed 8 bit 8-63 7 0-6 + // unsigned 16 bit 16-63 n/a 0-15 + // signed 16 bit 16-63 15 0-14 + + // We'll use masks to define the three regions. + // At first we'll assume the number is unsigned. + const uint32_t bit_width = AssumedBitWidth(type); + uint64_t magnitude_mask = + (bit_width == 64) ? -1 : ((uint64_t(1) << bit_width) - 1); + uint64_t sign_mask = 0; + uint64_t overflow_mask = ~magnitude_mask; + + if (value < 0 || IsSigned(type)) { + // Accommodate the sign bit. + magnitude_mask >>= 1; + sign_mask = magnitude_mask + 1; + } + + bool failed = false; + if (value < 0) { + // The top bits must all be 1 for a negative signed value. + failed = ((value & overflow_mask) != overflow_mask) || + ((value & sign_mask) != sign_mask); + } else { + if (is_hex) { + // Hex values are a bit special. They decode as unsigned values, but may + // represent a negative number. In this case, the overflow bits should + // be zero. + failed = (value & overflow_mask) != 0; + } else { + const uint64_t value_as_u64 = static_cast(value); + // Check overflow in the ordinary case. + failed = (value_as_u64 & magnitude_mask) != value_as_u64; + } + } + + if (failed) { + return false; + } + + // Sign extend hex the number. + if (is_hex && (value & sign_mask)) + *updated_value_for_hex = (value | overflow_mask); + + return true; +} + +// Parses a numeric value of a given type from the given text. The number +// should take up the entire string, and should be within bounds for the target +// type. On success, returns true and populates the object referenced by +// value_pointer. On failure, returns false. +template +bool ParseNumber(const char* text, T* value_pointer) { + // C++11 doesn't define std::istringstream(int8_t&), so calling this method + // with a single-byte type leads to implementation-defined behaviour. + // Similarly for uint8_t. + static_assert(sizeof(T) > 1, + "Single-byte types are not supported in this parse method"); + + if (!text) return false; + std::istringstream text_stream(text); + // Allow both decimal and hex input for integers. + // It also allows octal input, but we don't care about that case. + text_stream >> std::setbase(0); + text_stream >> *value_pointer; + + // We should have read something. + bool ok = (text[0] != 0) && !text_stream.bad(); + // It should have been all the text. + ok = ok && text_stream.eof(); + // It should have been in range. + ok = ok && !text_stream.fail(); + + // Work around a bug in the GNU C++11 library. It will happily parse + // "-1" for uint16_t as 65535. + if (ok && text[0] == '-') + ok = !ClampToZeroIfUnsignedType::Clamp(value_pointer); + + return ok; +} + +// Enum to indicate the parsing and encoding status. +enum class EncodeNumberStatus { + kSuccess = 0, + // Unsupported bit width etc. + kUnsupported, + // Expected type (NumberType) is not a scalar int or float, or putting a + // negative number in an unsigned literal. + kInvalidUsage, + // Number value does not fit the bit width of the expected type etc. + kInvalidText, +}; + +// Parses an integer value of a given |type| from the given |text| and encodes +// the number by the given |emit| function. On success, returns +// EncodeNumberStatus::kSuccess and the parsed number will be consumed by the +// given |emit| function word by word (least significant word first). On +// failure, this function returns the error code of the encoding status and +// |emit| function will not be called. If the string pointer |error_msg| is not +// a nullptr, it will be overwritten with error messages in case of failure. In +// case of success, |error_msg| will not be touched. Integers up to 64 bits are +// supported. +EncodeNumberStatus ParseAndEncodeIntegerNumber( + const char* text, const NumberType& type, + std::function emit, std::string* error_msg); + +// Parses a floating point value of a given |type| from the given |text| and +// encodes the number by the given |emit| funciton. On success, returns +// EncodeNumberStatus::kSuccess and the parsed number will be consumed by the +// given |emit| function word by word (least significant word first). On +// failure, this function returns the error code of the encoding status and +// |emit| function will not be called. If the string pointer |error_msg| is not +// a nullptr, it will be overwritten with error messages in case of failure. In +// case of success, |error_msg| will not be touched. Only 16, 32 and 64 bit +// floating point numbers are supported. +EncodeNumberStatus ParseAndEncodeFloatingPointNumber( + const char* text, const NumberType& type, + std::function emit, std::string* error_msg); + +// Parses an integer or floating point number of a given |type| from the given +// |text| and encodes the number by the given |emit| function. On success, +// returns EncodeNumberStatus::kSuccess and the parsed number will be consumed +// by the given |emit| function word by word (least significant word first). On +// failure, this function returns the error code of the encoding status and +// |emit| function will not be called. If the string pointer |error_msg| is not +// a nullptr, it will be overwritten with error messages in case of failure. In +// case of success, |error_msg| will not be touched. Integers up to 64 bits +// and 16/32/64 bit floating point values are supported. +EncodeNumberStatus ParseAndEncodeNumber(const char* text, + const NumberType& type, + std::function emit, + std::string* error_msg); + +} // namespace spvutils + +#endif // LIBSPIRV_UTIL_PARSE_NUMBER_H_ diff --git a/3rdparty/spirv-tools/source/util/string_utils.cpp b/3rdparty/spirv-tools/source/util/string_utils.cpp new file mode 100644 index 000000000..830f1a3b1 --- /dev/null +++ b/3rdparty/spirv-tools/source/util/string_utils.cpp @@ -0,0 +1,39 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "util/string_utils.h" + +namespace spvutils { + +std::string CardinalToOrdinal(size_t cardinal) { + const size_t mod10 = cardinal % 10; + const size_t mod100 = cardinal % 100; + std::string suffix; + if (mod10 == 1 && mod100 != 11) + suffix = "st"; + else if (mod10 == 2 && mod100 != 12) + suffix = "nd"; + else if (mod10 == 3 && mod100 != 13) + suffix = "rd"; + else + suffix = "th"; + + return ToString(cardinal) + suffix; +} + +} // namespace spvutils diff --git a/3rdparty/spirv-tools/source/util/string_utils.h b/3rdparty/spirv-tools/source/util/string_utils.h new file mode 100644 index 000000000..993b58dfc --- /dev/null +++ b/3rdparty/spirv-tools/source/util/string_utils.h @@ -0,0 +1,40 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_UTIL_STRING_UTILS_H_ +#define LIBSPIRV_UTIL_STRING_UTILS_H_ + +#include +#include + +#include "util/string_utils.h" + +namespace spvutils { + +// Converts arithmetic value |val| to its default string representation. +template +std::string ToString(T val) { + static_assert(std::is_arithmetic::value, + "spvutils::ToString is restricted to only arithmetic values"); + std::stringstream os; + os << val; + return os.str(); +} + +// Converts cardinal number to ordinal number string. +std::string CardinalToOrdinal(size_t cardinal); + +} // namespace spvutils + +#endif // LIBSPIRV_UTIL_STRING_UTILS_H_ diff --git a/3rdparty/spirv-tools/source/util/timer.cpp b/3rdparty/spirv-tools/source/util/timer.cpp new file mode 100644 index 000000000..722bc6c41 --- /dev/null +++ b/3rdparty/spirv-tools/source/util/timer.cpp @@ -0,0 +1,100 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "util/timer.h" + +#include +#include +#include +#include +#include + +namespace spvutils { + +#if defined(SPIRV_TIMER_ENABLED) + +void PrintTimerDescription(std::ostream* out, bool measure_mem_usage) { + if (out) { + *out << std::setw(30) << "PASS name" << std::setw(12) << "CPU time" + << std::setw(12) << "WALL time" << std::setw(12) << "USR time" + << std::setw(12) << "SYS time"; + if (measure_mem_usage) { + *out << std::setw(12) << "RSS delta" << std::setw(16) << "PGFault delta"; + } + *out << std::endl; + } +} + +// Do not change the order of invoking system calls. We want to make CPU/Wall +// time correct as much as possible. Calling functions to get CPU/Wall time must +// closely surround the target code of measuring. +void Timer::Start() { + if (report_stream_) { + if (getrusage(RUSAGE_SELF, &usage_before_) == -1) + usage_status_ |= kGetrusageFailed; + if (clock_gettime(CLOCK_MONOTONIC, &wall_before_) == -1) + usage_status_ |= kClockGettimeWalltimeFailed; + if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &cpu_before_) == -1) + usage_status_ |= kClockGettimeCPUtimeFailed; + } +} + +// The order of invoking system calls is important with the same reason as +// Timer::Start(). +void Timer::Stop() { + if (report_stream_ && usage_status_ == kSucceeded) { + if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &cpu_after_) == -1) + usage_status_ |= kClockGettimeCPUtimeFailed; + if (clock_gettime(CLOCK_MONOTONIC, &wall_after_) == -1) + usage_status_ |= kClockGettimeWalltimeFailed; + if (getrusage(RUSAGE_SELF, &usage_after_) == -1) + usage_status_ = kGetrusageFailed; + } +} + +void Timer::Report(const char* tag) { + if (!report_stream_) return; + + report_stream_->precision(2); + *report_stream_ << std::fixed << std::setw(30) << tag; + + if (usage_status_ & kClockGettimeCPUtimeFailed) + *report_stream_ << std::setw(12) << "Failed"; + else + *report_stream_ << std::setw(12) << CPUTime(); + + if (usage_status_ & kClockGettimeWalltimeFailed) + *report_stream_ << std::setw(12) << "Failed"; + else + *report_stream_ << std::setw(12) << WallTime(); + + if (usage_status_ & kGetrusageFailed) { + *report_stream_ << std::setw(12) << "Failed" << std::setw(12) << "Failed"; + if (measure_mem_usage_) { + *report_stream_ << std::setw(12) << "Failed" << std::setw(12) << "Failed"; + } + } else { + *report_stream_ << std::setw(12) << UserTime() << std::setw(12) + << SystemTime(); + if (measure_mem_usage_) { + *report_stream_ << std::fixed << std::setw(12) << RSS() << std::setw(16) + << PageFault(); + } + } + *report_stream_ << std::endl; +} + +#endif // defined(SPIRV_TIMER_ENABLED) + +} // namespace spvutils diff --git a/3rdparty/spirv-tools/source/util/timer.h b/3rdparty/spirv-tools/source/util/timer.h new file mode 100644 index 000000000..c6af24e7a --- /dev/null +++ b/3rdparty/spirv-tools/source/util/timer.h @@ -0,0 +1,387 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Contains utils for getting resource utilization + +#ifndef LIBSPIRV_UTIL_TIMER_H_ +#define LIBSPIRV_UTIL_TIMER_H_ + +#if defined(SPIRV_TIMER_ENABLED) + +#include +#include +#include + +// A macro to call spvutils::PrintTimerDescription(std::ostream*, bool). The +// first argument must be given as std::ostream*. If it is NULL, the function +// does nothing. Otherwise, it prints resource types measured by Timer class. +// The second is optional and if it is true, the function also prints resource +// type fields related to memory. Otherwise, it does not print memory related +// fields. Its default is false. In usual, this must be placed before calling +// Timer::Report() to inform what those fields printed by Timer::Report() +// indicate (or spvutils::PrintTimerDescription() must be used instead). +#define SPIRV_TIMER_DESCRIPTION(...) \ + spvutils::PrintTimerDescription(__VA_ARGS__) + +// Creates an object of ScopedTimer to measure the resource utilization for the +// scope surrounding it as the following example: +// +// { // <-- beginning of this scope +// +// /* ... code out of interest ... */ +// +// SPIRV_TIMER_SCOPED(std::cout, tag); +// +// /* ... lines of code that we want to know its resource usage ... */ +// +// } // <-- end of this scope. The destructor of ScopedTimer prints tag and +// the resource utilization to std::cout. +#define SPIRV_TIMER_SCOPED(...) \ + spvutils::ScopedTimer timer##__LINE__(__VA_ARGS__) + +namespace spvutils { + +// Prints the description of resource types measured by Timer class. If |out| is +// NULL, it does nothing. Otherwise, it prints resource types. The second is +// optional and if it is true, the function also prints resource type fields +// related to memory. Its default is false. In usual, this must be placed before +// calling Timer::Report() to inform what those fields printed by +// Timer::Report() indicate. +void PrintTimerDescription(std::ostream*, bool = false); + +// Status of Timer. kGetrusageFailed means it failed in calling getrusage(). +// kClockGettimeWalltimeFailed means it failed in getting wall time when calling +// clock_gettime(). kClockGettimeCPUtimeFailed means it failed in getting CPU +// time when calling clock_gettime(). +enum UsageStatus { + kSucceeded = 0, + kGetrusageFailed = 1 << 0, + kClockGettimeWalltimeFailed = 1 << 1, + kClockGettimeCPUtimeFailed = 1 << 2, +}; + +// Timer measures the resource utilization for a range of code. The resource +// utilization consists of CPU time (i.e., process time), WALL time (elapsed +// time), USR time, SYS time, RSS delta, and the delta of the number of page +// faults. RSS delta and the delta of the number of page faults are measured +// only when |measure_mem_usage| given to the constructor is true. This class +// should be used as the following example: +// +// spvutils::Timer timer(std::cout); +// timer.Start(); // <-- set |usage_before_|, |wall_before_|, +// and |cpu_before_| +// +// /* ... lines of code that we want to know its resource usage ... */ +// +// timer.Stop(); // <-- set |cpu_after_|, |wall_after_|, and +// |usage_after_| +// timer.Report(tag); // <-- print tag and the resource utilization to +// std::cout. +class Timer { + public: + Timer(std::ostream* out, bool measure_mem_usage = false) + : report_stream_(out), + usage_status_(kSucceeded), + measure_mem_usage_(measure_mem_usage) {} + + // Sets |usage_before_|, |wall_before_|, and |cpu_before_| as results of + // getrusage(), clock_gettime() for the wall time, and clock_gettime() for the + // CPU time respectively. Note that this method erases all previous state of + // |usage_before_|, |wall_before_|, |cpu_before_|. + virtual void Start(); + + // Sets |cpu_after_|, |wall_after_|, and |usage_after_| as results of + // clock_gettime() for the wall time, and clock_gettime() for the CPU time, + // getrusage() respectively. Note that this method erases all previous state + // of |cpu_after_|, |wall_after_|, |usage_after_|. + virtual void Stop(); + + // If |report_stream_| is NULL, it does nothing. Otherwise, it prints the + // resource utilization (i.e., CPU/WALL/USR/SYS time, RSS delta) between the + // time of calling Timer::Start() and the time of calling Timer::Stop(). If we + // cannot get a resource usage because of failures, it prints "Failed" instead + // for the resource. + void Report(const char* tag); + + // Returns the measured CPU Time (i.e., process time) for a range of code + // execution. If kClockGettimeCPUtimeFailed is set by the failure of calling + // clock_gettime(), it returns -1. + virtual double CPUTime() { + if (usage_status_ & kClockGettimeCPUtimeFailed) return -1; + return TimeDifference(cpu_before_, cpu_after_); + } + + // Returns the measured Wall Time (i.e., elapsed time) for a range of code + // execution. If kClockGettimeWalltimeFailed is set by the failure of + // calling clock_gettime(), it returns -1. + virtual double WallTime() { + if (usage_status_ & kClockGettimeWalltimeFailed) return -1; + return TimeDifference(wall_before_, wall_after_); + } + + // Returns the measured USR Time for a range of code execution. If + // kGetrusageFailed is set because of the failure of calling getrusage(), it + // returns -1. + virtual double UserTime() { + if (usage_status_ & kGetrusageFailed) return -1; + return TimeDifference(usage_before_.ru_utime, usage_after_.ru_utime); + } + + // Returns the measured SYS Time for a range of code execution. If + // kGetrusageFailed is set because of the failure of calling getrusage(), it + // returns -1. + virtual double SystemTime() { + if (usage_status_ & kGetrusageFailed) return -1; + return TimeDifference(usage_before_.ru_stime, usage_after_.ru_stime); + } + + // Returns the measured RSS delta for a range of code execution. If + // kGetrusageFailed is set because of the failure of calling getrusage(), it + // returns -1. + virtual long RSS() const { + if (usage_status_ & kGetrusageFailed) return -1; + return usage_after_.ru_maxrss - usage_before_.ru_maxrss; + } + + // Returns the measured the delta of the number of page faults for a range of + // code execution. If kGetrusageFailed is set because of the failure of + // calling getrusage(), it returns -1. + virtual long PageFault() const { + if (usage_status_ & kGetrusageFailed) return -1; + return (usage_after_.ru_minflt - usage_before_.ru_minflt) + + (usage_after_.ru_majflt - usage_before_.ru_majflt); + } + + virtual ~Timer() {} + + private: + // Returns the time gap between |from| and |to| in seconds. + static double TimeDifference(const timeval& from, const timeval& to) { + assert((to.tv_sec > from.tv_sec) || + (to.tv_sec == from.tv_sec && to.tv_usec >= from.tv_usec)); + return static_cast(to.tv_sec - from.tv_sec) + + static_cast(to.tv_usec - from.tv_usec) * .000001; + } + + // Returns the time gap between |from| and |to| in seconds. + static double TimeDifference(const timespec& from, const timespec& to) { + assert((to.tv_sec > from.tv_sec) || + (to.tv_sec == from.tv_sec && to.tv_nsec >= from.tv_nsec)); + return static_cast(to.tv_sec - from.tv_sec) + + static_cast(to.tv_nsec - from.tv_nsec) * .000000001; + } + + // Output stream to print out the resource utilization. If it is NULL, + // Report() does nothing. + std::ostream* report_stream_; + + // Status to stop measurement if a system call returns an error. + unsigned usage_status_; + + // Variable to save the result of clock_gettime(CLOCK_PROCESS_CPUTIME_ID) when + // Timer::Start() is called. It is used as the base status of CPU time. + timespec cpu_before_; + + // Variable to save the result of clock_gettime(CLOCK_MONOTONIC) when + // Timer::Start() is called. It is used as the base status of WALL time. + timespec wall_before_; + + // Variable to save the result of getrusage() when Timer::Start() is called. + // It is used as the base status of USR time, SYS time, and RSS. + rusage usage_before_; + + // Variable to save the result of clock_gettime(CLOCK_PROCESS_CPUTIME_ID) when + // Timer::Stop() is called. It is used as the last status of CPU time. The + // resouce usage is measured by subtracting |cpu_before_| from it. + timespec cpu_after_; + + // Variable to save the result of clock_gettime(CLOCK_MONOTONIC) when + // Timer::Stop() is called. It is used as the last status of WALL time. The + // resouce usage is measured by subtracting |wall_before_| from it. + timespec wall_after_; + + // Variable to save the result of getrusage() when Timer::Stop() is called. It + // is used as the last status of USR time, SYS time, and RSS. Those resouce + // usages are measured by subtracting |usage_before_| from it. + rusage usage_after_; + + // If true, Timer reports the memory usage information too. Otherwise, Timer + // reports only USR time, WALL time, SYS time. + bool measure_mem_usage_; +}; + +// The purpose of ScopedTimer is to measure the resource utilization for a +// scope. Simply creating a local variable of ScopedTimer will call +// Timer::Start() and it calls Timer::Stop() and Timer::Report() at the end of +// the scope by its destructor. When we use this class, we must choose the +// proper Timer class (for class TimerType template) in advance. This class +// should be used as the following example: +// +// { // <-- beginning of this scope +// +// /* ... code out of interest ... */ +// +// spvutils::ScopedTimer scopedtimer(std::cout, tag); +// +// /* ... lines of code that we want to know its resource usage ... */ +// +// } // <-- end of this scope. The destructor of ScopedTimer prints tag and +// the resource utilization to std::cout. +// +// The template is used to choose a Timer class. Currently, +// only options for the Timer class are Timer and MockTimer in the unit test. +template +class ScopedTimer { + public: + ScopedTimer(std::ostream* out, const char* tag, + bool measure_mem_usage = false) + : timer(new TimerType(out, measure_mem_usage)), tag_(tag) { + timer->Start(); + } + + // At the end of the scope surrounding the instance of this class, this + // destructor saves the last status of resource usage and reports it. + virtual ~ScopedTimer() { + timer->Stop(); + timer->Report(tag_); + delete timer; + } + + private: + // Actual timer that measures the resource utilization. It must be an instance + // of Timer class if there is no special reason to use other class. + TimerType* timer; + + // A tag that will be printed in front of the trace reported by Timer class. + const char* tag_; +}; + +// CumulativeTimer is the same as Timer class, but it supports a cumulative +// measurement as the following example: +// +// CumulativeTimer *ctimer = new CumulativeTimer(std::cout); +// ctimer->Start(); +// +// /* ... lines of code that we want to know its resource usage ... */ +// +// ctimer->Stop(); +// +// /* ... code out of interest ... */ +// +// ctimer->Start(); +// +// /* ... lines of code that we want to know its resource usage ... */ +// +// ctimer->Stop(); +// ctimer->Report(tag); +// delete ctimer; +// +class CumulativeTimer : public Timer { + public: + CumulativeTimer(std::ostream* out, bool measure_mem_usage = false) + : Timer(out, measure_mem_usage), + cpu_time_(0), + wall_time_(0), + usr_time_(0), + sys_time_(0), + rss_(0), + pgfaults_(0) {} + + // If we cannot get a resource usage because of failures, it sets -1 for the + // resource usage. + void Stop() override { + Timer::Stop(); + + if (cpu_time_ >= 0 && Timer::CPUTime() >= 0) + cpu_time_ += Timer::CPUTime(); + else + cpu_time_ = -1; + + if (wall_time_ >= 0 && Timer::WallTime() >= 0) + wall_time_ += Timer::WallTime(); + else + wall_time_ = -1; + + if (usr_time_ >= 0 && Timer::UserTime() >= 0) + usr_time_ += Timer::UserTime(); + else + usr_time_ = -1; + + if (sys_time_ >= 0 && Timer::SystemTime() >= 0) + sys_time_ += Timer::SystemTime(); + else + sys_time_ = -1; + + if (rss_ >= 0 && Timer::RSS() >= 0) + rss_ += Timer::RSS(); + else + rss_ = -1; + + if (pgfaults_ >= 0 && Timer::PageFault() >= 0) + pgfaults_ += Timer::PageFault(); + else + pgfaults_ = -1; + } + + // Returns the cumulative CPU Time (i.e., process time) for a range of code + // execution. + double CPUTime() override { return cpu_time_; } + + // Returns the cumulative Wall Time (i.e., elapsed time) for a range of code + // execution. + double WallTime() override { return wall_time_; } + + // Returns the cumulative USR Time for a range of code execution. + double UserTime() override { return usr_time_; } + + // Returns the cumulative SYS Time for a range of code execution. + double SystemTime() override { return sys_time_; } + + // Returns the cumulative RSS delta for a range of code execution. + long RSS() const override { return rss_; } + + // Returns the cumulative delta of number of page faults for a range of code + // execution. + long PageFault() const override { return pgfaults_; } + + private: + // Variable to save the cumulative CPU time (i.e., process time). + double cpu_time_; + + // Variable to save the cumulative wall time (i.e., elapsed time). + double wall_time_; + + // Variable to save the cumulative user time. + double usr_time_; + + // Variable to save the cumulative system time. + double sys_time_; + + // Variable to save the cumulative RSS delta. + long rss_; + + // Variable to save the cumulative delta of the number of page faults. + long pgfaults_; +}; + +} // namespace spvutils + +#else // defined(SPIRV_TIMER_ENABLED) + +#define SPIRV_TIMER_DESCRIPTION(...) +#define SPIRV_TIMER_SCOPED(...) + +#endif // defined(SPIRV_TIMER_ENABLED) + +#endif // LIBSPIRV_UTIL_TIMER_H_ diff --git a/3rdparty/spirv-tools/source/val/basic_block.cpp b/3rdparty/spirv-tools/source/val/basic_block.cpp new file mode 100644 index 000000000..a0b10fa71 --- /dev/null +++ b/3rdparty/spirv-tools/source/val/basic_block.cpp @@ -0,0 +1,145 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "val/basic_block.h" + +#include +#include +#include + +using std::vector; + +namespace libspirv { + +BasicBlock::BasicBlock(uint32_t label_id) + : id_(label_id), + immediate_dominator_(nullptr), + immediate_post_dominator_(nullptr), + predecessors_(), + successors_(), + type_(0), + reachable_(false) {} + +void BasicBlock::SetImmediateDominator(BasicBlock* dom_block) { + immediate_dominator_ = dom_block; +} + +void BasicBlock::SetImmediatePostDominator(BasicBlock* pdom_block) { + immediate_post_dominator_ = pdom_block; +} + +const BasicBlock* BasicBlock::immediate_dominator() const { + return immediate_dominator_; +} + +const BasicBlock* BasicBlock::immediate_post_dominator() const { + return immediate_post_dominator_; +} + +BasicBlock* BasicBlock::immediate_dominator() { return immediate_dominator_; } +BasicBlock* BasicBlock::immediate_post_dominator() { + return immediate_post_dominator_; +} + +void BasicBlock::RegisterSuccessors(const vector& next_blocks) { + for (auto& block : next_blocks) { + block->predecessors_.push_back(this); + successors_.push_back(block); + if (block->reachable_ == false) block->set_reachable(reachable_); + } +} + +void BasicBlock::RegisterBranchInstruction(SpvOp branch_instruction) { + if (branch_instruction == SpvOpUnreachable) reachable_ = false; + return; +} + +bool BasicBlock::dominates(const BasicBlock& other) const { + return (this == &other) || + !(other.dom_end() == + std::find(other.dom_begin(), other.dom_end(), this)); +} + +bool BasicBlock::postdominates(const BasicBlock& other) const { + return (this == &other) || + !(other.pdom_end() == + std::find(other.pdom_begin(), other.pdom_end(), this)); +} + +BasicBlock::DominatorIterator::DominatorIterator() : current_(nullptr) {} + +BasicBlock::DominatorIterator::DominatorIterator( + const BasicBlock* block, + std::function dominator_func) + : current_(block), dom_func_(dominator_func) {} + +BasicBlock::DominatorIterator& BasicBlock::DominatorIterator::operator++() { + if (current_ == dom_func_(current_)) { + current_ = nullptr; + } else { + current_ = dom_func_(current_); + } + return *this; +} + +const BasicBlock::DominatorIterator BasicBlock::dom_begin() const { + return DominatorIterator( + this, [](const BasicBlock* b) { return b->immediate_dominator(); }); +} + +BasicBlock::DominatorIterator BasicBlock::dom_begin() { + return DominatorIterator( + this, [](const BasicBlock* b) { return b->immediate_dominator(); }); +} + +const BasicBlock::DominatorIterator BasicBlock::dom_end() const { + return DominatorIterator(); +} + +BasicBlock::DominatorIterator BasicBlock::dom_end() { + return DominatorIterator(); +} + +const BasicBlock::DominatorIterator BasicBlock::pdom_begin() const { + return DominatorIterator( + this, [](const BasicBlock* b) { return b->immediate_post_dominator(); }); +} + +BasicBlock::DominatorIterator BasicBlock::pdom_begin() { + return DominatorIterator( + this, [](const BasicBlock* b) { return b->immediate_post_dominator(); }); +} + +const BasicBlock::DominatorIterator BasicBlock::pdom_end() const { + return DominatorIterator(); +} + +BasicBlock::DominatorIterator BasicBlock::pdom_end() { + return DominatorIterator(); +} + +bool operator==(const BasicBlock::DominatorIterator& lhs, + const BasicBlock::DominatorIterator& rhs) { + return lhs.current_ == rhs.current_; +} + +bool operator!=(const BasicBlock::DominatorIterator& lhs, + const BasicBlock::DominatorIterator& rhs) { + return !(lhs == rhs); +} + +const BasicBlock*& BasicBlock::DominatorIterator::operator*() { + return current_; +} +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/val/basic_block.h b/3rdparty/spirv-tools/source/val/basic_block.h new file mode 100644 index 000000000..c2a5bb8fd --- /dev/null +++ b/3rdparty/spirv-tools/source/val/basic_block.h @@ -0,0 +1,226 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_VAL_BASICBLOCK_H_ +#define LIBSPIRV_VAL_BASICBLOCK_H_ + +#include "latest_version_spirv_header.h" + +#include + +#include +#include +#include +#include + +namespace libspirv { + +enum BlockType : uint32_t { + kBlockTypeUndefined, + kBlockTypeHeader, + kBlockTypeLoop, + kBlockTypeMerge, + kBlockTypeBreak, + kBlockTypeContinue, + kBlockTypeReturn, + kBlockTypeCOUNT ///< Total number of block types. (must be the last element) +}; + +// This class represents a basic block in a SPIR-V module +class BasicBlock { + public: + /// Constructor for a BasicBlock + /// + /// @param[in] id The ID of the basic block + explicit BasicBlock(uint32_t id); + + /// Returns the id of the BasicBlock + uint32_t id() const { return id_; } + + /// Returns the predecessors of the BasicBlock + const std::vector* predecessors() const { + return &predecessors_; + } + + /// Returns the predecessors of the BasicBlock + std::vector* predecessors() { return &predecessors_; } + + /// Returns the successors of the BasicBlock + const std::vector* successors() const { return &successors_; } + + /// Returns the successors of the BasicBlock + std::vector* successors() { return &successors_; } + + /// Returns true if the block is reachable in the CFG + bool reachable() const { return reachable_; } + + /// Returns true if BasicBlock is of the given type + bool is_type(BlockType type) const { + if (type == kBlockTypeUndefined) return type_.none(); + return type_.test(type); + } + + /// Sets the reachability of the basic block in the CFG + void set_reachable(bool reachability) { reachable_ = reachability; } + + /// Sets the type of the BasicBlock + void set_type(BlockType type) { + if (type == kBlockTypeUndefined) + type_.reset(); + else + type_.set(type); + } + + /// Sets the immedate dominator of this basic block + /// + /// @param[in] dom_block The dominator block + void SetImmediateDominator(BasicBlock* dom_block); + + /// Sets the immedate post dominator of this basic block + /// + /// @param[in] pdom_block The post dominator block + void SetImmediatePostDominator(BasicBlock* pdom_block); + + /// Returns the immedate dominator of this basic block + BasicBlock* immediate_dominator(); + + /// Returns the immedate dominator of this basic block + const BasicBlock* immediate_dominator() const; + + /// Returns the immedate post dominator of this basic block + BasicBlock* immediate_post_dominator(); + + /// Returns the immedate post dominator of this basic block + const BasicBlock* immediate_post_dominator() const; + + /// Ends the block without a successor + void RegisterBranchInstruction(SpvOp branch_instruction); + + /// Adds @p next BasicBlocks as successors of this BasicBlock + void RegisterSuccessors( + const std::vector& next = std::vector()); + + /// Returns true if the id of the BasicBlock matches + bool operator==(const BasicBlock& other) const { return other.id_ == id_; } + + /// Returns true if the id of the BasicBlock matches + bool operator==(const uint32_t& other_id) const { return other_id == id_; } + + /// Returns true if this block dominates the other block. + /// Assumes dominators have been computed. + bool dominates(const BasicBlock& other) const; + + /// Returns true if this block postdominates the other block. + /// Assumes dominators have been computed. + bool postdominates(const BasicBlock& other) const; + + /// @brief A BasicBlock dominator iterator class + /// + /// This iterator will iterate over the (post)dominators of the block + class DominatorIterator + : public std::iterator { + public: + /// @brief Constructs the end of dominator iterator + /// + /// This will create an iterator which will represent the element + /// before the root node of the dominator tree + DominatorIterator(); + + /// @brief Constructs an iterator for the given block which points to + /// @p block + /// + /// @param block The block which is referenced by the iterator + /// @param dominator_func This function will be called to get the immediate + /// (post)dominator of the current block + DominatorIterator( + const BasicBlock* block, + std::function dominator_func); + + /// @brief Advances the iterator + DominatorIterator& operator++(); + + /// @brief Returns the current element + const BasicBlock*& operator*(); + + friend bool operator==(const DominatorIterator& lhs, + const DominatorIterator& rhs); + + private: + const BasicBlock* current_; + std::function dom_func_; + }; + + /// Returns a dominator iterator which points to the current block + const DominatorIterator dom_begin() const; + + /// Returns a dominator iterator which points to the current block + DominatorIterator dom_begin(); + + /// Returns a dominator iterator which points to one element past the first + /// block + const DominatorIterator dom_end() const; + + /// Returns a dominator iterator which points to one element past the first + /// block + DominatorIterator dom_end(); + + /// Returns a post dominator iterator which points to the current block + const DominatorIterator pdom_begin() const; + /// Returns a post dominator iterator which points to the current block + DominatorIterator pdom_begin(); + + /// Returns a post dominator iterator which points to one element past the + /// last block + const DominatorIterator pdom_end() const; + + /// Returns a post dominator iterator which points to one element past the + /// last block + DominatorIterator pdom_end(); + + private: + /// Id of the BasicBlock + const uint32_t id_; + + /// Pointer to the immediate dominator of the BasicBlock + BasicBlock* immediate_dominator_; + + /// Pointer to the immediate dominator of the BasicBlock + BasicBlock* immediate_post_dominator_; + + /// The set of predecessors of the BasicBlock + std::vector predecessors_; + + /// The set of successors of the BasicBlock + std::vector successors_; + + /// The type of the block + std::bitset type_; + + /// True if the block is reachable in the CFG + bool reachable_; +}; + +/// @brief Returns true if the iterators point to the same element or if both +/// iterators point to the @p dom_end block +bool operator==(const BasicBlock::DominatorIterator& lhs, + const BasicBlock::DominatorIterator& rhs); + +/// @brief Returns true if the iterators point to different elements and they +/// do not both point to the @p dom_end block +bool operator!=(const BasicBlock::DominatorIterator& lhs, + const BasicBlock::DominatorIterator& rhs); + +} // namespace libspirv + +#endif /// LIBSPIRV_VAL_BASICBLOCK_H_ diff --git a/3rdparty/spirv-tools/source/val/construct.cpp b/3rdparty/spirv-tools/source/val/construct.cpp new file mode 100644 index 000000000..c5f01dfb6 --- /dev/null +++ b/3rdparty/spirv-tools/source/val/construct.cpp @@ -0,0 +1,67 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "val/construct.h" + +#include +#include + +namespace libspirv { + +Construct::Construct(ConstructType construct_type, BasicBlock* entry, + BasicBlock* exit, std::vector constructs) + : type_(construct_type), + corresponding_constructs_(constructs), + entry_block_(entry), + exit_block_(exit) {} + +ConstructType Construct::type() const { return type_; } + +const std::vector& Construct::corresponding_constructs() const { + return corresponding_constructs_; +} +std::vector& Construct::corresponding_constructs() { + return corresponding_constructs_; +} + +bool ValidateConstructSize(ConstructType type, size_t size) { + switch (type) { + case ConstructType::kSelection: + return size == 0; + case ConstructType::kContinue: + return size == 1; + case ConstructType::kLoop: + return size == 1; + case ConstructType::kCase: + return size >= 1; + default: + assert(1 == 0 && "Type not defined"); + } + return false; +} + +void Construct::set_corresponding_constructs( + std::vector constructs) { + assert(ValidateConstructSize(type_, constructs.size())); + corresponding_constructs_ = constructs; +} + +const BasicBlock* Construct::entry_block() const { return entry_block_; } +BasicBlock* Construct::entry_block() { return entry_block_; } + +const BasicBlock* Construct::exit_block() const { return exit_block_; } +BasicBlock* Construct::exit_block() { return exit_block_; } + +void Construct::set_exit(BasicBlock* block) { exit_block_ = block; } +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/val/construct.h b/3rdparty/spirv-tools/source/val/construct.h new file mode 100644 index 000000000..594d8d14b --- /dev/null +++ b/3rdparty/spirv-tools/source/val/construct.h @@ -0,0 +1,132 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_VAL_CONSTRUCT_H_ +#define LIBSPIRV_VAL_CONSTRUCT_H_ + +#include +#include + +namespace libspirv { + +enum class ConstructType : int { + kNone = 0, + /// The set of blocks dominated by a selection header, minus the set of blocks + /// dominated by the header's merge block + kSelection, + /// The set of blocks dominated by an OpLoopMerge's Continue Target and post + /// dominated by the corresponding back + kContinue, + /// The set of blocks dominated by a loop header, minus the set of blocks + /// dominated by the loop's merge block, minus the loop's corresponding + /// continue construct + kLoop, + /// The set of blocks dominated by an OpSwitch's Target or Default, minus the + /// set of blocks dominated by the OpSwitch's merge block (this construct is + /// only defined for those OpSwitch Target or Default that are not equal to + /// the OpSwitch's corresponding merge block) + kCase +}; + +class BasicBlock; + +/// @brief This class tracks the CFG constructs as defined in the SPIR-V spec +class Construct { + public: + Construct(ConstructType type, BasicBlock* dominator, + BasicBlock* exit = nullptr, + std::vector constructs = std::vector()); + + /// Returns the type of the construct + ConstructType type() const; + + const std::vector& corresponding_constructs() const; + std::vector& corresponding_constructs(); + void set_corresponding_constructs(std::vector constructs); + + /// Returns the dominator block of the construct. + /// + /// This is usually the header block or the first block of the construct. + const BasicBlock* entry_block() const; + + /// Returns the dominator block of the construct. + /// + /// This is usually the header block or the first block of the construct. + BasicBlock* entry_block(); + + /// Returns the exit block of the construct. + /// + /// For a continue construct it is the backedge block of the corresponding + /// loop construct. For the case construct it is the block that branches to + /// the OpSwitch merge block or other case blocks. Otherwise it is the merge + /// block of the corresponding header block + const BasicBlock* exit_block() const; + + /// Returns the exit block of the construct. + /// + /// For a continue construct it is the backedge block of the corresponding + /// loop construct. For the case construct it is the block that branches to + /// the OpSwitch merge block or other case blocks. Otherwise it is the merge + /// block of the corresponding header block + BasicBlock* exit_block(); + + /// Sets the exit block for this construct. This is useful for continue + /// constructs which do not know the back-edge block during construction + void set_exit(BasicBlock* exit_block); + + // Returns whether the exit block of this construct is the merge block + // for an OpLoopMerge or OpSelectionMerge + bool ExitBlockIsMergeBlock() const { + return type_ == ConstructType::kLoop || type_ == ConstructType::kSelection; + } + + private: + /// The type of the construct + ConstructType type_; + + /// These are the constructs that are related to this construct. These + /// constructs can be the continue construct, for the corresponding loop + /// construct, the case construct that are part of the same OpSwitch + /// instruction + /// + /// Here is a table that describes what constructs are included in + /// @p corresponding_constructs_ + /// | this construct | corresponding construct | + /// |----------------|----------------------------------| + /// | loop | continue | + /// | continue | loop | + /// | case | other cases in the same OpSwitch | + /// + /// kContinue and kLoop constructs will always have corresponding + /// constructs even if they are represented by the same block + std::vector corresponding_constructs_; + + /// @brief Dominator block for the construct + /// + /// The dominator block for the construct. Depending on the construct this may + /// be a selection header, a continue target of a loop, a loop header or a + /// Target or Default block of a switch + BasicBlock* entry_block_; + + /// @brief Exiting block for the construct + /// + /// The exit block for the construct. This can be a merge block for the loop + /// and selection constructs, a back-edge block for a continue construct, or + /// the branching block for the case construct + BasicBlock* exit_block_; +}; + +} // namespace libspirv + +#endif /// LIBSPIRV_VAL_CONSTRUCT_H_ diff --git a/3rdparty/spirv-tools/source/val/decoration.h b/3rdparty/spirv-tools/source/val/decoration.h new file mode 100644 index 000000000..8d2899157 --- /dev/null +++ b/3rdparty/spirv-tools/source/val/decoration.h @@ -0,0 +1,87 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_VAL_DECORATION_H_ +#define LIBSPIRV_VAL_DECORATION_H_ + +#include +#include +#include + +#include "latest_version_spirv_header.h" + +namespace libspirv { + +// An object of this class represents a specific decoration including its +// parameters (if any). Decorations are used by OpDecorate and OpMemberDecorate, +// and they describe certain properties that can be assigned to one or several +// s. +// +// A Decoration object contains the decoration type (an enum), associated +// literal parameters, and struct member index. If the decoration does not apply +// to a struct member, then the index is kInvalidIndex. A Decoration object does +// not store the target Id, i.e. the Id to which it applies. It is +// possible for the same decoration to be applied to several s (and they +// might be assigned using separate SPIR-V instructions, possibly using an +// assignment through GroupDecorate). +// +// Example 1: Decoration for an object with no parameters: +// OpDecorate %obj Flat +// dec_type_ = SpvDecorationFlat +// params_ = empty vector +// struct_member_index_ = kInvalidMember +// +// Example 2: Decoration for an object with two parameters: +// OpDecorate %obj LinkageAttributes "link" Import +// dec_type_ = SpvDecorationLinkageAttributes +// params_ = vector { link, Import } +// struct_member_index_ = kInvalidMember +// +// Example 3: Decoration for a member of a structure with one parameter: +// OpMemberDecorate %struct 2 Offset 2 +// dec_type_ = SpvDecorationOffset +// params_ = vector { 2 } +// struct_member_index_ = 2 +// +class Decoration { + public: + enum { kInvalidMember = -1 }; + Decoration(SpvDecoration t, + const std::vector& parameters = std::vector(), + uint32_t member_index = kInvalidMember) + : dec_type_(t), params_(parameters), struct_member_index_(member_index) {} + + void set_struct_member_index(uint32_t index) { struct_member_index_ = index; } + int struct_member_index() const { return struct_member_index_; } + SpvDecoration dec_type() const { return dec_type_; } + std::vector& params() { return params_; } + const std::vector& params() const { return params_; } + + inline bool operator==(const Decoration& rhs) const { + return (dec_type_ == rhs.dec_type_ && params_ == rhs.params_ && + struct_member_index_ == rhs.struct_member_index_); + } + + private: + SpvDecoration dec_type_; + std::vector params_; + + // If the decoration applies to a member of a structure type, then the index + // of the member is stored here. Otherwise, this is kInvalidIndex. + int struct_member_index_; +}; + +} // namespace libspirv + +#endif /// LIBSPIRV_VAL_DECORATION_H_ diff --git a/3rdparty/spirv-tools/source/val/function.cpp b/3rdparty/spirv-tools/source/val/function.cpp new file mode 100644 index 000000000..7f49ad0c2 --- /dev/null +++ b/3rdparty/spirv-tools/source/val/function.cpp @@ -0,0 +1,389 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "val/function.h" + +#include + +#include +#include +#include +#include +#include + +#include "cfa.h" +#include "val/basic_block.h" +#include "val/construct.h" +#include "validate.h" + +using std::ignore; +using std::list; +using std::make_pair; +using std::pair; +using std::tie; +using std::vector; + +namespace libspirv { + +// Universal Limit of ResultID + 1 +static const uint32_t kInvalidId = 0x400000; + +Function::Function(uint32_t function_id, uint32_t result_type_id, + SpvFunctionControlMask function_control, + uint32_t function_type_id) + : id_(function_id), + function_type_id_(function_type_id), + result_type_id_(result_type_id), + function_control_(function_control), + declaration_type_(FunctionDecl::kFunctionDeclUnknown), + end_has_been_registered_(false), + blocks_(), + current_block_(nullptr), + pseudo_entry_block_(0), + pseudo_exit_block_(kInvalidId), + cfg_constructs_(), + variable_ids_(), + parameter_ids_() {} + +bool Function::IsFirstBlock(uint32_t block_id) const { + return !ordered_blocks_.empty() && *first_block() == block_id; +} + +spv_result_t Function::RegisterFunctionParameter(uint32_t parameter_id, + uint32_t type_id) { + assert(current_block_ == nullptr && + "RegisterFunctionParameter can only be called when parsing the binary " + "ouside of a block"); + // TODO(umar): Validate function parameter type order and count + // TODO(umar): Use these variables to validate parameter type + (void)parameter_id; + (void)type_id; + return SPV_SUCCESS; +} + +spv_result_t Function::RegisterLoopMerge(uint32_t merge_id, + uint32_t continue_id) { + RegisterBlock(merge_id, false); + RegisterBlock(continue_id, false); + BasicBlock& merge_block = blocks_.at(merge_id); + BasicBlock& continue_target_block = blocks_.at(continue_id); + assert(current_block_ && + "RegisterLoopMerge must be called when called within a block"); + + current_block_->set_type(kBlockTypeLoop); + merge_block.set_type(kBlockTypeMerge); + continue_target_block.set_type(kBlockTypeContinue); + Construct& loop_construct = + AddConstruct({ConstructType::kLoop, current_block_, &merge_block}); + Construct& continue_construct = + AddConstruct({ConstructType::kContinue, &continue_target_block}); + + continue_construct.set_corresponding_constructs({&loop_construct}); + loop_construct.set_corresponding_constructs({&continue_construct}); + merge_block_header_[&merge_block] = current_block_; + + return SPV_SUCCESS; +} + +spv_result_t Function::RegisterSelectionMerge(uint32_t merge_id) { + RegisterBlock(merge_id, false); + BasicBlock& merge_block = blocks_.at(merge_id); + current_block_->set_type(kBlockTypeHeader); + merge_block.set_type(kBlockTypeMerge); + merge_block_header_[&merge_block] = current_block_; + + AddConstruct({ConstructType::kSelection, current_block(), &merge_block}); + + return SPV_SUCCESS; +} + +spv_result_t Function::RegisterSetFunctionDeclType(FunctionDecl type) { + assert(declaration_type_ == FunctionDecl::kFunctionDeclUnknown); + declaration_type_ = type; + return SPV_SUCCESS; +} + +spv_result_t Function::RegisterBlock(uint32_t block_id, bool is_definition) { + assert( + declaration_type_ == FunctionDecl::kFunctionDeclDefinition && + "RegisterBlocks can only be called after declaration_type_ is defined"); + + std::unordered_map::iterator inserted_block; + bool success = false; + tie(inserted_block, success) = + blocks_.insert({block_id, BasicBlock(block_id)}); + if (is_definition) { // new block definition + assert(current_block_ == nullptr && + "Register Block can only be called when parsing a binary outside of " + "a BasicBlock"); + + undefined_blocks_.erase(block_id); + current_block_ = &inserted_block->second; + ordered_blocks_.push_back(current_block_); + if (IsFirstBlock(block_id)) current_block_->set_reachable(true); + } else if (success) { // Block doesn't exsist but this is not a definition + undefined_blocks_.insert(block_id); + } + + return SPV_SUCCESS; +} + +void Function::RegisterBlockEnd(vector next_list, + SpvOp branch_instruction) { + assert( + current_block_ && + "RegisterBlockEnd can only be called when parsing a binary in a block"); + vector next_blocks; + next_blocks.reserve(next_list.size()); + + std::unordered_map::iterator inserted_block; + bool success; + for (uint32_t successor_id : next_list) { + tie(inserted_block, success) = + blocks_.insert({successor_id, BasicBlock(successor_id)}); + if (success) { + undefined_blocks_.insert(successor_id); + } + next_blocks.push_back(&inserted_block->second); + } + + if (current_block_->is_type(kBlockTypeLoop)) { + // For each loop header, record the set of its successors, and include + // its continue target if the continue target is not the loop header + // itself. + std::vector& next_blocks_plus_continue_target = + loop_header_successors_plus_continue_target_map_[current_block_]; + next_blocks_plus_continue_target = next_blocks; + auto continue_target = + FindConstructForEntryBlock(current_block_, ConstructType::kLoop) + .corresponding_constructs() + .back() + ->entry_block(); + if (continue_target != current_block_) { + next_blocks_plus_continue_target.push_back(continue_target); + } + } + + current_block_->RegisterBranchInstruction(branch_instruction); + current_block_->RegisterSuccessors(next_blocks); + current_block_ = nullptr; + return; +} + +void Function::RegisterFunctionEnd() { + if (!end_has_been_registered_) { + end_has_been_registered_ = true; + + ComputeAugmentedCFG(); + } +} + +size_t Function::block_count() const { return blocks_.size(); } + +size_t Function::undefined_block_count() const { + return undefined_blocks_.size(); +} + +const vector& Function::ordered_blocks() const { + return ordered_blocks_; +} +vector& Function::ordered_blocks() { return ordered_blocks_; } + +const BasicBlock* Function::current_block() const { return current_block_; } +BasicBlock* Function::current_block() { return current_block_; } + +const list& Function::constructs() const { return cfg_constructs_; } +list& Function::constructs() { return cfg_constructs_; } + +const BasicBlock* Function::first_block() const { + if (ordered_blocks_.empty()) return nullptr; + return ordered_blocks_[0]; +} +BasicBlock* Function::first_block() { + if (ordered_blocks_.empty()) return nullptr; + return ordered_blocks_[0]; +} + +bool Function::IsBlockType(uint32_t merge_block_id, BlockType type) const { + bool ret = false; + const BasicBlock* block; + tie(block, ignore) = GetBlock(merge_block_id); + if (block) { + ret = block->is_type(type); + } + return ret; +} + +pair Function::GetBlock(uint32_t block_id) const { + const auto b = blocks_.find(block_id); + if (b != end(blocks_)) { + const BasicBlock* block = &(b->second); + bool defined = + undefined_blocks_.find(block->id()) == end(undefined_blocks_); + return make_pair(block, defined); + } else { + return make_pair(nullptr, false); + } +} + +pair Function::GetBlock(uint32_t block_id) { + const BasicBlock* out; + bool defined; + tie(out, defined) = const_cast(this)->GetBlock(block_id); + return make_pair(const_cast(out), defined); +} + +Function::GetBlocksFunction Function::AugmentedCFGSuccessorsFunction() const { + return [this](const BasicBlock* block) { + auto where = augmented_successors_map_.find(block); + return where == augmented_successors_map_.end() ? block->successors() + : &(*where).second; + }; +} + +Function::GetBlocksFunction +Function::AugmentedCFGSuccessorsFunctionIncludingHeaderToContinueEdge() const { + return [this](const BasicBlock* block) { + auto where = loop_header_successors_plus_continue_target_map_.find(block); + return where == loop_header_successors_plus_continue_target_map_.end() + ? AugmentedCFGSuccessorsFunction()(block) + : &(*where).second; + }; +} + +Function::GetBlocksFunction Function::AugmentedCFGPredecessorsFunction() const { + return [this](const BasicBlock* block) { + auto where = augmented_predecessors_map_.find(block); + return where == augmented_predecessors_map_.end() ? block->predecessors() + : &(*where).second; + }; +} + +void Function::ComputeAugmentedCFG() { + // Compute the successors of the pseudo-entry block, and + // the predecessors of the pseudo exit block. + auto succ_func = [](const BasicBlock* b) { return b->successors(); }; + auto pred_func = [](const BasicBlock* b) { return b->predecessors(); }; + spvtools::CFA::ComputeAugmentedCFG( + ordered_blocks_, &pseudo_entry_block_, &pseudo_exit_block_, + &augmented_successors_map_, &augmented_predecessors_map_, succ_func, + pred_func); +} + +Construct& Function::AddConstruct(const Construct& new_construct) { + cfg_constructs_.push_back(new_construct); + auto& result = cfg_constructs_.back(); + entry_block_to_construct_[std::make_pair(new_construct.entry_block(), + new_construct.type())] = &result; + return result; +} + +Construct& Function::FindConstructForEntryBlock(const BasicBlock* entry_block, + ConstructType type) { + auto where = + entry_block_to_construct_.find(std::make_pair(entry_block, type)); + assert(where != entry_block_to_construct_.end()); + auto construct_ptr = (*where).second; + assert(construct_ptr); + return *construct_ptr; +} + +int Function::GetBlockDepth(BasicBlock* bb) { + // Guard against nullptr. + if (!bb) { + return 0; + } + // Only calculate the depth if it's not already calculated. + // This function uses memoization to avoid duplicate CFG depth calculations. + if (block_depth_.find(bb) != block_depth_.end()) { + return block_depth_[bb]; + } + + BasicBlock* bb_dom = bb->immediate_dominator(); + if (!bb_dom || bb == bb_dom) { + // This block has no dominator, so it's at depth 0. + block_depth_[bb] = 0; + } else if (bb->is_type(kBlockTypeMerge)) { + // If this is a merge block, its depth is equal to the block before + // branching. + BasicBlock* header = merge_block_header_[bb]; + assert(header); + block_depth_[bb] = GetBlockDepth(header); + } else if (bb->is_type(kBlockTypeContinue)) { + // The depth of the continue block entry point is 1 + loop header depth. + Construct* continue_construct = + entry_block_to_construct_[std::make_pair(bb, ConstructType::kContinue)]; + assert(continue_construct); + // Continue construct has only 1 corresponding construct (loop header). + Construct* loop_construct = + continue_construct->corresponding_constructs()[0]; + assert(loop_construct); + BasicBlock* loop_header = loop_construct->entry_block(); + // The continue target may be the loop itself (while 1). + // In such cases, the depth of the continue block is: 1 + depth of the + // loop's dominator block. + if (loop_header == bb) { + block_depth_[bb] = 1 + GetBlockDepth(bb_dom); + } else { + block_depth_[bb] = 1 + GetBlockDepth(loop_header); + } + } else if (bb_dom->is_type(kBlockTypeHeader) || + bb_dom->is_type(kBlockTypeLoop)) { + // The dominator of the given block is a header block. So, the nesting + // depth of this block is: 1 + nesting depth of the header. + block_depth_[bb] = 1 + GetBlockDepth(bb_dom); + } else { + block_depth_[bb] = GetBlockDepth(bb_dom); + } + return block_depth_[bb]; +} + +void Function::RegisterExecutionModelLimitation(SpvExecutionModel model, + const std::string& message) { + execution_model_limitations_.push_back( + [model, message](SpvExecutionModel in_model, std::string* out_message) { + if (model != in_model) { + if (out_message) { + *out_message = message; + } + return false; + } + return true; + }); +} + +bool Function::IsCompatibleWithExecutionModel(SpvExecutionModel model, + std::string* reason) const { + bool return_value = true; + std::stringstream ss_reason; + + for (const auto& is_compatible : execution_model_limitations_) { + std::string message; + if (!is_compatible(model, &message)) { + if (!reason) return false; + return_value = false; + if (!message.empty()) { + ss_reason << message << "\n"; + } + } + } + + if (!return_value && reason) { + *reason = ss_reason.str(); + } + + return return_value; +} + +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/val/function.h b/3rdparty/spirv-tools/source/val/function.h new file mode 100644 index 000000000..1984654f0 --- /dev/null +++ b/3rdparty/spirv-tools/source/val/function.h @@ -0,0 +1,356 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_VAL_FUNCTION_H_ +#define LIBSPIRV_VAL_FUNCTION_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "latest_version_spirv_header.h" +#include "spirv-tools/libspirv.h" +#include "val/basic_block.h" +#include "val/construct.h" + +namespace libspirv { + +struct bb_constr_type_pair_hash { + std::size_t operator()( + const std::pair& p) const { + auto h1 = std::hash{}(p.first); + auto h2 = std::hash::type>{}( + static_cast::type>(p.second)); + return (h1 ^ h2); + } +}; + +enum class FunctionDecl { + kFunctionDeclUnknown, /// < Unknown function declaration + kFunctionDeclDeclaration, /// < Function declaration + kFunctionDeclDefinition /// < Function definition +}; + +/// This class manages all function declaration and definitions in a module. It +/// handles the state and id information while parsing a function in the SPIR-V +/// binary. +class Function { + public: + Function(uint32_t id, uint32_t result_type_id, + SpvFunctionControlMask function_control, uint32_t function_type_id); + + /// Registers a function parameter in the current function + /// @return Returns SPV_SUCCESS if the call was successful + spv_result_t RegisterFunctionParameter(uint32_t id, uint32_t type_id); + + /// Sets the declaration type of the current function + /// @return Returns SPV_SUCCESS if the call was successful + spv_result_t RegisterSetFunctionDeclType(FunctionDecl type); + + /// Registers a block in the current function. Subsequent block instructions + /// will target this block + /// @param id The ID of the label of the block + /// @return Returns SPV_SUCCESS if the call was successful + spv_result_t RegisterBlock(uint32_t id, bool is_definition = true); + + /// Registers a variable in the current block + /// + /// @param[in] type_id The type ID of the varaible + /// @param[in] id The ID of the varaible + /// @param[in] storage The storage of the variable + /// @param[in] init_id The initializer ID of the variable + /// + /// @return Returns SPV_SUCCESS if the call was successful + spv_result_t RegisterBlockVariable(uint32_t type_id, uint32_t id, + SpvStorageClass storage, uint32_t init_id); + + /// Registers a loop merge construct in the function + /// + /// @param[in] merge_id The merge block ID of the loop + /// @param[in] continue_id The continue block ID of the loop + /// + /// @return Returns SPV_SUCCESS if the call was successful + spv_result_t RegisterLoopMerge(uint32_t merge_id, uint32_t continue_id); + + /// Registers a selection merge construct in the function + /// @return Returns SPV_SUCCESS if the call was successful + spv_result_t RegisterSelectionMerge(uint32_t merge_id); + + /// Registers the end of the block + /// + /// @param[in] successors_list A list of ids to the block's successors + /// @param[in] branch_instruction the branch instruction that ended the block + void RegisterBlockEnd(std::vector successors_list, + SpvOp branch_instruction); + + /// Registers the end of the function. This is idempotent. + void RegisterFunctionEnd(); + + /// Returns true if the \p id block is the first block of this function + bool IsFirstBlock(uint32_t id) const; + + /// Returns true if the \p merge_block_id is a BlockType of \p type + bool IsBlockType(uint32_t merge_block_id, BlockType type) const; + + /// Returns a pair consisting of the BasicBlock with \p id and a bool + /// which is true if the block has been defined, and false if it is + /// declared but not defined. This function will return nullptr if the + /// \p id was not declared and not defined at the current point in the binary + std::pair GetBlock(uint32_t id) const; + std::pair GetBlock(uint32_t id); + + /// Returns the first block of the current function + const BasicBlock* first_block() const; + + /// Returns the first block of the current function + BasicBlock* first_block(); + + /// Returns a vector of all the blocks in the function + const std::vector& ordered_blocks() const; + + /// Returns a vector of all the blocks in the function + std::vector& ordered_blocks(); + + /// Returns a list of all the cfg constructs in the function + const std::list& constructs() const; + + /// Returns a list of all the cfg constructs in the function + std::list& constructs(); + + /// Returns the number of blocks in the current function being parsed + size_t block_count() const; + + /// Returns the id of the function + uint32_t id() const { return id_; } + + /// Returns return type id of the function + uint32_t GetResultTypeId() const { return result_type_id_; } + + /// Returns the number of blocks in the current function being parsed + size_t undefined_block_count() const; + const std::unordered_set& undefined_blocks() const { + return undefined_blocks_; + } + + /// Returns the block that is currently being parsed in the binary + BasicBlock* current_block(); + + /// Returns the block that is currently being parsed in the binary + const BasicBlock* current_block() const; + + // For dominance calculations, we want to analyze all the + // blocks in the function, even in degenerate control flow cases + // including unreachable blocks. We therefore make an "augmented CFG" + // which is the same as the ordinary CFG but adds: + // - A pseudo-entry node. + // - A pseudo-exit node. + // - A minimal set of edges so that a forward traversal from the + // pseudo-entry node will visit all nodes. + // - A minimal set of edges so that a backward traversal from the + // pseudo-exit node will visit all nodes. + // In particular, the pseudo-entry node is the unique source of the + // augmented CFG, and the psueo-exit node is the unique sink of the + // augmented CFG. + + /// Returns the pseudo exit block + BasicBlock* pseudo_entry_block() { return &pseudo_entry_block_; } + + /// Returns the pseudo exit block + const BasicBlock* pseudo_entry_block() const { return &pseudo_entry_block_; } + + /// Returns the pseudo exit block + BasicBlock* pseudo_exit_block() { return &pseudo_exit_block_; } + + /// Returns the pseudo exit block + const BasicBlock* pseudo_exit_block() const { return &pseudo_exit_block_; } + + using GetBlocksFunction = + std::function*(const BasicBlock*)>; + /// Returns the block successors function for the augmented CFG. + GetBlocksFunction AugmentedCFGSuccessorsFunction() const; + /// Like AugmentedCFGSuccessorsFunction, but also includes a forward edge from + /// a loop header block to its continue target, if they are different blocks. + GetBlocksFunction + AugmentedCFGSuccessorsFunctionIncludingHeaderToContinueEdge() const; + /// Returns the block predecessors function for the augmented CFG. + GetBlocksFunction AugmentedCFGPredecessorsFunction() const; + + /// Returns the control flow nesting depth of the given basic block. + /// This function only works when you have structured control flow. + /// This function should only be called after the control flow constructs have + /// been identified and dominators have been computed. + int GetBlockDepth(BasicBlock* bb); + + /// Prints a GraphViz digraph of the CFG of the current funciton + void PrintDotGraph() const; + + /// Prints a directed graph of the CFG of the current funciton + void PrintBlocks() const; + + /// Registers execution model limitation such as "Feature X is only available + /// with Execution Model Y". + void RegisterExecutionModelLimitation(SpvExecutionModel model, + const std::string& message); + + /// Registers execution model limitation with an |is_compatible| functor. + void RegisterExecutionModelLimitation( + std::function is_compatible) { + execution_model_limitations_.push_back(is_compatible); + } + + /// Returns true if the given execution model passes the limitations stored in + /// execution_model_limitations_. Returns false otherwise and fills optional + /// |reason| parameter. + bool IsCompatibleWithExecutionModel(SpvExecutionModel model, + std::string* reason = nullptr) const; + + // Inserts id to the set of functions called from this function. + void AddFunctionCallTarget(uint32_t call_target_id) { + function_call_targets_.insert(call_target_id); + } + + // Returns a set with ids of all functions called from this function. + const std::set function_call_targets() const { + return function_call_targets_; + } + + private: + // Computes the representation of the augmented CFG. + // Populates augmented_successors_map_ and augmented_predecessors_map_. + void ComputeAugmentedCFG(); + + // Adds a copy of the given Construct, and tracks it by its entry block. + // Returns a reference to the stored construct. + Construct& AddConstruct(const Construct& new_construct); + + // Returns a reference to the construct corresponding to the given entry + // block. + Construct& FindConstructForEntryBlock(const BasicBlock* entry_block, + ConstructType t); + + /// The result id of the OpLabel that defined this block + uint32_t id_; + + /// The type of the function + uint32_t function_type_id_; + + /// The type of the return value + uint32_t result_type_id_; + + /// The control fo the funciton + SpvFunctionControlMask function_control_; + + /// The type of declaration of each function + FunctionDecl declaration_type_; + + // Have we finished parsing this function? + bool end_has_been_registered_; + + /// The blocks in the function mapped by block ID + std::unordered_map blocks_; + + /// A list of blocks in the order they appeared in the binary + std::vector ordered_blocks_; + + /// Blocks which are forward referenced by blocks but not defined + std::unordered_set undefined_blocks_; + + /// The block that is currently being parsed + BasicBlock* current_block_; + + /// A pseudo entry node used in dominance analysis. + /// After the function end has been registered, the successor list of the + /// pseudo entry node is the minimal set of nodes such that all nodes in the + /// CFG can be reached by following successor lists. That is, the successors + /// will be: + /// - Any basic block without predecessors. This includes the entry + /// block to the function. + /// - A single node from each otherwise unreachable cycle in the CFG, if + /// such cycles exist. + /// The pseudo entry node does not appear in the predecessor or successor + /// list of any ordinary block. + /// It has no predecessors. + /// It has Id 0. + BasicBlock pseudo_entry_block_; + + /// A pseudo exit block used in dominance analysis. + /// After the function end has been registered, the predecessor list of the + /// pseudo exit node is the minimal set of nodes such that all nodes in the + /// CFG can be reached by following predecessor lists. That is, the + /// predecessors will be: + /// - Any basic block without successors. This includes any basic block + /// ending with an OpReturn, OpReturnValue or similar instructions. + /// - A single node from each otherwise unreachable cycle in the CFG, if + /// such cycles exist. + /// The pseudo exit node does not appear in the predecessor or successor + /// list of any ordinary block. + /// It has no successors. + BasicBlock pseudo_exit_block_; + + // Maps a block to its successors in the augmented CFG, if that set is + // different from its successors in the ordinary CFG. + std::unordered_map> + augmented_successors_map_; + // Maps a block to its predecessors in the augmented CFG, if that set is + // different from its predecessors in the ordinary CFG. + std::unordered_map> + augmented_predecessors_map_; + + // Maps a structured loop header to its CFG successors and also its + // continue target if that continue target is not the loop header + // itself. This might have duplicates. + std::unordered_map> + loop_header_successors_plus_continue_target_map_; + + /// The constructs that are available in this function + std::list cfg_constructs_; + + /// The variable IDs of the functions + std::vector variable_ids_; + + /// The function parameter ids of the functions + std::vector parameter_ids_; + + /// Maps a construct's entry block to the construct(s). + /// Since a basic block may be the entry block of different types of + /// constructs, the type of the construct should also be specified in order to + /// get the unique construct. + std::unordered_map, Construct*, + libspirv::bb_constr_type_pair_hash> + entry_block_to_construct_; + + /// This map provides the header block for a given merge block. + std::unordered_map merge_block_header_; + + /// Stores the control flow nesting depth of a given basic block + std::unordered_map block_depth_; + + /// Stores execution model limitations imposed by instructions used within the + /// function. The functor stored in the list return true if execution model + /// is compatible, false otherwise. If the functor returns false, it can also + /// optionally fill the string parameter with the reason for incompatibility. + std::list> + execution_model_limitations_; + + /// Stores ids of all functions called from this function. + std::set function_call_targets_; +}; + +} // namespace libspirv + +#endif /// LIBSPIRV_VAL_FUNCTION_H_ diff --git a/3rdparty/spirv-tools/source/val/instruction.cpp b/3rdparty/spirv-tools/source/val/instruction.cpp new file mode 100644 index 000000000..56bd37f5e --- /dev/null +++ b/3rdparty/spirv-tools/source/val/instruction.cpp @@ -0,0 +1,49 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "val/instruction.h" + +#include + +using std::make_pair; + +namespace libspirv { +#define OPERATOR(OP) \ + bool operator OP(const Instruction& lhs, const Instruction& rhs) { \ + return lhs.id() OP rhs.id(); \ + } \ + bool operator OP(const Instruction& lhs, uint32_t rhs) { \ + return lhs.id() OP rhs; \ + } + +OPERATOR(<) +OPERATOR(==) +#undef OPERATOR + +Instruction::Instruction(const spv_parsed_instruction_t* inst, + Function* defining_function, + BasicBlock* defining_block) + : words_(inst->words, inst->words + inst->num_words), + operands_(inst->operands, inst->operands + inst->num_operands), + inst_({words_.data(), inst->num_words, inst->opcode, inst->ext_inst_type, + inst->type_id, inst->result_id, operands_.data(), + inst->num_operands}), + function_(defining_function), + block_(defining_block), + uses_() {} + +void Instruction::RegisterUse(const Instruction* inst, uint32_t index) { + uses_.push_back(make_pair(inst, index)); +} +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/val/instruction.h b/3rdparty/spirv-tools/source/val/instruction.h new file mode 100644 index 000000000..96136320e --- /dev/null +++ b/3rdparty/spirv-tools/source/val/instruction.h @@ -0,0 +1,126 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_VAL_INSTRUCTION_H_ +#define LIBSPIRV_VAL_INSTRUCTION_H_ + +#include +#include +#include +#include +#include + +#include "spirv-tools/libspirv.h" +#include "table.h" + +namespace libspirv { + +class BasicBlock; +class Function; + +/// Wraps the spv_parsed_instruction struct along with use and definition of the +/// instruction's result id +class Instruction { + public: + explicit Instruction(const spv_parsed_instruction_t* inst, + Function* defining_function = nullptr, + BasicBlock* defining_block = nullptr); + + /// Registers the use of the Instruction in instruction \p inst at \p index + void RegisterUse(const Instruction* inst, uint32_t index); + + uint32_t id() const { return inst_.result_id; } + uint32_t type_id() const { return inst_.type_id; } + SpvOp opcode() const { return static_cast(inst_.opcode); } + + /// Returns the Function where the instruction was defined. nullptr if it was + /// defined outside of a Function + const Function* function() const { return function_; } + + /// Returns the BasicBlock where the instruction was defined. nullptr if it + /// was defined outside of a BasicBlock + const BasicBlock* block() const { return block_; } + + /// Returns a vector of pairs of all references to this instruction's result + /// id. The first element is the instruction in which this result id was + /// referenced and the second is the index of the word in that instruction + /// where this result id appeared + const std::vector>& uses() const { + return uses_; + } + + /// The word used to define the Instruction + uint32_t word(size_t index) const { return words_[index]; } + + /// The words used to define the Instruction + const std::vector& words() const { return words_; } + + /// The operands of the Instruction + const std::vector& operands() const { + return operands_; + } + + /// Provides direct access to the stored C instruction object. + const spv_parsed_instruction_t& c_inst() const { return inst_; } + + // Casts the words belonging to the operand under |index| to |T| and returns. + template + T GetOperandAs(size_t index) const { + const spv_parsed_operand_t& operand = operands_.at(index); + assert(operand.num_words * 4 >= sizeof(T)); + assert(operand.offset + operand.num_words <= inst_.num_words); + return *reinterpret_cast(&words_[operand.offset]); + } + + private: + const std::vector words_; + const std::vector operands_; + spv_parsed_instruction_t inst_; + + /// The function in which this instruction was declared + Function* function_; + + /// The basic block in which this instruction was declared + BasicBlock* block_; + + /// This is a vector of pairs of all references to this instruction's result + /// id. The first element is the instruction in which this result id was + /// referenced and the second is the index of the word in the referencing + /// instruction where this instruction appeared + std::vector> uses_; +}; + +#define OPERATOR(OP) \ + bool operator OP(const Instruction& lhs, const Instruction& rhs); \ + bool operator OP(const Instruction& lhs, uint32_t rhs) + +OPERATOR(<); +OPERATOR(==); +#undef OPERATOR + +} // namespace libspirv + +// custom specialization of std::hash for Instruction +namespace std { +template <> +struct hash { + typedef libspirv::Instruction argument_type; + typedef std::size_t result_type; + result_type operator()(const argument_type& inst) const { + return hash()(inst.id()); + } +}; +} // namespace std + +#endif // LIBSPIRV_VAL_INSTRUCTION_H_ diff --git a/3rdparty/spirv-tools/source/val/validation_state.cpp b/3rdparty/spirv-tools/source/val/validation_state.cpp new file mode 100644 index 000000000..50ebb6220 --- /dev/null +++ b/3rdparty/spirv-tools/source/val/validation_state.cpp @@ -0,0 +1,795 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "val/validation_state.h" + +#include + +#include "opcode.h" +#include "val/basic_block.h" +#include "val/construct.h" +#include "val/function.h" + +using std::deque; +using std::make_pair; +using std::pair; +using std::string; +using std::unordered_map; +using std::vector; + +namespace libspirv { + +namespace { +bool IsInstructionInLayoutSection(ModuleLayoutSection layout, SpvOp op) { + // See Section 2.4 + bool out = false; + // clang-format off + switch (layout) { + case kLayoutCapabilities: out = op == SpvOpCapability; break; + case kLayoutExtensions: out = op == SpvOpExtension; break; + case kLayoutExtInstImport: out = op == SpvOpExtInstImport; break; + case kLayoutMemoryModel: out = op == SpvOpMemoryModel; break; + case kLayoutEntryPoint: out = op == SpvOpEntryPoint; break; + case kLayoutExecutionMode: out = op == SpvOpExecutionMode; break; + case kLayoutDebug1: + switch (op) { + case SpvOpSourceContinued: + case SpvOpSource: + case SpvOpSourceExtension: + case SpvOpString: + out = true; + break; + default: break; + } + break; + case kLayoutDebug2: + switch (op) { + case SpvOpName: + case SpvOpMemberName: + out = true; + break; + default: break; + } + break; + case kLayoutDebug3: + // Only OpModuleProcessed is allowed here. + out = (op == SpvOpModuleProcessed); + break; + case kLayoutAnnotations: + switch (op) { + case SpvOpDecorate: + case SpvOpMemberDecorate: + case SpvOpGroupDecorate: + case SpvOpGroupMemberDecorate: + case SpvOpDecorationGroup: + case SpvOpDecorateId: + case SpvOpDecorateStringGOOGLE: + case SpvOpMemberDecorateStringGOOGLE: + out = true; + break; + default: break; + } + break; + case kLayoutTypes: + if (spvOpcodeGeneratesType(op) || spvOpcodeIsConstant(op)) { + out = true; + break; + } + switch (op) { + case SpvOpTypeForwardPointer: + case SpvOpVariable: + case SpvOpLine: + case SpvOpNoLine: + case SpvOpUndef: + out = true; + break; + default: break; + } + break; + case kLayoutFunctionDeclarations: + case kLayoutFunctionDefinitions: + // NOTE: These instructions should NOT be in these layout sections + if (spvOpcodeGeneratesType(op) || spvOpcodeIsConstant(op)) { + out = false; + break; + } + switch (op) { + case SpvOpCapability: + case SpvOpExtension: + case SpvOpExtInstImport: + case SpvOpMemoryModel: + case SpvOpEntryPoint: + case SpvOpExecutionMode: + case SpvOpSourceContinued: + case SpvOpSource: + case SpvOpSourceExtension: + case SpvOpString: + case SpvOpName: + case SpvOpMemberName: + case SpvOpModuleProcessed: + case SpvOpDecorate: + case SpvOpMemberDecorate: + case SpvOpGroupDecorate: + case SpvOpGroupMemberDecorate: + case SpvOpDecorationGroup: + case SpvOpTypeForwardPointer: + out = false; + break; + default: + out = true; + break; + } + } + // clang-format on + return out; +} + +} // anonymous namespace + +ValidationState_t::ValidationState_t(const spv_const_context ctx, + const spv_const_validator_options opt) + : context_(ctx), + options_(opt), + instruction_counter_(0), + unresolved_forward_ids_{}, + operand_names_{}, + current_layout_section_(kLayoutCapabilities), + module_functions_(), + module_capabilities_(), + module_extensions_(), + ordered_instructions_(), + all_definitions_(), + global_vars_(), + local_vars_(), + struct_nesting_depth_(), + grammar_(ctx), + addressing_model_(SpvAddressingModelLogical), + memory_model_(SpvMemoryModelSimple), + in_function_(false) { + assert(opt && "Validator options may not be Null."); +} + +spv_result_t ValidationState_t::ForwardDeclareId(uint32_t id) { + unresolved_forward_ids_.insert(id); + return SPV_SUCCESS; +} + +spv_result_t ValidationState_t::RemoveIfForwardDeclared(uint32_t id) { + unresolved_forward_ids_.erase(id); + return SPV_SUCCESS; +} + +spv_result_t ValidationState_t::RegisterForwardPointer(uint32_t id) { + forward_pointer_ids_.insert(id); + return SPV_SUCCESS; +} + +bool ValidationState_t::IsForwardPointer(uint32_t id) const { + return (forward_pointer_ids_.find(id) != forward_pointer_ids_.end()); +} + +void ValidationState_t::AssignNameToId(uint32_t id, string name) { + operand_names_[id] = name; +} + +string ValidationState_t::getIdName(uint32_t id) const { + std::stringstream out; + out << id; + if (operand_names_.find(id) != end(operand_names_)) { + out << "[" << operand_names_.at(id) << "]"; + } + return out.str(); +} + +string ValidationState_t::getIdOrName(uint32_t id) const { + std::stringstream out; + if (operand_names_.find(id) != end(operand_names_)) { + out << operand_names_.at(id); + } else { + out << id; + } + return out.str(); +} + +size_t ValidationState_t::unresolved_forward_id_count() const { + return unresolved_forward_ids_.size(); +} + +vector ValidationState_t::UnresolvedForwardIds() const { + vector out(begin(unresolved_forward_ids_), + end(unresolved_forward_ids_)); + return out; +} + +bool ValidationState_t::IsDefinedId(uint32_t id) const { + return all_definitions_.find(id) != end(all_definitions_); +} + +const Instruction* ValidationState_t::FindDef(uint32_t id) const { + auto it = all_definitions_.find(id); + if (it == all_definitions_.end()) return nullptr; + return it->second; +} + +Instruction* ValidationState_t::FindDef(uint32_t id) { + auto it = all_definitions_.find(id); + if (it == all_definitions_.end()) return nullptr; + return it->second; +} + +// Increments the instruction count. Used for diagnostic +int ValidationState_t::increment_instruction_count() { + return instruction_counter_++; +} + +ModuleLayoutSection ValidationState_t::current_layout_section() const { + return current_layout_section_; +} + +void ValidationState_t::ProgressToNextLayoutSectionOrder() { + // Guard against going past the last element(kLayoutFunctionDefinitions) + if (current_layout_section_ <= kLayoutFunctionDefinitions) { + current_layout_section_ = + static_cast(current_layout_section_ + 1); + } +} + +bool ValidationState_t::IsOpcodeInCurrentLayoutSection(SpvOp op) { + return IsInstructionInLayoutSection(current_layout_section_, op); +} + +DiagnosticStream ValidationState_t::diag(spv_result_t error_code) const { + return libspirv::DiagnosticStream( + {0, 0, static_cast(instruction_counter_)}, context_->consumer, + error_code); +} + +deque& ValidationState_t::functions() { return module_functions_; } + +Function& ValidationState_t::current_function() { + assert(in_function_body()); + return module_functions_.back(); +} + +const Function& ValidationState_t::current_function() const { + assert(in_function_body()); + return module_functions_.back(); +} + +const Function* ValidationState_t::function(uint32_t id) const { + const auto it = id_to_function_.find(id); + if (it == id_to_function_.end()) return nullptr; + return it->second; +} + +bool ValidationState_t::in_function_body() const { return in_function_; } + +bool ValidationState_t::in_block() const { + return module_functions_.empty() == false && + module_functions_.back().current_block() != nullptr; +} + +void ValidationState_t::RegisterCapability(SpvCapability cap) { + // Avoid redundant work. Otherwise the recursion could induce work + // quadrdatic in the capability dependency depth. (Ok, not much, but + // it's something.) + if (module_capabilities_.Contains(cap)) return; + + module_capabilities_.Add(cap); + spv_operand_desc desc; + if (SPV_SUCCESS == + grammar_.lookupOperand(SPV_OPERAND_TYPE_CAPABILITY, cap, &desc)) { + CapabilitySet(desc->numCapabilities, desc->capabilities) + .ForEach([this](SpvCapability c) { RegisterCapability(c); }); + } + + switch (cap) { + case SpvCapabilityKernel: + features_.group_ops_reduce_and_scans = true; + break; + case SpvCapabilityInt16: + features_.declare_int16_type = true; + break; + case SpvCapabilityFloat16: + case SpvCapabilityFloat16Buffer: + features_.declare_float16_type = true; + break; + case SpvCapabilityStorageUniformBufferBlock16: + case SpvCapabilityStorageUniform16: + case SpvCapabilityStoragePushConstant16: + case SpvCapabilityStorageInputOutput16: + features_.declare_int16_type = true; + features_.declare_float16_type = true; + features_.free_fp_rounding_mode = true; + break; + case SpvCapabilityVariablePointers: + features_.variable_pointers = true; + features_.variable_pointers_storage_buffer = true; + break; + case SpvCapabilityVariablePointersStorageBuffer: + features_.variable_pointers_storage_buffer = true; + break; + default: + break; + } +} + +void ValidationState_t::RegisterExtension(Extension ext) { + if (module_extensions_.Contains(ext)) return; + + module_extensions_.Add(ext); + + switch (ext) { + case kSPV_AMD_gpu_shader_half_float: + // SPV_AMD_gpu_shader_half_float enables float16 type. + // https://github.com/KhronosGroup/SPIRV-Tools/issues/1375 + features_.declare_float16_type = true; + break; + case kSPV_AMD_shader_ballot: + // The grammar doesn't encode the fact that SPV_AMD_shader_ballot + // enables the use of group operations Reduce, InclusiveScan, + // and ExclusiveScan. Enable it manually. + // https://github.com/KhronosGroup/SPIRV-Tools/issues/991 + features_.group_ops_reduce_and_scans = true; + break; + default: + break; + } +} + +bool ValidationState_t::HasAnyOfCapabilities( + const CapabilitySet& capabilities) const { + return module_capabilities_.HasAnyOf(capabilities); +} + +bool ValidationState_t::HasAnyOfExtensions( + const ExtensionSet& extensions) const { + return module_extensions_.HasAnyOf(extensions); +} + +void ValidationState_t::set_addressing_model(SpvAddressingModel am) { + addressing_model_ = am; +} + +SpvAddressingModel ValidationState_t::addressing_model() const { + return addressing_model_; +} + +void ValidationState_t::set_memory_model(SpvMemoryModel mm) { + memory_model_ = mm; +} + +SpvMemoryModel ValidationState_t::memory_model() const { return memory_model_; } + +spv_result_t ValidationState_t::RegisterFunction( + uint32_t id, uint32_t ret_type_id, SpvFunctionControlMask function_control, + uint32_t function_type_id) { + assert(in_function_body() == false && + "RegisterFunction can only be called when parsing the binary outside " + "of another function"); + in_function_ = true; + module_functions_.emplace_back(id, ret_type_id, function_control, + function_type_id); + id_to_function_.emplace(id, ¤t_function()); + + // TODO(umar): validate function type and type_id + + return SPV_SUCCESS; +} + +spv_result_t ValidationState_t::RegisterFunctionEnd() { + assert(in_function_body() == true && + "RegisterFunctionEnd can only be called when parsing the binary " + "inside of another function"); + assert(in_block() == false && + "RegisterFunctionParameter can only be called when parsing the binary " + "ouside of a block"); + current_function().RegisterFunctionEnd(); + in_function_ = false; + return SPV_SUCCESS; +} + +void ValidationState_t::RegisterInstruction( + const spv_parsed_instruction_t& inst) { + if (in_function_body()) { + ordered_instructions_.emplace_back(&inst, ¤t_function(), + current_function().current_block()); + } else { + ordered_instructions_.emplace_back(&inst, nullptr, nullptr); + } + uint32_t id = ordered_instructions_.back().id(); + if (id) { + all_definitions_.insert(make_pair(id, &ordered_instructions_.back())); + } + + // If the instruction is using an OpTypeSampledImage as an operand, it should + // be recorded. The validator will ensure that all usages of an + // OpTypeSampledImage and its definition are in the same basic block. + for (uint16_t i = 0; i < inst.num_operands; ++i) { + const spv_parsed_operand_t& operand = inst.operands[i]; + if (SPV_OPERAND_TYPE_ID == operand.type) { + const uint32_t operand_word = inst.words[operand.offset]; + Instruction* operand_inst = FindDef(operand_word); + if (operand_inst && SpvOpSampledImage == operand_inst->opcode()) { + RegisterSampledImageConsumer(operand_word, inst.result_id); + } + } + } +} + +std::vector ValidationState_t::getSampledImageConsumers( + uint32_t sampled_image_id) const { + std::vector result; + auto iter = sampled_image_consumers_.find(sampled_image_id); + if (iter != sampled_image_consumers_.end()) { + result = iter->second; + } + return result; +} + +void ValidationState_t::RegisterSampledImageConsumer(uint32_t sampled_image_id, + uint32_t consumer_id) { + sampled_image_consumers_[sampled_image_id].push_back(consumer_id); +} + +uint32_t ValidationState_t::getIdBound() const { return id_bound_; } + +void ValidationState_t::setIdBound(const uint32_t bound) { id_bound_ = bound; } + +bool ValidationState_t::RegisterUniqueTypeDeclaration( + const spv_parsed_instruction_t& inst) { + std::vector key; + key.push_back(static_cast(inst.opcode)); + for (int index = 0; index < inst.num_operands; ++index) { + const spv_parsed_operand_t& operand = inst.operands[index]; + + if (operand.type == SPV_OPERAND_TYPE_RESULT_ID) continue; + + const int words_begin = operand.offset; + const int words_end = words_begin + operand.num_words; + assert(words_end <= static_cast(inst.num_words)); + + key.insert(key.end(), inst.words + words_begin, inst.words + words_end); + } + + return unique_type_declarations_.insert(std::move(key)).second; +} + +uint32_t ValidationState_t::GetTypeId(uint32_t id) const { + const Instruction* inst = FindDef(id); + return inst ? inst->type_id() : 0; +} + +SpvOp ValidationState_t::GetIdOpcode(uint32_t id) const { + const Instruction* inst = FindDef(id); + return inst ? inst->opcode() : SpvOpNop; +} + +uint32_t ValidationState_t::GetComponentType(uint32_t id) const { + const Instruction* inst = FindDef(id); + assert(inst); + + switch (inst->opcode()) { + case SpvOpTypeFloat: + case SpvOpTypeInt: + case SpvOpTypeBool: + return id; + + case SpvOpTypeVector: + return inst->word(2); + + case SpvOpTypeMatrix: + return GetComponentType(inst->word(2)); + + default: + break; + } + + if (inst->type_id()) return GetComponentType(inst->type_id()); + + assert(0); + return 0; +} + +uint32_t ValidationState_t::GetDimension(uint32_t id) const { + const Instruction* inst = FindDef(id); + assert(inst); + + switch (inst->opcode()) { + case SpvOpTypeFloat: + case SpvOpTypeInt: + case SpvOpTypeBool: + return 1; + + case SpvOpTypeVector: + case SpvOpTypeMatrix: + return inst->word(3); + + default: + break; + } + + if (inst->type_id()) return GetDimension(inst->type_id()); + + assert(0); + return 0; +} + +uint32_t ValidationState_t::GetBitWidth(uint32_t id) const { + const uint32_t component_type_id = GetComponentType(id); + const Instruction* inst = FindDef(component_type_id); + assert(inst); + + if (inst->opcode() == SpvOpTypeFloat || inst->opcode() == SpvOpTypeInt) + return inst->word(2); + + if (inst->opcode() == SpvOpTypeBool) return 1; + + assert(0); + return 0; +} + +bool ValidationState_t::IsFloatScalarType(uint32_t id) const { + const Instruction* inst = FindDef(id); + assert(inst); + return inst->opcode() == SpvOpTypeFloat; +} + +bool ValidationState_t::IsFloatVectorType(uint32_t id) const { + const Instruction* inst = FindDef(id); + assert(inst); + + if (inst->opcode() == SpvOpTypeVector) { + return IsFloatScalarType(GetComponentType(id)); + } + + return false; +} + +bool ValidationState_t::IsFloatScalarOrVectorType(uint32_t id) const { + const Instruction* inst = FindDef(id); + assert(inst); + + if (inst->opcode() == SpvOpTypeFloat) { + return true; + } + + if (inst->opcode() == SpvOpTypeVector) { + return IsFloatScalarType(GetComponentType(id)); + } + + return false; +} + +bool ValidationState_t::IsIntScalarType(uint32_t id) const { + const Instruction* inst = FindDef(id); + assert(inst); + return inst->opcode() == SpvOpTypeInt; +} + +bool ValidationState_t::IsIntVectorType(uint32_t id) const { + const Instruction* inst = FindDef(id); + assert(inst); + + if (inst->opcode() == SpvOpTypeVector) { + return IsIntScalarType(GetComponentType(id)); + } + + return false; +} + +bool ValidationState_t::IsIntScalarOrVectorType(uint32_t id) const { + const Instruction* inst = FindDef(id); + assert(inst); + + if (inst->opcode() == SpvOpTypeInt) { + return true; + } + + if (inst->opcode() == SpvOpTypeVector) { + return IsIntScalarType(GetComponentType(id)); + } + + return false; +} + +bool ValidationState_t::IsUnsignedIntScalarType(uint32_t id) const { + const Instruction* inst = FindDef(id); + assert(inst); + return inst->opcode() == SpvOpTypeInt && inst->word(3) == 0; +} + +bool ValidationState_t::IsUnsignedIntVectorType(uint32_t id) const { + const Instruction* inst = FindDef(id); + assert(inst); + + if (inst->opcode() == SpvOpTypeVector) { + return IsUnsignedIntScalarType(GetComponentType(id)); + } + + return false; +} + +bool ValidationState_t::IsSignedIntScalarType(uint32_t id) const { + const Instruction* inst = FindDef(id); + assert(inst); + return inst->opcode() == SpvOpTypeInt && inst->word(3) == 1; +} + +bool ValidationState_t::IsSignedIntVectorType(uint32_t id) const { + const Instruction* inst = FindDef(id); + assert(inst); + + if (inst->opcode() == SpvOpTypeVector) { + return IsSignedIntScalarType(GetComponentType(id)); + } + + return false; +} + +bool ValidationState_t::IsBoolScalarType(uint32_t id) const { + const Instruction* inst = FindDef(id); + assert(inst); + return inst->opcode() == SpvOpTypeBool; +} + +bool ValidationState_t::IsBoolVectorType(uint32_t id) const { + const Instruction* inst = FindDef(id); + assert(inst); + + if (inst->opcode() == SpvOpTypeVector) { + return IsBoolScalarType(GetComponentType(id)); + } + + return false; +} + +bool ValidationState_t::IsBoolScalarOrVectorType(uint32_t id) const { + const Instruction* inst = FindDef(id); + assert(inst); + + if (inst->opcode() == SpvOpTypeBool) { + return true; + } + + if (inst->opcode() == SpvOpTypeVector) { + return IsBoolScalarType(GetComponentType(id)); + } + + return false; +} + +bool ValidationState_t::IsFloatMatrixType(uint32_t id) const { + const Instruction* inst = FindDef(id); + assert(inst); + + if (inst->opcode() == SpvOpTypeMatrix) { + return IsFloatScalarType(GetComponentType(id)); + } + + return false; +} + +bool ValidationState_t::GetMatrixTypeInfo(uint32_t id, uint32_t* num_rows, + uint32_t* num_cols, + uint32_t* column_type, + uint32_t* component_type) const { + if (!id) return false; + + const Instruction* mat_inst = FindDef(id); + assert(mat_inst); + if (mat_inst->opcode() != SpvOpTypeMatrix) return false; + + const uint32_t vec_type = mat_inst->word(2); + const Instruction* vec_inst = FindDef(vec_type); + assert(vec_inst); + + if (vec_inst->opcode() != SpvOpTypeVector) { + assert(0); + return false; + } + + *num_cols = mat_inst->word(3); + *num_rows = vec_inst->word(3); + *column_type = mat_inst->word(2); + *component_type = vec_inst->word(2); + + return true; +} + +bool ValidationState_t::GetStructMemberTypes( + uint32_t struct_type_id, std::vector* member_types) const { + member_types->clear(); + if (!struct_type_id) return false; + + const Instruction* inst = FindDef(struct_type_id); + assert(inst); + if (inst->opcode() != SpvOpTypeStruct) return false; + + *member_types = + std::vector(inst->words().cbegin() + 2, inst->words().cend()); + + if (member_types->empty()) return false; + + return true; +} + +bool ValidationState_t::IsPointerType(uint32_t id) const { + const Instruction* inst = FindDef(id); + assert(inst); + return inst->opcode() == SpvOpTypePointer; +} + +bool ValidationState_t::GetPointerTypeInfo(uint32_t id, uint32_t* data_type, + uint32_t* storage_class) const { + if (!id) return false; + + const Instruction* inst = FindDef(id); + assert(inst); + if (inst->opcode() != SpvOpTypePointer) return false; + + *storage_class = inst->word(2); + *data_type = inst->word(3); + return true; +} + +uint32_t ValidationState_t::GetOperandTypeId( + const spv_parsed_instruction_t* inst, size_t operand_index) const { + assert(operand_index < inst->num_operands); + const spv_parsed_operand_t& operand = inst->operands[operand_index]; + assert(operand.num_words == 1); + return GetTypeId(inst->words[operand.offset]); +} + +bool ValidationState_t::GetConstantValUint64(uint32_t id, uint64_t* val) const { + const Instruction* inst = FindDef(id); + if (!inst) { + assert(0 && "Instruction not found"); + return false; + } + + if (inst->opcode() != SpvOpConstant && inst->opcode() != SpvOpSpecConstant) + return false; + + if (!IsIntScalarType(inst->type_id())) return false; + + if (inst->words().size() == 4) { + *val = inst->word(3); + } else { + assert(inst->words().size() == 5); + *val = inst->word(3); + *val |= uint64_t(inst->word(4)) << 32; + } + return true; +} + +std::tuple ValidationState_t::EvalInt32IfConst( + uint32_t id) { + const Instruction* const inst = FindDef(id); + assert(inst); + const uint32_t type = inst->type_id(); + + if (!IsIntScalarType(type) || GetBitWidth(type) != 32) { + return std::make_tuple(false, false, 0); + } + + if (inst->opcode() != SpvOpConstant && inst->opcode() != SpvOpSpecConstant) { + return std::make_tuple(true, false, 0); + } + + assert(inst->words().size() == 4); + return std::make_tuple(true, true, inst->word(3)); +} + +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/val/validation_state.h b/3rdparty/spirv-tools/source/val/validation_state.h new file mode 100644 index 000000000..3e382c80e --- /dev/null +++ b/3rdparty/spirv-tools/source/val/validation_state.h @@ -0,0 +1,566 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_VAL_VALIDATIONSTATE_H_ +#define LIBSPIRV_VAL_VALIDATIONSTATE_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "assembly_grammar.h" +#include "decoration.h" +#include "diagnostic.h" +#include "enum_set.h" +#include "latest_version_spirv_header.h" +#include "spirv-tools/libspirv.h" +#include "spirv_definition.h" +#include "val/function.h" +#include "val/instruction.h" + +namespace libspirv { + +/// This enum represents the sections of a SPIRV module. See section 2.4 +/// of the SPIRV spec for additional details of the order. The enumerant values +/// are in the same order as the vector returned by GetModuleOrder +enum ModuleLayoutSection { + kLayoutCapabilities, /// < Section 2.4 #1 + kLayoutExtensions, /// < Section 2.4 #2 + kLayoutExtInstImport, /// < Section 2.4 #3 + kLayoutMemoryModel, /// < Section 2.4 #4 + kLayoutEntryPoint, /// < Section 2.4 #5 + kLayoutExecutionMode, /// < Section 2.4 #6 + kLayoutDebug1, /// < Section 2.4 #7 > 1 + kLayoutDebug2, /// < Section 2.4 #7 > 2 + kLayoutDebug3, /// < Section 2.4 #7 > 3 + kLayoutAnnotations, /// < Section 2.4 #8 + kLayoutTypes, /// < Section 2.4 #9 + kLayoutFunctionDeclarations, /// < Section 2.4 #10 + kLayoutFunctionDefinitions /// < Section 2.4 #11 +}; + +/// This class manages the state of the SPIR-V validation as it is being parsed. +class ValidationState_t { + public: + // Features that can optionally be turned on by a capability. + struct Feature { + bool declare_int16_type = false; // Allow OpTypeInt with 16 bit width? + bool declare_float16_type = false; // Allow OpTypeFloat with 16 bit width? + bool free_fp_rounding_mode = false; // Allow the FPRoundingMode decoration + // and its vaules to be used without + // requiring any capability + + // Allow functionalities enabled by VariablePointers capability. + bool variable_pointers = false; + // Allow functionalities enabled by VariablePointersStorageBuffer + // capability. + bool variable_pointers_storage_buffer = false; + + // Permit group oerations Reduce, InclusiveScan, ExclusiveScan + bool group_ops_reduce_and_scans = false; + }; + + ValidationState_t(const spv_const_context context, + const spv_const_validator_options opt); + + /// Returns the context + spv_const_context context() const { return context_; } + + /// Returns the command line options + spv_const_validator_options options() const { return options_; } + + /// Forward declares the id in the module + spv_result_t ForwardDeclareId(uint32_t id); + + /// Removes a forward declared ID if it has been defined + spv_result_t RemoveIfForwardDeclared(uint32_t id); + + /// Registers an ID as a forward pointer + spv_result_t RegisterForwardPointer(uint32_t id); + + /// Returns whether or not an ID is a forward pointer + bool IsForwardPointer(uint32_t id) const; + + /// Assigns a name to an ID + void AssignNameToId(uint32_t id, std::string name); + + /// Returns a string representation of the ID in the format [Name] where + /// the is the numeric valid of the id and the Name is a name assigned by + /// the OpName instruction + std::string getIdName(uint32_t id) const; + + /// Accessor function for ID bound. + uint32_t getIdBound() const; + + /// Mutator function for ID bound. + void setIdBound(uint32_t bound); + + /// Like getIdName but does not display the id if the \p id has a name + std::string getIdOrName(uint32_t id) const; + + /// Returns the number of ID which have been forward referenced but not + /// defined + size_t unresolved_forward_id_count() const; + + /// Returns a vector of unresolved forward ids. + std::vector UnresolvedForwardIds() const; + + /// Returns true if the id has been defined + bool IsDefinedId(uint32_t id) const; + + /// Increments the instruction count. Used for diagnostic + int increment_instruction_count(); + + /// Returns the current layout section which is being processed + ModuleLayoutSection current_layout_section() const; + + /// Increments the module_layout_order_section_ + void ProgressToNextLayoutSectionOrder(); + + /// Determines if the op instruction is part of the current section + bool IsOpcodeInCurrentLayoutSection(SpvOp op); + + libspirv::DiagnosticStream diag(spv_result_t error_code) const; + + /// Returns the function states + std::deque& functions(); + + /// Returns the function states + Function& current_function(); + const Function& current_function() const; + + /// Returns function state with the given id, or nullptr if no such function. + const Function* function(uint32_t id) const; + + /// Returns true if the called after a function instruction but before the + /// function end instruction + bool in_function_body() const; + + /// Returns true if called after a label instruction but before a branch + /// instruction + bool in_block() const; + + /// Registers the given as an Entry Point with |execution_model|. + void RegisterEntryPointId(const uint32_t id, + SpvExecutionModel execution_model) { + entry_points_.push_back(id); + entry_point_interfaces_.emplace(id, std::vector()); + entry_point_to_execution_models_[id].insert(execution_model); + } + + /// Returns a list of entry point function ids + const std::vector& entry_points() const { return entry_points_; } + + /// Adds a new interface id to the interfaces of the given entry point. + void RegisterInterfaceForEntryPoint(uint32_t entry_point, + uint32_t interface) { + entry_point_interfaces_[entry_point].push_back(interface); + } + + /// Registers execution mode for the given entry point. + void RegisterExecutionModeForEntryPoint(uint32_t entry_point, + SpvExecutionMode execution_mode) { + entry_point_to_execution_modes_[entry_point].insert(execution_mode); + } + + /// Returns the interfaces of a given entry point. If the given id is not a + /// valid Entry Point id, std::out_of_range exception is thrown. + const std::vector& entry_point_interfaces( + uint32_t entry_point) const { + return entry_point_interfaces_.at(entry_point); + } + + /// Returns Execution Models for the given Entry Point. + /// Returns nullptr if none found (would trigger assertion). + const std::set* GetExecutionModels( + uint32_t entry_point) const { + const auto it = entry_point_to_execution_models_.find(entry_point); + if (it == entry_point_to_execution_models_.end()) { + assert(0); + return nullptr; + } + return &it->second; + } + + /// Returns Execution Modes for the given Entry Point. + /// Returns nullptr if none found. + const std::set* GetExecutionModes( + uint32_t entry_point) const { + const auto it = entry_point_to_execution_modes_.find(entry_point); + if (it == entry_point_to_execution_modes_.end()) { + return nullptr; + } + return &it->second; + } + + /// Inserts an to the set of functions that are target of OpFunctionCall. + void AddFunctionCallTarget(const uint32_t id) { + function_call_targets_.insert(id); + current_function().AddFunctionCallTarget(id); + } + + /// Returns whether or not a function is the target of OpFunctionCall. + bool IsFunctionCallTarget(const uint32_t id) { + return (function_call_targets_.find(id) != function_call_targets_.end()); + } + + /// Registers the capability and its dependent capabilities + void RegisterCapability(SpvCapability cap); + + /// Registers the extension. + void RegisterExtension(Extension ext); + + /// Registers the function in the module. Subsequent instructions will be + /// called against this function + spv_result_t RegisterFunction(uint32_t id, uint32_t ret_type_id, + SpvFunctionControlMask function_control, + uint32_t function_type_id); + + /// Register a function end instruction + spv_result_t RegisterFunctionEnd(); + + /// Returns true if the capability is enabled in the module. + bool HasCapability(SpvCapability cap) const { + return module_capabilities_.Contains(cap); + } + + /// Returns true if the extension is enabled in the module. + bool HasExtension(Extension ext) const { + return module_extensions_.Contains(ext); + } + + /// Returns true if any of the capabilities is enabled, or if |capabilities| + /// is an empty set. + bool HasAnyOfCapabilities(const libspirv::CapabilitySet& capabilities) const; + + /// Returns true if any of the extensions is enabled, or if |extensions| + /// is an empty set. + bool HasAnyOfExtensions(const libspirv::ExtensionSet& extensions) const; + + /// Sets the addressing model of this module (logical/physical). + void set_addressing_model(SpvAddressingModel am); + + /// Returns the addressing model of this module, or Logical if uninitialized. + SpvAddressingModel addressing_model() const; + + /// Sets the memory model of this module. + void set_memory_model(SpvMemoryModel mm); + + /// Returns the memory model of this module, or Simple if uninitialized. + SpvMemoryModel memory_model() const; + + const AssemblyGrammar& grammar() const { return grammar_; } + + /// Registers the instruction + void RegisterInstruction(const spv_parsed_instruction_t& inst); + + /// Registers the decoration for the given + void RegisterDecorationForId(uint32_t id, const Decoration& dec) { + id_decorations_[id].push_back(dec); + } + + /// Registers the list of decorations for the given + template + void RegisterDecorationsForId(uint32_t id, InputIt begin, InputIt end) { + std::vector& cur_decs = id_decorations_[id]; + cur_decs.insert(cur_decs.end(), begin, end); + } + + /// Registers the list of decorations for the given member of the given + /// structure. + template + void RegisterDecorationsForStructMember(uint32_t struct_id, + uint32_t member_index, InputIt begin, + InputIt end) { + RegisterDecorationsForId(struct_id, begin, end); + for (auto& decoration : id_decorations_[struct_id]) { + decoration.set_struct_member_index(member_index); + } + } + + /// Returns all the decorations for the given . If no decorations exist + /// for the , it registers an empty vector for it in the map and + /// returns the empty vector. + std::vector& id_decorations(uint32_t id) { + return id_decorations_[id]; + } + const std::vector& id_decorations(uint32_t id) const { + // TODO: This would throw or generate SIGABRT if id has no + // decorations. Remove/refactor this function. + return id_decorations_.at(id); + } + + // Returns const pointer to the internal decoration container. + const std::map>& id_decorations() const { + return id_decorations_; + } + + /// Finds id's def, if it exists. If found, returns the definition otherwise + /// nullptr + const Instruction* FindDef(uint32_t id) const; + + /// Finds id's def, if it exists. If found, returns the definition otherwise + /// nullptr + Instruction* FindDef(uint32_t id); + + /// Returns a deque of instructions in the order they appear in the binary + const std::deque& ordered_instructions() const { + return ordered_instructions_; + } + + /// Returns a map of instructions mapped by their result id + const std::unordered_map& all_definitions() const { + return all_definitions_; + } + + /// Returns a vector containing the Ids of instructions that consume the given + /// SampledImage id. + std::vector getSampledImageConsumers(uint32_t id) const; + + /// Records cons_id as a consumer of sampled_image_id. + void RegisterSampledImageConsumer(uint32_t sampled_image_id, + uint32_t cons_id); + + /// Returns the set of Global Variables. + std::unordered_set& global_vars() { return global_vars_; } + + /// Returns the set of Local Variables. + std::unordered_set& local_vars() { return local_vars_; } + + /// Returns the number of Global Variables. + size_t num_global_vars() { return global_vars_.size(); } + + /// Returns the number of Local Variables. + size_t num_local_vars() { return local_vars_.size(); } + + /// Inserts a new to the set of Global Variables. + void registerGlobalVariable(const uint32_t id) { global_vars_.insert(id); } + + /// Inserts a new to the set of Local Variables. + void registerLocalVariable(const uint32_t id) { local_vars_.insert(id); } + + /// Sets the struct nesting depth for a given struct ID + void set_struct_nesting_depth(uint32_t id, uint32_t depth) { + struct_nesting_depth_[id] = depth; + } + + /// Returns the nesting depth of a given structure ID + uint32_t struct_nesting_depth(uint32_t id) { + return struct_nesting_depth_[id]; + } + + /// Records that the structure type has a member decorated with a built-in. + void RegisterStructTypeWithBuiltInMember(uint32_t id) { + builtin_structs_.insert(id); + } + + /// Returns true if the struct type with the given Id has a BuiltIn member. + bool IsStructTypeWithBuiltInMember(uint32_t id) const { + return (builtin_structs_.find(id) != builtin_structs_.end()); + } + + // Returns the state of optional features. + const Feature& features() const { return features_; } + + /// Adds the instruction data to unique_type_declarations_. + /// Returns false if an identical type declaration already exists. + bool RegisterUniqueTypeDeclaration(const spv_parsed_instruction_t& inst); + + // Returns type_id of the scalar component of |id|. + // |id| can be either + // - scalar, vector or matrix type + // - object of either scalar, vector or matrix type + uint32_t GetComponentType(uint32_t id) const; + + // Returns + // - 1 for scalar types or objects + // - vector size for vector types or objects + // - num columns for matrix types or objects + // Should not be called with any other arguments (will return zero and invoke + // assertion). + uint32_t GetDimension(uint32_t id) const; + + // Returns bit width of scalar or component. + // |id| can be + // - scalar, vector or matrix type + // - object of either scalar, vector or matrix type + // Will invoke assertion and return 0 if |id| is none of the above. + uint32_t GetBitWidth(uint32_t id) const; + + // Provides detailed information on matrix type. + // Returns false iff |id| is not matrix type. + bool GetMatrixTypeInfo(uint32_t id, uint32_t* num_rows, uint32_t* num_cols, + uint32_t* column_type, uint32_t* component_type) const; + + // Collects struct member types into |member_types|. + // Returns false iff not struct type or has no members. + // Deletes prior contents of |member_types|. + bool GetStructMemberTypes(uint32_t struct_type_id, + std::vector* member_types) const; + + // Returns true iff |id| is a type corresponding to the name of the function. + // Only works for types not for objects. + bool IsFloatScalarType(uint32_t id) const; + bool IsFloatVectorType(uint32_t id) const; + bool IsFloatScalarOrVectorType(uint32_t id) const; + bool IsFloatMatrixType(uint32_t id) const; + bool IsIntScalarType(uint32_t id) const; + bool IsIntVectorType(uint32_t id) const; + bool IsIntScalarOrVectorType(uint32_t id) const; + bool IsUnsignedIntScalarType(uint32_t id) const; + bool IsUnsignedIntVectorType(uint32_t id) const; + bool IsSignedIntScalarType(uint32_t id) const; + bool IsSignedIntVectorType(uint32_t id) const; + bool IsBoolScalarType(uint32_t id) const; + bool IsBoolVectorType(uint32_t id) const; + bool IsBoolScalarOrVectorType(uint32_t id) const; + bool IsPointerType(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; + + // Returns type_id if id has type or zero otherwise. + uint32_t GetTypeId(uint32_t id) const; + + // Returns opcode of the instruction which issued the id or OpNop if the + // instruction is not registered. + SpvOp GetIdOpcode(uint32_t id) const; + + // Returns type_id for given id operand if it has a type or zero otherwise. + // |operand_index| is expected to be pointing towards an operand which is an + // id. + uint32_t GetOperandTypeId(const spv_parsed_instruction_t* inst, + size_t operand_index) const; + + // Provides information on pointer type. Returns false iff not pointer type. + bool GetPointerTypeInfo(uint32_t id, uint32_t* data_type, + uint32_t* storage_class) const; + + // Tries to evaluate a 32-bit signed or unsigned scalar integer constant. + // Returns tuple . + std::tuple EvalInt32IfConst(uint32_t id); + + private: + ValidationState_t(const ValidationState_t&); + + const spv_const_context context_; + + /// Stores the Validator command line options. Must be a valid options object. + const spv_const_validator_options options_; + + /// Tracks the number of instructions evaluated by the validator + int instruction_counter_; + + /// IDs which have been forward declared but have not been defined + std::unordered_set unresolved_forward_ids_; + + /// IDs that have been declared as forward pointers. + std::unordered_set forward_pointer_ids_; + + /// Stores a vector of instructions that use the result of a given + /// OpSampledImage instruction. + std::unordered_map> sampled_image_consumers_; + + /// A map of operand IDs and their names defined by the OpName instruction + std::unordered_map operand_names_; + + /// The section of the code being processed + ModuleLayoutSection current_layout_section_; + + /// A list of functions in the module. + /// Pointers to objects in this container are guaranteed to be stable and + /// valid until the end of lifetime of the validation state. + std::deque module_functions_; + + /// Capabilities declared in the module + libspirv::CapabilitySet module_capabilities_; + + /// Extensions declared in the module + libspirv::ExtensionSet module_extensions_; + + /// List of all instructions in the order they appear in the binary + /// Pointers to objects in this container are guaranteed to be stable and + /// valid until the end of lifetime of the validation state. + std::deque ordered_instructions_; + + /// Instructions that can be referenced by Ids + std::unordered_map all_definitions_; + + /// IDs that are entry points, ie, arguments to OpEntryPoint. + std::vector entry_points_; + + /// Maps an entry point id to its interfaces. + std::unordered_map> entry_point_interfaces_; + + /// Functions IDs that are target of OpFunctionCall. + std::unordered_set function_call_targets_; + + /// ID Bound from the Header + uint32_t id_bound_; + + /// Set of Global Variable IDs (Storage Class other than 'Function') + std::unordered_set global_vars_; + + /// Set of Local Variable IDs ('Function' Storage Class) + std::unordered_set local_vars_; + + /// Set of struct types that have members with a BuiltIn decoration. + std::unordered_set builtin_structs_; + + /// Structure Nesting Depth + std::unordered_map struct_nesting_depth_; + + /// Stores the list of decorations for a given + std::map> id_decorations_; + + /// Stores type declarations which need to be unique (i.e. non-aggregates), + /// in the form [opcode, operand words], result_id is not stored. + /// Using ordered set to avoid the need for a vector hash function. + /// The size of this container is expected not to exceed double-digits. + std::set> unique_type_declarations_; + + AssemblyGrammar grammar_; + + SpvAddressingModel addressing_model_; + SpvMemoryModel memory_model_; + + /// NOTE: See correspoding getter functions + bool in_function_; + + /// The state of optional features. These are determined by capabilities + /// declared by the module. + Feature features_; + + /// Maps function ids to function stat objects. + std::unordered_map id_to_function_; + + /// Mapping entry point -> execution models. It is presumed that the same + /// function could theoretically be used as 'main' by multiple OpEntryPoint + /// instructions. + std::unordered_map> + entry_point_to_execution_models_; + + /// Mapping entry point -> execution modes. + std::unordered_map> + entry_point_to_execution_modes_; +}; + +} // namespace libspirv + +#endif /// LIBSPIRV_VAL_VALIDATIONSTATE_H_ diff --git a/3rdparty/spirv-tools/source/validate.cpp b/3rdparty/spirv-tools/source/validate.cpp new file mode 100644 index 000000000..2c3386c9b --- /dev/null +++ b/3rdparty/spirv-tools/source/validate.cpp @@ -0,0 +1,430 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "validate.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "binary.h" +#include "diagnostic.h" +#include "enum_string_mapping.h" +#include "extensions.h" +#include "instruction.h" +#include "opcode.h" +#include "operand.h" +#include "spirv-tools/libspirv.h" +#include "spirv_constant.h" +#include "spirv_endian.h" +#include "spirv_target_env.h" +#include "spirv_validator_options.h" +#include "val/construct.h" +#include "val/function.h" +#include "val/validation_state.h" + +using std::function; +using std::ostream_iterator; +using std::string; +using std::stringstream; +using std::transform; +using std::vector; +using std::placeholders::_1; + +using libspirv::CfgPass; +using libspirv::DataRulesPass; +using libspirv::Extension; +using libspirv::IdPass; +using libspirv::InstructionPass; +using libspirv::LiteralsPass; +using libspirv::ModuleLayoutPass; +using libspirv::ValidationState_t; + +spv_result_t spvValidateIDs(const spv_instruction_t* pInsts, + const uint64_t count, + const ValidationState_t& state, + spv_position position) { + position->index = SPV_INDEX_INSTRUCTION; + if (auto error = spvValidateInstructionIDs(pInsts, count, state, position)) + return error; + return SPV_SUCCESS; +} + +namespace { + +// TODO(umar): Validate header +// TODO(umar): The binary parser validates the magic word, and the length of the +// header, but nothing else. +spv_result_t setHeader(void* user_data, spv_endianness_t endian, uint32_t magic, + uint32_t version, uint32_t generator, uint32_t id_bound, + uint32_t reserved) { + // Record the ID bound so that the validator can ensure no ID is out of bound. + ValidationState_t& _ = *(reinterpret_cast(user_data)); + _.setIdBound(id_bound); + + (void)endian; + (void)magic; + (void)version; + (void)generator; + (void)id_bound; + (void)reserved; + return SPV_SUCCESS; +} + +// Improves diagnostic messages by collecting names of IDs +// NOTE: This function returns void and is not involved in validation +void DebugInstructionPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + switch (inst->opcode) { + case SpvOpName: { + const uint32_t target = *(inst->words + inst->operands[0].offset); + const char* str = + reinterpret_cast(inst->words + inst->operands[1].offset); + _.AssignNameToId(target, str); + } break; + case SpvOpMemberName: { + const uint32_t target = *(inst->words + inst->operands[0].offset); + const char* str = + reinterpret_cast(inst->words + inst->operands[2].offset); + _.AssignNameToId(target, str); + } break; + case SpvOpSourceContinued: + case SpvOpSource: + case SpvOpSourceExtension: + case SpvOpString: + case SpvOpLine: + case SpvOpNoLine: + + default: + break; + } +} + +// Parses OpExtension instruction and registers extension. +void RegisterExtension(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + const std::string extension_str = libspirv::GetExtensionString(inst); + Extension extension; + if (!GetExtensionFromString(extension_str.c_str(), &extension)) { + // The error will be logged in the ProcessInstruction pass. + return; + } + + _.RegisterExtension(extension); +} + +// Parses the beginning of the module searching for OpExtension instructions. +// Registers extensions if recognized. Returns SPV_REQUESTED_TERMINATION +// once an instruction which is not SpvOpCapability and SpvOpExtension is +// encountered. According to the SPIR-V spec extensions are declared after +// capabilities and before everything else. +spv_result_t ProcessExtensions(void* user_data, + const spv_parsed_instruction_t* inst) { + const SpvOp opcode = static_cast(inst->opcode); + if (opcode == SpvOpCapability) return SPV_SUCCESS; + + if (opcode == SpvOpExtension) { + ValidationState_t& _ = *(reinterpret_cast(user_data)); + RegisterExtension(_, inst); + return SPV_SUCCESS; + } + + // OpExtension block is finished, requesting termination. + return SPV_REQUESTED_TERMINATION; +} + +spv_result_t ProcessInstruction(void* user_data, + const spv_parsed_instruction_t* inst) { + ValidationState_t& _ = *(reinterpret_cast(user_data)); + _.increment_instruction_count(); + if (static_cast(inst->opcode) == SpvOpEntryPoint) { + const auto entry_point = inst->words[2]; + const SpvExecutionModel execution_model = SpvExecutionModel(inst->words[1]); + _.RegisterEntryPointId(entry_point, execution_model); + // Operand 3 and later are the of interfaces for the entry point. + for (int i = 3; i < inst->num_operands; ++i) { + _.RegisterInterfaceForEntryPoint(entry_point, + inst->words[inst->operands[i].offset]); + } + } + if (static_cast(inst->opcode) == SpvOpFunctionCall) { + _.AddFunctionCallTarget(inst->words[3]); + } + + DebugInstructionPass(_, inst); + if (auto error = CapabilityPass(_, inst)) return error; + if (auto error = DataRulesPass(_, inst)) return error; + if (auto error = IdPass(_, inst)) return error; + if (auto error = ModuleLayoutPass(_, inst)) return error; + if (auto error = CfgPass(_, inst)) return error; + if (auto error = InstructionPass(_, inst)) return error; + if (auto error = TypeUniquePass(_, inst)) return error; + if (auto error = ArithmeticsPass(_, inst)) return error; + if (auto error = CompositesPass(_, inst)) return error; + if (auto error = ConversionPass(_, inst)) return error; + if (auto error = DerivativesPass(_, inst)) return error; + if (auto error = LogicalsPass(_, inst)) return error; + if (auto error = BitwisePass(_, inst)) return error; + if (auto error = ExtInstPass(_, inst)) return error; + if (auto error = ImagePass(_, inst)) return error; + if (auto error = AtomicsPass(_, inst)) return error; + if (auto error = BarriersPass(_, inst)) return error; + if (auto error = PrimitivesPass(_, inst)) return error; + if (auto error = LiteralsPass(_, inst)) return error; + + return SPV_SUCCESS; +} + +void printDot(const ValidationState_t& _, const libspirv::BasicBlock& other) { + string block_string; + if (other.successors()->empty()) { + block_string += "end "; + } else { + for (auto block : *other.successors()) { + block_string += _.getIdOrName(block->id()) + " "; + } + } + printf("%10s -> {%s\b}\n", _.getIdOrName(other.id()).c_str(), + block_string.c_str()); +} + +void PrintBlocks(ValidationState_t& _, libspirv::Function func) { + assert(func.first_block()); + + printf("%10s -> %s\n", _.getIdOrName(func.id()).c_str(), + _.getIdOrName(func.first_block()->id()).c_str()); + for (const auto& block : func.ordered_blocks()) { + printDot(_, *block); + } +} + +#ifdef __clang__ +#define UNUSED(func) [[gnu::unused]] func +#elif defined(__GNUC__) +#define UNUSED(func) \ + func __attribute__((unused)); \ + func +#elif defined(_MSC_VER) +#define UNUSED(func) func +#endif + +UNUSED(void PrintDotGraph(ValidationState_t& _, libspirv::Function func)) { + if (func.first_block()) { + string func_name(_.getIdOrName(func.id())); + printf("digraph %s {\n", func_name.c_str()); + PrintBlocks(_, func); + printf("}\n"); + } +} + +spv_result_t ValidateBinaryUsingContextAndValidationState( + const spv_context_t& context, const uint32_t* words, const size_t num_words, + spv_diagnostic* pDiagnostic, ValidationState_t* vstate) { + auto binary = std::unique_ptr( + new spv_const_binary_t{words, num_words}); + + spv_endianness_t endian; + spv_position_t position = {}; + if (spvBinaryEndianness(binary.get(), &endian)) { + return libspirv::DiagnosticStream(position, context.consumer, + SPV_ERROR_INVALID_BINARY) + << "Invalid SPIR-V magic number."; + } + + spv_header_t header; + if (spvBinaryHeaderGet(binary.get(), endian, &header)) { + return libspirv::DiagnosticStream(position, context.consumer, + SPV_ERROR_INVALID_BINARY) + << "Invalid SPIR-V header."; + } + + if (header.version > spvVersionForTargetEnv(context.target_env)) { + return libspirv::DiagnosticStream(position, context.consumer, + SPV_ERROR_WRONG_VERSION) + << "Invalid SPIR-V binary version " + << SPV_SPIRV_VERSION_MAJOR_PART(header.version) << "." + << SPV_SPIRV_VERSION_MINOR_PART(header.version) + << " for target environment " + << spvTargetEnvDescription(context.target_env) << "."; + } + + // Look for OpExtension instructions and register extensions. + // Diagnostics if any will be produced in the next pass (ProcessInstruction). + spvBinaryParse(&context, vstate, words, num_words, + /* parsed_header = */ nullptr, ProcessExtensions, + /* diagnostic = */ nullptr); + + // NOTE: Parse the module and perform inline validation checks. These + // checks do not require the the knowledge of the whole module. + if (auto error = spvBinaryParse(&context, vstate, words, num_words, setHeader, + ProcessInstruction, pDiagnostic)) + return error; + + if (vstate->in_function_body()) + return vstate->diag(SPV_ERROR_INVALID_LAYOUT) + << "Missing OpFunctionEnd at end of module."; + + // TODO(umar): Add validation checks which require the parsing of the entire + // module. Use the information from the ProcessInstruction pass to make the + // checks. + if (vstate->unresolved_forward_id_count() > 0) { + stringstream ss; + vector ids = vstate->UnresolvedForwardIds(); + + transform(begin(ids), end(ids), ostream_iterator(ss, " "), + bind(&ValidationState_t::getIdName, std::ref(*vstate), _1)); + + auto id_str = ss.str(); + return vstate->diag(SPV_ERROR_INVALID_ID) + << "The following forward referenced IDs have not been defined:\n" + << id_str.substr(0, id_str.size() - 1); + } + + // Validate the preconditions involving adjacent instructions. e.g. SpvOpPhi + // must only be preceeded by SpvOpLabel, SpvOpPhi, or SpvOpLine. + if (auto error = ValidateAdjacency(*vstate)) return error; + + // CFG checks are performed after the binary has been parsed + // and the CFGPass has collected information about the control flow + if (auto error = PerformCfgChecks(*vstate)) return error; + if (auto error = UpdateIdUse(*vstate)) return error; + if (auto error = CheckIdDefinitionDominateUse(*vstate)) return error; + if (auto error = ValidateDecorations(*vstate)) return error; + + // Entry point validation. Based on 2.16.1 (Universal Validation Rules) of the + // SPIRV spec: + // * There is at least one OpEntryPoint instruction, unless the Linkage + // capability is being used. + // * No function can be targeted by both an OpEntryPoint instruction and an + // OpFunctionCall instruction. + if (vstate->entry_points().empty() && + !vstate->HasCapability(SpvCapabilityLinkage)) { + return vstate->diag(SPV_ERROR_INVALID_BINARY) + << "No OpEntryPoint instruction was found. This is only allowed if " + "the Linkage capability is being used."; + } + for (const auto& entry_point : vstate->entry_points()) { + if (vstate->IsFunctionCallTarget(entry_point)) { + return vstate->diag(SPV_ERROR_INVALID_BINARY) + << "A function (" << entry_point + << ") may not be targeted by both an OpEntryPoint instruction and " + "an OpFunctionCall instruction."; + } + } + + // NOTE: Copy each instruction for easier processing + std::vector instructions; + // Expect average instruction length to be a bit over 2 words. + instructions.reserve(binary->wordCount / 2); + uint64_t index = SPV_INDEX_INSTRUCTION; + while (index < binary->wordCount) { + uint16_t wordCount; + uint16_t opcode; + spvOpcodeSplit(spvFixWord(binary->code[index], endian), &wordCount, + &opcode); + spv_instruction_t inst; + spvInstructionCopy(&binary->code[index], static_cast(opcode), + wordCount, endian, &inst); + instructions.emplace_back(std::move(inst)); + index += wordCount; + } + + position.index = SPV_INDEX_INSTRUCTION; + if (auto error = spvValidateIDs(instructions.data(), instructions.size(), + *vstate, &position)) + return error; + + if (auto error = ValidateBuiltIns(*vstate)) return error; + + return SPV_SUCCESS; +} +} // anonymous namespace + +spv_result_t spvValidate(const spv_const_context context, + const spv_const_binary binary, + spv_diagnostic* pDiagnostic) { + return spvValidateBinary(context, binary->code, binary->wordCount, + pDiagnostic); +} + +spv_result_t spvValidateBinary(const spv_const_context context, + const uint32_t* words, const size_t num_words, + spv_diagnostic* pDiagnostic) { + spv_context_t hijack_context = *context; + if (pDiagnostic) { + *pDiagnostic = nullptr; + libspirv::UseDiagnosticAsMessageConsumer(&hijack_context, pDiagnostic); + } + + // This interface is used for default command line options. + spv_validator_options default_options = spvValidatorOptionsCreate(); + + // Create the ValidationState using the context and default options. + ValidationState_t vstate(&hijack_context, default_options); + + spv_result_t result = ValidateBinaryUsingContextAndValidationState( + hijack_context, words, num_words, pDiagnostic, &vstate); + + spvValidatorOptionsDestroy(default_options); + return result; +} + +spv_result_t spvValidateWithOptions(const spv_const_context context, + spv_const_validator_options options, + const spv_const_binary binary, + spv_diagnostic* pDiagnostic) { + spv_context_t hijack_context = *context; + if (pDiagnostic) { + *pDiagnostic = nullptr; + libspirv::UseDiagnosticAsMessageConsumer(&hijack_context, pDiagnostic); + } + + // Create the ValidationState using the context. + ValidationState_t vstate(&hijack_context, options); + + return ValidateBinaryUsingContextAndValidationState( + hijack_context, binary->code, binary->wordCount, pDiagnostic, &vstate); +} + +namespace spvtools { + +spv_result_t ValidateBinaryAndKeepValidationState( + const spv_const_context context, spv_const_validator_options options, + const uint32_t* words, const size_t num_words, spv_diagnostic* pDiagnostic, + std::unique_ptr* vstate) { + spv_context_t hijack_context = *context; + if (pDiagnostic) { + *pDiagnostic = nullptr; + libspirv::UseDiagnosticAsMessageConsumer(&hijack_context, pDiagnostic); + } + + vstate->reset(new ValidationState_t(&hijack_context, options)); + + return ValidateBinaryUsingContextAndValidationState( + hijack_context, words, num_words, pDiagnostic, vstate->get()); +} + +spv_result_t ValidateInstructionAndUpdateValidationState( + ValidationState_t* vstate, const spv_parsed_instruction_t* inst) { + return ProcessInstruction(vstate, inst); +} + +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/validate.h b/3rdparty/spirv-tools/source/validate.h new file mode 100644 index 000000000..a4f6dde28 --- /dev/null +++ b/3rdparty/spirv-tools/source/validate.h @@ -0,0 +1,228 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_VALIDATE_H_ +#define LIBSPIRV_VALIDATE_H_ + +#include +#include +#include + +#include "instruction.h" +#include "message.h" +#include "spirv-tools/libspirv.h" +#include "table.h" + +namespace libspirv { + +class ValidationState_t; +class BasicBlock; + +/// A function that returns a vector of BasicBlocks given a BasicBlock. Used to +/// get the successor and predecessor nodes of a CFG block +using get_blocks_func = + std::function*(const BasicBlock*)>; + +/// @brief Performs the Control Flow Graph checks +/// +/// @param[in] _ the validation state of the module +/// +/// @return SPV_SUCCESS if no errors are found. SPV_ERROR_INVALID_CFG otherwise +spv_result_t PerformCfgChecks(ValidationState_t& _); + +/// @brief Updates the use vectors of all instructions that can be referenced +/// +/// This function will update the vector which define where an instruction was +/// referenced in the binary. +/// +/// @param[in] _ the validation state of the module +/// +/// @return SPV_SUCCESS if no errors are found. +spv_result_t UpdateIdUse(ValidationState_t& _); + +/// @brief This function checks all ID definitions dominate their use in the +/// CFG. +/// +/// This function will iterate over all ID definitions that are defined in the +/// functions of a module and make sure that the definitions appear in a +/// block that dominates their use. +/// +/// @param[in] _ the validation state of the module +/// +/// @return SPV_SUCCESS if no errors are found. SPV_ERROR_INVALID_ID otherwise +spv_result_t CheckIdDefinitionDominateUse(const ValidationState_t& _); + +/// @brief This function checks for preconditions involving the adjacent +/// instructions. +/// +/// This function will iterate over all instructions and check for any required +/// predecessor and/or successor instructions. e.g. SpvOpPhi must only be +/// preceeded by SpvOpLabel, SpvOpPhi, or SpvOpLine. +/// +/// @param[in] _ the validation state of the module +/// +/// @return SPV_SUCCESS if no errors are found. SPV_ERROR_INVALID_DATA otherwise +spv_result_t ValidateAdjacency(ValidationState_t& _); + +/// @brief Updates the immediate dominator for each of the block edges +/// +/// Updates the immediate dominator of the blocks for each of the edges +/// provided by the @p dom_edges parameter +/// +/// @param[in,out] dom_edges The edges of the dominator tree +/// @param[in] set_func This function will be called to updated the Immediate +/// dominator +void UpdateImmediateDominators( + const std::vector>& dom_edges, + std::function set_func); + +/// @brief Prints all of the dominators of a BasicBlock +/// +/// @param[in] block The dominators of this block will be printed +void printDominatorList(BasicBlock& block); + +/// Performs logical layout validation as described in section 2.4 of the SPIR-V +/// spec. +spv_result_t ModuleLayoutPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst); + +/// Performs Control Flow Graph validation of a module +spv_result_t CfgPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst); + +/// Performs Id and SSA validation of a module +spv_result_t IdPass(ValidationState_t& _, const spv_parsed_instruction_t* inst); + +/// Performs validation of the Data Rules subsection of 2.16.1 Universal +/// Validation Rules. +/// TODO(ehsann): add more comments here as more validation code is added. +spv_result_t DataRulesPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst); + +/// Performs instruction validation. +spv_result_t InstructionPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst); + +/// Performs decoration validation. +spv_result_t ValidateDecorations(ValidationState_t& _); + +/// Performs validation of built-in variables. +spv_result_t ValidateBuiltIns(const ValidationState_t& _); + +/// Validates that type declarations are unique, unless multiple declarations +/// of the same data type are allowed by the specification. +/// (see section 2.8 Types and Variables) +spv_result_t TypeUniquePass(ValidationState_t& _, + const spv_parsed_instruction_t* inst); + +/// Validates correctness of arithmetic instructions. +spv_result_t ArithmeticsPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst); + +/// Validates correctness of composite instructions. +spv_result_t CompositesPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst); + +/// Validates correctness of conversion instructions. +spv_result_t ConversionPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst); + +/// Validates correctness of derivative instructions. +spv_result_t DerivativesPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst); + +/// Validates correctness of logical instructions. +spv_result_t LogicalsPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst); + +/// Validates correctness of bitwise instructions. +spv_result_t BitwisePass(ValidationState_t& _, + const spv_parsed_instruction_t* inst); + +/// Validates correctness of image instructions. +spv_result_t ImagePass(ValidationState_t& _, + const spv_parsed_instruction_t* inst); + +/// Validates correctness of atomic instructions. +spv_result_t AtomicsPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst); + +/// Validates correctness of barrier instructions. +spv_result_t BarriersPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst); + +/// Validates correctness of literal numbers. +spv_result_t LiteralsPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst); + +/// Validates correctness of ExtInst instructions. +spv_result_t ExtInstPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst); + +// Validates that capability declarations use operands allowed in the current +// context. +spv_result_t CapabilityPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst); + +/// Validates correctness of primitive instructions. +spv_result_t PrimitivesPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst); + +} // namespace libspirv + +/// @brief Validate the ID usage of the instruction stream +/// +/// @param[in] pInsts stream of instructions +/// @param[in] instCount number of instructions +/// @param[in] usedefs use-def info from module parsing +/// @param[in,out] position current position in the stream +/// +/// @return result code +spv_result_t spvValidateInstructionIDs(const spv_instruction_t* pInsts, + const uint64_t instCount, + const libspirv::ValidationState_t& state, + spv_position position); + +/// @brief Validate the ID's within a SPIR-V binary +/// +/// @param[in] pInstructions array of instructions +/// @param[in] count number of elements in instruction array +/// @param[in] bound the binary header +/// @param[in,out] position current word in the binary +/// @param[in] consumer message consumer callback +/// +/// @return result code +spv_result_t spvValidateIDs(const spv_instruction_t* pInstructions, + const uint64_t count, const uint32_t bound, + spv_position position, + const spvtools::MessageConsumer& consumer); + +namespace spvtools { +// Performs validation for the SPIRV-V module binary. +// The main difference between this API and spvValidateBinary is that the +// "Validation State" is not destroyed upon function return; it lives on and is +// pointed to by the vstate unique_ptr. +spv_result_t ValidateBinaryAndKeepValidationState( + const spv_const_context context, spv_const_validator_options options, + const uint32_t* words, const size_t num_words, spv_diagnostic* pDiagnostic, + std::unique_ptr* vstate); + +// Performs validation for a single instruction and updates given validation +// state. +spv_result_t ValidateInstructionAndUpdateValidationState( + libspirv::ValidationState_t* vstate, const spv_parsed_instruction_t* inst); + +} // namespace spvtools + +#endif // LIBSPIRV_VALIDATE_H_ diff --git a/3rdparty/spirv-tools/source/validate_adjacency.cpp b/3rdparty/spirv-tools/source/validate_adjacency.cpp new file mode 100644 index 000000000..75cea5294 --- /dev/null +++ b/3rdparty/spirv-tools/source/validate_adjacency.cpp @@ -0,0 +1,84 @@ +// Copyright (c) 2018 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Validates correctness of the intra-block preconditions of SPIR-V +// instructions. + +#include "validate.h" + +#include + +#include "diagnostic.h" +#include "opcode.h" +#include "val/instruction.h" +#include "val/validation_state.h" + +namespace libspirv { + +spv_result_t ValidateAdjacency(ValidationState_t& _) { + const auto& instructions = _.ordered_instructions(); + for (auto i = instructions.cbegin(); i != instructions.cend(); ++i) { + switch (i->opcode()) { + case SpvOpPhi: + if (i != instructions.cbegin()) { + switch (prev(i)->opcode()) { + case SpvOpLabel: + case SpvOpPhi: + case SpvOpLine: + break; + default: + return _.diag(SPV_ERROR_INVALID_DATA) + << "OpPhi must appear before all non-OpPhi instructions " + << "(except for OpLine, which can be mixed with OpPhi)."; + } + } + break; + case SpvOpLoopMerge: + if (next(i) != instructions.cend()) { + switch (next(i)->opcode()) { + case SpvOpBranch: + case SpvOpBranchConditional: + break; + default: + return _.diag(SPV_ERROR_INVALID_DATA) + << "OpLoopMerge must immediately precede either an " + << "OpBranch or OpBranchConditional instruction. " + << "OpLoopMerge must be the second-to-last instruction in " + << "its block."; + } + } + break; + case SpvOpSelectionMerge: + if (next(i) != instructions.cend()) { + switch (next(i)->opcode()) { + case SpvOpBranchConditional: + case SpvOpSwitch: + break; + default: + return _.diag(SPV_ERROR_INVALID_DATA) + << "OpSelectionMerge must immediately precede either an " + << "OpBranchConditional or OpSwitch instruction. " + << "OpSelectionMerge must be the second-to-last " + << "instruction in its block."; + } + } + default: + break; + } + } + + return SPV_SUCCESS; +} + +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/validate_arithmetics.cpp b/3rdparty/spirv-tools/source/validate_arithmetics.cpp new file mode 100644 index 000000000..783dbf4de --- /dev/null +++ b/3rdparty/spirv-tools/source/validate_arithmetics.cpp @@ -0,0 +1,471 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Performs validation of arithmetic instructions. + +#include "validate.h" + +#include "diagnostic.h" +#include "opcode.h" +#include "val/instruction.h" +#include "val/validation_state.h" + +namespace libspirv { + +namespace { + +// Returns operand word for given instruction and operand index. +// The operand is expected to only have one word. +inline uint32_t GetOperandWord(const spv_parsed_instruction_t* inst, + size_t operand_index) { + assert(operand_index < inst->num_operands); + const spv_parsed_operand_t& operand = inst->operands[operand_index]; + assert(operand.num_words == 1); + return inst->words[operand.offset]; +} + +// Returns the type id of instruction operand at |operand_index|. +// The operand is expected to be an id. +inline uint32_t GetOperandTypeId(ValidationState_t& _, + const spv_parsed_instruction_t* inst, + size_t operand_index) { + return _.GetTypeId(GetOperandWord(inst, operand_index)); +} +} // namespace + +// Validates correctness of arithmetic instructions. +spv_result_t ArithmeticsPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + const SpvOp opcode = static_cast(inst->opcode); + const uint32_t result_type = inst->type_id; + + switch (opcode) { + case SpvOpFAdd: + case SpvOpFSub: + case SpvOpFMul: + case SpvOpFDiv: + case SpvOpFRem: + case SpvOpFMod: + case SpvOpFNegate: { + if (!_.IsFloatScalarType(result_type) && + !_.IsFloatVectorType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected floating scalar or vector type as Result Type: " + << spvOpcodeString(opcode); + + for (size_t operand_index = 2; operand_index < inst->num_operands; + ++operand_index) { + if (GetOperandTypeId(_, inst, operand_index) != result_type) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected arithmetic operands to be of Result Type: " + << spvOpcodeString(opcode) << " operand index " + << operand_index; + } + break; + } + + case SpvOpUDiv: + case SpvOpUMod: { + if (!_.IsUnsignedIntScalarType(result_type) && + !_.IsUnsignedIntVectorType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected unsigned int scalar or vector type as Result Type: " + << spvOpcodeString(opcode); + + for (size_t operand_index = 2; operand_index < inst->num_operands; + ++operand_index) { + if (GetOperandTypeId(_, inst, operand_index) != result_type) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected arithmetic operands to be of Result Type: " + << spvOpcodeString(opcode) << " operand index " + << operand_index; + } + break; + } + + case SpvOpISub: + case SpvOpIAdd: + case SpvOpIMul: + case SpvOpSDiv: + case SpvOpSMod: + case SpvOpSRem: + case SpvOpSNegate: { + if (!_.IsIntScalarType(result_type) && !_.IsIntVectorType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected int scalar or vector type as Result Type: " + << spvOpcodeString(opcode); + + const uint32_t dimension = _.GetDimension(result_type); + const uint32_t bit_width = _.GetBitWidth(result_type); + + for (size_t operand_index = 2; operand_index < inst->num_operands; + ++operand_index) { + const uint32_t type_id = GetOperandTypeId(_, inst, operand_index); + if (!type_id || + (!_.IsIntScalarType(type_id) && !_.IsIntVectorType(type_id))) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected int scalar or vector type as operand: " + << spvOpcodeString(opcode) << " operand index " + << operand_index; + + if (_.GetDimension(type_id) != dimension) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected arithmetic operands to have the same dimension " + << "as Result Type: " << spvOpcodeString(opcode) + << " operand index " << operand_index; + + if (_.GetBitWidth(type_id) != bit_width) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected arithmetic operands to have the same bit width " + << "as Result Type: " << spvOpcodeString(opcode) + << " operand index " << operand_index; + } + break; + } + + case SpvOpDot: { + if (!_.IsFloatScalarType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected float scalar type as Result Type: " + << spvOpcodeString(opcode); + + uint32_t first_vector_num_components = 0; + + for (size_t operand_index = 2; operand_index < inst->num_operands; + ++operand_index) { + const uint32_t type_id = GetOperandTypeId(_, inst, operand_index); + + if (!type_id || !_.IsFloatVectorType(type_id)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected float vector as operand: " + << spvOpcodeString(opcode) << " operand index " + << operand_index; + + const uint32_t component_type = _.GetComponentType(type_id); + if (component_type != result_type) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected component type to be equal to Result Type: " + << spvOpcodeString(opcode) << " operand index " + << operand_index; + + const uint32_t num_components = _.GetDimension(type_id); + if (operand_index == 2) { + first_vector_num_components = num_components; + } else if (num_components != first_vector_num_components) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected operands to have the same number of componenets: " + << spvOpcodeString(opcode); + } + } + break; + } + + case SpvOpVectorTimesScalar: { + if (!_.IsFloatVectorType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected float vector type as Result Type: " + << spvOpcodeString(opcode); + + const uint32_t vector_type_id = GetOperandTypeId(_, inst, 2); + if (result_type != vector_type_id) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected vector operand type to be equal to Result Type: " + << spvOpcodeString(opcode); + + const uint32_t component_type = _.GetComponentType(vector_type_id); + + const uint32_t scalar_type_id = GetOperandTypeId(_, inst, 3); + if (component_type != scalar_type_id) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected scalar operand type to be equal to the component " + << "type of the vector operand: " << spvOpcodeString(opcode); + + break; + } + + case SpvOpMatrixTimesScalar: { + if (!_.IsFloatMatrixType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected float matrix type as Result Type: " + << spvOpcodeString(opcode); + + const uint32_t matrix_type_id = GetOperandTypeId(_, inst, 2); + if (result_type != matrix_type_id) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected matrix operand type to be equal to Result Type: " + << spvOpcodeString(opcode); + + const uint32_t component_type = _.GetComponentType(matrix_type_id); + + const uint32_t scalar_type_id = GetOperandTypeId(_, inst, 3); + if (component_type != scalar_type_id) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected scalar operand type to be equal to the component " + << "type of the matrix operand: " << spvOpcodeString(opcode); + + break; + } + + case SpvOpVectorTimesMatrix: { + const uint32_t vector_type_id = GetOperandTypeId(_, inst, 2); + const uint32_t matrix_type_id = GetOperandTypeId(_, inst, 3); + + if (!_.IsFloatVectorType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected float vector type as Result Type: " + << spvOpcodeString(opcode); + + const uint32_t res_component_type = _.GetComponentType(result_type); + + if (!vector_type_id || !_.IsFloatVectorType(vector_type_id)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected float vector type as left operand: " + << spvOpcodeString(opcode); + + if (res_component_type != _.GetComponentType(vector_type_id)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected component types of Result Type and vector to be " + << "equal: " << spvOpcodeString(opcode); + + uint32_t matrix_num_rows = 0; + uint32_t matrix_num_cols = 0; + uint32_t matrix_col_type = 0; + uint32_t matrix_component_type = 0; + if (!_.GetMatrixTypeInfo(matrix_type_id, &matrix_num_rows, + &matrix_num_cols, &matrix_col_type, + &matrix_component_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected float matrix type as right operand: " + << spvOpcodeString(opcode); + + if (res_component_type != matrix_component_type) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected component types of Result Type and matrix to be " + << "equal: " << spvOpcodeString(opcode); + + if (matrix_num_cols != _.GetDimension(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected number of columns of the matrix to be equal to " + << "Result Type vector size: " << spvOpcodeString(opcode); + + if (matrix_num_rows != _.GetDimension(vector_type_id)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected number of rows of the matrix to be equal to the " + << "vector operand size: " << spvOpcodeString(opcode); + + break; + } + + case SpvOpMatrixTimesVector: { + const uint32_t matrix_type_id = GetOperandTypeId(_, inst, 2); + const uint32_t vector_type_id = GetOperandTypeId(_, inst, 3); + + if (!_.IsFloatVectorType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected float vector type as Result Type: " + << spvOpcodeString(opcode); + + uint32_t matrix_num_rows = 0; + uint32_t matrix_num_cols = 0; + uint32_t matrix_col_type = 0; + uint32_t matrix_component_type = 0; + if (!_.GetMatrixTypeInfo(matrix_type_id, &matrix_num_rows, + &matrix_num_cols, &matrix_col_type, + &matrix_component_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected float matrix type as left operand: " + << spvOpcodeString(opcode); + + if (result_type != matrix_col_type) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected column type of the matrix to be equal to Result " + "Type: " + << spvOpcodeString(opcode); + + if (!vector_type_id || !_.IsFloatVectorType(vector_type_id)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected float vector type as right operand: " + << spvOpcodeString(opcode); + + if (matrix_component_type != _.GetComponentType(vector_type_id)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected component types of the operands to be equal: " + << spvOpcodeString(opcode); + + if (matrix_num_cols != _.GetDimension(vector_type_id)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected number of columns of the matrix to be equal to the " + << "vector size: " << spvOpcodeString(opcode); + + break; + } + + case SpvOpMatrixTimesMatrix: { + const uint32_t left_type_id = GetOperandTypeId(_, inst, 2); + const uint32_t right_type_id = GetOperandTypeId(_, inst, 3); + + uint32_t res_num_rows = 0; + uint32_t res_num_cols = 0; + uint32_t res_col_type = 0; + uint32_t res_component_type = 0; + if (!_.GetMatrixTypeInfo(result_type, &res_num_rows, &res_num_cols, + &res_col_type, &res_component_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected float matrix type as Result Type: " + << spvOpcodeString(opcode); + + uint32_t left_num_rows = 0; + uint32_t left_num_cols = 0; + uint32_t left_col_type = 0; + uint32_t left_component_type = 0; + if (!_.GetMatrixTypeInfo(left_type_id, &left_num_rows, &left_num_cols, + &left_col_type, &left_component_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected float matrix type as left operand: " + << spvOpcodeString(opcode); + + uint32_t right_num_rows = 0; + uint32_t right_num_cols = 0; + uint32_t right_col_type = 0; + uint32_t right_component_type = 0; + if (!_.GetMatrixTypeInfo(right_type_id, &right_num_rows, &right_num_cols, + &right_col_type, &right_component_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected float matrix type as right operand: " + << spvOpcodeString(opcode); + + if (!_.IsFloatScalarType(res_component_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected float matrix type as Result Type: " + << spvOpcodeString(opcode); + + if (res_col_type != left_col_type) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected column types of Result Type and left matrix to be " + << "equal: " << spvOpcodeString(opcode); + + if (res_component_type != right_component_type) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected component types of Result Type and right matrix to " + "be " + << "equal: " << spvOpcodeString(opcode); + + if (res_num_cols != right_num_cols) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected number of columns of Result Type and right matrix " + "to " + << "be equal: " << spvOpcodeString(opcode); + + if (left_num_cols != right_num_rows) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected number of columns of left matrix and number of " + "rows " + << "of right matrix to be equal: " << spvOpcodeString(opcode); + + assert(left_num_rows == res_num_rows); + break; + } + + case SpvOpOuterProduct: { + const uint32_t left_type_id = GetOperandTypeId(_, inst, 2); + const uint32_t right_type_id = GetOperandTypeId(_, inst, 3); + + uint32_t res_num_rows = 0; + uint32_t res_num_cols = 0; + uint32_t res_col_type = 0; + uint32_t res_component_type = 0; + if (!_.GetMatrixTypeInfo(result_type, &res_num_rows, &res_num_cols, + &res_col_type, &res_component_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected float matrix type as Result Type: " + << spvOpcodeString(opcode); + + if (left_type_id != res_col_type) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected column type of Result Type to be equal to the type " + << "of the left operand: " << spvOpcodeString(opcode); + + if (!right_type_id || !_.IsFloatVectorType(right_type_id)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected float vector type as right operand: " + << spvOpcodeString(opcode); + + if (res_component_type != _.GetComponentType(right_type_id)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected component types of the operands to be equal: " + << spvOpcodeString(opcode); + + if (res_num_cols != _.GetDimension(right_type_id)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected number of columns of the matrix to be equal to the " + << "vector size of the right operand: " + << spvOpcodeString(opcode); + + break; + } + + case SpvOpIAddCarry: + case SpvOpISubBorrow: + case SpvOpUMulExtended: + case SpvOpSMulExtended: { + std::vector result_types; + if (!_.GetStructMemberTypes(result_type, &result_types)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected a struct as Result Type: " + << spvOpcodeString(opcode); + + if (result_types.size() != 2) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Result Type struct to have two members: " + << spvOpcodeString(opcode); + + if (opcode == SpvOpSMulExtended) { + if (!_.IsIntScalarType(result_types[0]) && + !_.IsIntVectorType(result_types[0])) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Result Type struct member types to be integer " + "scalar " + << "or vector: " << spvOpcodeString(opcode); + } else { + if (!_.IsUnsignedIntScalarType(result_types[0]) && + !_.IsUnsignedIntVectorType(result_types[0])) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Result Type struct member types to be unsigned " + << "integer scalar or vector: " << spvOpcodeString(opcode); + } + + if (result_types[0] != result_types[1]) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Result Type struct member types to be identical: " + << spvOpcodeString(opcode); + + const uint32_t left_type_id = GetOperandTypeId(_, inst, 2); + const uint32_t right_type_id = GetOperandTypeId(_, inst, 3); + + if (left_type_id != result_types[0] || right_type_id != result_types[0]) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected both operands to be of Result Type member type: " + << spvOpcodeString(opcode); + + break; + } + + default: + break; + } + + return SPV_SUCCESS; +} + +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/validate_atomics.cpp b/3rdparty/spirv-tools/source/validate_atomics.cpp new file mode 100644 index 000000000..8c0c535f9 --- /dev/null +++ b/3rdparty/spirv-tools/source/validate_atomics.cpp @@ -0,0 +1,321 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Validates correctness of atomic SPIR-V instructions. + +#include "validate.h" + +#include "diagnostic.h" +#include "opcode.h" +#include "spirv_target_env.h" +#include "util/bitutils.h" +#include "val/instruction.h" +#include "val/validation_state.h" + +namespace libspirv { + +// Validates Memory Scope operand. +spv_result_t ValidateMemoryScope(ValidationState_t& _, + const spv_parsed_instruction_t* inst, + uint32_t id) { + const SpvOp opcode = static_cast(inst->opcode); + bool is_int32 = false, is_const_int32 = false; + uint32_t value = 0; + std::tie(is_int32, is_const_int32, value) = _.EvalInt32IfConst(id); + + if (!is_int32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) << ": expected Scope to be 32-bit int"; + } + + if (!is_const_int32) { + return SPV_SUCCESS; + } + +#if 0 + // TODO(atgoo@github.com): this check fails Vulkan CTS, reenable once fixed. + if (spvIsVulkanEnv(_.context()->target_env)) { + if (value != SpvScopeDevice && value != SpvScopeWorkgroup && + value != SpvScopeInvocation) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": in Vulkan environment memory scope is limited to Device, " + "Workgroup and Invocation"; + } + } +#endif + + // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments. + + return SPV_SUCCESS; +} + +// Validates a Memory Semantics operand. +spv_result_t ValidateMemorySemantics(ValidationState_t& _, + const spv_parsed_instruction_t* inst, + uint32_t operand_index) { + const SpvOp opcode = static_cast(inst->opcode); + bool is_int32 = false, is_const_int32 = false; + uint32_t flags = 0; + const uint32_t memory_semantics_id = + inst->words[inst->operands[operand_index].offset]; + std::tie(is_int32, is_const_int32, flags) = + _.EvalInt32IfConst(memory_semantics_id); + + if (!is_int32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Memory Semantics to be 32-bit int"; + } + + if (!is_const_int32) { + return SPV_SUCCESS; + } + + if (spvutils::CountSetBits( + flags & + (SpvMemorySemanticsAcquireMask | SpvMemorySemanticsReleaseMask | + SpvMemorySemanticsAcquireReleaseMask | + SpvMemorySemanticsSequentiallyConsistentMask)) > 1) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": no more than one of the following Memory Semantics bits can " + "be set at the same time: Acquire, Release, AcquireRelease or " + "SequentiallyConsistent"; + } + + if (flags & SpvMemorySemanticsUniformMemoryMask && + !_.HasCapability(SpvCapabilityShader)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": Memory Semantics UniformMemory requires capability Shader"; + } + + if (flags & SpvMemorySemanticsAtomicCounterMemoryMask && + !_.HasCapability(SpvCapabilityAtomicStorage)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": Memory Semantics UniformMemory requires capability " + "AtomicStorage"; + } + + if (opcode == SpvOpAtomicFlagClear && + (flags & SpvMemorySemanticsAcquireMask || + flags & SpvMemorySemanticsAcquireReleaseMask)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Memory Semantics Acquire and AcquireRelease cannot be used with " + << spvOpcodeString(opcode); + } + + if (opcode == SpvOpAtomicCompareExchange && operand_index == 5 && + (flags & SpvMemorySemanticsReleaseMask || + flags & SpvMemorySemanticsAcquireReleaseMask)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": Memory Semantics Release and AcquireRelease cannot be used " + "for operand Unequal"; + } + + if (spvIsVulkanEnv(_.context()->target_env)) { + if (opcode == SpvOpAtomicLoad && + (flags & SpvMemorySemanticsReleaseMask || + flags & SpvMemorySemanticsAcquireReleaseMask || + flags & SpvMemorySemanticsSequentiallyConsistentMask)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec disallows OpAtomicLoad with Memory Semantics " + "Release, AcquireRelease and SequentiallyConsistent"; + } + + if (opcode == SpvOpAtomicStore && + (flags & SpvMemorySemanticsAcquireMask || + flags & SpvMemorySemanticsAcquireReleaseMask || + flags & SpvMemorySemanticsSequentiallyConsistentMask)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec disallows OpAtomicStore with Memory Semantics " + "Acquire, AcquireRelease and SequentiallyConsistent"; + } + } + + // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments. + + return SPV_SUCCESS; +} + +// Validates correctness of atomic instructions. +spv_result_t AtomicsPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + const SpvOp opcode = static_cast(inst->opcode); + const uint32_t result_type = inst->type_id; + + switch (opcode) { + case SpvOpAtomicLoad: + case SpvOpAtomicStore: + case SpvOpAtomicExchange: + case SpvOpAtomicCompareExchange: + case SpvOpAtomicCompareExchangeWeak: + case SpvOpAtomicIIncrement: + case SpvOpAtomicIDecrement: + case SpvOpAtomicIAdd: + case SpvOpAtomicISub: + case SpvOpAtomicSMin: + case SpvOpAtomicUMin: + case SpvOpAtomicSMax: + case SpvOpAtomicUMax: + case SpvOpAtomicAnd: + case SpvOpAtomicOr: + case SpvOpAtomicXor: + case SpvOpAtomicFlagTestAndSet: + case SpvOpAtomicFlagClear: { + if (_.HasCapability(SpvCapabilityKernel) && + (opcode == SpvOpAtomicLoad || opcode == SpvOpAtomicExchange || + opcode == SpvOpAtomicCompareExchange)) { + if (!_.IsFloatScalarType(result_type) && + !_.IsIntScalarType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Result Type to be int or float scalar type"; + } + } else if (opcode == SpvOpAtomicFlagTestAndSet) { + if (!_.IsBoolScalarType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Result Type to be bool scalar type"; + } + } else if (opcode == SpvOpAtomicFlagClear || opcode == SpvOpAtomicStore) { + assert(result_type == 0); + } else { + if (!_.IsIntScalarType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Result Type to be int scalar type"; + } + if (spvIsVulkanEnv(_.context()->target_env) && + _.GetBitWidth(result_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": according to the Vulkan spec atomic Result Type needs " + "to be a 32-bit int scalar type"; + } + } + + uint32_t operand_index = + opcode == SpvOpAtomicFlagClear || opcode == SpvOpAtomicStore ? 0 : 2; + const uint32_t pointer_type = _.GetOperandTypeId(inst, operand_index++); + + uint32_t data_type = 0; + uint32_t storage_class = 0; + if (!_.GetPointerTypeInfo(pointer_type, &data_type, &storage_class)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Pointer to be of type OpTypePointer"; + } + + switch (storage_class) { + case SpvStorageClassUniform: + case SpvStorageClassWorkgroup: + case SpvStorageClassCrossWorkgroup: + case SpvStorageClassGeneric: + case SpvStorageClassAtomicCounter: + case SpvStorageClassImage: + case SpvStorageClassStorageBuffer: + break; + default: + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Pointer Storage Class to be Uniform, " + "Workgroup, CrossWorkgroup, Generic, AtomicCounter, Image " + "or StorageBuffer"; + } + + if (opcode == SpvOpAtomicFlagTestAndSet || + opcode == SpvOpAtomicFlagClear) { + if (!_.IsIntScalarType(data_type) || _.GetBitWidth(data_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Pointer to point to a value of 32-bit int type"; + } + } else if (opcode == SpvOpAtomicStore) { + if (!_.IsFloatScalarType(data_type) && !_.IsIntScalarType(data_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Pointer to be a pointer to int or float " + << "scalar type"; + } + } else { + if (data_type != result_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Pointer to point to a value of type Result " + "Type"; + } + } + + const uint32_t memory_scope = + inst->words[inst->operands[operand_index++].offset]; + if (auto error = ValidateMemoryScope(_, inst, memory_scope)) { + return error; + } + + if (auto error = ValidateMemorySemantics(_, inst, operand_index++)) + return error; + + if (opcode == SpvOpAtomicCompareExchange || + opcode == SpvOpAtomicCompareExchangeWeak) { + if (auto error = ValidateMemorySemantics(_, inst, operand_index++)) + return error; + } + + if (opcode == SpvOpAtomicStore) { + const uint32_t value_type = _.GetOperandTypeId(inst, 3); + if (value_type != data_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Value type and the type pointed to by Pointer " + "to" + << " be the same"; + } + } else if (opcode != SpvOpAtomicLoad && opcode != SpvOpAtomicIIncrement && + opcode != SpvOpAtomicIDecrement && + opcode != SpvOpAtomicFlagTestAndSet && + opcode != SpvOpAtomicFlagClear) { + const uint32_t value_type = _.GetOperandTypeId(inst, operand_index++); + if (value_type != result_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Value to be of type Result Type"; + } + } + + if (opcode == SpvOpAtomicCompareExchange || + opcode == SpvOpAtomicCompareExchangeWeak) { + const uint32_t comparator_type = + _.GetOperandTypeId(inst, operand_index++); + if (comparator_type != result_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Comparator to be of type Result Type"; + } + } + + break; + } + + default: + break; + } + + return SPV_SUCCESS; +} + +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/validate_barriers.cpp b/3rdparty/spirv-tools/source/validate_barriers.cpp new file mode 100644 index 000000000..9c77b8fc8 --- /dev/null +++ b/3rdparty/spirv-tools/source/validate_barriers.cpp @@ -0,0 +1,272 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Validates correctness of barrier SPIR-V instructions. + +#include "validate.h" + +#include "diagnostic.h" +#include "opcode.h" +#include "spirv_constant.h" +#include "spirv_target_env.h" +#include "util/bitutils.h" +#include "val/instruction.h" +#include "val/validation_state.h" + +namespace libspirv { + +namespace { + +// Validates Execution Scope operand. +spv_result_t ValidateExecutionScope(ValidationState_t& _, + const spv_parsed_instruction_t* inst, + uint32_t id) { + const SpvOp opcode = static_cast(inst->opcode); + bool is_int32 = false, is_const_int32 = false; + uint32_t value = 0; + std::tie(is_int32, is_const_int32, value) = _.EvalInt32IfConst(id); + + if (!is_int32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Execution Scope to be a 32-bit int"; + } + + if (!is_const_int32) { + return SPV_SUCCESS; + } + + if (spvIsVulkanEnv(_.context()->target_env)) { + if (value != SpvScopeWorkgroup && value != SpvScopeSubgroup) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": in Vulkan environment Execution Scope is limited to " + "Workgroup and Subgroup"; + } + } + + // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments. + + return SPV_SUCCESS; +} + +// Validates Memory Scope operand. +spv_result_t ValidateMemoryScope(ValidationState_t& _, + const spv_parsed_instruction_t* inst, + uint32_t id) { + const SpvOp opcode = static_cast(inst->opcode); + bool is_int32 = false, is_const_int32 = false; + uint32_t value = 0; + std::tie(is_int32, is_const_int32, value) = _.EvalInt32IfConst(id); + + if (!is_int32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Memory Scope to be a 32-bit int"; + } + + if (!is_const_int32) { + return SPV_SUCCESS; + } + + if (spvIsVulkanEnv(_.context()->target_env)) { + if (value != SpvScopeDevice && value != SpvScopeWorkgroup && + value != SpvScopeInvocation) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": in Vulkan environment Memory Scope is limited to Device, " + "Workgroup and Invocation"; + } + } + + // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments. + + return SPV_SUCCESS; +} + +// Validates Memory Semantics operand. +spv_result_t ValidateMemorySemantics(ValidationState_t& _, + const spv_parsed_instruction_t* inst, + uint32_t id) { + const SpvOp opcode = static_cast(inst->opcode); + bool is_int32 = false, is_const_int32 = false; + uint32_t value = 0; + std::tie(is_int32, is_const_int32, value) = _.EvalInt32IfConst(id); + + if (!is_int32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Memory Semantics to be a 32-bit int"; + } + + if (!is_const_int32) { + return SPV_SUCCESS; + } + + const size_t num_memory_order_set_bits = spvutils::CountSetBits( + value & (SpvMemorySemanticsAcquireMask | SpvMemorySemanticsReleaseMask | + SpvMemorySemanticsAcquireReleaseMask | + SpvMemorySemanticsSequentiallyConsistentMask)); + + if (num_memory_order_set_bits > 1) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": Memory Semantics can have at most one of the following bits " + "set: Acquire, Release, AcquireRelease or SequentiallyConsistent"; + } + + if (spvIsVulkanEnv(_.context()->target_env)) { + const bool includes_storage_class = + value & (SpvMemorySemanticsUniformMemoryMask | + SpvMemorySemanticsWorkgroupMemoryMask | + SpvMemorySemanticsImageMemoryMask); + + if (opcode == SpvOpMemoryBarrier && !num_memory_order_set_bits) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": Vulkan specification requires Memory Semantics to have one " + "of the following bits set: Acquire, Release, AcquireRelease " + "or SequentiallyConsistent"; + } + + if (opcode == SpvOpMemoryBarrier && !includes_storage_class) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Memory Semantics to include a Vulkan-supported " + "storage class"; + } + +#if 0 + // TODO(atgoo@github.com): this check fails Vulkan CTS, reenable once fixed. + if (opcode == SpvOpControlBarrier && value && !includes_storage_class) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Memory Semantics to include a Vulkan-supported " + "storage class if Memory Semantics is not None"; + } +#endif + } + + // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments. + + return SPV_SUCCESS; +} + +} // anonymous namespace + +// Validates correctness of barrier instructions. +spv_result_t BarriersPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + const SpvOp opcode = static_cast(inst->opcode); + const uint32_t result_type = inst->type_id; + + switch (opcode) { + case SpvOpControlBarrier: { + if (spvVersionForTargetEnv(_.context()->target_env) < + SPV_SPIRV_VERSION_WORD(1, 3)) { + _.current_function().RegisterExecutionModelLimitation( + [](SpvExecutionModel model, std::string* message) { + if (model != SpvExecutionModelTessellationControl && + model != SpvExecutionModelGLCompute && + model != SpvExecutionModelKernel) { + if (message) { + *message = + "OpControlBarrier requires one of the following " + "Execution " + "Models: TessellationControl, GLCompute or Kernel"; + } + return false; + } + return true; + }); + } + + const uint32_t execution_scope = inst->words[1]; + const uint32_t memory_scope = inst->words[2]; + const uint32_t memory_semantics = inst->words[3]; + + if (auto error = ValidateExecutionScope(_, inst, execution_scope)) { + return error; + } + + if (auto error = ValidateMemoryScope(_, inst, memory_scope)) { + return error; + } + + if (auto error = ValidateMemorySemantics(_, inst, memory_semantics)) { + return error; + } + break; + } + + case SpvOpMemoryBarrier: { + const uint32_t memory_scope = inst->words[1]; + const uint32_t memory_semantics = inst->words[2]; + + if (auto error = ValidateMemoryScope(_, inst, memory_scope)) { + return error; + } + + if (auto error = ValidateMemorySemantics(_, inst, memory_semantics)) { + return error; + } + break; + } + + case SpvOpNamedBarrierInitialize: { + if (_.GetIdOpcode(result_type) != SpvOpTypeNamedBarrier) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Result Type to be OpTypeNamedBarrier"; + } + + const uint32_t subgroup_count_type = _.GetOperandTypeId(inst, 2); + if (!_.IsIntScalarType(subgroup_count_type) || + _.GetBitWidth(subgroup_count_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Subgroup Count to be a 32-bit int"; + } + break; + } + + case SpvOpMemoryNamedBarrier: { + const uint32_t named_barrier_type = _.GetOperandTypeId(inst, 0); + if (_.GetIdOpcode(named_barrier_type) != SpvOpTypeNamedBarrier) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Named Barrier to be of type OpTypeNamedBarrier"; + } + + const uint32_t memory_scope = inst->words[2]; + const uint32_t memory_semantics = inst->words[3]; + + if (auto error = ValidateMemoryScope(_, inst, memory_scope)) { + return error; + } + + if (auto error = ValidateMemorySemantics(_, inst, memory_semantics)) { + return error; + } + break; + } + + default: + break; + } + + return SPV_SUCCESS; +} + +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/validate_bitwise.cpp b/3rdparty/spirv-tools/source/validate_bitwise.cpp new file mode 100644 index 000000000..94978d93f --- /dev/null +++ b/3rdparty/spirv-tools/source/validate_bitwise.cpp @@ -0,0 +1,239 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Validates correctness of bitwise instructions. + +#include "validate.h" + +#include "diagnostic.h" +#include "opcode.h" +#include "val/instruction.h" +#include "val/validation_state.h" + +namespace libspirv { + +namespace { + +// Returns operand word for given instruction and operand index. +// The operand is expected to only have one word. +inline uint32_t GetOperandWord(const spv_parsed_instruction_t* inst, + size_t operand_index) { + assert(operand_index < inst->num_operands); + const spv_parsed_operand_t& operand = inst->operands[operand_index]; + assert(operand.num_words == 1); + return inst->words[operand.offset]; +} + +// Returns the type id of instruction operand at |operand_index|. +// The operand is expected to be an id. +inline uint32_t GetOperandTypeId(ValidationState_t& _, + const spv_parsed_instruction_t* inst, + size_t operand_index) { + return _.GetTypeId(GetOperandWord(inst, operand_index)); +} +} // namespace + +// Validates correctness of bitwise instructions. +spv_result_t BitwisePass(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + const SpvOp opcode = static_cast(inst->opcode); + const uint32_t result_type = inst->type_id; + + switch (opcode) { + case SpvOpShiftRightLogical: + case SpvOpShiftRightArithmetic: + case SpvOpShiftLeftLogical: { + if (!_.IsIntScalarType(result_type) && !_.IsIntVectorType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected int scalar or vector type as Result Type: " + << spvOpcodeString(opcode); + + const uint32_t result_dimension = _.GetDimension(result_type); + const uint32_t base_type = GetOperandTypeId(_, inst, 2); + const uint32_t shift_type = GetOperandTypeId(_, inst, 3); + + if (!base_type || + (!_.IsIntScalarType(base_type) && !_.IsIntVectorType(base_type))) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Base to be int scalar or vector: " + << spvOpcodeString(opcode); + + if (_.GetDimension(base_type) != result_dimension) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Base to have the same dimension " + << "as Result Type: " << spvOpcodeString(opcode); + + if (_.GetBitWidth(base_type) != _.GetBitWidth(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Base to have the same bit width " + << "as Result Type: " << spvOpcodeString(opcode); + + if (!shift_type || + (!_.IsIntScalarType(shift_type) && !_.IsIntVectorType(shift_type))) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Shift to be int scalar or vector: " + << spvOpcodeString(opcode); + + if (_.GetDimension(shift_type) != result_dimension) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Shift to have the same dimension " + << "as Result Type: " << spvOpcodeString(opcode); + break; + } + + case SpvOpBitwiseOr: + case SpvOpBitwiseXor: + case SpvOpBitwiseAnd: + case SpvOpNot: { + if (!_.IsIntScalarType(result_type) && !_.IsIntVectorType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected int scalar or vector type as Result Type: " + << spvOpcodeString(opcode); + + const uint32_t result_dimension = _.GetDimension(result_type); + const uint32_t result_bit_width = _.GetBitWidth(result_type); + + for (size_t operand_index = 2; operand_index < inst->num_operands; + ++operand_index) { + const uint32_t type_id = GetOperandTypeId(_, inst, operand_index); + if (!type_id || + (!_.IsIntScalarType(type_id) && !_.IsIntVectorType(type_id))) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected int scalar or vector as operand: " + << spvOpcodeString(opcode) << " operand index " + << operand_index; + + if (_.GetDimension(type_id) != result_dimension) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected operands to have the same dimension " + << "as Result Type: " << spvOpcodeString(opcode) + << " operand index " << operand_index; + + if (_.GetBitWidth(type_id) != result_bit_width) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected operands to have the same bit width " + << "as Result Type: " << spvOpcodeString(opcode) + << " operand index " << operand_index; + } + break; + } + + case SpvOpBitFieldInsert: { + if (!_.IsIntScalarType(result_type) && !_.IsIntVectorType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected int scalar or vector type as Result Type: " + << spvOpcodeString(opcode); + + const uint32_t base_type = GetOperandTypeId(_, inst, 2); + const uint32_t insert_type = GetOperandTypeId(_, inst, 3); + const uint32_t offset_type = GetOperandTypeId(_, inst, 4); + const uint32_t count_type = GetOperandTypeId(_, inst, 5); + + if (base_type != result_type) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Base Type to be equal to Result Type: " + << spvOpcodeString(opcode); + + if (insert_type != result_type) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Insert Type to be equal to Result Type: " + << spvOpcodeString(opcode); + + if (!offset_type || !_.IsIntScalarType(offset_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Offset Type to be int scalar: " + << spvOpcodeString(opcode); + + if (!count_type || !_.IsIntScalarType(count_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Count Type to be int scalar: " + << spvOpcodeString(opcode); + break; + } + + case SpvOpBitFieldSExtract: + case SpvOpBitFieldUExtract: { + if (!_.IsIntScalarType(result_type) && !_.IsIntVectorType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected int scalar or vector type as Result Type: " + << spvOpcodeString(opcode); + + const uint32_t base_type = GetOperandTypeId(_, inst, 2); + const uint32_t offset_type = GetOperandTypeId(_, inst, 3); + const uint32_t count_type = GetOperandTypeId(_, inst, 4); + + if (base_type != result_type) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Base Type to be equal to Result Type: " + << spvOpcodeString(opcode); + + if (!offset_type || !_.IsIntScalarType(offset_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Offset Type to be int scalar: " + << spvOpcodeString(opcode); + + if (!count_type || !_.IsIntScalarType(count_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Count Type to be int scalar: " + << spvOpcodeString(opcode); + break; + } + + case SpvOpBitReverse: { + if (!_.IsIntScalarType(result_type) && !_.IsIntVectorType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected int scalar or vector type as Result Type: " + << spvOpcodeString(opcode); + + const uint32_t base_type = GetOperandTypeId(_, inst, 2); + + if (base_type != result_type) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Base Type to be equal to Result Type: " + << spvOpcodeString(opcode); + break; + } + + case SpvOpBitCount: { + if (!_.IsIntScalarType(result_type) && !_.IsIntVectorType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected int scalar or vector type as Result Type: " + << spvOpcodeString(opcode); + + const uint32_t base_type = GetOperandTypeId(_, inst, 2); + if (!base_type || + (!_.IsIntScalarType(base_type) && !_.IsIntVectorType(base_type))) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Base Type to be int scalar or vector: " + << spvOpcodeString(opcode); + + const uint32_t base_dimension = _.GetDimension(base_type); + const uint32_t result_dimension = _.GetDimension(result_type); + + if (base_dimension != result_dimension) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Base dimension to be equal to Result Type " + "dimension: " + << spvOpcodeString(opcode); + break; + } + + default: + break; + } + + return SPV_SUCCESS; +} + +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/validate_builtins.cpp b/3rdparty/spirv-tools/source/validate_builtins.cpp new file mode 100644 index 000000000..c494a888e --- /dev/null +++ b/3rdparty/spirv-tools/source/validate_builtins.cpp @@ -0,0 +1,2386 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Validates correctness of built-in variables. + +#include "validate.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "diagnostic.h" +#include "opcode.h" +#include "spirv_target_env.h" +#include "util/bitutils.h" +#include "val/instruction.h" +#include "val/validation_state.h" + +namespace libspirv { + +namespace { + +// Returns a short textual description of the id defined by the given +// instruction. +std::string GetIdDesc(const Instruction& inst) { + std::ostringstream ss; + ss << "ID <" << inst.id() << "> (Op" << spvOpcodeString(inst.opcode()) << ")"; + return ss.str(); +} + +// Gets underlying data type which is +// - member type if instruction is OpTypeStruct +// (member index is taken from decoration). +// - data type if id creates a pointer. +// - type of the constant if instruction is OpConst or OpSpecConst. +// +// Fails in any other case. The function is based on built-ins allowed by +// the Vulkan spec. +// TODO: If non-Vulkan validation rules are added then it might need +// to be refactored. +spv_result_t GetUnderlyingType(const ValidationState_t& _, + const Decoration& decoration, + const Instruction& inst, + uint32_t* underlying_type) { + if (decoration.struct_member_index() != Decoration::kInvalidMember) { + assert(inst.opcode() == SpvOpTypeStruct); + *underlying_type = inst.word(decoration.struct_member_index() + 2); + return SPV_SUCCESS; + } + + assert(inst.opcode() != SpvOpTypeStruct); + + if (spvOpcodeIsConstant(inst.opcode())) { + *underlying_type = inst.type_id(); + return SPV_SUCCESS; + } + + uint32_t storage_class = 0; + if (!_.GetPointerTypeInfo(inst.type_id(), underlying_type, &storage_class)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << GetIdDesc(inst) + << " is decorated with BuiltIn. BuiltIn decoration should only be " + "applied to struct types, variables and constants."; + } + return SPV_SUCCESS; +} + +// Returns Storage Class used by the instruction if applicable. +// Returns SpvStorageClassMax if not. +SpvStorageClass GetStorageClass(const Instruction& inst) { + switch (inst.opcode()) { + case SpvOpTypePointer: + case SpvOpTypeForwardPointer: { + return SpvStorageClass(inst.word(2)); + } + case SpvOpVariable: { + return SpvStorageClass(inst.word(3)); + } + case SpvOpGenericCastToPtrExplicit: { + return SpvStorageClass(inst.word(4)); + } + default: { break; } + } + return SpvStorageClassMax; +} + +// Helper class managing validation of built-ins. +// TODO: Generic functionality of this class can be moved into +// ValidationState_t to be made available to other users. +class BuiltInsValidator { + public: + BuiltInsValidator(const ValidationState_t& vstate) : _(vstate) {} + + // Run validation. + spv_result_t Run(); + + private: + // Goes through all decorations in the module, if decoration is BuiltIn + // calls ValidateSingleBuiltInAtDefinition(). + spv_result_t ValidateBuiltInsAtDefinition(); + + // Validates the instruction defining an id with built-in decoration. + // Can be called multiple times for the same id, if multiple built-ins are + // specified. Seeds id_to_at_reference_checks_ with decorated ids if needed. + spv_result_t ValidateSingleBuiltInAtDefinition(const Decoration& decoration, + const Instruction& inst); + + // The following section contains functions which are called when id defined + // by |inst| is decorated with BuiltIn |decoration|. + // Most functions are specific to a single built-in and have naming scheme: + // ValidateXYZAtDefinition. Some functions are common to multiple kinds of + // BuiltIn. + spv_result_t ValidateClipOrCullDistanceAtDefinition( + const Decoration& decoration, const Instruction& inst); + spv_result_t ValidateFragCoordAtDefinition(const Decoration& decoration, + const Instruction& inst); + spv_result_t ValidateFragDepthAtDefinition(const Decoration& decoration, + const Instruction& inst); + spv_result_t ValidateFrontFacingAtDefinition(const Decoration& decoration, + const Instruction& inst); + spv_result_t ValidateHelperInvocationAtDefinition( + const Decoration& decoration, const Instruction& inst); + spv_result_t ValidateInvocationIdAtDefinition(const Decoration& decoration, + const Instruction& inst); + spv_result_t ValidateInstanceIndexAtDefinition(const Decoration& decoration, + const Instruction& inst); + spv_result_t ValidateLayerOrViewportIndexAtDefinition( + const Decoration& decoration, const Instruction& inst); + spv_result_t ValidatePatchVerticesAtDefinition(const Decoration& decoration, + const Instruction& inst); + spv_result_t ValidatePointCoordAtDefinition(const Decoration& decoration, + const Instruction& inst); + spv_result_t ValidatePointSizeAtDefinition(const Decoration& decoration, + const Instruction& inst); + spv_result_t ValidatePositionAtDefinition(const Decoration& decoration, + const Instruction& inst); + spv_result_t ValidatePrimitiveIdAtDefinition(const Decoration& decoration, + const Instruction& inst); + spv_result_t ValidateSampleIdAtDefinition(const Decoration& decoration, + const Instruction& inst); + spv_result_t ValidateSampleMaskAtDefinition(const Decoration& decoration, + const Instruction& inst); + spv_result_t ValidateSamplePositionAtDefinition(const Decoration& decoration, + const Instruction& inst); + spv_result_t ValidateTessCoordAtDefinition(const Decoration& decoration, + const Instruction& inst); + spv_result_t ValidateTessLevelOuterAtDefinition(const Decoration& decoration, + const Instruction& inst); + spv_result_t ValidateTessLevelInnerAtDefinition(const Decoration& decoration, + const Instruction& inst); + spv_result_t ValidateVertexIndexAtDefinition(const Decoration& decoration, + const Instruction& inst); + spv_result_t ValidateWorkgroupSizeAtDefinition(const Decoration& decoration, + const Instruction& inst); + // Used for GlobalInvocationId, LocalInvocationId, NumWorkgroups, WorkgroupId. + spv_result_t ValidateComputeShaderI32Vec3InputAtDefinition( + const Decoration& decoration, const Instruction& inst); + + // The following section contains functions which are called when id defined + // by |referenced_inst| is + // 1. referenced by |referenced_from_inst| + // 2. dependent on |built_in_inst| which is decorated with BuiltIn + // |decoration|. Most functions are specific to a single built-in and have + // naming scheme: ValidateXYZAtReference. Some functions are common to + // multiple kinds of BuiltIn. + spv_result_t ValidateFragCoordAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + + spv_result_t ValidateFragDepthAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + + spv_result_t ValidateFrontFacingAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + + spv_result_t ValidateHelperInvocationAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + + spv_result_t ValidateInvocationIdAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + + spv_result_t ValidateInstanceIndexAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + + spv_result_t ValidatePatchVerticesAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + + spv_result_t ValidatePointCoordAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + + spv_result_t ValidatePointSizeAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + + spv_result_t ValidatePositionAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + + spv_result_t ValidatePrimitiveIdAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + + spv_result_t ValidateSampleIdAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + + spv_result_t ValidateSampleMaskAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + + spv_result_t ValidateSamplePositionAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + + spv_result_t ValidateTessCoordAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + + spv_result_t ValidateTessLevelAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + + spv_result_t ValidateVertexIndexAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + + spv_result_t ValidateLayerOrViewportIndexAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + + spv_result_t ValidateWorkgroupSizeAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + + spv_result_t ValidateClipOrCullDistanceAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + + // Used for GlobalInvocationId, LocalInvocationId, NumWorkgroups, WorkgroupId. + spv_result_t ValidateComputeShaderI32Vec3InputAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + + // Validates that |built_in_inst| is not (even indirectly) referenced from + // within a function which can be called with |execution_model|. + // + // |comment| - text explaining why the restriction was imposed. + // |decoration| - BuiltIn decoration which causes the restriction. + // |referenced_inst| - instruction which is dependent on |built_in_inst| and + // defines the id which was referenced. + // |referenced_from_inst| - instruction which references id defined by + // |referenced_inst| from within a function. + spv_result_t ValidateNotCalledWithExecutionModel( + const char* comment, SpvExecutionModel execution_model, + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst); + + // The following section contains functions which check that the decorated + // variable has the type specified in the function name. |diag| would be + // called with a corresponding error message, if validation is not successful. + spv_result_t ValidateBool( + const Decoration& decoration, const Instruction& inst, + const std::function& diag); + spv_result_t ValidateI32( + const Decoration& decoration, const Instruction& inst, + const std::function& diag); + spv_result_t ValidateI32Vec( + const Decoration& decoration, const Instruction& inst, + uint32_t num_components, + const std::function& diag); + spv_result_t ValidateI32Arr( + const Decoration& decoration, const Instruction& inst, + const std::function& diag); + spv_result_t ValidateF32( + const Decoration& decoration, const Instruction& inst, + const std::function& diag); + spv_result_t ValidateF32Vec( + const Decoration& decoration, const Instruction& inst, + uint32_t num_components, + const std::function& diag); + // If |num_components| is zero, the number of components is not checked. + spv_result_t ValidateF32Arr( + const Decoration& decoration, const Instruction& inst, + uint32_t num_components, + const std::function& diag); + + // Generates strings like "Member #0 of struct ID <2>". + std::string GetDefinitionDesc(const Decoration& decoration, + const Instruction& inst) const; + + // Generates strings like "ID <51> (OpTypePointer) is referencing ID <2> + // (OpTypeStruct) which is decorated with BuiltIn Position". + std::string GetReferenceDesc( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst, + SpvExecutionModel execution_model = SpvExecutionModelMax) const; + + // Generates strings like "ID <51> (OpTypePointer) uses storage class + // UniformConstant". + std::string GetStorageClassDesc(const Instruction& inst) const; + + // Updates inner working of the class. Is called sequentially for every + // instruction. + void Update(const Instruction& inst); + + // Traverses call tree and computes function_to_entry_points_. + void ComputeFunctionToEntryPointMapping(); + + const ValidationState_t& _; + + // Mapping id -> list of rules which validate instruction referencing the + // id. Rules can create new rules and add them to this container. + // Using std::map, and not std::unordered_map to avoid iterator invalidation + // during rehashing. + std::map>> + id_to_at_reference_checks_; + + // Id of the function we are currently inside. 0 if not inside a function. + uint32_t function_id_ = 0; + + // Entry points which can (indirectly) call the current function. + // The pointer either points to a vector inside to function_to_entry_points_ + // or to no_entry_points_. The pointer is guaranteed to never be null. + const std::vector no_entry_points; + const std::vector* entry_points_ = &no_entry_points; + + // Mapping function -> array of entry points inside this + // module which can (indirectly) call the function. + std::unordered_map> function_to_entry_points_; + + // Execution models with which the current function can be called. + std::set execution_models_; +}; + +void BuiltInsValidator::Update(const Instruction& inst) { + const SpvOp opcode = inst.opcode(); + if (opcode == SpvOpFunction) { + // Entering a function. + assert(function_id_ == 0); + function_id_ = inst.id(); + execution_models_.clear(); + const auto it = function_to_entry_points_.find(function_id_); + if (it == function_to_entry_points_.end()) { + entry_points_ = &no_entry_points; + } else { + entry_points_ = &it->second; + // Collect execution models from all entry points from which the current + // function can be called. + for (const uint32_t entry_point : *entry_points_) { + if (const auto* models = _.GetExecutionModels(entry_point)) { + execution_models_.insert(models->begin(), models->end()); + } + } + } + } + + if (opcode == SpvOpFunctionEnd) { + // Exiting a function. + assert(function_id_ != 0); + function_id_ = 0; + entry_points_ = &no_entry_points; + execution_models_.clear(); + } +} + +void BuiltInsValidator::ComputeFunctionToEntryPointMapping() { + // TODO: Move this into validation_state.cpp. + for (const uint32_t entry_point : _.entry_points()) { + std::stack call_stack; + std::set visited; + call_stack.push(entry_point); + while (!call_stack.empty()) { + const uint32_t called_func_id = call_stack.top(); + call_stack.pop(); + if (!visited.insert(called_func_id).second) continue; + + function_to_entry_points_[called_func_id].push_back(entry_point); + + const Function* called_func = _.function(called_func_id); + assert(called_func); + for (const uint32_t new_call : called_func->function_call_targets()) { + call_stack.push(new_call); + } + } + } +} + +std::string BuiltInsValidator::GetDefinitionDesc( + const Decoration& decoration, const Instruction& inst) const { + std::ostringstream ss; + if (decoration.struct_member_index() != Decoration::kInvalidMember) { + assert(inst.opcode() == SpvOpTypeStruct); + ss << "Member #" << decoration.struct_member_index(); + ss << " of struct ID <" << inst.id() << ">"; + } else { + ss << GetIdDesc(inst); + } + return ss.str(); +} + +std::string BuiltInsValidator::GetReferenceDesc( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, const Instruction& referenced_from_inst, + SpvExecutionModel execution_model) const { + std::ostringstream ss; + ss << GetIdDesc(referenced_from_inst) << " is referencing " + << GetIdDesc(referenced_inst); + if (built_in_inst.id() != referenced_inst.id()) { + ss << " which is dependent on " << GetIdDesc(built_in_inst); + } + + ss << " which is decorated with BuiltIn "; + ss << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + decoration.params()[0]); + if (function_id_) { + ss << " in function <" << function_id_ << ">"; + if (execution_model != SpvExecutionModelMax) { + ss << " called with execution model "; + ss << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_EXECUTION_MODEL, + execution_model); + } + } + ss << "."; + return ss.str(); +} + +std::string BuiltInsValidator::GetStorageClassDesc( + const Instruction& inst) const { + std::ostringstream ss; + ss << GetIdDesc(inst) << " uses storage class "; + ss << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_STORAGE_CLASS, + GetStorageClass(inst)); + ss << "."; + return ss.str(); +} + +spv_result_t BuiltInsValidator::ValidateBool( + const Decoration& decoration, const Instruction& inst, + const std::function& diag) { + uint32_t underlying_type = 0; + if (spv_result_t error = + GetUnderlyingType(_, decoration, inst, &underlying_type)) { + return error; + } + + if (!_.IsBoolScalarType(underlying_type)) { + return diag(GetDefinitionDesc(decoration, inst) + " is not a bool scalar."); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateI32( + const Decoration& decoration, const Instruction& inst, + const std::function& diag) { + uint32_t underlying_type = 0; + if (spv_result_t error = + GetUnderlyingType(_, decoration, inst, &underlying_type)) { + return error; + } + + if (!_.IsIntScalarType(underlying_type)) { + return diag(GetDefinitionDesc(decoration, inst) + " is not an int scalar."); + } + + const uint32_t bit_width = _.GetBitWidth(underlying_type); + if (bit_width != 32) { + std::ostringstream ss; + ss << GetDefinitionDesc(decoration, inst) << " has bit width " << bit_width + << "."; + return diag(ss.str()); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateF32( + const Decoration& decoration, const Instruction& inst, + const std::function& diag) { + uint32_t underlying_type = 0; + if (spv_result_t error = + GetUnderlyingType(_, decoration, inst, &underlying_type)) { + return error; + } + + if (!_.IsFloatScalarType(underlying_type)) { + return diag(GetDefinitionDesc(decoration, inst) + + " is not a float scalar."); + } + + const uint32_t bit_width = _.GetBitWidth(underlying_type); + if (bit_width != 32) { + std::ostringstream ss; + ss << GetDefinitionDesc(decoration, inst) << " has bit width " << bit_width + << "."; + return diag(ss.str()); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateI32Vec( + const Decoration& decoration, const Instruction& inst, + uint32_t num_components, + const std::function& diag) { + uint32_t underlying_type = 0; + if (spv_result_t error = + GetUnderlyingType(_, decoration, inst, &underlying_type)) { + return error; + } + + if (!_.IsIntVectorType(underlying_type)) { + return diag(GetDefinitionDesc(decoration, inst) + " is not an int vector."); + } + + const uint32_t actual_num_components = _.GetDimension(underlying_type); + if (_.GetDimension(underlying_type) != num_components) { + std::ostringstream ss; + ss << GetDefinitionDesc(decoration, inst) << " has " + << actual_num_components << " components."; + return diag(ss.str()); + } + + const uint32_t bit_width = _.GetBitWidth(underlying_type); + if (bit_width != 32) { + std::ostringstream ss; + ss << GetDefinitionDesc(decoration, inst) + << " has components with bit width " << bit_width << "."; + return diag(ss.str()); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateF32Vec( + const Decoration& decoration, const Instruction& inst, + uint32_t num_components, + const std::function& diag) { + uint32_t underlying_type = 0; + if (spv_result_t error = + GetUnderlyingType(_, decoration, inst, &underlying_type)) { + return error; + } + + if (!_.IsFloatVectorType(underlying_type)) { + return diag(GetDefinitionDesc(decoration, inst) + + " is not a float vector."); + } + + const uint32_t actual_num_components = _.GetDimension(underlying_type); + if (_.GetDimension(underlying_type) != num_components) { + std::ostringstream ss; + ss << GetDefinitionDesc(decoration, inst) << " has " + << actual_num_components << " components."; + return diag(ss.str()); + } + + const uint32_t bit_width = _.GetBitWidth(underlying_type); + if (bit_width != 32) { + std::ostringstream ss; + ss << GetDefinitionDesc(decoration, inst) + << " has components with bit width " << bit_width << "."; + return diag(ss.str()); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateI32Arr( + const Decoration& decoration, const Instruction& inst, + const std::function& diag) { + uint32_t underlying_type = 0; + if (spv_result_t error = + GetUnderlyingType(_, decoration, inst, &underlying_type)) { + return error; + } + + const Instruction* const type_inst = _.FindDef(underlying_type); + if (type_inst->opcode() != SpvOpTypeArray) { + return diag(GetDefinitionDesc(decoration, inst) + " is not an array."); + } + + const uint32_t component_type = type_inst->word(2); + if (!_.IsIntScalarType(component_type)) { + return diag(GetDefinitionDesc(decoration, inst) + + " components are not int scalar."); + } + + const uint32_t bit_width = _.GetBitWidth(component_type); + if (bit_width != 32) { + std::ostringstream ss; + ss << GetDefinitionDesc(decoration, inst) + << " has components with bit width " << bit_width << "."; + return diag(ss.str()); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateF32Arr( + const Decoration& decoration, const Instruction& inst, + uint32_t num_components, + const std::function& diag) { + uint32_t underlying_type = 0; + if (spv_result_t error = + GetUnderlyingType(_, decoration, inst, &underlying_type)) { + return error; + } + + const Instruction* const type_inst = _.FindDef(underlying_type); + if (type_inst->opcode() != SpvOpTypeArray) { + return diag(GetDefinitionDesc(decoration, inst) + " is not an array."); + } + + const uint32_t component_type = type_inst->word(2); + if (!_.IsFloatScalarType(component_type)) { + return diag(GetDefinitionDesc(decoration, inst) + + " components are not float scalar."); + } + + const uint32_t bit_width = _.GetBitWidth(component_type); + if (bit_width != 32) { + std::ostringstream ss; + ss << GetDefinitionDesc(decoration, inst) + << " has components with bit width " << bit_width << "."; + return diag(ss.str()); + } + + if (num_components != 0) { + uint64_t actual_num_components = 0; + if (!_.GetConstantValUint64(type_inst->word(3), &actual_num_components)) { + assert(0 && "Array type definition is corrupt"); + } + if (actual_num_components != num_components) { + std::ostringstream ss; + ss << GetDefinitionDesc(decoration, inst) << " has " + << actual_num_components << " components."; + return diag(ss.str()); + } + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateNotCalledWithExecutionModel( + const char* comment, SpvExecutionModel execution_model, + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + if (function_id_) { + if (execution_models_.count(execution_model)) { + const char* execution_model_str = _.grammar().lookupOperandName( + SPV_OPERAND_TYPE_EXECUTION_MODEL, execution_model); + const char* built_in_str = _.grammar().lookupOperandName( + SPV_OPERAND_TYPE_BUILT_IN, decoration.params()[0]); + return _.diag(SPV_ERROR_INVALID_DATA) + << comment << " " << GetIdDesc(referenced_inst) << " depends on " + << GetIdDesc(built_in_inst) << " which is decorated with BuiltIn " + << built_in_str << "." + << " Id <" << referenced_inst.id() << "> is later referenced by " + << GetIdDesc(referenced_from_inst) << " in function <" + << function_id_ << "> which is called with execution model " + << execution_model_str << "."; + } + } else { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back( + std::bind(&BuiltInsValidator::ValidateNotCalledWithExecutionModel, this, + comment, execution_model, decoration, built_in_inst, + referenced_from_inst, std::placeholders::_1)); + } + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateClipOrCullDistanceAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (spv_result_t error = ValidateF32Arr( + decoration, inst, /* Any number of components */ 0, + [this, &decoration](const std::string& message) -> spv_result_t { + return _.diag(SPV_ERROR_INVALID_DATA) + << "According to the Vulkan spec BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + decoration.params()[0]) + << " variable needs to be a 32-bit float array. " + << message; + })) { + return error; + } + } + + // Seed at reference checks with this built-in. + return ValidateClipOrCullDistanceAtReference(decoration, inst, inst, inst); +} + +spv_result_t BuiltInsValidator::ValidateClipOrCullDistanceAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); + if (storage_class != SpvStorageClassMax && + storage_class != SpvStorageClassInput && + storage_class != SpvStorageClassOutput) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + decoration.params()[0]) + << " to be only used for variables with Input or Output storage " + "class. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst) + << " " << GetStorageClassDesc(referenced_from_inst); + } + + if (storage_class == SpvStorageClassInput) { + assert(function_id_ == 0); + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateNotCalledWithExecutionModel, this, + "Vulkan spec doesn't allow BuiltIn ClipDistance/CullDistance to be " + "used for variables with Input storage class if execution model is " + "Vertex.", + SpvExecutionModelVertex, decoration, built_in_inst, + referenced_from_inst, std::placeholders::_1)); + } + + if (storage_class == SpvStorageClassOutput) { + assert(function_id_ == 0); + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateNotCalledWithExecutionModel, this, + "Vulkan spec doesn't allow BuiltIn ClipDistance/CullDistance to be " + "used for variables with Output storage class if execution model is " + "Fragment.", + SpvExecutionModelFragment, decoration, built_in_inst, + referenced_from_inst, std::placeholders::_1)); + } + + for (const SpvExecutionModel execution_model : execution_models_) { + switch (execution_model) { + case SpvExecutionModelFragment: + case SpvExecutionModelVertex: + case SpvExecutionModelTessellationControl: + case SpvExecutionModelTessellationEvaluation: + case SpvExecutionModelGeometry: { + // Ok. + break; + } + + default: { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + decoration.params()[0]) + << " to be used only with Fragment, Vertex, " + "TessellationControl, TessellationEvaluation or Geometry " + "execution models. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst, execution_model); + } + } + } + } + + if (function_id_ == 0) { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back( + std::bind(&BuiltInsValidator::ValidateClipOrCullDistanceAtReference, + this, decoration, built_in_inst, referenced_from_inst, + std::placeholders::_1)); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateFragCoordAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (spv_result_t error = ValidateF32Vec( + decoration, inst, 4, + [this](const std::string& message) -> spv_result_t { + return _.diag(SPV_ERROR_INVALID_DATA) + << "According to the Vulkan spec BuiltIn FragCoord " + "variable needs to be a 4-component 32-bit float " + "vector. " + << message; + })) { + return error; + } + } + + // Seed at reference checks with this built-in. + return ValidateFragCoordAtReference(decoration, inst, inst, inst); +} + +spv_result_t BuiltInsValidator::ValidateFragCoordAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); + if (storage_class != SpvStorageClassMax && + storage_class != SpvStorageClassInput) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn FragCoord to be only used for " + "variables with Input storage class. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst) + << " " << GetStorageClassDesc(referenced_from_inst); + } + + for (const SpvExecutionModel execution_model : execution_models_) { + if (execution_model != SpvExecutionModelFragment) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn FragCoord to be used only with " + "Fragment execution model. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst, execution_model); + } + } + } + + if (function_id_ == 0) { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateFragCoordAtReference, this, decoration, + built_in_inst, referenced_from_inst, std::placeholders::_1)); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateFragDepthAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (spv_result_t error = ValidateF32( + decoration, inst, + [this](const std::string& message) -> spv_result_t { + return _.diag(SPV_ERROR_INVALID_DATA) + << "According to the Vulkan spec BuiltIn FragDepth " + "variable needs to be a 32-bit float scalar. " + << message; + })) { + return error; + } + } + + // Seed at reference checks with this built-in. + return ValidateFragDepthAtReference(decoration, inst, inst, inst); +} + +spv_result_t BuiltInsValidator::ValidateFragDepthAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); + if (storage_class != SpvStorageClassMax && + storage_class != SpvStorageClassOutput) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn FragDepth to be only used for " + "variables with Output storage class. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst) + << " " << GetStorageClassDesc(referenced_from_inst); + } + + for (const SpvExecutionModel execution_model : execution_models_) { + if (execution_model != SpvExecutionModelFragment) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn FragDepth to be used only with " + "Fragment execution model. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst, execution_model); + } + } + + for (const uint32_t entry_point : *entry_points_) { + // Every entry point from which this function is called needs to have + // Execution Mode DepthReplacing. + const auto* modes = _.GetExecutionModes(entry_point); + if (!modes || !modes->count(SpvExecutionModeDepthReplacing)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec requires DepthReplacing execution mode to be " + "declared when using BuiltIn FragDepth. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst); + } + } + } + + if (function_id_ == 0) { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateFragDepthAtReference, this, decoration, + built_in_inst, referenced_from_inst, std::placeholders::_1)); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateFrontFacingAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (spv_result_t error = ValidateBool( + decoration, inst, + [this](const std::string& message) -> spv_result_t { + return _.diag(SPV_ERROR_INVALID_DATA) + << "According to the Vulkan spec BuiltIn FrontFacing " + "variable needs to be a bool scalar. " + << message; + })) { + return error; + } + } + + // Seed at reference checks with this built-in. + return ValidateFrontFacingAtReference(decoration, inst, inst, inst); +} + +spv_result_t BuiltInsValidator::ValidateFrontFacingAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); + if (storage_class != SpvStorageClassMax && + storage_class != SpvStorageClassInput) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn FrontFacing to be only used for " + "variables with Input storage class. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst) + << " " << GetStorageClassDesc(referenced_from_inst); + } + + for (const SpvExecutionModel execution_model : execution_models_) { + if (execution_model != SpvExecutionModelFragment) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn FrontFacing to be used only with " + "Fragment execution model. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst, execution_model); + } + } + } + + if (function_id_ == 0) { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateFrontFacingAtReference, this, decoration, + built_in_inst, referenced_from_inst, std::placeholders::_1)); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateHelperInvocationAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (spv_result_t error = ValidateBool( + decoration, inst, + [this](const std::string& message) -> spv_result_t { + return _.diag(SPV_ERROR_INVALID_DATA) + << "According to the Vulkan spec BuiltIn HelperInvocation " + "variable needs to be a bool scalar. " + << message; + })) { + return error; + } + } + + // Seed at reference checks with this built-in. + return ValidateHelperInvocationAtReference(decoration, inst, inst, inst); +} + +spv_result_t BuiltInsValidator::ValidateHelperInvocationAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); + if (storage_class != SpvStorageClassMax && + storage_class != SpvStorageClassInput) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn HelperInvocation to be only used " + "for variables with Input storage class. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst) + << " " << GetStorageClassDesc(referenced_from_inst); + } + + for (const SpvExecutionModel execution_model : execution_models_) { + if (execution_model != SpvExecutionModelFragment) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn HelperInvocation to be used only " + "with Fragment execution model. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst, execution_model); + } + } + } + + if (function_id_ == 0) { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back( + std::bind(&BuiltInsValidator::ValidateHelperInvocationAtReference, this, + decoration, built_in_inst, referenced_from_inst, + std::placeholders::_1)); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateInvocationIdAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (spv_result_t error = ValidateI32( + decoration, inst, + [this](const std::string& message) -> spv_result_t { + return _.diag(SPV_ERROR_INVALID_DATA) + << "According to the Vulkan spec BuiltIn InvocationId " + "variable needs to be a 32-bit int scalar. " + << message; + })) { + return error; + } + } + + // Seed at reference checks with this built-in. + return ValidateInvocationIdAtReference(decoration, inst, inst, inst); +} + +spv_result_t BuiltInsValidator::ValidateInvocationIdAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); + if (storage_class != SpvStorageClassMax && + storage_class != SpvStorageClassInput) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn InvocationId to be only used for " + "variables with Input storage class. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst) + << " " << GetStorageClassDesc(referenced_from_inst); + } + + for (const SpvExecutionModel execution_model : execution_models_) { + if (execution_model != SpvExecutionModelTessellationControl && + execution_model != SpvExecutionModelGeometry) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn InvocationId to be used only " + "with TessellationControl or Geometry execution models. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst, execution_model); + } + } + } + + if (function_id_ == 0) { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateInvocationIdAtReference, this, decoration, + built_in_inst, referenced_from_inst, std::placeholders::_1)); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateInstanceIndexAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (spv_result_t error = ValidateI32( + decoration, inst, + [this](const std::string& message) -> spv_result_t { + return _.diag(SPV_ERROR_INVALID_DATA) + << "According to the Vulkan spec BuiltIn InstanceIndex " + "variable needs to be a 32-bit int scalar. " + << message; + })) { + return error; + } + } + + // Seed at reference checks with this built-in. + return ValidateInstanceIndexAtReference(decoration, inst, inst, inst); +} + +spv_result_t BuiltInsValidator::ValidateInstanceIndexAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); + if (storage_class != SpvStorageClassMax && + storage_class != SpvStorageClassInput) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn InstanceIndex to be only used for " + "variables with Input storage class. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst) + << " " << GetStorageClassDesc(referenced_from_inst); + } + + for (const SpvExecutionModel execution_model : execution_models_) { + if (execution_model != SpvExecutionModelVertex) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn InstanceIndex to be used only " + "with Vertex execution model. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst, execution_model); + } + } + } + + if (function_id_ == 0) { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateInstanceIndexAtReference, this, decoration, + built_in_inst, referenced_from_inst, std::placeholders::_1)); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidatePatchVerticesAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (spv_result_t error = ValidateI32( + decoration, inst, + [this](const std::string& message) -> spv_result_t { + return _.diag(SPV_ERROR_INVALID_DATA) + << "According to the Vulkan spec BuiltIn PatchVertices " + "variable needs to be a 32-bit int scalar. " + << message; + })) { + return error; + } + } + + // Seed at reference checks with this built-in. + return ValidatePatchVerticesAtReference(decoration, inst, inst, inst); +} + +spv_result_t BuiltInsValidator::ValidatePatchVerticesAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); + if (storage_class != SpvStorageClassMax && + storage_class != SpvStorageClassInput) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn PatchVertices to be only used for " + "variables with Input storage class. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst) + << " " << GetStorageClassDesc(referenced_from_inst); + } + + for (const SpvExecutionModel execution_model : execution_models_) { + if (execution_model != SpvExecutionModelTessellationControl && + execution_model != SpvExecutionModelTessellationEvaluation) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn PatchVertices to be used only " + "with TessellationControl or TessellationEvaluation " + "execution models. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst, execution_model); + } + } + } + + if (function_id_ == 0) { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidatePatchVerticesAtReference, this, decoration, + built_in_inst, referenced_from_inst, std::placeholders::_1)); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidatePointCoordAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (spv_result_t error = ValidateF32Vec( + decoration, inst, 2, + [this](const std::string& message) -> spv_result_t { + return _.diag(SPV_ERROR_INVALID_DATA) + << "According to the Vulkan spec BuiltIn PointCoord " + "variable needs to be a 2-component 32-bit float " + "vector. " + << message; + })) { + return error; + } + } + + // Seed at reference checks with this built-in. + return ValidatePointCoordAtReference(decoration, inst, inst, inst); +} + +spv_result_t BuiltInsValidator::ValidatePointCoordAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); + if (storage_class != SpvStorageClassMax && + storage_class != SpvStorageClassInput) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn PointCoord to be only used for " + "variables with Input storage class. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst) + << " " << GetStorageClassDesc(referenced_from_inst); + } + + for (const SpvExecutionModel execution_model : execution_models_) { + if (execution_model != SpvExecutionModelFragment) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn PointCoord to be used only with " + "Fragment execution model. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst, execution_model); + } + } + } + + if (function_id_ == 0) { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidatePointCoordAtReference, this, decoration, + built_in_inst, referenced_from_inst, std::placeholders::_1)); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidatePointSizeAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (spv_result_t error = ValidateF32( + decoration, inst, + [this](const std::string& message) -> spv_result_t { + return _.diag(SPV_ERROR_INVALID_DATA) + << "According to the Vulkan spec BuiltIn PointSize " + "variable needs to be a 32-bit float scalar. " + << message; + })) { + return error; + } + } + + // Seed at reference checks with this built-in. + return ValidatePointSizeAtReference(decoration, inst, inst, inst); +} + +spv_result_t BuiltInsValidator::ValidatePointSizeAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); + if (storage_class != SpvStorageClassMax && + storage_class != SpvStorageClassInput && + storage_class != SpvStorageClassOutput) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn PointSize to be only used for " + "variables with Input or Output storage class. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst) + << " " << GetStorageClassDesc(referenced_from_inst); + } + + if (storage_class == SpvStorageClassInput) { + assert(function_id_ == 0); + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateNotCalledWithExecutionModel, this, + "Vulkan spec doesn't allow BuiltIn PointSize to be used for " + "variables with Input storage class if execution model is Vertex.", + SpvExecutionModelVertex, decoration, built_in_inst, + referenced_from_inst, std::placeholders::_1)); + } + + for (const SpvExecutionModel execution_model : execution_models_) { + switch (execution_model) { + case SpvExecutionModelVertex: + case SpvExecutionModelTessellationControl: + case SpvExecutionModelTessellationEvaluation: + case SpvExecutionModelGeometry: { + // Ok. + break; + } + + default: { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn PointSize to be used only with " + "Vertex, TessellationControl, TessellationEvaluation or " + "Geometry execution models. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst, execution_model); + } + } + } + } + + if (function_id_ == 0) { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidatePointSizeAtReference, this, decoration, + built_in_inst, referenced_from_inst, std::placeholders::_1)); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidatePositionAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (spv_result_t error = ValidateF32Vec( + decoration, inst, 4, + [this](const std::string& message) -> spv_result_t { + return _.diag(SPV_ERROR_INVALID_DATA) + << "According to the Vulkan spec BuiltIn Position " + "variable needs to be a 4-component 32-bit float " + "vector. " + << message; + })) { + return error; + } + } + + // Seed at reference checks with this built-in. + return ValidatePositionAtReference(decoration, inst, inst, inst); +} + +spv_result_t BuiltInsValidator::ValidatePositionAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); + if (storage_class != SpvStorageClassMax && + storage_class != SpvStorageClassInput && + storage_class != SpvStorageClassOutput) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn Position to be only used for " + "variables with Input or Output storage class. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst) + << " " << GetStorageClassDesc(referenced_from_inst); + } + + if (storage_class == SpvStorageClassInput) { + assert(function_id_ == 0); + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateNotCalledWithExecutionModel, this, + "Vulkan spec doesn't allow BuiltIn Position to be used for variables " + "with Input storage class if execution model is Vertex.", + SpvExecutionModelVertex, decoration, built_in_inst, + referenced_from_inst, std::placeholders::_1)); + } + + for (const SpvExecutionModel execution_model : execution_models_) { + switch (execution_model) { + case SpvExecutionModelVertex: + case SpvExecutionModelTessellationControl: + case SpvExecutionModelTessellationEvaluation: + case SpvExecutionModelGeometry: { + // Ok. + break; + } + + default: { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn Position to be used only with " + "Vertex, TessellationControl, TessellationEvaluation or " + "Geometry execution models. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst, execution_model); + } + } + } + } + + if (function_id_ == 0) { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidatePositionAtReference, this, decoration, + built_in_inst, referenced_from_inst, std::placeholders::_1)); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidatePrimitiveIdAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (spv_result_t error = ValidateI32( + decoration, inst, + [this](const std::string& message) -> spv_result_t { + return _.diag(SPV_ERROR_INVALID_DATA) + << "According to the Vulkan spec BuiltIn PrimitiveId " + "variable needs to be a 32-bit int scalar. " + << message; + })) { + return error; + } + } + + // Seed at reference checks with this built-in. + return ValidatePrimitiveIdAtReference(decoration, inst, inst, inst); +} + +spv_result_t BuiltInsValidator::ValidatePrimitiveIdAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); + if (storage_class != SpvStorageClassMax && + storage_class != SpvStorageClassInput && + storage_class != SpvStorageClassOutput) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn PrimitiveId to be only used for " + "variables with Input or Output storage class. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst) + << " " << GetStorageClassDesc(referenced_from_inst); + } + + if (storage_class == SpvStorageClassOutput) { + assert(function_id_ == 0); + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateNotCalledWithExecutionModel, this, + "Vulkan spec doesn't allow BuiltIn PrimitiveId to be used for " + "variables with Output storage class if execution model is " + "TessellationControl.", + SpvExecutionModelTessellationControl, decoration, built_in_inst, + referenced_from_inst, std::placeholders::_1)); + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateNotCalledWithExecutionModel, this, + "Vulkan spec doesn't allow BuiltIn PrimitiveId to be used for " + "variables with Output storage class if execution model is " + "TessellationEvaluation.", + SpvExecutionModelTessellationEvaluation, decoration, built_in_inst, + referenced_from_inst, std::placeholders::_1)); + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateNotCalledWithExecutionModel, this, + "Vulkan spec doesn't allow BuiltIn PrimitiveId to be used for " + "variables with Output storage class if execution model is Fragment.", + SpvExecutionModelFragment, decoration, built_in_inst, + referenced_from_inst, std::placeholders::_1)); + } + + for (const SpvExecutionModel execution_model : execution_models_) { + switch (execution_model) { + case SpvExecutionModelFragment: + case SpvExecutionModelTessellationControl: + case SpvExecutionModelTessellationEvaluation: + case SpvExecutionModelGeometry: { + // Ok. + break; + } + + default: { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn PrimitiveId to be used only " + "with Fragment, TessellationControl, " + "TessellationEvaluation or Geometry execution models. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst, execution_model); + } + } + } + } + + if (function_id_ == 0) { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidatePrimitiveIdAtReference, this, decoration, + built_in_inst, referenced_from_inst, std::placeholders::_1)); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateSampleIdAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (spv_result_t error = ValidateI32( + decoration, inst, + [this](const std::string& message) -> spv_result_t { + return _.diag(SPV_ERROR_INVALID_DATA) + << "According to the Vulkan spec BuiltIn SampleId " + "variable needs to be a 32-bit int scalar. " + << message; + })) { + return error; + } + } + + // Seed at reference checks with this built-in. + return ValidateSampleIdAtReference(decoration, inst, inst, inst); +} + +spv_result_t BuiltInsValidator::ValidateSampleIdAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); + if (storage_class != SpvStorageClassMax && + storage_class != SpvStorageClassInput) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn SampleId to be only used for " + "variables with Input storage class. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst) + << " " << GetStorageClassDesc(referenced_from_inst); + } + + for (const SpvExecutionModel execution_model : execution_models_) { + if (execution_model != SpvExecutionModelFragment) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn SampleId to be used only with " + "Fragment execution model. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst, execution_model); + } + } + } + + if (function_id_ == 0) { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateSampleIdAtReference, this, decoration, + built_in_inst, referenced_from_inst, std::placeholders::_1)); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateSampleMaskAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (spv_result_t error = ValidateI32Arr( + decoration, inst, + [this](const std::string& message) -> spv_result_t { + return _.diag(SPV_ERROR_INVALID_DATA) + << "According to the Vulkan spec BuiltIn SampleMask " + "variable needs to be a 32-bit int array. " + << message; + })) { + return error; + } + } + + // Seed at reference checks with this built-in. + return ValidateSampleMaskAtReference(decoration, inst, inst, inst); +} + +spv_result_t BuiltInsValidator::ValidateSampleMaskAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); + if (storage_class != SpvStorageClassMax && + storage_class != SpvStorageClassInput && + storage_class != SpvStorageClassOutput) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn SampleMask to be only used for " + "variables with Input or Output storage class. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst) + << " " << GetStorageClassDesc(referenced_from_inst); + } + + for (const SpvExecutionModel execution_model : execution_models_) { + if (execution_model != SpvExecutionModelFragment) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn SampleMask to be used only with " + "Fragment execution model. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst, execution_model); + } + } + } + + if (function_id_ == 0) { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateSampleMaskAtReference, this, decoration, + built_in_inst, referenced_from_inst, std::placeholders::_1)); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateSamplePositionAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (spv_result_t error = ValidateF32Vec( + decoration, inst, 2, + [this](const std::string& message) -> spv_result_t { + return _.diag(SPV_ERROR_INVALID_DATA) + << "According to the Vulkan spec BuiltIn SamplePosition " + "variable needs to be a 2-component 32-bit float " + "vector. " + << message; + })) { + return error; + } + } + + // Seed at reference checks with this built-in. + return ValidateSamplePositionAtReference(decoration, inst, inst, inst); +} + +spv_result_t BuiltInsValidator::ValidateSamplePositionAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); + if (storage_class != SpvStorageClassMax && + storage_class != SpvStorageClassInput) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn SamplePosition to be only used for " + "variables with Input storage class. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst) + << " " << GetStorageClassDesc(referenced_from_inst); + } + + for (const SpvExecutionModel execution_model : execution_models_) { + if (execution_model != SpvExecutionModelFragment) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn SamplePosition to be used only " + "with " + "Fragment execution model. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst, execution_model); + } + } + } + + if (function_id_ == 0) { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateSamplePositionAtReference, this, decoration, + built_in_inst, referenced_from_inst, std::placeholders::_1)); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateTessCoordAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (spv_result_t error = ValidateF32Vec( + decoration, inst, 3, + [this](const std::string& message) -> spv_result_t { + return _.diag(SPV_ERROR_INVALID_DATA) + << "According to the Vulkan spec BuiltIn TessCoord " + "variable needs to be a 3-component 32-bit float " + "vector. " + << message; + })) { + return error; + } + } + + // Seed at reference checks with this built-in. + return ValidateTessCoordAtReference(decoration, inst, inst, inst); +} + +spv_result_t BuiltInsValidator::ValidateTessCoordAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); + if (storage_class != SpvStorageClassMax && + storage_class != SpvStorageClassInput) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn TessCoord to be only used for " + "variables with Input storage class. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst) + << " " << GetStorageClassDesc(referenced_from_inst); + } + + for (const SpvExecutionModel execution_model : execution_models_) { + if (execution_model != SpvExecutionModelTessellationEvaluation) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn TessCoord to be used only with " + "TessellationEvaluation execution model. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst, execution_model); + } + } + } + + if (function_id_ == 0) { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateTessCoordAtReference, this, decoration, + built_in_inst, referenced_from_inst, std::placeholders::_1)); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateTessLevelOuterAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (spv_result_t error = ValidateF32Arr( + decoration, inst, 4, + [this](const std::string& message) -> spv_result_t { + return _.diag(SPV_ERROR_INVALID_DATA) + << "According to the Vulkan spec BuiltIn TessLevelOuter " + "variable needs to be a 4-component 32-bit float " + "array. " + << message; + })) { + return error; + } + } + + // Seed at reference checks with this built-in. + return ValidateTessLevelAtReference(decoration, inst, inst, inst); +} + +spv_result_t BuiltInsValidator::ValidateTessLevelInnerAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (spv_result_t error = ValidateF32Arr( + decoration, inst, 2, + [this](const std::string& message) -> spv_result_t { + return _.diag(SPV_ERROR_INVALID_DATA) + << "According to the Vulkan spec BuiltIn TessLevelOuter " + "variable needs to be a 2-component 32-bit float " + "array. " + << message; + })) { + return error; + } + } + + // Seed at reference checks with this built-in. + return ValidateTessLevelAtReference(decoration, inst, inst, inst); +} + +spv_result_t BuiltInsValidator::ValidateTessLevelAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); + if (storage_class != SpvStorageClassMax && + storage_class != SpvStorageClassInput && + storage_class != SpvStorageClassOutput) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + decoration.params()[0]) + << " to be only used for variables with Input or Output storage " + "class. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst) + << " " << GetStorageClassDesc(referenced_from_inst); + } + + if (storage_class == SpvStorageClassInput) { + assert(function_id_ == 0); + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateNotCalledWithExecutionModel, this, + "Vulkan spec doesn't allow TessLevelOuter/TessLevelInner to be used " + "for variables with Input storage class if execution model is " + "TessellationControl.", + SpvExecutionModelTessellationControl, decoration, built_in_inst, + referenced_from_inst, std::placeholders::_1)); + } + + if (storage_class == SpvStorageClassOutput) { + assert(function_id_ == 0); + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateNotCalledWithExecutionModel, this, + "Vulkan spec doesn't allow TessLevelOuter/TessLevelInner to be used " + "for variables with Output storage class if execution model is " + "TessellationEvaluation.", + SpvExecutionModelTessellationEvaluation, decoration, built_in_inst, + referenced_from_inst, std::placeholders::_1)); + } + + for (const SpvExecutionModel execution_model : execution_models_) { + switch (execution_model) { + case SpvExecutionModelTessellationControl: + case SpvExecutionModelTessellationEvaluation: { + // Ok. + break; + } + + default: { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + decoration.params()[0]) + << " to be used only with TessellationControl or " + "TessellationEvaluation execution models. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst, execution_model); + } + } + } + } + + if (function_id_ == 0) { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateTessLevelAtReference, this, decoration, + built_in_inst, referenced_from_inst, std::placeholders::_1)); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateVertexIndexAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (spv_result_t error = ValidateI32( + decoration, inst, + [this](const std::string& message) -> spv_result_t { + return _.diag(SPV_ERROR_INVALID_DATA) + << "According to the Vulkan spec BuiltIn VertexIndex " + "variable needs to be a 32-bit int scalar. " + << message; + })) { + return error; + } + } + + // Seed at reference checks with this built-in. + return ValidateVertexIndexAtReference(decoration, inst, inst, inst); +} + +spv_result_t BuiltInsValidator::ValidateVertexIndexAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); + if (storage_class != SpvStorageClassMax && + storage_class != SpvStorageClassInput) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn VertexIndex to be only used for " + "variables with Input storage class. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst) + << " " << GetStorageClassDesc(referenced_from_inst); + } + + for (const SpvExecutionModel execution_model : execution_models_) { + if (execution_model != SpvExecutionModelVertex) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn VertexIndex to be used only with " + "Vertex execution model. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst, execution_model); + } + } + } + + if (function_id_ == 0) { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateVertexIndexAtReference, this, decoration, + built_in_inst, referenced_from_inst, std::placeholders::_1)); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateLayerOrViewportIndexAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (spv_result_t error = ValidateI32( + decoration, inst, + [this, &decoration](const std::string& message) -> spv_result_t { + return _.diag(SPV_ERROR_INVALID_DATA) + << "According to the Vulkan spec BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + decoration.params()[0]) + << "variable needs to be a 32-bit int scalar. " << message; + })) { + return error; + } + } + + // Seed at reference checks with this built-in. + return ValidateLayerOrViewportIndexAtReference(decoration, inst, inst, inst); +} + +spv_result_t BuiltInsValidator::ValidateLayerOrViewportIndexAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); + if (storage_class != SpvStorageClassMax && + storage_class != SpvStorageClassInput && + storage_class != SpvStorageClassOutput) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + decoration.params()[0]) + << " to be only used for variables with Input or Output storage " + "class. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst) + << " " << GetStorageClassDesc(referenced_from_inst); + } + + if (storage_class == SpvStorageClassInput) { + assert(function_id_ == 0); + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateNotCalledWithExecutionModel, this, + "Vulkan spec doesn't allow BuiltIn Layer and ViewportIndex to be " + "used for variables with Input storage class if execution model is " + "Geometry.", + SpvExecutionModelGeometry, decoration, built_in_inst, + referenced_from_inst, std::placeholders::_1)); + } + + if (storage_class == SpvStorageClassOutput) { + assert(function_id_ == 0); + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateNotCalledWithExecutionModel, this, + "Vulkan spec doesn't allow BuiltIn Layer and ViewportIndex to be " + "used for variables with Output storage class if execution model is " + "Fragment.", + SpvExecutionModelFragment, decoration, built_in_inst, + referenced_from_inst, std::placeholders::_1)); + } + + for (const SpvExecutionModel execution_model : execution_models_) { + switch (execution_model) { + case SpvExecutionModelGeometry: + case SpvExecutionModelFragment: { + // Ok. + break; + } + + default: { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + decoration.params()[0]) + << " to be used only with Fragment or Geometry execution " + "models. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst, execution_model); + } + } + } + } + + if (function_id_ == 0) { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back( + std::bind(&BuiltInsValidator::ValidateLayerOrViewportIndexAtReference, + this, decoration, built_in_inst, referenced_from_inst, + std::placeholders::_1)); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateComputeShaderI32Vec3InputAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (spv_result_t error = ValidateI32Vec( + decoration, inst, 3, + [this, &decoration](const std::string& message) -> spv_result_t { + return _.diag(SPV_ERROR_INVALID_DATA) + << "According to the Vulkan spec BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + decoration.params()[0]) + << " variable needs to be a 3-component 32-bit int " + "vector. " + << message; + })) { + return error; + } + } + + // Seed at reference checks with this built-in. + return ValidateComputeShaderI32Vec3InputAtReference(decoration, inst, inst, + inst); +} + +spv_result_t BuiltInsValidator::ValidateComputeShaderI32Vec3InputAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); + if (storage_class != SpvStorageClassMax && + storage_class != SpvStorageClassInput) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + decoration.params()[0]) + << " to be only used for variables with Input storage class. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst) + << " " << GetStorageClassDesc(referenced_from_inst); + } + + for (const SpvExecutionModel execution_model : execution_models_) { + if (execution_model != SpvExecutionModelGLCompute) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + decoration.params()[0]) + << " to be used only with GLCompute execution model. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst, execution_model); + } + } + } + + if (function_id_ == 0) { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateComputeShaderI32Vec3InputAtReference, this, + decoration, built_in_inst, referenced_from_inst, + std::placeholders::_1)); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateWorkgroupSizeAtDefinition( + const Decoration& decoration, const Instruction& inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + if (!spvOpcodeIsConstant(inst.opcode())) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec requires BuiltIn WorkgroupSize to be a constant. " + << GetIdDesc(inst) << " is not a constant."; + } + + if (spv_result_t error = ValidateI32Vec( + decoration, inst, 3, + [this](const std::string& message) -> spv_result_t { + return _.diag(SPV_ERROR_INVALID_DATA) + << "According to the Vulkan spec BuiltIn WorkgroupSize " + "variable " + "needs to be a 3-component 32-bit int vector. " + << message; + })) { + return error; + } + } + + // Seed at reference checks with this built-in. + return ValidateWorkgroupSizeAtReference(decoration, inst, inst, inst); +} + +spv_result_t BuiltInsValidator::ValidateWorkgroupSizeAtReference( + const Decoration& decoration, const Instruction& built_in_inst, + const Instruction& referenced_inst, + const Instruction& referenced_from_inst) { + if (spvIsVulkanEnv(_.context()->target_env)) { + const SpvStorageClass storage_class = GetStorageClass(referenced_from_inst); + if (storage_class != SpvStorageClassMax && + storage_class != SpvStorageClassInput) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + decoration.params()[0]) + << " to be only used for variables with Input storage class. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst) + << " " << GetStorageClassDesc(referenced_from_inst); + } + + for (const SpvExecutionModel execution_model : execution_models_) { + if (execution_model != SpvExecutionModelGLCompute) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Vulkan spec allows BuiltIn " + << _.grammar().lookupOperandName(SPV_OPERAND_TYPE_BUILT_IN, + decoration.params()[0]) + << " to be used only with GLCompute execution model. " + << GetReferenceDesc(decoration, built_in_inst, referenced_inst, + referenced_from_inst, execution_model); + } + } + } + + if (function_id_ == 0) { + // Propagate this rule to all dependant ids in the global scope. + id_to_at_reference_checks_[referenced_from_inst.id()].push_back(std::bind( + &BuiltInsValidator::ValidateWorkgroupSizeAtReference, this, decoration, + built_in_inst, referenced_from_inst, std::placeholders::_1)); + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateSingleBuiltInAtDefinition( + const Decoration& decoration, const Instruction& inst) { + const SpvBuiltIn label = SpvBuiltIn(decoration.params()[0]); + // If you are adding a new BuiltIn enum, please register it here. + // If the newly added enum has validation rules associated with it + // consider leaving a TODO and/or creating an issue. + switch (label) { + case SpvBuiltInClipDistance: + case SpvBuiltInCullDistance: { + return ValidateClipOrCullDistanceAtDefinition(decoration, inst); + } + case SpvBuiltInFragCoord: { + return ValidateFragCoordAtDefinition(decoration, inst); + } + case SpvBuiltInFragDepth: { + return ValidateFragDepthAtDefinition(decoration, inst); + } + case SpvBuiltInFrontFacing: { + return ValidateFrontFacingAtDefinition(decoration, inst); + } + case SpvBuiltInGlobalInvocationId: + case SpvBuiltInLocalInvocationId: + case SpvBuiltInNumWorkgroups: + case SpvBuiltInWorkgroupId: { + return ValidateComputeShaderI32Vec3InputAtDefinition(decoration, inst); + } + case SpvBuiltInHelperInvocation: { + return ValidateHelperInvocationAtDefinition(decoration, inst); + } + case SpvBuiltInInvocationId: { + return ValidateInvocationIdAtDefinition(decoration, inst); + } + case SpvBuiltInInstanceIndex: { + return ValidateInstanceIndexAtDefinition(decoration, inst); + } + case SpvBuiltInLayer: + case SpvBuiltInViewportIndex: { + return ValidateLayerOrViewportIndexAtDefinition(decoration, inst); + } + case SpvBuiltInPatchVertices: { + return ValidatePatchVerticesAtDefinition(decoration, inst); + } + case SpvBuiltInPointCoord: { + return ValidatePointCoordAtDefinition(decoration, inst); + } + case SpvBuiltInPointSize: { + return ValidatePointSizeAtDefinition(decoration, inst); + } + case SpvBuiltInPosition: { + return ValidatePositionAtDefinition(decoration, inst); + } + case SpvBuiltInPrimitiveId: { + return ValidatePrimitiveIdAtDefinition(decoration, inst); + } + case SpvBuiltInSampleId: { + return ValidateSampleIdAtDefinition(decoration, inst); + } + case SpvBuiltInSampleMask: { + return ValidateSampleMaskAtDefinition(decoration, inst); + } + case SpvBuiltInSamplePosition: { + return ValidateSamplePositionAtDefinition(decoration, inst); + } + case SpvBuiltInTessCoord: { + return ValidateTessCoordAtDefinition(decoration, inst); + } + case SpvBuiltInTessLevelOuter: { + return ValidateTessLevelOuterAtDefinition(decoration, inst); + } + case SpvBuiltInTessLevelInner: { + return ValidateTessLevelInnerAtDefinition(decoration, inst); + } + case SpvBuiltInVertexIndex: { + return ValidateVertexIndexAtDefinition(decoration, inst); + } + case SpvBuiltInWorkgroupSize: { + return ValidateWorkgroupSizeAtDefinition(decoration, inst); + } + case SpvBuiltInVertexId: + case SpvBuiltInInstanceId: + case SpvBuiltInLocalInvocationIndex: + case SpvBuiltInWorkDim: + case SpvBuiltInGlobalSize: + case SpvBuiltInEnqueuedWorkgroupSize: + case SpvBuiltInGlobalOffset: + case SpvBuiltInGlobalLinearId: + case SpvBuiltInSubgroupSize: + case SpvBuiltInSubgroupMaxSize: + case SpvBuiltInNumSubgroups: + case SpvBuiltInNumEnqueuedSubgroups: + case SpvBuiltInSubgroupId: + case SpvBuiltInSubgroupLocalInvocationId: + case SpvBuiltInSubgroupEqMaskKHR: + case SpvBuiltInSubgroupGeMaskKHR: + case SpvBuiltInSubgroupGtMaskKHR: + case SpvBuiltInSubgroupLeMaskKHR: + case SpvBuiltInSubgroupLtMaskKHR: + case SpvBuiltInBaseVertex: + case SpvBuiltInBaseInstance: + case SpvBuiltInDrawIndex: + case SpvBuiltInDeviceIndex: + case SpvBuiltInViewIndex: + case SpvBuiltInBaryCoordNoPerspAMD: + case SpvBuiltInBaryCoordNoPerspCentroidAMD: + case SpvBuiltInBaryCoordNoPerspSampleAMD: + case SpvBuiltInBaryCoordSmoothAMD: + case SpvBuiltInBaryCoordSmoothCentroidAMD: + case SpvBuiltInBaryCoordSmoothSampleAMD: + case SpvBuiltInBaryCoordPullModelAMD: + case SpvBuiltInFragStencilRefEXT: + case SpvBuiltInViewportMaskNV: + case SpvBuiltInSecondaryPositionNV: + case SpvBuiltInSecondaryViewportMaskNV: + case SpvBuiltInPositionPerViewNV: + case SpvBuiltInViewportMaskPerViewNV: + case SpvBuiltInFullyCoveredEXT: + case SpvBuiltInMax: { + // No validation rules (for the moment). + break; + } + } + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::ValidateBuiltInsAtDefinition() { + for (const auto& kv : _.id_decorations()) { + const uint32_t id = kv.first; + const auto& decorations = kv.second; + if (decorations.empty()) { + continue; + } + + const Instruction* inst = _.FindDef(id); + assert(inst); + + for (const auto& decoration : kv.second) { + if (decoration.dec_type() != SpvDecorationBuiltIn) { + continue; + } + + if (spv_result_t error = + ValidateSingleBuiltInAtDefinition(decoration, *inst)) { + return error; + } + } + } + + return SPV_SUCCESS; +} + +spv_result_t BuiltInsValidator::Run() { + // First pass: validate all built-ins at definition and seed + // id_to_at_reference_checks_ with built-ins. + if (auto error = ValidateBuiltInsAtDefinition()) { + return error; + } + + if (id_to_at_reference_checks_.empty()) { + // No validation tasks were seeded. Nothing else to do. + return SPV_SUCCESS; + } + + ComputeFunctionToEntryPointMapping(); + + // Second pass: validate every id reference in the module using + // rules in id_to_at_reference_checks_. + for (const Instruction& inst : _.ordered_instructions()) { + Update(inst); + + std::set already_checked; + + for (const auto& operand : inst.operands()) { + if (!spvIsIdType(operand.type)) { + // Not id. + continue; + } + + const uint32_t id = inst.word(operand.offset); + if (id == inst.id()) { + // No need to check result id. + continue; + } + + if (!already_checked.insert(id).second) { + // The instruction has already referenced this id. + continue; + } + + // Instruction references the id. Run all checks associated with the id on + // the instruction. id_to_at_reference_checks_ can be modified in the + // process, iterators are safe because it's a tree-based map. + const auto it = id_to_at_reference_checks_.find(id); + if (it != id_to_at_reference_checks_.end()) { + for (const auto& check : it->second) { + if (spv_result_t error = check(inst)) { + return error; + } + } + } + } + } + + return SPV_SUCCESS; +} + +} // anonymous namespace + +// Validates correctness of built-in variables. +spv_result_t ValidateBuiltIns(const ValidationState_t& _) { + if (!spvIsVulkanEnv(_.context()->target_env)) { + // Early return. All currently implemented rules are based on Vulkan spec. + // + // TODO: If you are adding validation rules for environments other than + // Vulkan (or general rules which are not environment independent), then you + // need to modify or remove this condition. Consider also adding early + // returns into BuiltIn-specific rules, so that the system doesn't spawn new + // rules which don't do anything. + return SPV_SUCCESS; + } + + BuiltInsValidator validator(_); + return validator.Run(); +} + +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/validate_capability.cpp b/3rdparty/spirv-tools/source/validate_capability.cpp new file mode 100644 index 000000000..1a47ebc5e --- /dev/null +++ b/3rdparty/spirv-tools/source/validate_capability.cpp @@ -0,0 +1,262 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Validates OpCapability instruction. + +#include "validate.h" + +#include +#include + +#include "diagnostic.h" +#include "opcode.h" +#include "val/instruction.h" +#include "val/validation_state.h" + +namespace libspirv { + +namespace { + +bool IsSupportGuaranteedVulkan_1_0(uint32_t capability) { + switch (capability) { + case SpvCapabilityMatrix: + case SpvCapabilityShader: + case SpvCapabilityInputAttachment: + case SpvCapabilitySampled1D: + case SpvCapabilityImage1D: + case SpvCapabilitySampledBuffer: + case SpvCapabilityImageBuffer: + case SpvCapabilityImageQuery: + case SpvCapabilityDerivativeControl: + return true; + } + return false; +} + +bool IsSupportOptionalVulkan_1_0(uint32_t capability) { + switch (capability) { + case SpvCapabilityGeometry: + case SpvCapabilityTessellation: + case SpvCapabilityFloat64: + case SpvCapabilityInt64: + case SpvCapabilityInt16: + case SpvCapabilityTessellationPointSize: + case SpvCapabilityGeometryPointSize: + case SpvCapabilityImageGatherExtended: + case SpvCapabilityStorageImageMultisample: + case SpvCapabilityUniformBufferArrayDynamicIndexing: + case SpvCapabilitySampledImageArrayDynamicIndexing: + case SpvCapabilityStorageBufferArrayDynamicIndexing: + case SpvCapabilityStorageImageArrayDynamicIndexing: + case SpvCapabilityClipDistance: + case SpvCapabilityCullDistance: + case SpvCapabilityImageCubeArray: + case SpvCapabilitySampleRateShading: + case SpvCapabilitySparseResidency: + case SpvCapabilityMinLod: + case SpvCapabilitySampledCubeArray: + case SpvCapabilityImageMSArray: + case SpvCapabilityStorageImageExtendedFormats: + case SpvCapabilityInterpolationFunction: + case SpvCapabilityStorageImageReadWithoutFormat: + case SpvCapabilityStorageImageWriteWithoutFormat: + case SpvCapabilityMultiViewport: + return true; + } + return false; +} + +bool IsSupportGuaranteedOpenCL_1_2(uint32_t capability, bool embedded_profile) { + switch (capability) { + case SpvCapabilityAddresses: + case SpvCapabilityFloat16Buffer: + case SpvCapabilityGroups: + case SpvCapabilityInt16: + case SpvCapabilityInt8: + case SpvCapabilityKernel: + case SpvCapabilityLinkage: + case SpvCapabilityVector16: + return true; + case SpvCapabilityInt64: + return !embedded_profile; + case SpvCapabilityPipes: + return embedded_profile; + } + return false; +} + +bool IsSupportGuaranteedOpenCL_2_0(uint32_t capability, bool embedded_profile) { + if (IsSupportGuaranteedOpenCL_1_2(capability, embedded_profile)) return true; + + switch (capability) { + case SpvCapabilityDeviceEnqueue: + case SpvCapabilityGenericPointer: + case SpvCapabilityPipes: + return true; + } + return false; +} + +bool IsSupportGuaranteedOpenCL_2_2(uint32_t capability, bool embedded_profile) { + if (IsSupportGuaranteedOpenCL_2_0(capability, embedded_profile)) return true; + + switch (capability) { + case SpvCapabilitySubgroupDispatch: + case SpvCapabilityPipeStorage: + return true; + } + return false; +} + +bool IsSupportOptionalOpenCL_1_2(uint32_t capability) { + switch (capability) { + case SpvCapabilityImageBasic: + case SpvCapabilityFloat64: + return true; + } + return false; +} + +// Checks if |capability| was enabled by extension. +bool IsEnabledByExtension(ValidationState_t& _, uint32_t capability) { + spv_operand_desc operand_desc = nullptr; + _.grammar().lookupOperand(SPV_OPERAND_TYPE_CAPABILITY, capability, + &operand_desc); + + // operand_desc is expected to be not null, otherwise validator would have + // failed at an earlier stage. This 'assert' is 'just in case'. + assert(operand_desc); + + ExtensionSet operand_exts(operand_desc->numExtensions, + operand_desc->extensions); + if (operand_exts.IsEmpty()) return false; + + return _.HasAnyOfExtensions(operand_exts); +} + +bool IsEnabledByCapabilityOpenCL_1_2(ValidationState_t& _, + uint32_t capability) { + if (_.HasCapability(SpvCapabilityImageBasic)) { + switch (capability) { + case SpvCapabilityLiteralSampler: + case SpvCapabilitySampled1D: + case SpvCapabilityImage1D: + case SpvCapabilitySampledBuffer: + case SpvCapabilityImageBuffer: + return true; + } + return false; + } + return false; +} + +bool IsEnabledByCapabilityOpenCL_2_0(ValidationState_t& _, + uint32_t capability) { + if (_.HasCapability(SpvCapabilityImageBasic)) { + switch (capability) { + case SpvCapabilityImageReadWrite: + case SpvCapabilityLiteralSampler: + case SpvCapabilitySampled1D: + case SpvCapabilityImage1D: + case SpvCapabilitySampledBuffer: + case SpvCapabilityImageBuffer: + return true; + } + return false; + } + return false; +} + +} // namespace + +// Validates that capability declarations use operands allowed in the current +// context. +spv_result_t CapabilityPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + const SpvOp opcode = static_cast(inst->opcode); + if (opcode != SpvOpCapability) return SPV_SUCCESS; + + assert(inst->num_operands == 1); + + const spv_parsed_operand_t& operand = inst->operands[0]; + + assert(operand.num_words == 1); + assert(operand.offset < inst->num_words); + + const uint32_t capability = inst->words[operand.offset]; + const auto capability_str = [&_, capability]() { + spv_operand_desc desc = nullptr; + if (_.grammar().lookupOperand(SPV_OPERAND_TYPE_CAPABILITY, capability, + &desc) != SPV_SUCCESS || + !desc) { + return std::string("Unknown"); + } + return std::string(desc->name); + }; + + const auto env = _.context()->target_env; + const bool opencl_embedded = env == SPV_ENV_OPENCL_EMBEDDED_1_2 || + env == SPV_ENV_OPENCL_EMBEDDED_2_0 || + env == SPV_ENV_OPENCL_EMBEDDED_2_1 || + env == SPV_ENV_OPENCL_EMBEDDED_2_2; + const std::string opencl_profile = opencl_embedded ? "Embedded" : "Full"; + if (env == SPV_ENV_VULKAN_1_0) { + if (!IsSupportGuaranteedVulkan_1_0(capability) && + !IsSupportOptionalVulkan_1_0(capability) && + !IsEnabledByExtension(_, capability)) { + return _.diag(SPV_ERROR_INVALID_CAPABILITY) + << "Capability " << capability_str() + << " is not allowed by Vulkan 1.0 specification" + << " (or requires extension)"; + } + } else if (env == SPV_ENV_OPENCL_1_2 || env == SPV_ENV_OPENCL_EMBEDDED_1_2) { + if (!IsSupportGuaranteedOpenCL_1_2(capability, opencl_embedded) && + !IsSupportOptionalOpenCL_1_2(capability) && + !IsEnabledByExtension(_, capability) && + !IsEnabledByCapabilityOpenCL_1_2(_, capability)) { + return _.diag(SPV_ERROR_INVALID_CAPABILITY) + << "Capability " << capability_str() + << " is not allowed by OpenCL 1.2 " << opencl_profile + << " Profile specification" + << " (or requires extension or capability)"; + } + } else if (env == SPV_ENV_OPENCL_2_0 || env == SPV_ENV_OPENCL_EMBEDDED_2_0 || + env == SPV_ENV_OPENCL_2_1 || env == SPV_ENV_OPENCL_EMBEDDED_2_1) { + if (!IsSupportGuaranteedOpenCL_2_0(capability, opencl_embedded) && + !IsSupportOptionalOpenCL_1_2(capability) && + !IsEnabledByExtension(_, capability) && + !IsEnabledByCapabilityOpenCL_2_0(_, capability)) { + return _.diag(SPV_ERROR_INVALID_CAPABILITY) + << "Capability " << capability_str() + << " is not allowed by OpenCL 2.0/2.1 " << opencl_profile + << " Profile specification" + << " (or requires extension or capability)"; + } + } else if (env == SPV_ENV_OPENCL_2_2 || env == SPV_ENV_OPENCL_EMBEDDED_2_2) { + if (!IsSupportGuaranteedOpenCL_2_2(capability, opencl_embedded) && + !IsSupportOptionalOpenCL_1_2(capability) && + !IsEnabledByExtension(_, capability) && + !IsEnabledByCapabilityOpenCL_2_0(_, capability)) { + return _.diag(SPV_ERROR_INVALID_CAPABILITY) + << "Capability " << capability_str() + << " is not allowed by OpenCL 2.2 " << opencl_profile + << " Profile specification" + << " (or requires extension or capability)"; + } + } + + return SPV_SUCCESS; +} + +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/validate_cfg.cpp b/3rdparty/spirv-tools/source/validate_cfg.cpp new file mode 100644 index 000000000..d601e12f7 --- /dev/null +++ b/3rdparty/spirv-tools/source/validate_cfg.cpp @@ -0,0 +1,430 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cfa.h" +#include "validate.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "spirv_validator_options.h" +#include "val/basic_block.h" +#include "val/construct.h" +#include "val/function.h" +#include "val/validation_state.h" + +using std::find; +using std::function; +using std::get; +using std::ignore; +using std::make_pair; +using std::make_tuple; +using std::numeric_limits; +using std::pair; +using std::string; +using std::tie; +using std::transform; +using std::tuple; +using std::unordered_map; +using std::unordered_set; +using std::vector; + +using libspirv::BasicBlock; + +namespace libspirv { + +namespace { + +using bb_ptr = BasicBlock*; +using cbb_ptr = const BasicBlock*; +using bb_iter = vector::const_iterator; + +} // namespace + +void printDominatorList(const BasicBlock& b) { + std::cout << b.id() << " is dominated by: "; + const BasicBlock* bb = &b; + while (bb->immediate_dominator() != bb) { + bb = bb->immediate_dominator(); + std::cout << bb->id() << " "; + } +} + +#define CFG_ASSERT(ASSERT_FUNC, TARGET) \ + if (spv_result_t rcode = ASSERT_FUNC(_, TARGET)) return rcode + +spv_result_t FirstBlockAssert(ValidationState_t& _, uint32_t target) { + if (_.current_function().IsFirstBlock(target)) { + return _.diag(SPV_ERROR_INVALID_CFG) + << "First block " << _.getIdName(target) << " of function " + << _.getIdName(_.current_function().id()) << " is targeted by block " + << _.getIdName(_.current_function().current_block()->id()); + } + return SPV_SUCCESS; +} + +spv_result_t MergeBlockAssert(ValidationState_t& _, uint32_t merge_block) { + if (_.current_function().IsBlockType(merge_block, kBlockTypeMerge)) { + return _.diag(SPV_ERROR_INVALID_CFG) + << "Block " << _.getIdName(merge_block) + << " is already a merge block for another header"; + } + return SPV_SUCCESS; +} + +/// Update the continue construct's exit blocks once the backedge blocks are +/// identified in the CFG. +void UpdateContinueConstructExitBlocks( + Function& function, const vector>& back_edges) { + auto& constructs = function.constructs(); + // TODO(umar): Think of a faster way to do this + for (auto& edge : back_edges) { + uint32_t back_edge_block_id; + uint32_t loop_header_block_id; + tie(back_edge_block_id, loop_header_block_id) = edge; + auto is_this_header = [=](Construct& c) { + return c.type() == ConstructType::kLoop && + c.entry_block()->id() == loop_header_block_id; + }; + + for (auto construct : constructs) { + if (is_this_header(construct)) { + Construct* continue_construct = + construct.corresponding_constructs().back(); + assert(continue_construct->type() == ConstructType::kContinue); + + BasicBlock* back_edge_block; + tie(back_edge_block, ignore) = function.GetBlock(back_edge_block_id); + continue_construct->set_exit(back_edge_block); + } + } + } +} + +tuple ConstructNames(ConstructType type) { + string construct_name, header_name, exit_name; + + switch (type) { + case ConstructType::kSelection: + construct_name = "selection"; + header_name = "selection header"; + exit_name = "merge block"; + break; + case ConstructType::kLoop: + construct_name = "loop"; + header_name = "loop header"; + exit_name = "merge block"; + break; + case ConstructType::kContinue: + construct_name = "continue"; + header_name = "continue target"; + exit_name = "back-edge block"; + break; + case ConstructType::kCase: + construct_name = "case"; + header_name = "case entry block"; + exit_name = "case exit block"; + break; + default: + assert(1 == 0 && "Not defined type"); + } + + return make_tuple(construct_name, header_name, exit_name); +} + +/// Constructs an error message for construct validation errors +string ConstructErrorString(const Construct& construct, + const string& header_string, + const string& exit_string, + const string& dominate_text) { + string construct_name, header_name, exit_name; + tie(construct_name, header_name, exit_name) = + ConstructNames(construct.type()); + + // TODO(umar): Add header block for continue constructs to error message + return "The " + construct_name + " construct with the " + header_name + " " + + header_string + " " + dominate_text + " the " + exit_name + " " + + exit_string; +} + +spv_result_t StructuredControlFlowChecks( + const ValidationState_t& _, const Function& function, + const vector>& back_edges) { + /// Check all backedges target only loop headers and have exactly one + /// back-edge branching to it + + // Map a loop header to blocks with back-edges to the loop header. + std::map> loop_latch_blocks; + for (auto back_edge : back_edges) { + uint32_t back_edge_block; + uint32_t header_block; + tie(back_edge_block, header_block) = back_edge; + if (!function.IsBlockType(header_block, kBlockTypeLoop)) { + return _.diag(SPV_ERROR_INVALID_CFG) + << "Back-edges (" << _.getIdName(back_edge_block) << " -> " + << _.getIdName(header_block) + << ") can only be formed between a block and a loop header."; + } + loop_latch_blocks[header_block].insert(back_edge_block); + } + + // Check the loop headers have exactly one back-edge branching to it + for (BasicBlock* loop_header : function.ordered_blocks()) { + if (!loop_header->reachable()) continue; + if (!loop_header->is_type(kBlockTypeLoop)) continue; + auto loop_header_id = loop_header->id(); + auto num_latch_blocks = loop_latch_blocks[loop_header_id].size(); + if (num_latch_blocks != 1) { + return _.diag(SPV_ERROR_INVALID_CFG) + << "Loop header " << _.getIdName(loop_header_id) + << " is targeted by " << num_latch_blocks + << " back-edge blocks but the standard requires exactly one"; + } + } + + // Check construct rules + for (const Construct& construct : function.constructs()) { + auto header = construct.entry_block(); + auto merge = construct.exit_block(); + + if (header->reachable() && !merge) { + string construct_name, header_name, exit_name; + tie(construct_name, header_name, exit_name) = + ConstructNames(construct.type()); + return _.diag(SPV_ERROR_INTERNAL) + << "Construct " + construct_name + " with " + header_name + " " + + _.getIdName(header->id()) + " does not have a " + + exit_name + ". This may be a bug in the validator."; + } + + // If the exit block is reachable then it's dominated by the + // header. + if (merge && merge->reachable()) { + if (!header->dominates(*merge)) { + return _.diag(SPV_ERROR_INVALID_CFG) << ConstructErrorString( + construct, _.getIdName(header->id()), + _.getIdName(merge->id()), "does not dominate"); + } + // If it's really a merge block for a selection or loop, then it must be + // *strictly* dominated by the header. + if (construct.ExitBlockIsMergeBlock() && (header == merge)) { + return _.diag(SPV_ERROR_INVALID_CFG) << ConstructErrorString( + construct, _.getIdName(header->id()), + _.getIdName(merge->id()), "does not strictly dominate"); + } + } + // Check post-dominance for continue constructs. But dominance and + // post-dominance only make sense when the construct is reachable. + if (header->reachable() && construct.type() == ConstructType::kContinue) { + if (!merge->postdominates(*header)) { + return _.diag(SPV_ERROR_INVALID_CFG) << ConstructErrorString( + construct, _.getIdName(header->id()), + _.getIdName(merge->id()), "is not post dominated by"); + } + } + // TODO(umar): an OpSwitch block dominates all its defined case + // constructs + // TODO(umar): each case construct has at most one branch to another + // case construct + // TODO(umar): each case construct is branched to by at most one other + // case construct + // TODO(umar): if Target T1 branches to Target T2, or if Target T1 + // branches to the Default and the Default branches to Target T2, then + // T1 must immediately precede T2 in the list of the OpSwitch Target + // operands + } + return SPV_SUCCESS; +} + +spv_result_t PerformCfgChecks(ValidationState_t& _) { + for (auto& function : _.functions()) { + // Check all referenced blocks are defined within a function + if (function.undefined_block_count() != 0) { + string undef_blocks("{"); + for (auto undefined_block : function.undefined_blocks()) { + undef_blocks += _.getIdName(undefined_block) + " "; + } + return _.diag(SPV_ERROR_INVALID_CFG) + << "Block(s) " << undef_blocks << "\b}" + << " are referenced but not defined in function " + << _.getIdName(function.id()); + } + + // Set each block's immediate dominator and immediate postdominator, + // and find all back-edges. + // + // We want to analyze all the blocks in the function, even in degenerate + // control flow cases including unreachable blocks. So use the augmented + // CFG to ensure we cover all the blocks. + vector postorder; + vector postdom_postorder; + vector> back_edges; + auto ignore_block = [](cbb_ptr) {}; + auto ignore_edge = [](cbb_ptr, cbb_ptr) {}; + if (!function.ordered_blocks().empty()) { + /// calculate dominators + spvtools::CFA::DepthFirstTraversal( + function.first_block(), function.AugmentedCFGSuccessorsFunction(), + ignore_block, [&](cbb_ptr b) { postorder.push_back(b); }, + ignore_edge); + auto edges = spvtools::CFA::CalculateDominators( + postorder, function.AugmentedCFGPredecessorsFunction()); + for (auto edge : edges) { + edge.first->SetImmediateDominator(edge.second); + } + + /// calculate post dominators + spvtools::CFA::DepthFirstTraversal( + function.pseudo_exit_block(), + function.AugmentedCFGPredecessorsFunction(), ignore_block, + [&](cbb_ptr b) { postdom_postorder.push_back(b); }, ignore_edge); + auto postdom_edges = + spvtools::CFA::CalculateDominators( + postdom_postorder, function.AugmentedCFGSuccessorsFunction()); + for (auto edge : postdom_edges) { + edge.first->SetImmediatePostDominator(edge.second); + } + /// calculate back edges. + spvtools::CFA::DepthFirstTraversal( + function.pseudo_entry_block(), + function + .AugmentedCFGSuccessorsFunctionIncludingHeaderToContinueEdge(), + ignore_block, ignore_block, [&](cbb_ptr from, cbb_ptr to) { + back_edges.emplace_back(from->id(), to->id()); + }); + } + UpdateContinueConstructExitBlocks(function, back_edges); + + auto& blocks = function.ordered_blocks(); + if (!blocks.empty()) { + // Check if the order of blocks in the binary appear before the blocks + // they dominate + for (auto block = begin(blocks) + 1; block != end(blocks); ++block) { + if (auto idom = (*block)->immediate_dominator()) { + if (idom != function.pseudo_entry_block() && + block == std::find(begin(blocks), block, idom)) { + return _.diag(SPV_ERROR_INVALID_CFG) + << "Block " << _.getIdName((*block)->id()) + << " appears in the binary before its dominator " + << _.getIdName(idom->id()); + } + } + } + // If we have structed control flow, check that no block has a control + // flow nesting depth larger than the limit. + if (_.HasCapability(SpvCapabilityShader)) { + const int control_flow_nesting_depth_limit = + _.options()->universal_limits_.max_control_flow_nesting_depth; + for (auto block = begin(blocks); block != end(blocks); ++block) { + if (function.GetBlockDepth(*block) > + control_flow_nesting_depth_limit) { + return _.diag(SPV_ERROR_INVALID_CFG) + << "Maximum Control Flow nesting depth exceeded."; + } + } + } + } + + /// Structured control flow checks are only required for shader capabilities + if (_.HasCapability(SpvCapabilityShader)) { + if (auto error = StructuredControlFlowChecks(_, function, back_edges)) + return error; + } + } + return SPV_SUCCESS; +} + +spv_result_t CfgPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + SpvOp opcode = static_cast(inst->opcode); + switch (opcode) { + case SpvOpLabel: + if (auto error = _.current_function().RegisterBlock(inst->result_id)) + return error; + break; + case SpvOpLoopMerge: { + uint32_t merge_block = inst->words[inst->operands[0].offset]; + uint32_t continue_block = inst->words[inst->operands[1].offset]; + CFG_ASSERT(MergeBlockAssert, merge_block); + + if (auto error = _.current_function().RegisterLoopMerge(merge_block, + continue_block)) + return error; + } break; + case SpvOpSelectionMerge: { + uint32_t merge_block = inst->words[inst->operands[0].offset]; + CFG_ASSERT(MergeBlockAssert, merge_block); + + if (auto error = _.current_function().RegisterSelectionMerge(merge_block)) + return error; + } break; + case SpvOpBranch: { + uint32_t target = inst->words[inst->operands[0].offset]; + CFG_ASSERT(FirstBlockAssert, target); + + _.current_function().RegisterBlockEnd({target}, opcode); + } break; + case SpvOpBranchConditional: { + uint32_t tlabel = inst->words[inst->operands[1].offset]; + uint32_t flabel = inst->words[inst->operands[2].offset]; + CFG_ASSERT(FirstBlockAssert, tlabel); + CFG_ASSERT(FirstBlockAssert, flabel); + + _.current_function().RegisterBlockEnd({tlabel, flabel}, opcode); + } break; + + case SpvOpSwitch: { + vector cases; + for (int i = 1; i < inst->num_operands; i += 2) { + uint32_t target = inst->words[inst->operands[i].offset]; + CFG_ASSERT(FirstBlockAssert, target); + cases.push_back(target); + } + _.current_function().RegisterBlockEnd({cases}, opcode); + } break; + case SpvOpReturn: { + const uint32_t return_type = _.current_function().GetResultTypeId(); + const Instruction* return_type_inst = _.FindDef(return_type); + assert(return_type_inst); + if (return_type_inst->opcode() != SpvOpTypeVoid) + return _.diag(SPV_ERROR_INVALID_CFG) + << "OpReturn can only be called from a function with void " + << "return type."; + } + // Fallthrough. + case SpvOpKill: + case SpvOpReturnValue: + case SpvOpUnreachable: + _.current_function().RegisterBlockEnd(vector(), opcode); + if (opcode == SpvOpKill) { + _.current_function().RegisterExecutionModelLimitation( + SpvExecutionModelFragment, + "OpKill requires Fragment execution model"); + } + break; + default: + break; + } + return SPV_SUCCESS; +} +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/validate_composites.cpp b/3rdparty/spirv-tools/source/validate_composites.cpp new file mode 100644 index 000000000..c163786e5 --- /dev/null +++ b/3rdparty/spirv-tools/source/validate_composites.cpp @@ -0,0 +1,481 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Validates correctness of composite SPIR-V instructions. + +#include "validate.h" + +#include "diagnostic.h" +#include "opcode.h" +#include "val/instruction.h" +#include "val/validation_state.h" + +namespace libspirv { + +namespace { + +// Returns the type of the value accessed by OpCompositeExtract or +// OpCompositeInsert instruction. The function traverses the hierarchy of +// nested data structures (structs, arrays, vectors, matrices) as directed by +// the sequence of indices in the instruction. May return error if traversal +// fails (encountered non-composite, out of bounds, nesting too deep). +// Returns the type of Composite operand if the instruction has no indices. +spv_result_t GetExtractInsertValueType(ValidationState_t& _, + const spv_parsed_instruction_t& inst, + uint32_t* member_type) { + const SpvOp opcode = static_cast(inst.opcode); + assert(opcode == SpvOpCompositeExtract || opcode == SpvOpCompositeInsert); + uint32_t word_index = opcode == SpvOpCompositeExtract ? 4 : 5; + const uint32_t num_words = static_cast(inst.num_words); + const uint32_t composite_id_index = word_index - 1; + + const uint32_t num_indices = num_words - word_index; + const uint32_t kCompositeExtractInsertMaxNumIndices = 255; + if (num_indices > kCompositeExtractInsertMaxNumIndices) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "The number of indexes in Op" << spvOpcodeString(opcode) + << " may not exceed " << kCompositeExtractInsertMaxNumIndices + << ". Found " << num_indices << " indexes."; + } + + *member_type = _.GetTypeId(inst.words[composite_id_index]); + if (*member_type == 0) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Composite to be an object of composite type"; + } + + for (; word_index < num_words; ++word_index) { + const uint32_t component_index = inst.words[word_index]; + const Instruction* const type_inst = _.FindDef(*member_type); + assert(type_inst); + switch (type_inst->opcode()) { + case SpvOpTypeVector: { + *member_type = type_inst->word(2); + const uint32_t vector_size = type_inst->word(3); + if (component_index >= vector_size) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": vector access is out of bounds, vector size is " + << vector_size << ", but access index is " << component_index; + } + break; + } + case SpvOpTypeMatrix: { + *member_type = type_inst->word(2); + const uint32_t num_cols = type_inst->word(3); + if (component_index >= num_cols) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": matrix access is out of bounds, matrix has " << num_cols + << " columns, but access index is " << component_index; + } + break; + } + case SpvOpTypeArray: { + uint64_t array_size = 0; + auto size = _.FindDef(type_inst->word(3)); + *member_type = type_inst->word(2); + if (spvOpcodeIsSpecConstant(size->opcode())) { + // Cannot verify against the size of this array. + break; + } + + if (!_.GetConstantValUint64(type_inst->word(3), &array_size)) { + assert(0 && "Array type definition is corrupt"); + } + if (component_index >= array_size) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": array access is out of bounds, array size is " + << array_size << ", but access index is " << component_index; + } + break; + } + case SpvOpTypeRuntimeArray: { + *member_type = type_inst->word(2); + // Array size is unknown. + break; + } + case SpvOpTypeStruct: { + const size_t num_struct_members = type_inst->words().size() - 2; + if (component_index >= num_struct_members) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Index is out of bounds: Op" << spvOpcodeString(opcode) + << " can not find index " << component_index + << " into the structure '" << type_inst->id() + << "'. This structure has " << num_struct_members + << " members. Largest valid index is " + << num_struct_members - 1 << "."; + } + *member_type = type_inst->word(component_index + 2); + break; + } + default: + return _.diag(SPV_ERROR_INVALID_DATA) + << "Op" << spvOpcodeString(opcode) + << " reached non-composite type while indexes still remain to " + "be traversed."; + } + } + + return SPV_SUCCESS; +} + +} // anonymous namespace + +// Validates correctness of composite instructions. +spv_result_t CompositesPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + const SpvOp opcode = static_cast(inst->opcode); + const uint32_t result_type = inst->type_id; + const uint32_t num_operands = static_cast(inst->num_operands); + + switch (opcode) { + case SpvOpVectorExtractDynamic: { + const SpvOp result_opcode = _.GetIdOpcode(result_type); + if (!spvOpcodeIsScalarType(result_opcode)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Result Type to be a scalar type"; + } + + const uint32_t vector_type = _.GetOperandTypeId(inst, 2); + const SpvOp vector_opcode = _.GetIdOpcode(vector_type); + if (vector_opcode != SpvOpTypeVector) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Vector type to be OpTypeVector"; + } + + if (_.GetComponentType(vector_type) != result_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Vector component type to be equal to Result Type"; + } + + const uint32_t index_type = _.GetOperandTypeId(inst, 3); + if (!_.IsIntScalarType(index_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Index to be int scalar"; + } + + break; + } + + case SpvOpVectorInsertDynamic: { + const SpvOp result_opcode = _.GetIdOpcode(result_type); + if (result_opcode != SpvOpTypeVector) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Result Type to be OpTypeVector"; + } + + const uint32_t vector_type = _.GetOperandTypeId(inst, 2); + if (vector_type != result_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Vector type to be equal to Result Type"; + } + + const uint32_t component_type = _.GetOperandTypeId(inst, 3); + if (_.GetComponentType(result_type) != component_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Component type to be equal to Result Type " + << "component type"; + } + + const uint32_t index_type = _.GetOperandTypeId(inst, 4); + if (!_.IsIntScalarType(index_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Index to be int scalar"; + } + + break; + } + + case SpvOpVectorShuffle: { + // Handled in validate_id.cpp. + // TODO(atgoo@github.com) Consider moving it here. + break; + } + + case SpvOpCompositeConstruct: { + const SpvOp result_opcode = _.GetIdOpcode(result_type); + switch (result_opcode) { + case SpvOpTypeVector: { + const uint32_t num_result_components = _.GetDimension(result_type); + const uint32_t result_component_type = + _.GetComponentType(result_type); + uint32_t given_component_count = 0; + + if (num_operands <= 3) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected number of constituents to be at least 2"; + } + + for (uint32_t operand_index = 2; operand_index < num_operands; + ++operand_index) { + const uint32_t operand_type = + _.GetOperandTypeId(inst, operand_index); + if (operand_type == result_component_type) { + ++given_component_count; + } else { + if (_.GetIdOpcode(operand_type) != SpvOpTypeVector || + _.GetComponentType(operand_type) != result_component_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Constituents to be scalars or vectors of " + << "the same type as Result Type components"; + } + + given_component_count += _.GetDimension(operand_type); + } + } + + if (num_result_components != given_component_count) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected total number of given components to be equal " + << "to the size of Result Type vector"; + } + + break; + } + case SpvOpTypeMatrix: { + uint32_t result_num_rows = 0; + uint32_t result_num_cols = 0; + uint32_t result_col_type = 0; + uint32_t result_component_type = 0; + if (!_.GetMatrixTypeInfo(result_type, &result_num_rows, + &result_num_cols, &result_col_type, + &result_component_type)) { + assert(0); + } + + if (result_num_cols + 2 != num_operands) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected total number of Constituents to be equal " + << "to the number of columns of Result Type matrix"; + } + + for (uint32_t operand_index = 2; operand_index < num_operands; + ++operand_index) { + const uint32_t operand_type = + _.GetOperandTypeId(inst, operand_index); + if (operand_type != result_col_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Constituent type to be equal to the column " + << "type Result Type matrix"; + } + } + + break; + } + case SpvOpTypeArray: { + const Instruction* const array_inst = _.FindDef(result_type); + assert(array_inst); + assert(array_inst->opcode() == SpvOpTypeArray); + + auto size = _.FindDef(array_inst->word(3)); + if (spvOpcodeIsSpecConstant(size->opcode())) { + // Cannot verify against the size of this array. + break; + } + + uint64_t array_size = 0; + if (!_.GetConstantValUint64(array_inst->word(3), &array_size)) { + assert(0 && "Array type definition is corrupt"); + } + + if (array_size + 2 != num_operands) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected total number of Constituents to be equal " + << "to the number of elements of Result Type array"; + } + + const uint32_t result_component_type = array_inst->word(2); + for (uint32_t operand_index = 2; operand_index < num_operands; + ++operand_index) { + const uint32_t operand_type = + _.GetOperandTypeId(inst, operand_index); + if (operand_type != result_component_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Constituent type to be equal to the column " + << "type Result Type array"; + } + } + + break; + } + case SpvOpTypeStruct: { + const Instruction* const struct_inst = _.FindDef(result_type); + assert(struct_inst); + assert(struct_inst->opcode() == SpvOpTypeStruct); + + if (struct_inst->operands().size() + 1 != num_operands) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected total number of Constituents to be equal " + << "to the number of members of Result Type struct"; + } + + for (uint32_t operand_index = 2; operand_index < num_operands; + ++operand_index) { + const uint32_t operand_type = + _.GetOperandTypeId(inst, operand_index); + const uint32_t member_type = struct_inst->word(operand_index); + if (operand_type != member_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Constituent type to be equal to the " + << "corresponding member type of Result Type struct"; + } + } + + break; + } + default: { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Result Type to be a composite type"; + } + } + + break; + } + + case SpvOpCompositeExtract: { + uint32_t member_type = 0; + if (spv_result_t error = + GetExtractInsertValueType(_, *inst, &member_type)) { + return error; + } + + if (result_type != member_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Op" << spvOpcodeString(opcode) << " result type (Op" + << spvOpcodeString(_.GetIdOpcode(result_type)) + << ") does not match the type that results from indexing into " + "the " + "composite (Op" + << spvOpcodeString(_.GetIdOpcode(member_type)) << ")."; + } + break; + } + + case SpvOpCompositeInsert: { + const uint32_t object_type = _.GetOperandTypeId(inst, 2); + const uint32_t composite_type = _.GetOperandTypeId(inst, 3); + + if (result_type != composite_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "The Result Type must be the same as Composite type in Op" + << spvOpcodeString(opcode) << " yielding Result Id " + << result_type << "."; + } + + uint32_t member_type = 0; + if (spv_result_t error = + GetExtractInsertValueType(_, *inst, &member_type)) { + return error; + } + + if (object_type != member_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "The Object type (Op" + << spvOpcodeString(_.GetIdOpcode(object_type)) << ") in Op" + << spvOpcodeString(opcode) + << " does not match the type that results from indexing into " + "the Composite (Op" + << spvOpcodeString(_.GetIdOpcode(member_type)) << ")."; + } + break; + } + + case SpvOpCopyObject: { + if (!spvOpcodeGeneratesType(_.GetIdOpcode(result_type))) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Result Type to be a type"; + } + + const uint32_t operand_type = _.GetOperandTypeId(inst, 2); + if (operand_type != result_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Result Type and Operand type to be the same"; + } + + break; + } + + case SpvOpTranspose: { + uint32_t result_num_rows = 0; + uint32_t result_num_cols = 0; + uint32_t result_col_type = 0; + uint32_t result_component_type = 0; + if (!_.GetMatrixTypeInfo(result_type, &result_num_rows, &result_num_cols, + &result_col_type, &result_component_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Result Type to be a matrix type"; + } + + const uint32_t matrix_type = _.GetOperandTypeId(inst, 2); + uint32_t matrix_num_rows = 0; + uint32_t matrix_num_cols = 0; + uint32_t matrix_col_type = 0; + uint32_t matrix_component_type = 0; + if (!_.GetMatrixTypeInfo(matrix_type, &matrix_num_rows, &matrix_num_cols, + &matrix_col_type, &matrix_component_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Matrix to be of type OpTypeMatrix"; + } + + if (result_component_type != matrix_component_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected component types of Matrix and Result Type to be " + << "identical"; + } + + if (result_num_rows != matrix_num_cols || + result_num_cols != matrix_num_rows) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected number of columns and the column size of Matrix " + << "to be the reverse of those of Result Type"; + } + + break; + } + + default: + break; + } + + return SPV_SUCCESS; +} + +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/validate_conversion.cpp b/3rdparty/spirv-tools/source/validate_conversion.cpp new file mode 100644 index 000000000..75ff6a2e1 --- /dev/null +++ b/3rdparty/spirv-tools/source/validate_conversion.cpp @@ -0,0 +1,412 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Validates correctness of conversion instructions. + +#include "validate.h" + +#include "diagnostic.h" +#include "opcode.h" +#include "val/instruction.h" +#include "val/validation_state.h" + +namespace libspirv { + +// Validates correctness of conversion instructions. +spv_result_t ConversionPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + const SpvOp opcode = static_cast(inst->opcode); + const uint32_t result_type = inst->type_id; + + switch (opcode) { + case SpvOpConvertFToU: { + if (!_.IsUnsignedIntScalarType(result_type) && + !_.IsUnsignedIntVectorType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected unsigned int scalar or vector type as Result Type: " + << spvOpcodeString(opcode); + + const uint32_t input_type = _.GetOperandTypeId(inst, 2); + if (!input_type || (!_.IsFloatScalarType(input_type) && + !_.IsFloatVectorType(input_type))) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input to be float scalar or vector: " + << spvOpcodeString(opcode); + + if (_.GetDimension(result_type) != _.GetDimension(input_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input to have the same dimension as Result Type: " + << spvOpcodeString(opcode); + + break; + } + + case SpvOpConvertFToS: { + if (!_.IsIntScalarType(result_type) && !_.IsIntVectorType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected int scalar or vector type as Result Type: " + << spvOpcodeString(opcode); + + const uint32_t input_type = _.GetOperandTypeId(inst, 2); + if (!input_type || (!_.IsFloatScalarType(input_type) && + !_.IsFloatVectorType(input_type))) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input to be float scalar or vector: " + << spvOpcodeString(opcode); + + if (_.GetDimension(result_type) != _.GetDimension(input_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input to have the same dimension as Result Type: " + << spvOpcodeString(opcode); + + break; + } + + case SpvOpConvertSToF: + case SpvOpConvertUToF: { + if (!_.IsFloatScalarType(result_type) && + !_.IsFloatVectorType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected float scalar or vector type as Result Type: " + << spvOpcodeString(opcode); + + const uint32_t input_type = _.GetOperandTypeId(inst, 2); + if (!input_type || + (!_.IsIntScalarType(input_type) && !_.IsIntVectorType(input_type))) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input to be int scalar or vector: " + << spvOpcodeString(opcode); + + if (_.GetDimension(result_type) != _.GetDimension(input_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input to have the same dimension as Result Type: " + << spvOpcodeString(opcode); + + break; + } + + case SpvOpUConvert: { + if (!_.IsUnsignedIntScalarType(result_type) && + !_.IsUnsignedIntVectorType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected unsigned int scalar or vector type as Result Type: " + << spvOpcodeString(opcode); + + const uint32_t input_type = _.GetOperandTypeId(inst, 2); + if (!input_type || + (!_.IsIntScalarType(input_type) && !_.IsIntVectorType(input_type))) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input to be int scalar or vector: " + << spvOpcodeString(opcode); + + if (_.GetDimension(result_type) != _.GetDimension(input_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input to have the same dimension as Result Type: " + << spvOpcodeString(opcode); + + if (_.GetBitWidth(result_type) == _.GetBitWidth(input_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input to have different bit width from Result " + "Type: " + << spvOpcodeString(opcode); + break; + } + + case SpvOpSConvert: { + if (!_.IsIntScalarType(result_type) && !_.IsIntVectorType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected int scalar or vector type as Result Type: " + << spvOpcodeString(opcode); + + const uint32_t input_type = _.GetOperandTypeId(inst, 2); + if (!input_type || + (!_.IsIntScalarType(input_type) && !_.IsIntVectorType(input_type))) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input to be int scalar or vector: " + << spvOpcodeString(opcode); + + if (_.GetDimension(result_type) != _.GetDimension(input_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input to have the same dimension as Result Type: " + << spvOpcodeString(opcode); + + if (_.GetBitWidth(result_type) == _.GetBitWidth(input_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input to have different bit width from Result " + "Type: " + << spvOpcodeString(opcode); + break; + } + + case SpvOpFConvert: { + if (!_.IsFloatScalarType(result_type) && + !_.IsFloatVectorType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected float scalar or vector type as Result Type: " + << spvOpcodeString(opcode); + + const uint32_t input_type = _.GetOperandTypeId(inst, 2); + if (!input_type || (!_.IsFloatScalarType(input_type) && + !_.IsFloatVectorType(input_type))) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input to be float scalar or vector: " + << spvOpcodeString(opcode); + + if (_.GetDimension(result_type) != _.GetDimension(input_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input to have the same dimension as Result Type: " + << spvOpcodeString(opcode); + + if (_.GetBitWidth(result_type) == _.GetBitWidth(input_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input to have different bit width from Result " + "Type: " + << spvOpcodeString(opcode); + break; + } + + case SpvOpQuantizeToF16: { + if ((!_.IsFloatScalarType(result_type) && + !_.IsFloatVectorType(result_type)) || + _.GetBitWidth(result_type) != 32) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected 32-bit float scalar or vector type as Result Type: " + << spvOpcodeString(opcode); + + const uint32_t input_type = _.GetOperandTypeId(inst, 2); + if (input_type != result_type) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input type to be equal to Result Type: " + << spvOpcodeString(opcode); + break; + } + + case SpvOpConvertPtrToU: { + if (!_.IsUnsignedIntScalarType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected unsigned int scalar type as Result Type: " + << spvOpcodeString(opcode); + + const uint32_t input_type = _.GetOperandTypeId(inst, 2); + if (!_.IsPointerType(input_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input to be a pointer: " << spvOpcodeString(opcode); + break; + } + + case SpvOpSatConvertSToU: + case SpvOpSatConvertUToS: { + if (!_.IsIntScalarType(result_type) && !_.IsIntVectorType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected int scalar or vector type as Result Type: " + << spvOpcodeString(opcode); + + const uint32_t input_type = _.GetOperandTypeId(inst, 2); + if (!input_type || + (!_.IsIntScalarType(input_type) && !_.IsIntVectorType(input_type))) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected int scalar or vector as input: " + << spvOpcodeString(opcode); + + if (_.GetDimension(result_type) != _.GetDimension(input_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input to have the same dimension as Result Type: " + << spvOpcodeString(opcode); + break; + } + + case SpvOpConvertUToPtr: { + if (!_.IsPointerType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Result Type to be a pointer: " + << spvOpcodeString(opcode); + + const uint32_t input_type = _.GetOperandTypeId(inst, 2); + if (!_.IsIntScalarType(input_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected int scalar as input: " << spvOpcodeString(opcode); + break; + } + + case SpvOpPtrCastToGeneric: { + uint32_t result_storage_class = 0; + uint32_t result_data_type = 0; + if (!_.GetPointerTypeInfo(result_type, &result_data_type, + &result_storage_class)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Result Type to be a pointer: " + << spvOpcodeString(opcode); + + if (result_storage_class != SpvStorageClassGeneric) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Result Type to have storage class Generic: " + << spvOpcodeString(opcode); + + const uint32_t input_type = _.GetOperandTypeId(inst, 2); + uint32_t input_storage_class = 0; + uint32_t input_data_type = 0; + if (!_.GetPointerTypeInfo(input_type, &input_data_type, + &input_storage_class)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input to be a pointer: " << spvOpcodeString(opcode); + + if (input_storage_class != SpvStorageClassWorkgroup && + input_storage_class != SpvStorageClassCrossWorkgroup && + input_storage_class != SpvStorageClassFunction) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input to have storage class Workgroup, " + << "CrossWorkgroup or Function: " << spvOpcodeString(opcode); + + if (result_data_type != input_data_type) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input and Result Type to point to the same type: " + << spvOpcodeString(opcode); + break; + } + + case SpvOpGenericCastToPtr: { + uint32_t result_storage_class = 0; + uint32_t result_data_type = 0; + if (!_.GetPointerTypeInfo(result_type, &result_data_type, + &result_storage_class)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Result Type to be a pointer: " + << spvOpcodeString(opcode); + + if (result_storage_class != SpvStorageClassWorkgroup && + result_storage_class != SpvStorageClassCrossWorkgroup && + result_storage_class != SpvStorageClassFunction) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Result Type to have storage class Workgroup, " + << "CrossWorkgroup or Function: " << spvOpcodeString(opcode); + + const uint32_t input_type = _.GetOperandTypeId(inst, 2); + uint32_t input_storage_class = 0; + uint32_t input_data_type = 0; + if (!_.GetPointerTypeInfo(input_type, &input_data_type, + &input_storage_class)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input to be a pointer: " << spvOpcodeString(opcode); + + if (input_storage_class != SpvStorageClassGeneric) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input to have storage class Generic: " + << spvOpcodeString(opcode); + + if (result_data_type != input_data_type) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input and Result Type to point to the same type: " + << spvOpcodeString(opcode); + break; + } + + case SpvOpGenericCastToPtrExplicit: { + uint32_t result_storage_class = 0; + uint32_t result_data_type = 0; + if (!_.GetPointerTypeInfo(result_type, &result_data_type, + &result_storage_class)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Result Type to be a pointer: " + << spvOpcodeString(opcode); + + const uint32_t target_storage_class = inst->words[4]; + if (result_storage_class != target_storage_class) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Result Type to be of target storage class: " + << spvOpcodeString(opcode); + + const uint32_t input_type = _.GetOperandTypeId(inst, 2); + uint32_t input_storage_class = 0; + uint32_t input_data_type = 0; + if (!_.GetPointerTypeInfo(input_type, &input_data_type, + &input_storage_class)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input to be a pointer: " << spvOpcodeString(opcode); + + if (input_storage_class != SpvStorageClassGeneric) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input to have storage class Generic: " + << spvOpcodeString(opcode); + + if (result_data_type != input_data_type) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input and Result Type to point to the same type: " + << spvOpcodeString(opcode); + + if (target_storage_class != SpvStorageClassWorkgroup && + target_storage_class != SpvStorageClassCrossWorkgroup && + target_storage_class != SpvStorageClassFunction) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected target storage class to be Workgroup, " + << "CrossWorkgroup or Function: " << spvOpcodeString(opcode); + break; + } + + case SpvOpBitcast: { + const uint32_t input_type = _.GetOperandTypeId(inst, 2); + if (!input_type) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input to have a type: " << spvOpcodeString(opcode); + + const bool result_is_pointer = _.IsPointerType(result_type); + const bool result_is_int_scalar = _.IsIntScalarType(result_type); + const bool input_is_pointer = _.IsPointerType(input_type); + const bool input_is_int_scalar = _.IsIntScalarType(input_type); + + if (!result_is_pointer && !result_is_int_scalar && + !_.IsIntVectorType(result_type) && + !_.IsFloatScalarType(result_type) && + !_.IsFloatVectorType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Result Type to be a pointer or int or float vector " + << "or scalar type: " << spvOpcodeString(opcode); + + if (!input_is_pointer && !input_is_int_scalar && + !_.IsIntVectorType(input_type) && !_.IsFloatScalarType(input_type) && + !_.IsFloatVectorType(input_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input to be a pointer or int or float vector " + << "or scalar: " << spvOpcodeString(opcode); + + if (result_is_pointer && !input_is_pointer && !input_is_int_scalar) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input to be a pointer or int scalar if Result Type " + << "is pointer: " << spvOpcodeString(opcode); + + if (input_is_pointer && !result_is_pointer && !result_is_int_scalar) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Pointer can only be converted to another pointer or int " + << "scalar: " << spvOpcodeString(opcode); + + if (!result_is_pointer && !input_is_pointer) { + const uint32_t result_size = + _.GetBitWidth(result_type) * _.GetDimension(result_type); + const uint32_t input_size = + _.GetBitWidth(input_type) * _.GetDimension(input_type); + if (result_size != input_size) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected input to have the same total bit width as " + << "Result Type: " << spvOpcodeString(opcode); + } + break; + } + + default: + break; + } + + return SPV_SUCCESS; +} + +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/validate_datarules.cpp b/3rdparty/spirv-tools/source/validate_datarules.cpp new file mode 100644 index 000000000..7d51ecaba --- /dev/null +++ b/3rdparty/spirv-tools/source/validate_datarules.cpp @@ -0,0 +1,273 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Ensures Data Rules are followed according to the specifications. + +#include "validate.h" + +#include +#include +#include + +#include "diagnostic.h" +#include "opcode.h" +#include "operand.h" +#include "val/instruction.h" +#include "val/validation_state.h" + +using libspirv::CapabilitySet; +using libspirv::DiagnosticStream; +using libspirv::ValidationState_t; + +namespace { + +// Validates that the number of components in the vector is valid. +// Vector types can only be parameterized as having 2, 3, or 4 components. +// If the Vector16 capability is added, 8 and 16 components are also allowed. +spv_result_t ValidateVecNumComponents(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + // Operand 2 specifies the number of components in the vector. + const uint32_t num_components = inst->words[inst->operands[2].offset]; + if (num_components == 2 || num_components == 3 || num_components == 4) { + return SPV_SUCCESS; + } + if (num_components == 8 || num_components == 16) { + if (_.HasCapability(SpvCapabilityVector16)) { + return SPV_SUCCESS; + } + return _.diag(SPV_ERROR_INVALID_DATA) + << "Having " << num_components << " components for " + << spvOpcodeString(static_cast(inst->opcode)) + << " requires the Vector16 capability"; + } + return _.diag(SPV_ERROR_INVALID_DATA) + << "Illegal number of components (" << num_components << ") for " + << spvOpcodeString(static_cast(inst->opcode)); +} + +// Validates that the number of bits specifed for a float type is valid. +// Scalar floating-point types can be parameterized only with 32-bits. +// Float16 capability allows using a 16-bit OpTypeFloat. +// Float16Buffer capability allows creation of a 16-bit OpTypeFloat. +// Float64 capability allows using a 64-bit OpTypeFloat. +spv_result_t ValidateFloatSize(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + // Operand 1 is the number of bits for this float + const uint32_t num_bits = inst->words[inst->operands[1].offset]; + if (num_bits == 32) { + return SPV_SUCCESS; + } + if (num_bits == 16) { + if (_.features().declare_float16_type) { + return SPV_SUCCESS; + } + return _.diag(SPV_ERROR_INVALID_DATA) + << "Using a 16-bit floating point " + << "type requires the Float16 or Float16Buffer capability," + " or an extension that explicitly enables 16-bit floating point."; + } + if (num_bits == 64) { + if (_.HasCapability(SpvCapabilityFloat64)) { + return SPV_SUCCESS; + } + return _.diag(SPV_ERROR_INVALID_DATA) + << "Using a 64-bit floating point " + << "type requires the Float64 capability."; + } + return _.diag(SPV_ERROR_INVALID_DATA) + << "Invalid number of bits (" << num_bits << ") used for OpTypeFloat."; +} + +// Validates that the number of bits specified for an Int type is valid. +// Scalar integer types can be parameterized only with 32-bits. +// Int8, Int16, and Int64 capabilities allow using 8-bit, 16-bit, and 64-bit +// integers, respectively. +spv_result_t ValidateIntSize(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + // Operand 1 is the number of bits for this integer. + const uint32_t num_bits = inst->words[inst->operands[1].offset]; + if (num_bits == 32) { + return SPV_SUCCESS; + } + if (num_bits == 8) { + if (_.HasCapability(SpvCapabilityInt8)) { + return SPV_SUCCESS; + } + return _.diag(SPV_ERROR_INVALID_DATA) + << "Using an 8-bit integer type requires the Int8 capability."; + } + if (num_bits == 16) { + if (_.features().declare_int16_type) { + return SPV_SUCCESS; + } + return _.diag(SPV_ERROR_INVALID_DATA) + << "Using a 16-bit integer type requires the Int16 capability," + " or an extension that explicitly enables 16-bit integers."; + } + if (num_bits == 64) { + if (_.HasCapability(SpvCapabilityInt64)) { + return SPV_SUCCESS; + } + return _.diag(SPV_ERROR_INVALID_DATA) + << "Using a 64-bit integer type requires the Int64 capability."; + } + return _.diag(SPV_ERROR_INVALID_DATA) + << "Invalid number of bits (" << num_bits << ") used for OpTypeInt."; +} + +// Validates that the matrix is parameterized with floating-point types. +spv_result_t ValidateMatrixColumnType(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + // Find the component type of matrix columns (must be vector). + // Operand 1 is the of the type specified for matrix columns. + auto type_id = inst->words[inst->operands[1].offset]; + auto col_type_instr = _.FindDef(type_id); + if (col_type_instr->opcode() != SpvOpTypeVector) { + return _.diag(SPV_ERROR_INVALID_ID) + << "Columns in a matrix must be of type vector."; + } + + // Trace back once more to find out the type of components in the vector. + // Operand 1 is the of the type of data in the vector. + auto comp_type_id = + col_type_instr->words()[col_type_instr->operands()[1].offset]; + auto comp_type_instruction = _.FindDef(comp_type_id); + if (comp_type_instruction->opcode() != SpvOpTypeFloat) { + return _.diag(SPV_ERROR_INVALID_DATA) << "Matrix types can only be " + "parameterized with " + "floating-point types."; + } + return SPV_SUCCESS; +} + +// Validates that the matrix has 2,3, or 4 columns. +spv_result_t ValidateMatrixNumCols(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + // Operand 2 is the number of columns in the matrix. + const uint32_t num_cols = inst->words[inst->operands[2].offset]; + if (num_cols != 2 && num_cols != 3 && num_cols != 4) { + return _.diag(SPV_ERROR_INVALID_DATA) << "Matrix types can only be " + "parameterized as having only 2, " + "3, or 4 columns."; + } + return SPV_SUCCESS; +} + +// Validates that OpSpecConstant specializes to either int or float type. +spv_result_t ValidateSpecConstNumerical(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + // Operand 0 is the of the type that we're specializing to. + auto type_id = inst->words[inst->operands[0].offset]; + auto type_instruction = _.FindDef(type_id); + auto type_opcode = type_instruction->opcode(); + if (type_opcode != SpvOpTypeInt && type_opcode != SpvOpTypeFloat) { + return _.diag(SPV_ERROR_INVALID_DATA) << "Specialization constant must be " + "an integer or floating-point " + "number."; + } + return SPV_SUCCESS; +} + +// Validates that OpSpecConstantTrue and OpSpecConstantFalse specialize to bool. +spv_result_t ValidateSpecConstBoolean(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + // Find out the type that we're specializing to. + auto type_instruction = _.FindDef(inst->type_id); + if (type_instruction->opcode() != SpvOpTypeBool) { + return _.diag(SPV_ERROR_INVALID_ID) << "Specialization constant must be " + "a boolean type."; + } + return SPV_SUCCESS; +} + +// Records the of the forward pointer to be used for validation. +spv_result_t ValidateForwardPointer(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + // Record the (which is operand 0) to ensure it's used properly. + // OpTypeStruct can only include undefined pointers that are + // previously declared as a ForwardPointer + return (_.RegisterForwardPointer(inst->words[inst->operands[0].offset])); +} + +// Validates that any undefined component of the struct is a forward pointer. +// It is valid to declare a forward pointer, and use its as one of the +// components of a struct. +spv_result_t ValidateStruct(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + // Struct components are operands 1, 2, etc. + for (unsigned i = 1; i < inst->num_operands; i++) { + auto type_id = inst->words[inst->operands[i].offset]; + auto type_instruction = _.FindDef(type_id); + if (type_instruction == nullptr && !_.IsForwardPointer(type_id)) { + return _.diag(SPV_ERROR_INVALID_ID) + << "Forward reference operands in an OpTypeStruct must first be " + "declared using OpTypeForwardPointer."; + } + } + return SPV_SUCCESS; +} + +} // anonymous namespace + +namespace libspirv { + +// Validates that Data Rules are followed according to the specifications. +// (Data Rules subsection of 2.16.1 Universal Validation Rules) +spv_result_t DataRulesPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + switch (inst->opcode) { + case SpvOpTypeVector: { + if (auto error = ValidateVecNumComponents(_, inst)) return error; + break; + } + case SpvOpTypeFloat: { + if (auto error = ValidateFloatSize(_, inst)) return error; + break; + } + case SpvOpTypeInt: { + if (auto error = ValidateIntSize(_, inst)) return error; + break; + } + case SpvOpTypeMatrix: { + if (auto error = ValidateMatrixColumnType(_, inst)) return error; + if (auto error = ValidateMatrixNumCols(_, inst)) return error; + break; + } + // TODO(ehsan): Add OpSpecConstantComposite validation code. + // TODO(ehsan): Add OpSpecConstantOp validation code (if any). + case SpvOpSpecConstant: { + if (auto error = ValidateSpecConstNumerical(_, inst)) return error; + break; + } + case SpvOpSpecConstantFalse: + case SpvOpSpecConstantTrue: { + if (auto error = ValidateSpecConstBoolean(_, inst)) return error; + break; + } + case SpvOpTypeForwardPointer: { + if (auto error = ValidateForwardPointer(_, inst)) return error; + break; + } + case SpvOpTypeStruct: { + if (auto error = ValidateStruct(_, inst)) return error; + break; + } + // TODO(ehsan): add more data rules validation here. + default: { break; } + } + + return SPV_SUCCESS; +} + +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/validate_decorations.cpp b/3rdparty/spirv-tools/source/validate_decorations.cpp new file mode 100644 index 000000000..11d3ebca9 --- /dev/null +++ b/3rdparty/spirv-tools/source/validate_decorations.cpp @@ -0,0 +1,195 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "validate.h" + +#include +#include + +#include "diagnostic.h" +#include "opcode.h" +#include "spirv_target_env.h" +#include "val/validation_state.h" + +using libspirv::Decoration; +using libspirv::DiagnosticStream; +using libspirv::Instruction; +using libspirv::ValidationState_t; + +namespace { + +// Returns whether the given variable has a BuiltIn decoration. +bool isBuiltInVar(uint32_t var_id, ValidationState_t& vstate) { + const auto& decorations = vstate.id_decorations(var_id); + return std::any_of( + decorations.begin(), decorations.end(), + [](const Decoration& d) { return SpvDecorationBuiltIn == d.dec_type(); }); +} + +// Returns whether the given structure type has any members with BuiltIn +// decoration. +bool isBuiltInStruct(uint32_t struct_id, ValidationState_t& vstate) { + const auto& decorations = vstate.id_decorations(struct_id); + return std::any_of( + decorations.begin(), decorations.end(), [](const Decoration& d) { + return SpvDecorationBuiltIn == d.dec_type() && + Decoration::kInvalidMember != d.struct_member_index(); + }); +} + +// Returns true if the given ID has the Import LinkageAttributes decoration. +bool hasImportLinkageAttribute(uint32_t id, ValidationState_t& vstate) { + const auto& decorations = vstate.id_decorations(id); + return std::any_of(decorations.begin(), decorations.end(), + [](const Decoration& d) { + return SpvDecorationLinkageAttributes == d.dec_type() && + d.params().size() >= 2u && + d.params().back() == SpvLinkageTypeImport; + }); +} + +spv_result_t CheckLinkageAttrOfFunctions(ValidationState_t& vstate) { + for (const auto& function : vstate.functions()) { + if (function.block_count() == 0u) { + // A function declaration (an OpFunction with no basic blocks), must have + // a Linkage Attributes Decoration with the Import Linkage Type. + if (!hasImportLinkageAttribute(function.id(), vstate)) { + return vstate.diag(SPV_ERROR_INVALID_BINARY) + << "Function declaration (id " << function.id() + << ") must have a LinkageAttributes decoration with the Import " + "Linkage type."; + } + } else { + if (hasImportLinkageAttribute(function.id(), vstate)) { + return vstate.diag(SPV_ERROR_INVALID_BINARY) + << "Function definition (id " << function.id() + << ") may not be decorated with Import Linkage type."; + } + } + } + return SPV_SUCCESS; +} + +// Checks whether an imported variable is initialized by this module. +spv_result_t CheckImportedVariableInitialization(ValidationState_t& vstate) { + // According the SPIR-V Spec 2.16.1, it is illegal to initialize an imported + // variable. This means that a module-scope OpVariable with initialization + // value cannot be marked with the Import Linkage Type (import type id = 1). + for (auto global_var_id : vstate.global_vars()) { + // Initializer is an optional argument for OpVariable. If initializer + // is present, the instruction will have 5 words. + auto variable_instr = vstate.FindDef(global_var_id); + if (variable_instr->words().size() == 5u && + hasImportLinkageAttribute(global_var_id, vstate)) { + return vstate.diag(SPV_ERROR_INVALID_ID) + << "A module-scope OpVariable with initialization value " + "cannot be marked with the Import Linkage Type."; + } + } + return SPV_SUCCESS; +} + +// Checks whether a builtin variable is valid. +spv_result_t CheckBuiltInVariable(uint32_t var_id, ValidationState_t& vstate) { + const auto& decorations = vstate.id_decorations(var_id); + for (const auto& d : decorations) { + if (spvIsVulkanEnv(vstate.context()->target_env)) { + if (d.dec_type() == SpvDecorationLocation || + d.dec_type() == SpvDecorationComponent) { + return vstate.diag(SPV_ERROR_INVALID_ID) + << "A BuiltIn variable (id " << var_id + << ") cannot have any Location or Component decorations"; + } + } + } + return SPV_SUCCESS; +} + +// Checks whether proper decorations have been appied to the entry points. +spv_result_t CheckDecorationsOfEntryPoints(ValidationState_t& vstate) { + for (uint32_t entry_point : vstate.entry_points()) { + const auto& interfaces = vstate.entry_point_interfaces(entry_point); + int num_builtin_inputs = 0; + int num_builtin_outputs = 0; + for (auto interface : interfaces) { + Instruction* var_instr = vstate.FindDef(interface); + if (SpvOpVariable != var_instr->opcode()) { + return vstate.diag(SPV_ERROR_INVALID_ID) + << "Interfaces passed to OpEntryPoint must be of type " + "OpTypeVariable. Found Op" + << spvOpcodeString(static_cast(var_instr->opcode())) + << "."; + } + const uint32_t ptr_id = var_instr->word(1); + Instruction* ptr_instr = vstate.FindDef(ptr_id); + // It is guaranteed (by validator ID checks) that ptr_instr is + // OpTypePointer. Word 3 of this instruction is the type being pointed to. + const uint32_t type_id = ptr_instr->word(3); + Instruction* type_instr = vstate.FindDef(type_id); + const auto storage_class = + static_cast(var_instr->word(3)); + if (storage_class != SpvStorageClassInput && + storage_class != SpvStorageClassOutput) { + return vstate.diag(SPV_ERROR_INVALID_ID) + << "OpEntryPoint interfaces must be OpVariables with " + "Storage Class of Input(1) or Output(3). Found Storage Class " + << storage_class << " for Entry Point id " << entry_point << "."; + } + if (type_instr && SpvOpTypeStruct == type_instr->opcode() && + isBuiltInStruct(type_id, vstate)) { + if (storage_class == SpvStorageClassInput) ++num_builtin_inputs; + if (storage_class == SpvStorageClassOutput) ++num_builtin_outputs; + if (num_builtin_inputs > 1 || num_builtin_outputs > 1) break; + if (auto error = CheckBuiltInVariable(interface, vstate)) return error; + } else if (isBuiltInVar(interface, vstate)) { + if (auto error = CheckBuiltInVariable(interface, vstate)) return error; + } + } + if (num_builtin_inputs > 1 || num_builtin_outputs > 1) { + return vstate.diag(SPV_ERROR_INVALID_BINARY) + << "There must be at most one object per Storage Class that can " + "contain a structure type containing members decorated with " + "BuiltIn, consumed per entry-point. Entry Point id " + << entry_point << " does not meet this requirement."; + } + // The LinkageAttributes Decoration cannot be applied to functions targeted + // by an OpEntryPoint instruction + for (auto& decoration : vstate.id_decorations(entry_point)) { + if (SpvDecorationLinkageAttributes == decoration.dec_type()) { + const char* linkage_name = + reinterpret_cast(&decoration.params()[0]); + return vstate.diag(SPV_ERROR_INVALID_BINARY) + << "The LinkageAttributes Decoration (Linkage name: " + << linkage_name << ") cannot be applied to function id " + << entry_point + << " because it is targeted by an OpEntryPoint instruction."; + } + } + } + return SPV_SUCCESS; +} + +} // anonymous namespace + +namespace libspirv { + +// Validates that decorations have been applied properly. +spv_result_t ValidateDecorations(ValidationState_t& vstate) { + if (auto error = CheckImportedVariableInitialization(vstate)) return error; + if (auto error = CheckDecorationsOfEntryPoints(vstate)) return error; + if (auto error = CheckLinkageAttrOfFunctions(vstate)) return error; + return SPV_SUCCESS; +} + +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/validate_derivatives.cpp b/3rdparty/spirv-tools/source/validate_derivatives.cpp new file mode 100644 index 000000000..299f027f8 --- /dev/null +++ b/3rdparty/spirv-tools/source/validate_derivatives.cpp @@ -0,0 +1,69 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Validates correctness of derivative SPIR-V instructions. + +#include "validate.h" + +#include "diagnostic.h" +#include "opcode.h" +#include "val/instruction.h" +#include "val/validation_state.h" + +namespace libspirv { + +// Validates correctness of derivative instructions. +spv_result_t DerivativesPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + const SpvOp opcode = static_cast(inst->opcode); + const uint32_t result_type = inst->type_id; + + switch (opcode) { + case SpvOpDPdx: + case SpvOpDPdy: + case SpvOpFwidth: + case SpvOpDPdxFine: + case SpvOpDPdyFine: + case SpvOpFwidthFine: + case SpvOpDPdxCoarse: + case SpvOpDPdyCoarse: + case SpvOpFwidthCoarse: { + if (!_.IsFloatScalarOrVectorType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Result Type to be float scalar or vector type: " + << spvOpcodeString(opcode); + } + + const uint32_t p_type = _.GetOperandTypeId(inst, 2); + if (p_type != result_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected P type and Result Type to be the same: " + << spvOpcodeString(opcode); + } + + _.current_function().RegisterExecutionModelLimitation( + SpvExecutionModelFragment, std::string( + "Derivative instructions require Fragment execution model: ") + + spvOpcodeString(opcode)); + break; + } + + default: + break; + } + + return SPV_SUCCESS; +} + +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/validate_ext_inst.cpp b/3rdparty/spirv-tools/source/validate_ext_inst.cpp new file mode 100644 index 000000000..e74dbdc2d --- /dev/null +++ b/3rdparty/spirv-tools/source/validate_ext_inst.cpp @@ -0,0 +1,1976 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Validates correctness of ExtInst SPIR-V instructions. + +#include "validate.h" + +#include + +#include "latest_version_glsl_std_450_header.h" +#include "latest_version_opencl_std_header.h" + +#include "diagnostic.h" +#include "opcode.h" +#include "val/instruction.h" +#include "val/validation_state.h" + +namespace libspirv { + +namespace { + +uint32_t GetSizeTBitWidth(const ValidationState_t& _) { + if (_.addressing_model() == SpvAddressingModelPhysical32) return 32; + + if (_.addressing_model() == SpvAddressingModelPhysical64) return 64; + + return 0; +} + +} // anonymous namespace + +// Validates correctness of ExtInst instructions. +spv_result_t ExtInstPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + const SpvOp opcode = static_cast(inst->opcode); + const uint32_t result_type = inst->type_id; + const uint32_t num_operands = inst->num_operands; + + if (opcode != SpvOpExtInst) return SPV_SUCCESS; + + const uint32_t ext_inst_set = inst->words[3]; + const uint32_t ext_inst_index = inst->words[4]; + const spv_ext_inst_type_t ext_inst_type = + spv_ext_inst_type_t(inst->ext_inst_type); + + auto ext_inst_name = [&_, ext_inst_set, ext_inst_type, ext_inst_index]() { + spv_ext_inst_desc desc = nullptr; + if (_.grammar().lookupExtInst(ext_inst_type, ext_inst_index, &desc) != + SPV_SUCCESS || + !desc) { + return std::string("Unknown ExtInst"); + } + + auto* import_inst = _.FindDef(ext_inst_set); + assert(import_inst); + + std::ostringstream ss; + ss << reinterpret_cast(import_inst->words().data() + 2); + ss << " "; + ss << desc->name; + + return ss.str(); + }; + + if (ext_inst_type == SPV_EXT_INST_TYPE_GLSL_STD_450) { + const GLSLstd450 ext_inst_key = GLSLstd450(ext_inst_index); + switch (ext_inst_key) { + case GLSLstd450Round: + case GLSLstd450RoundEven: + case GLSLstd450FAbs: + case GLSLstd450Trunc: + case GLSLstd450FSign: + case GLSLstd450Floor: + case GLSLstd450Ceil: + case GLSLstd450Fract: + case GLSLstd450Sqrt: + case GLSLstd450InverseSqrt: + case GLSLstd450FMin: + case GLSLstd450FMax: + case GLSLstd450FClamp: + case GLSLstd450FMix: + case GLSLstd450Step: + case GLSLstd450SmoothStep: + case GLSLstd450Fma: + case GLSLstd450Normalize: + case GLSLstd450FaceForward: + case GLSLstd450Reflect: + case GLSLstd450NMin: + case GLSLstd450NMax: + case GLSLstd450NClamp: { + if (!_.IsFloatScalarOrVectorType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a float scalar or vector type"; + } + + for (uint32_t operand_index = 4; operand_index < num_operands; + ++operand_index) { + const uint32_t operand_type = _.GetOperandTypeId(inst, operand_index); + if (result_type != operand_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected types of all operands to be equal to Result " + "Type"; + } + } + break; + } + + case GLSLstd450SAbs: + case GLSLstd450SSign: + case GLSLstd450UMin: + case GLSLstd450SMin: + case GLSLstd450UMax: + case GLSLstd450SMax: + case GLSLstd450UClamp: + case GLSLstd450SClamp: + case GLSLstd450FindILsb: + case GLSLstd450FindUMsb: + case GLSLstd450FindSMsb: { + if (!_.IsIntScalarOrVectorType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be an int scalar or vector type"; + } + + const uint32_t result_type_bit_width = _.GetBitWidth(result_type); + const uint32_t result_type_dimension = _.GetDimension(result_type); + + for (uint32_t operand_index = 4; operand_index < num_operands; + ++operand_index) { + const uint32_t operand_type = _.GetOperandTypeId(inst, operand_index); + if (!_.IsIntScalarOrVectorType(operand_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected all operands to be int scalars or vectors"; + } + + if (result_type_dimension != _.GetDimension(operand_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected all operands to have the same dimension as " + << "Result Type"; + } + + if (result_type_bit_width != _.GetBitWidth(operand_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected all operands to have the same bit width as " + << "Result Type"; + } + + if (ext_inst_key == GLSLstd450FindUMsb || + ext_inst_key == GLSLstd450FindSMsb) { + if (result_type_bit_width != 32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "this instruction is currently limited to 32-bit width " + << "components"; + } + } + } + break; + } + + case GLSLstd450Radians: + case GLSLstd450Degrees: + case GLSLstd450Sin: + case GLSLstd450Cos: + case GLSLstd450Tan: + case GLSLstd450Asin: + case GLSLstd450Acos: + case GLSLstd450Atan: + case GLSLstd450Sinh: + case GLSLstd450Cosh: + case GLSLstd450Tanh: + case GLSLstd450Asinh: + case GLSLstd450Acosh: + case GLSLstd450Atanh: + case GLSLstd450Exp: + case GLSLstd450Exp2: + case GLSLstd450Log: + case GLSLstd450Log2: + case GLSLstd450Atan2: + case GLSLstd450Pow: { + if (!_.IsFloatScalarOrVectorType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a 16 or 32-bit scalar or " + "vector float type"; + } + + const uint32_t result_type_bit_width = _.GetBitWidth(result_type); + if (result_type_bit_width != 16 && result_type_bit_width != 32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a 16 or 32-bit scalar or " + "vector float type"; + } + + for (uint32_t operand_index = 4; operand_index < num_operands; + ++operand_index) { + const uint32_t operand_type = _.GetOperandTypeId(inst, operand_index); + if (result_type != operand_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected types of all operands to be equal to Result " + "Type"; + } + } + break; + } + + case GLSLstd450Determinant: { + const uint32_t x_type = _.GetOperandTypeId(inst, 4); + uint32_t num_rows = 0; + uint32_t num_cols = 0; + uint32_t col_type = 0; + uint32_t component_type = 0; + if (!_.GetMatrixTypeInfo(x_type, &num_rows, &num_cols, &col_type, + &component_type) || + num_rows != num_cols) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand X to be a square matrix"; + } + + if (result_type != component_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand X component type to be equal to " + << "Result Type"; + } + break; + } + + case GLSLstd450MatrixInverse: { + uint32_t num_rows = 0; + uint32_t num_cols = 0; + uint32_t col_type = 0; + uint32_t component_type = 0; + if (!_.GetMatrixTypeInfo(result_type, &num_rows, &num_cols, &col_type, + &component_type) || + num_rows != num_cols) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a square matrix"; + } + + const uint32_t x_type = _.GetOperandTypeId(inst, 4); + if (result_type != x_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand X type to be equal to Result Type"; + } + break; + } + + case GLSLstd450Modf: { + if (!_.IsFloatScalarOrVectorType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a scalar or vector float type"; + } + + const uint32_t x_type = _.GetOperandTypeId(inst, 4); + const uint32_t i_type = _.GetOperandTypeId(inst, 5); + + if (x_type != result_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand X type to be equal to Result Type"; + } + + uint32_t i_storage_class = 0; + uint32_t i_data_type = 0; + if (!_.GetPointerTypeInfo(i_type, &i_data_type, &i_storage_class)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand I to be a pointer"; + } + + if (i_data_type != result_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand I data type to be equal to Result Type"; + } + + break; + } + + case GLSLstd450ModfStruct: { + std::vector result_types; + if (!_.GetStructMemberTypes(result_type, &result_types) || + result_types.size() != 2 || + !_.IsFloatScalarOrVectorType(result_types[0]) || + result_types[1] != result_types[0]) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a struct with two identical " + << "scalar or vector float type members"; + } + + const uint32_t x_type = _.GetOperandTypeId(inst, 4); + if (x_type != result_types[0]) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand X type to be equal to members of " + << "Result Type struct"; + } + break; + } + + case GLSLstd450Frexp: { + if (!_.IsFloatScalarOrVectorType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a scalar or vector float type"; + } + + const uint32_t x_type = _.GetOperandTypeId(inst, 4); + const uint32_t exp_type = _.GetOperandTypeId(inst, 5); + + if (x_type != result_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand X type to be equal to Result Type"; + } + + uint32_t exp_storage_class = 0; + uint32_t exp_data_type = 0; + if (!_.GetPointerTypeInfo(exp_type, &exp_data_type, + &exp_storage_class)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand Exp to be a pointer"; + } + + if (!_.IsIntScalarOrVectorType(exp_data_type) || + _.GetBitWidth(exp_data_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand Exp data type to be a 32-bit int scalar " + << "or vector type"; + } + + if (_.GetDimension(result_type) != _.GetDimension(exp_data_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand Exp data type to have the same component " + << "number as Result Type"; + } + + break; + } + + case GLSLstd450Ldexp: { + if (!_.IsFloatScalarOrVectorType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a scalar or vector float type"; + } + + const uint32_t x_type = _.GetOperandTypeId(inst, 4); + const uint32_t exp_type = _.GetOperandTypeId(inst, 5); + + if (x_type != result_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand X type to be equal to Result Type"; + } + + if (!_.IsIntScalarOrVectorType(exp_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand Exp to be a 32-bit int scalar " + << "or vector type"; + } + + if (_.GetDimension(result_type) != _.GetDimension(exp_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand Exp to have the same component " + << "number as Result Type"; + } + + break; + } + + case GLSLstd450FrexpStruct: { + std::vector result_types; + if (!_.GetStructMemberTypes(result_type, &result_types) || + result_types.size() != 2 || + !_.IsFloatScalarOrVectorType(result_types[0]) || + !_.IsIntScalarOrVectorType(result_types[1]) || + _.GetBitWidth(result_types[1]) != 32 || + _.GetDimension(result_types[0]) != + _.GetDimension(result_types[1])) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a struct with two members, " + << "first member a float scalar or vector, second member " + << "a 32-bit int scalar or vector with the same number of " + << "components as the first member"; + } + + const uint32_t x_type = _.GetOperandTypeId(inst, 4); + if (x_type != result_types[0]) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand X type to be equal to the first member " + << "of Result Type struct"; + } + break; + } + + case GLSLstd450PackSnorm4x8: + case GLSLstd450PackUnorm4x8: { + if (!_.IsIntScalarType(result_type) || + _.GetBitWidth(result_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be 32-bit int scalar type"; + } + + const uint32_t v_type = _.GetOperandTypeId(inst, 4); + if (!_.IsFloatVectorType(v_type) || _.GetDimension(v_type) != 4 || + _.GetBitWidth(v_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand V to be a 32-bit float vector of size 4"; + } + break; + } + + case GLSLstd450PackSnorm2x16: + case GLSLstd450PackUnorm2x16: + case GLSLstd450PackHalf2x16: { + if (!_.IsIntScalarType(result_type) || + _.GetBitWidth(result_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be 32-bit int scalar type"; + } + + const uint32_t v_type = _.GetOperandTypeId(inst, 4); + if (!_.IsFloatVectorType(v_type) || _.GetDimension(v_type) != 2 || + _.GetBitWidth(v_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand V to be a 32-bit float vector of size 2"; + } + break; + } + + case GLSLstd450PackDouble2x32: { + if (!_.IsFloatScalarType(result_type) || + _.GetBitWidth(result_type) != 64) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be 64-bit float scalar type"; + } + + const uint32_t v_type = _.GetOperandTypeId(inst, 4); + if (!_.IsIntVectorType(v_type) || _.GetDimension(v_type) != 2 || + _.GetBitWidth(v_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand V to be a 32-bit int vector of size 2"; + } + break; + } + + case GLSLstd450UnpackSnorm4x8: + case GLSLstd450UnpackUnorm4x8: { + if (!_.IsFloatVectorType(result_type) || + _.GetDimension(result_type) != 4 || + _.GetBitWidth(result_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a 32-bit float vector of size " + "4"; + } + + const uint32_t v_type = _.GetOperandTypeId(inst, 4); + if (!_.IsIntScalarType(v_type) || _.GetBitWidth(v_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand P to be a 32-bit int scalar"; + } + break; + } + + case GLSLstd450UnpackSnorm2x16: + case GLSLstd450UnpackUnorm2x16: + case GLSLstd450UnpackHalf2x16: { + if (!_.IsFloatVectorType(result_type) || + _.GetDimension(result_type) != 2 || + _.GetBitWidth(result_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a 32-bit float vector of size " + "2"; + } + + const uint32_t v_type = _.GetOperandTypeId(inst, 4); + if (!_.IsIntScalarType(v_type) || _.GetBitWidth(v_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand P to be a 32-bit int scalar"; + } + break; + } + + case GLSLstd450UnpackDouble2x32: { + if (!_.IsIntVectorType(result_type) || + _.GetDimension(result_type) != 2 || + _.GetBitWidth(result_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a 32-bit int vector of size " + "2"; + } + + const uint32_t v_type = _.GetOperandTypeId(inst, 4); + if (!_.IsFloatScalarType(v_type) || _.GetBitWidth(v_type) != 64) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand V to be a 64-bit float scalar"; + } + break; + } + + case GLSLstd450Length: { + if (!_.IsFloatScalarType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a float scalar type"; + } + + const uint32_t x_type = _.GetOperandTypeId(inst, 4); + if (!_.IsFloatScalarOrVectorType(x_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand X to be of float scalar or vector type"; + } + + if (result_type != _.GetComponentType(x_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand X component type to be equal to Result " + "Type"; + } + break; + } + + case GLSLstd450Distance: { + if (!_.IsFloatScalarType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a float scalar type"; + } + + const uint32_t p0_type = _.GetOperandTypeId(inst, 4); + if (!_.IsFloatScalarOrVectorType(p0_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand P0 to be of float scalar or vector type"; + } + + if (result_type != _.GetComponentType(p0_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand P0 component type to be equal to " + << "Result Type"; + } + + const uint32_t p1_type = _.GetOperandTypeId(inst, 5); + if (!_.IsFloatScalarOrVectorType(p1_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand P1 to be of float scalar or vector type"; + } + + if (result_type != _.GetComponentType(p1_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand P1 component type to be equal to " + << "Result Type"; + } + + if (_.GetDimension(p0_type) != _.GetDimension(p1_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operands P0 and P1 to have the same number of " + << "components"; + } + break; + } + + case GLSLstd450Cross: { + if (!_.IsFloatVectorType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a float vector type"; + } + + if (_.GetDimension(result_type) != 3) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to have 3 components"; + } + + const uint32_t x_type = _.GetOperandTypeId(inst, 4); + const uint32_t y_type = _.GetOperandTypeId(inst, 5); + + if (x_type != result_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand X type to be equal to Result Type"; + } + + if (y_type != result_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand Y type to be equal to Result Type"; + } + break; + } + + case GLSLstd450Refract: { + if (!_.IsFloatScalarOrVectorType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a float scalar or vector type"; + } + + const uint32_t i_type = _.GetOperandTypeId(inst, 4); + const uint32_t n_type = _.GetOperandTypeId(inst, 5); + const uint32_t eta_type = _.GetOperandTypeId(inst, 6); + + if (result_type != i_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand I to be of type equal to Result Type"; + } + + if (result_type != n_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand N to be of type equal to Result Type"; + } + + const uint32_t eta_type_bit_width = _.GetBitWidth(eta_type); + if (!_.IsFloatScalarType(eta_type) || + (eta_type_bit_width != 16 && eta_type_bit_width != 32)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand Eta to be a 16 or 32-bit float scalar"; + } + break; + } + + case GLSLstd450InterpolateAtCentroid: + case GLSLstd450InterpolateAtSample: + case GLSLstd450InterpolateAtOffset: { + if (!_.HasCapability(SpvCapabilityInterpolationFunction)) { + return _.diag(SPV_ERROR_INVALID_CAPABILITY) + << ext_inst_name() + << " requires capability InterpolationFunction"; + } + + if (!_.IsFloatScalarOrVectorType(result_type) || + _.GetBitWidth(result_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a 32-bit float scalar " + << "or vector type"; + } + + const uint32_t interpolant_type = _.GetOperandTypeId(inst, 4); + uint32_t interpolant_storage_class = 0; + uint32_t interpolant_data_type = 0; + if (!_.GetPointerTypeInfo(interpolant_type, &interpolant_data_type, + &interpolant_storage_class)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Interpolant to be a pointer"; + } + + if (result_type != interpolant_data_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Interpolant data type to be equal to Result Type"; + } + + if (interpolant_storage_class != SpvStorageClassInput) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Interpolant storage class to be Input"; + } + + if (ext_inst_key == GLSLstd450InterpolateAtSample) { + const uint32_t sample_type = _.GetOperandTypeId(inst, 5); + if (!_.IsIntScalarType(sample_type) || + _.GetBitWidth(sample_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Sample to be 32-bit integer"; + } + } + + if (ext_inst_key == GLSLstd450InterpolateAtOffset) { + const uint32_t offset_type = _.GetOperandTypeId(inst, 5); + if (!_.IsFloatVectorType(offset_type) || + _.GetDimension(offset_type) != 2 || + _.GetBitWidth(offset_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Offset to be a vector of 2 32-bit floats"; + } + } + + _.current_function().RegisterExecutionModelLimitation( + SpvExecutionModelFragment, + ext_inst_name() + + std::string(" requires Fragment execution model")); + break; + } + + case GLSLstd450IMix: { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Extended instruction GLSLstd450IMix is not supported"; + } + + case GLSLstd450Bad: { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Encountered extended instruction GLSLstd450Bad"; + } + + case GLSLstd450Count: { + assert(0); + break; + } + } + } else if (ext_inst_type == SPV_EXT_INST_TYPE_OPENCL_STD) { + const OpenCLLIB::Entrypoints ext_inst_key = + OpenCLLIB::Entrypoints(ext_inst_index); + switch (ext_inst_key) { + case OpenCLLIB::Acos: + case OpenCLLIB::Acosh: + case OpenCLLIB::Acospi: + case OpenCLLIB::Asin: + case OpenCLLIB::Asinh: + case OpenCLLIB::Asinpi: + case OpenCLLIB::Atan: + case OpenCLLIB::Atan2: + case OpenCLLIB::Atanh: + case OpenCLLIB::Atanpi: + case OpenCLLIB::Atan2pi: + case OpenCLLIB::Cbrt: + case OpenCLLIB::Ceil: + case OpenCLLIB::Copysign: + case OpenCLLIB::Cos: + case OpenCLLIB::Cosh: + case OpenCLLIB::Cospi: + case OpenCLLIB::Erfc: + case OpenCLLIB::Erf: + case OpenCLLIB::Exp: + case OpenCLLIB::Exp2: + case OpenCLLIB::Exp10: + case OpenCLLIB::Expm1: + case OpenCLLIB::Fabs: + case OpenCLLIB::Fdim: + case OpenCLLIB::Floor: + case OpenCLLIB::Fma: + case OpenCLLIB::Fmax: + case OpenCLLIB::Fmin: + case OpenCLLIB::Fmod: + case OpenCLLIB::Hypot: + case OpenCLLIB::Lgamma: + case OpenCLLIB::Log: + case OpenCLLIB::Log2: + case OpenCLLIB::Log10: + case OpenCLLIB::Log1p: + case OpenCLLIB::Logb: + case OpenCLLIB::Mad: + case OpenCLLIB::Maxmag: + case OpenCLLIB::Minmag: + case OpenCLLIB::Nextafter: + case OpenCLLIB::Pow: + case OpenCLLIB::Powr: + case OpenCLLIB::Remainder: + case OpenCLLIB::Rint: + case OpenCLLIB::Round: + case OpenCLLIB::Rsqrt: + case OpenCLLIB::Sin: + case OpenCLLIB::Sinh: + case OpenCLLIB::Sinpi: + case OpenCLLIB::Sqrt: + case OpenCLLIB::Tan: + case OpenCLLIB::Tanh: + case OpenCLLIB::Tanpi: + case OpenCLLIB::Tgamma: + case OpenCLLIB::Trunc: + case OpenCLLIB::Half_cos: + case OpenCLLIB::Half_divide: + case OpenCLLIB::Half_exp: + case OpenCLLIB::Half_exp2: + case OpenCLLIB::Half_exp10: + case OpenCLLIB::Half_log: + case OpenCLLIB::Half_log2: + case OpenCLLIB::Half_log10: + case OpenCLLIB::Half_powr: + case OpenCLLIB::Half_recip: + case OpenCLLIB::Half_rsqrt: + case OpenCLLIB::Half_sin: + case OpenCLLIB::Half_sqrt: + case OpenCLLIB::Half_tan: + case OpenCLLIB::Native_cos: + case OpenCLLIB::Native_divide: + case OpenCLLIB::Native_exp: + case OpenCLLIB::Native_exp2: + case OpenCLLIB::Native_exp10: + case OpenCLLIB::Native_log: + case OpenCLLIB::Native_log2: + case OpenCLLIB::Native_log10: + case OpenCLLIB::Native_powr: + case OpenCLLIB::Native_recip: + case OpenCLLIB::Native_rsqrt: + case OpenCLLIB::Native_sin: + case OpenCLLIB::Native_sqrt: + case OpenCLLIB::Native_tan: + case OpenCLLIB::FClamp: + case OpenCLLIB::Degrees: + case OpenCLLIB::FMax_common: + case OpenCLLIB::FMin_common: + case OpenCLLIB::Mix: + case OpenCLLIB::Radians: + case OpenCLLIB::Step: + case OpenCLLIB::Smoothstep: + case OpenCLLIB::Sign: { + if (!_.IsFloatScalarOrVectorType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a float scalar or vector type"; + } + + const uint32_t num_components = _.GetDimension(result_type); + if (num_components > 4 && num_components != 8 && num_components != 16) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a scalar or a vector with 2, " + "3, 4, 8 or 16 components"; + } + + for (uint32_t operand_index = 4; operand_index < num_operands; + ++operand_index) { + const uint32_t operand_type = _.GetOperandTypeId(inst, operand_index); + if (result_type != operand_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected types of all operands to be equal to Result " + "Type"; + } + } + break; + } + + case OpenCLLIB::Fract: + case OpenCLLIB::Modf: + case OpenCLLIB::Sincos: + case OpenCLLIB::Remquo: { + if (!_.IsFloatScalarOrVectorType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a float scalar or vector type"; + } + + const uint32_t num_components = _.GetDimension(result_type); + if (num_components > 4 && num_components != 8 && num_components != 16) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a scalar or a vector with 2, " + "3, 4, 8 or 16 components"; + } + + uint32_t operand_index = 4; + const uint32_t x_type = _.GetOperandTypeId(inst, operand_index++); + if (result_type != x_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected type of operand X to be equal to Result Type"; + } + + if (ext_inst_key == OpenCLLIB::Remquo) { + const uint32_t y_type = _.GetOperandTypeId(inst, operand_index++); + if (result_type != y_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected type of operand Y to be equal to Result Type"; + } + } + + const uint32_t p_type = _.GetOperandTypeId(inst, operand_index++); + uint32_t p_storage_class = 0; + uint32_t p_data_type = 0; + if (!_.GetPointerTypeInfo(p_type, &p_data_type, &p_storage_class)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected the last operand to be a pointer"; + } + + if (p_storage_class != SpvStorageClassGeneric && + p_storage_class != SpvStorageClassCrossWorkgroup && + p_storage_class != SpvStorageClassWorkgroup && + p_storage_class != SpvStorageClassFunction) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected storage class of the pointer to be Generic, " + "CrossWorkgroup, Workgroup or Function"; + } + + if (result_type != p_data_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected data type of the pointer to be equal to Result " + "Type"; + } + break; + } + + case OpenCLLIB::Frexp: + case OpenCLLIB::Lgamma_r: { + if (!_.IsFloatScalarOrVectorType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a float scalar or vector type"; + } + + const uint32_t num_components = _.GetDimension(result_type); + if (num_components > 4 && num_components != 8 && num_components != 16) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a scalar or a vector with 2, " + "3, 4, 8 or 16 components"; + } + + const uint32_t x_type = _.GetOperandTypeId(inst, 4); + if (result_type != x_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected type of operand X to be equal to Result Type"; + } + + const uint32_t p_type = _.GetOperandTypeId(inst, 5); + uint32_t p_storage_class = 0; + uint32_t p_data_type = 0; + if (!_.GetPointerTypeInfo(p_type, &p_data_type, &p_storage_class)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected the last operand to be a pointer"; + } + + if (p_storage_class != SpvStorageClassGeneric && + p_storage_class != SpvStorageClassCrossWorkgroup && + p_storage_class != SpvStorageClassWorkgroup && + p_storage_class != SpvStorageClassFunction) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected storage class of the pointer to be Generic, " + "CrossWorkgroup, Workgroup or Function"; + } + + if (!_.IsIntScalarOrVectorType(p_data_type) || + _.GetBitWidth(p_data_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected data type of the pointer to be a 32-bit int " + "scalar or vector type"; + } + + if (_.GetDimension(p_data_type) != num_components) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected data type of the pointer to have the same number " + "of components as Result Type"; + } + break; + } + + case OpenCLLIB::Ilogb: { + if (!_.IsIntScalarOrVectorType(result_type) || + _.GetBitWidth(result_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a 32-bit int scalar or vector " + "type"; + } + + const uint32_t num_components = _.GetDimension(result_type); + if (num_components > 4 && num_components != 8 && num_components != 16) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a scalar or a vector with 2, " + "3, 4, 8 or 16 components"; + } + + const uint32_t x_type = _.GetOperandTypeId(inst, 4); + if (!_.IsFloatScalarOrVectorType(x_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand X to be a float scalar or vector"; + } + + if (_.GetDimension(x_type) != num_components) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand X to have the same number of components " + "as Result Type"; + } + break; + } + + case OpenCLLIB::Ldexp: + case OpenCLLIB::Pown: + case OpenCLLIB::Rootn: { + if (!_.IsFloatScalarOrVectorType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a float scalar or vector type"; + } + + const uint32_t num_components = _.GetDimension(result_type); + if (num_components > 4 && num_components != 8 && num_components != 16) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a scalar or a vector with 2, " + "3, 4, 8 or 16 components"; + } + + const uint32_t x_type = _.GetOperandTypeId(inst, 4); + if (result_type != x_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected type of operand X to be equal to Result Type"; + } + + const uint32_t exp_type = _.GetOperandTypeId(inst, 5); + if (!_.IsIntScalarOrVectorType(exp_type) || + _.GetBitWidth(exp_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected the exponent to be a 32-bit int scalar or vector"; + } + + if (_.GetDimension(exp_type) != num_components) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected the exponent to have the same number of " + "components as Result Type"; + } + break; + } + + case OpenCLLIB::Nan: { + if (!_.IsFloatScalarOrVectorType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a float scalar or vector type"; + } + + const uint32_t num_components = _.GetDimension(result_type); + if (num_components > 4 && num_components != 8 && num_components != 16) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a scalar or a vector with 2, " + "3, 4, 8 or 16 components"; + } + + const uint32_t nancode_type = _.GetOperandTypeId(inst, 4); + if (!_.IsIntScalarOrVectorType(nancode_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Nancode to be an int scalar or vector type"; + } + + if (_.GetDimension(nancode_type) != num_components) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Nancode to have the same number of components as " + "Result Type"; + } + + if (_.GetBitWidth(result_type) != _.GetBitWidth(nancode_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Nancode to have the same bit width as Result " + "Type"; + } + break; + } + + case OpenCLLIB::SAbs: + case OpenCLLIB::SAbs_diff: + case OpenCLLIB::SAdd_sat: + case OpenCLLIB::UAdd_sat: + case OpenCLLIB::SHadd: + case OpenCLLIB::UHadd: + case OpenCLLIB::SRhadd: + case OpenCLLIB::URhadd: + case OpenCLLIB::SClamp: + case OpenCLLIB::UClamp: + case OpenCLLIB::Clz: + case OpenCLLIB::Ctz: + case OpenCLLIB::SMad_hi: + case OpenCLLIB::UMad_sat: + case OpenCLLIB::SMad_sat: + case OpenCLLIB::SMax: + case OpenCLLIB::UMax: + case OpenCLLIB::SMin: + case OpenCLLIB::UMin: + case OpenCLLIB::SMul_hi: + case OpenCLLIB::Rotate: + case OpenCLLIB::SSub_sat: + case OpenCLLIB::USub_sat: + case OpenCLLIB::Popcount: + case OpenCLLIB::UAbs: + case OpenCLLIB::UAbs_diff: + case OpenCLLIB::UMul_hi: + case OpenCLLIB::UMad_hi: { + if (!_.IsIntScalarOrVectorType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be an int scalar or vector type"; + } + + const uint32_t num_components = _.GetDimension(result_type); + if (num_components > 4 && num_components != 8 && num_components != 16) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a scalar or a vector with 2, " + "3, 4, 8 or 16 components"; + } + + for (uint32_t operand_index = 4; operand_index < num_operands; + ++operand_index) { + const uint32_t operand_type = _.GetOperandTypeId(inst, operand_index); + if (result_type != operand_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected types of all operands to be equal to Result " + "Type"; + } + } + break; + } + + case OpenCLLIB::U_Upsample: + case OpenCLLIB::S_Upsample: { + if (!_.IsIntScalarOrVectorType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be an int scalar or vector " + "type"; + } + + const uint32_t result_num_components = _.GetDimension(result_type); + if (result_num_components > 4 && result_num_components != 8 && + result_num_components != 16) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a scalar or a vector with 2, " + "3, 4, 8 or 16 components"; + } + + const uint32_t result_bit_width = _.GetBitWidth(result_type); + if (result_bit_width != 16 && result_bit_width != 32 && + result_bit_width != 64) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected bit width of Result Type components to be 16, 32 " + "or 64"; + } + + const uint32_t hi_type = _.GetOperandTypeId(inst, 4); + const uint32_t lo_type = _.GetOperandTypeId(inst, 5); + + if (hi_type != lo_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Hi and Lo operands to have the same type"; + } + + if (result_num_components != _.GetDimension(hi_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Hi and Lo operands to have the same number of " + "components as Result Type"; + } + + if (result_bit_width != 2 * _.GetBitWidth(hi_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected bit width of components of Hi and Lo operands to " + "be half of the bit width of components of Result Type"; + } + break; + } + + case OpenCLLIB::SMad24: + case OpenCLLIB::UMad24: + case OpenCLLIB::SMul24: + case OpenCLLIB::UMul24: { + if (!_.IsIntScalarOrVectorType(result_type) || + _.GetBitWidth(result_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a 32-bit int scalar or vector " + "type"; + } + + const uint32_t num_components = _.GetDimension(result_type); + if (num_components > 4 && num_components != 8 && num_components != 16) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a scalar or a vector with 2, " + "3, 4, 8 or 16 components"; + } + + for (uint32_t operand_index = 4; operand_index < num_operands; + ++operand_index) { + const uint32_t operand_type = _.GetOperandTypeId(inst, operand_index); + if (result_type != operand_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected types of all operands to be equal to Result " + "Type"; + } + } + break; + } + + case OpenCLLIB::Cross: { + if (!_.IsFloatVectorType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a float vector type"; + } + + const uint32_t num_components = _.GetDimension(result_type); + if (num_components != 3 && num_components != 4) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to have 3 or 4 components"; + } + + const uint32_t x_type = _.GetOperandTypeId(inst, 4); + const uint32_t y_type = _.GetOperandTypeId(inst, 5); + + if (x_type != result_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand X type to be equal to Result Type"; + } + + if (y_type != result_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand Y type to be equal to Result Type"; + } + break; + } + + case OpenCLLIB::Distance: + case OpenCLLIB::Fast_distance: { + if (!_.IsFloatScalarType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a float scalar type"; + } + + const uint32_t p0_type = _.GetOperandTypeId(inst, 4); + if (!_.IsFloatScalarOrVectorType(p0_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand P0 to be of float scalar or vector type"; + } + + const uint32_t num_components = _.GetDimension(p0_type); + if (num_components > 4) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand P0 to have no more than 4 components"; + } + + if (result_type != _.GetComponentType(p0_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand P0 component type to be equal to " + << "Result Type"; + } + + const uint32_t p1_type = _.GetOperandTypeId(inst, 5); + if (p0_type != p1_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operands P0 and P1 to be of the same type"; + } + break; + } + + case OpenCLLIB::Length: + case OpenCLLIB::Fast_length: { + if (!_.IsFloatScalarType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a float scalar type"; + } + + const uint32_t p_type = _.GetOperandTypeId(inst, 4); + if (!_.IsFloatScalarOrVectorType(p_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand P to be a float scalar or vector"; + } + + const uint32_t num_components = _.GetDimension(p_type); + if (num_components > 4) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand P to have no more than 4 components"; + } + + if (result_type != _.GetComponentType(p_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand P component type to be equal to Result " + "Type"; + } + break; + } + + case OpenCLLIB::Normalize: + case OpenCLLIB::Fast_normalize: { + if (!_.IsFloatScalarOrVectorType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a float scalar or vector type"; + } + + const uint32_t num_components = _.GetDimension(result_type); + if (num_components > 4) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to have no more than 4 components"; + } + + const uint32_t p_type = _.GetOperandTypeId(inst, 4); + if (p_type != result_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand P type to be equal to Result Type"; + } + break; + } + + case OpenCLLIB::Bitselect: { + if (!_.IsFloatScalarOrVectorType(result_type) && + !_.IsIntScalarOrVectorType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be an int or float scalar or " + "vector type"; + } + + const uint32_t num_components = _.GetDimension(result_type); + if (num_components > 4 && num_components != 8 && num_components != 16) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a scalar or a vector with 2, " + "3, 4, 8 or 16 components"; + } + + for (uint32_t operand_index = 4; operand_index < num_operands; + ++operand_index) { + const uint32_t operand_type = _.GetOperandTypeId(inst, operand_index); + if (result_type != operand_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected types of all operands to be equal to Result " + "Type"; + } + } + break; + } + + case OpenCLLIB::Select: { + if (!_.IsFloatScalarOrVectorType(result_type) && + !_.IsIntScalarOrVectorType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be an int or float scalar or " + "vector type"; + } + + const uint32_t num_components = _.GetDimension(result_type); + if (num_components > 4 && num_components != 8 && num_components != 16) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a scalar or a vector with 2, " + "3, 4, 8 or 16 components"; + } + + const uint32_t a_type = _.GetOperandTypeId(inst, 4); + const uint32_t b_type = _.GetOperandTypeId(inst, 5); + const uint32_t c_type = _.GetOperandTypeId(inst, 6); + + if (result_type != a_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand A type to be equal to Result Type"; + } + + if (result_type != b_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand B type to be equal to Result Type"; + } + + if (!_.IsIntScalarOrVectorType(c_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand C to be an int scalar or vector"; + } + + if (num_components != _.GetDimension(c_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand C to have the same number of components " + "as Result Type"; + } + + if (_.GetBitWidth(result_type) != _.GetBitWidth(c_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand C to have the same bit width as Result " + "Type"; + } + break; + } + + case OpenCLLIB::Vloadn: { + if (!_.IsFloatVectorType(result_type) && + !_.IsIntVectorType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be an int or float vector type"; + } + + const uint32_t num_components = _.GetDimension(result_type); + if (num_components > 4 && num_components != 8 && num_components != 16) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to have 2, 3, 4, 8 or 16 components"; + } + + const uint32_t offset_type = _.GetOperandTypeId(inst, 4); + const uint32_t p_type = _.GetOperandTypeId(inst, 5); + + const uint32_t size_t_bit_width = GetSizeTBitWidth(_); + if (!size_t_bit_width) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() + << " can only be used with physical addressing models"; + } + + if (!_.IsIntScalarType(offset_type) || + _.GetBitWidth(offset_type) != size_t_bit_width) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand Offset to be of type size_t (" + << size_t_bit_width + << "-bit integer for the addressing model used in the module)"; + } + + uint32_t p_storage_class = 0; + uint32_t p_data_type = 0; + if (!_.GetPointerTypeInfo(p_type, &p_data_type, &p_storage_class)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand P to be a pointer"; + } + + if (p_storage_class != SpvStorageClassUniformConstant && + p_storage_class != SpvStorageClassGeneric) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand P storage class to be UniformConstant or " + "Generic"; + } + + if (_.GetComponentType(result_type) != p_data_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand P data type to be equal to component " + "type of Result Type"; + } + + const uint32_t n_value = inst->words[7]; + if (num_components != n_value) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected literal N to be equal to the number of " + "components of Result Type"; + } + break; + } + + case OpenCLLIB::Vstoren: { + if (_.GetIdOpcode(result_type) != SpvOpTypeVoid) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": expected Result Type to be void"; + } + + const uint32_t data_type = _.GetOperandTypeId(inst, 4); + const uint32_t offset_type = _.GetOperandTypeId(inst, 5); + const uint32_t p_type = _.GetOperandTypeId(inst, 6); + + if (!_.IsFloatVectorType(data_type) && !_.IsIntVectorType(data_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Data to be an int or float vector"; + } + + const uint32_t num_components = _.GetDimension(data_type); + if (num_components > 4 && num_components != 8 && num_components != 16) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Data to have 2, 3, 4, 8 or 16 components"; + } + + const uint32_t size_t_bit_width = GetSizeTBitWidth(_); + if (!size_t_bit_width) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() + << " can only be used with physical addressing models"; + } + + if (!_.IsIntScalarType(offset_type) || + _.GetBitWidth(offset_type) != size_t_bit_width) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand Offset to be of type size_t (" + << size_t_bit_width + << "-bit integer for the addressing model used in the module)"; + } + + uint32_t p_storage_class = 0; + uint32_t p_data_type = 0; + if (!_.GetPointerTypeInfo(p_type, &p_data_type, &p_storage_class)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand P to be a pointer"; + } + + if (p_storage_class != SpvStorageClassGeneric) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand P storage class to be Generic"; + } + + if (_.GetComponentType(data_type) != p_data_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand P data type to be equal to the type of " + "operand Data components"; + } + break; + } + + case OpenCLLIB::Vload_half: { + if (!_.IsFloatScalarType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a float scalar type"; + } + + const uint32_t offset_type = _.GetOperandTypeId(inst, 4); + const uint32_t p_type = _.GetOperandTypeId(inst, 5); + + const uint32_t size_t_bit_width = GetSizeTBitWidth(_); + if (!size_t_bit_width) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() + << " can only be used with physical addressing models"; + } + + if (!_.IsIntScalarType(offset_type) || + _.GetBitWidth(offset_type) != size_t_bit_width) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand Offset to be of type size_t (" + << size_t_bit_width + << "-bit integer for the addressing model used in the module)"; + } + + uint32_t p_storage_class = 0; + uint32_t p_data_type = 0; + if (!_.GetPointerTypeInfo(p_type, &p_data_type, &p_storage_class)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand P to be a pointer"; + } + + if (p_storage_class != SpvStorageClassUniformConstant && + p_storage_class != SpvStorageClassGeneric && + p_storage_class != SpvStorageClassCrossWorkgroup && + p_storage_class != SpvStorageClassWorkgroup && + p_storage_class != SpvStorageClassFunction) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand P storage class to be UniformConstant, " + "Generic, CrossWorkgroup, Workgroup or Function"; + } + + if (!_.IsFloatScalarType(p_data_type) || + _.GetBitWidth(p_data_type) != 16) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand P data type to be 16-bit float scalar"; + } + break; + } + + case OpenCLLIB::Vload_halfn: + case OpenCLLIB::Vloada_halfn: { + if (!_.IsFloatVectorType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a float vector type"; + } + + const uint32_t num_components = _.GetDimension(result_type); + if (num_components > 4 && num_components != 8 && num_components != 16) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to have 2, 3, 4, 8 or 16 components"; + } + + const uint32_t offset_type = _.GetOperandTypeId(inst, 4); + const uint32_t p_type = _.GetOperandTypeId(inst, 5); + + const uint32_t size_t_bit_width = GetSizeTBitWidth(_); + if (!size_t_bit_width) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() + << " can only be used with physical addressing models"; + } + + if (!_.IsIntScalarType(offset_type) || + _.GetBitWidth(offset_type) != size_t_bit_width) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand Offset to be of type size_t (" + << size_t_bit_width + << "-bit integer for the addressing model used in the module)"; + } + + uint32_t p_storage_class = 0; + uint32_t p_data_type = 0; + if (!_.GetPointerTypeInfo(p_type, &p_data_type, &p_storage_class)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand P to be a pointer"; + } + + if (p_storage_class != SpvStorageClassUniformConstant && + p_storage_class != SpvStorageClassGeneric && + p_storage_class != SpvStorageClassCrossWorkgroup && + p_storage_class != SpvStorageClassWorkgroup && + p_storage_class != SpvStorageClassFunction) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand P storage class to be UniformConstant, " + "Generic, CrossWorkgroup, Workgroup or Function"; + } + + if (!_.IsFloatScalarType(p_data_type) || + _.GetBitWidth(p_data_type) != 16) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand P data type to be 16-bit float scalar"; + } + + const uint32_t n_value = inst->words[7]; + if (num_components != n_value) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected literal N to be equal to the number of " + "components of Result Type"; + } + break; + } + + case OpenCLLIB::Vstore_half: + case OpenCLLIB::Vstore_half_r: + case OpenCLLIB::Vstore_halfn: + case OpenCLLIB::Vstore_halfn_r: + case OpenCLLIB::Vstorea_halfn: + case OpenCLLIB::Vstorea_halfn_r: { + if (_.GetIdOpcode(result_type) != SpvOpTypeVoid) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": expected Result Type to be void"; + } + + const uint32_t data_type = _.GetOperandTypeId(inst, 4); + const uint32_t offset_type = _.GetOperandTypeId(inst, 5); + const uint32_t p_type = _.GetOperandTypeId(inst, 6); + const uint32_t data_type_bit_width = _.GetBitWidth(data_type); + + if (ext_inst_key == OpenCLLIB::Vstore_half || + ext_inst_key == OpenCLLIB::Vstore_half_r) { + if (!_.IsFloatScalarType(data_type) || + (data_type_bit_width != 32 && data_type_bit_width != 64)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Data to be a 32 or 64-bit float scalar"; + } + } else { + if (!_.IsFloatVectorType(data_type) || + (data_type_bit_width != 32 && data_type_bit_width != 64)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Data to be a 32 or 64-bit float vector"; + } + + const uint32_t num_components = _.GetDimension(data_type); + if (num_components > 4 && num_components != 8 && + num_components != 16) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Data to have 2, 3, 4, 8 or 16 components"; + } + } + + const uint32_t size_t_bit_width = GetSizeTBitWidth(_); + if (!size_t_bit_width) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() + << " can only be used with physical addressing models"; + } + + if (!_.IsIntScalarType(offset_type) || + _.GetBitWidth(offset_type) != size_t_bit_width) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand Offset to be of type size_t (" + << size_t_bit_width + << "-bit integer for the addressing model used in the module)"; + } + + uint32_t p_storage_class = 0; + uint32_t p_data_type = 0; + if (!_.GetPointerTypeInfo(p_type, &p_data_type, &p_storage_class)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand P to be a pointer"; + } + + if (p_storage_class != SpvStorageClassGeneric && + p_storage_class != SpvStorageClassCrossWorkgroup && + p_storage_class != SpvStorageClassWorkgroup && + p_storage_class != SpvStorageClassFunction) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand P storage class to be Generic, " + "CrossWorkgroup, Workgroup or Function"; + } + + if (!_.IsFloatScalarType(p_data_type) || + _.GetBitWidth(p_data_type) != 16) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand P data type to be 16-bit float scalar"; + } + + // Rounding mode enum is checked by assembler. + break; + } + + case OpenCLLIB::Shuffle: + case OpenCLLIB::Shuffle2: { + if (!_.IsFloatVectorType(result_type) && + !_.IsIntVectorType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be an int or float vector type"; + } + + const uint32_t result_num_components = _.GetDimension(result_type); + if (result_num_components != 2 && result_num_components != 4 && + result_num_components != 8 && result_num_components != 16) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to have 2, 4, 8 or 16 components"; + } + + uint32_t operand_index = 4; + const uint32_t x_type = _.GetOperandTypeId(inst, operand_index++); + + if (ext_inst_key == OpenCLLIB::Shuffle2) { + const uint32_t y_type = _.GetOperandTypeId(inst, operand_index++); + if (x_type != y_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operands X and Y to be of the same type"; + } + } + + const uint32_t shuffle_mask_type = + _.GetOperandTypeId(inst, operand_index++); + + if (!_.IsFloatVectorType(x_type) && !_.IsIntVectorType(x_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand X to be an int or float vector"; + } + + const uint32_t x_num_components = _.GetDimension(x_type); + if (x_num_components != 2 && x_num_components != 4 && + x_num_components != 8 && x_num_components != 16) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand X to have 2, 4, 8 or 16 components"; + } + + const uint32_t result_component_type = _.GetComponentType(result_type); + + if (result_component_type != _.GetComponentType(x_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand X and Result Type to have equal " + "component types"; + } + + if (!_.IsIntVectorType(shuffle_mask_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand Shuffle Mask to be an int vector"; + } + + if (result_num_components != _.GetDimension(shuffle_mask_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand Shuffle Mask to have the same number of " + "components as Result Type"; + } + + if (_.GetBitWidth(result_component_type) != + _.GetBitWidth(shuffle_mask_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand Shuffle Mask components to have the same " + "bit width as Result Type components"; + } + break; + } + + case OpenCLLIB::Printf: { + if (!_.IsIntScalarType(result_type) || + _.GetBitWidth(result_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a 32-bit int type"; + } + + const uint32_t format_type = _.GetOperandTypeId(inst, 4); + uint32_t format_storage_class = 0; + uint32_t format_data_type = 0; + if (!_.GetPointerTypeInfo(format_type, &format_data_type, + &format_storage_class)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand Format to be a pointer"; + } + + if (format_storage_class != SpvStorageClassUniformConstant) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Format storage class to be UniformConstant"; + } + + if (!_.IsIntScalarType(format_data_type) || + _.GetBitWidth(format_data_type) != 8) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Format data type to be 8-bit int"; + } + break; + } + + case OpenCLLIB::Prefetch: { + if (_.GetIdOpcode(result_type) != SpvOpTypeVoid) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": expected Result Type to be void"; + } + + const uint32_t p_type = _.GetOperandTypeId(inst, 4); + const uint32_t num_elements_type = _.GetOperandTypeId(inst, 5); + + uint32_t p_storage_class = 0; + uint32_t p_data_type = 0; + if (!_.GetPointerTypeInfo(p_type, &p_data_type, &p_storage_class)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand Ptr to be a pointer"; + } + + if (p_storage_class != SpvStorageClassCrossWorkgroup) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand Ptr storage class to be CrossWorkgroup"; + } + + if (!_.IsFloatScalarOrVectorType(p_data_type) && + !_.IsIntScalarOrVectorType(p_data_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Ptr data type to be int or float scalar or " + "vector"; + } + + const uint32_t num_components = _.GetDimension(p_data_type); + if (num_components > 4 && num_components != 8 && num_components != 16) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected Result Type to be a scalar or a vector with 2, " + "3, 4, 8 or 16 components"; + } + + const uint32_t size_t_bit_width = GetSizeTBitWidth(_); + if (!size_t_bit_width) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() + << " can only be used with physical addressing models"; + } + + if (!_.IsIntScalarType(num_elements_type) || + _.GetBitWidth(num_elements_type) != size_t_bit_width) { + return _.diag(SPV_ERROR_INVALID_DATA) + << ext_inst_name() << ": " + << "expected operand Num Elements to be of type size_t (" + << size_t_bit_width + << "-bit integer for the addressing model used in the module)"; + } + break; + } + } + } + + return SPV_SUCCESS; +} + +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/validate_id.cpp b/3rdparty/spirv-tools/source/validate_id.cpp new file mode 100644 index 000000000..b305f496f --- /dev/null +++ b/3rdparty/spirv-tools/source/validate_id.cpp @@ -0,0 +1,2767 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "validate.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "diagnostic.h" +#include "instruction.h" +#include "message.h" +#include "opcode.h" +#include "operand.h" +#include "spirv-tools/libspirv.h" +#include "spirv_validator_options.h" +#include "val/function.h" +#include "val/validation_state.h" + +using libspirv::Decoration; +using libspirv::Function; +using libspirv::ValidationState_t; +using std::function; +using std::ignore; +using std::make_pair; +using std::pair; +using std::unordered_set; +using std::vector; + +namespace { + +class idUsage { + public: + idUsage(spv_const_context context, const spv_instruction_t* pInsts, + const uint64_t instCountArg, const SpvMemoryModel memoryModelArg, + const SpvAddressingModel addressingModelArg, + const ValidationState_t& module, const vector& entry_points, + spv_position positionArg, const spvtools::MessageConsumer& consumer) + : targetEnv(context->target_env), + opcodeTable(context->opcode_table), + operandTable(context->operand_table), + extInstTable(context->ext_inst_table), + firstInst(pInsts), + instCount(instCountArg), + memoryModel(memoryModelArg), + addressingModel(addressingModelArg), + position(positionArg), + consumer_(consumer), + module_(module), + entry_points_(entry_points) {} + + bool isValid(const spv_instruction_t* inst); + + template + bool isValid(const spv_instruction_t* inst, const spv_opcode_desc); + + private: + const spv_target_env targetEnv; + const spv_opcode_table opcodeTable; + const spv_operand_table operandTable; + const spv_ext_inst_table extInstTable; + const spv_instruction_t* const firstInst; + const uint64_t instCount; + const SpvMemoryModel memoryModel; + const SpvAddressingModel addressingModel; + spv_position position; + const spvtools::MessageConsumer& consumer_; + const ValidationState_t& module_; + vector entry_points_; + + // Returns true if the two instructions represent structs that, as far as the + // validator can tell, have the exact same data layout. + bool AreLayoutCompatibleStructs(const libspirv::Instruction* type1, + const libspirv::Instruction* type2); + + // Returns true if the operands to the OpTypeStruct instruction defining the + // types are the same or are layout compatible types. |type1| and |type2| must + // be OpTypeStruct instructions. + bool HaveLayoutCompatibleMembers(const libspirv::Instruction* type1, + const libspirv::Instruction* type2); + + // Returns true if all decorations that affect the data layout of the struct + // (like Offset), are the same for the two types. |type1| and |type2| must be + // OpTypeStruct instructions. + bool HaveSameLayoutDecorations(const libspirv::Instruction* type1, + const libspirv::Instruction* type2); + bool HasConflictingMemberOffsets( + const vector& type1_decorations, + const vector& type2_decorations) const; +}; + +#define DIAG(INDEX) \ + position->index += INDEX; \ + libspirv::DiagnosticStream helper(*position, consumer_, \ + SPV_ERROR_INVALID_DIAGNOSTIC); \ + helper + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc) { + assert(0 && "Unimplemented!"); + return false; +} +#endif // 0 + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto typeIndex = 1; + auto type = module_.FindDef(inst->words[typeIndex]); + if (!type || SpvOpTypeStruct != type->opcode()) { + DIAG(typeIndex) << "OpMemberName Type '" << inst->words[typeIndex] + << "' is not a struct type."; + return false; + } + auto memberIndex = 2; + auto member = inst->words[memberIndex]; + auto memberCount = (uint32_t)(type->words().size() - 2); + if (memberCount <= member) { + DIAG(memberIndex) << "OpMemberName Member '" + << inst->words[memberIndex] + << "' index is larger than Type '" << type->id() + << "'s member count."; + return false; + } + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto fileIndex = 1; + auto file = module_.FindDef(inst->words[fileIndex]); + if (!file || SpvOpString != file->opcode()) { + DIAG(fileIndex) << "OpLine Target '" << inst->words[fileIndex] + << "' is not an OpString."; + return false; + } + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto decorationIndex = 2; + auto decoration = inst->words[decorationIndex]; + if (decoration == SpvDecorationSpecId) { + auto targetIndex = 1; + auto target = module_.FindDef(inst->words[targetIndex]); + if (!target || !spvOpcodeIsScalarSpecConstant(target->opcode())) { + DIAG(targetIndex) << "OpDecorate SpectId decoration target '" + << inst->words[decorationIndex] + << "' is not a scalar specialization constant."; + return false; + } + } + // TODO: Add validations for all decorations. + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto structTypeIndex = 1; + auto structType = module_.FindDef(inst->words[structTypeIndex]); + if (!structType || SpvOpTypeStruct != structType->opcode()) { + DIAG(structTypeIndex) << "OpMemberDecorate Structure type '" + << inst->words[structTypeIndex] + << "' is not a struct type."; + return false; + } + auto memberIndex = 2; + auto member = inst->words[memberIndex]; + auto memberCount = static_cast(structType->words().size() - 2); + if (memberCount < member) { + DIAG(memberIndex) << "Index " << member + << " provided in OpMemberDecorate for struct " + << inst->words[structTypeIndex] + << " is out of bounds. The structure has " << memberCount + << " members. Largest valid index is " << memberCount - 1 + << "."; + return false; + } + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto decorationGroupIndex = 1; + auto decorationGroup = module_.FindDef(inst->words[decorationGroupIndex]); + + for (auto pair : decorationGroup->uses()) { + auto use = pair.first; + if (use->opcode() != SpvOpDecorate && use->opcode() != SpvOpGroupDecorate && + use->opcode() != SpvOpGroupMemberDecorate && + use->opcode() != SpvOpName) { + DIAG(decorationGroupIndex) << "Result id of OpDecorationGroup can only " + << "be targeted by OpName, OpGroupDecorate, " + << "OpDecorate, and OpGroupMemberDecorate"; + return false; + } + } + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto decorationGroupIndex = 1; + auto decorationGroup = module_.FindDef(inst->words[decorationGroupIndex]); + if (!decorationGroup || SpvOpDecorationGroup != decorationGroup->opcode()) { + DIAG(decorationGroupIndex) + << "OpGroupDecorate Decoration group '" + << inst->words[decorationGroupIndex] << "' is not a decoration group."; + return false; + } + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto decorationGroupIndex = 1; + auto decorationGroup = module_.FindDef(inst->words[decorationGroupIndex]); + if (!decorationGroup || SpvOpDecorationGroup != decorationGroup->opcode()) { + DIAG(decorationGroupIndex) + << "OpGroupMemberDecorate Decoration group '" + << inst->words[decorationGroupIndex] << "' is not a decoration group."; + return false; + } + // Grammar checks ensures that the number of arguments to this instruction + // is an odd number: 1 decoration group + (id,literal) pairs. + for (size_t i = 2; i + 1 < inst->words.size(); i = i + 2) { + const uint32_t struct_id = inst->words[i]; + const uint32_t index = inst->words[i + 1]; + auto struct_instr = module_.FindDef(struct_id); + if (!struct_instr || SpvOpTypeStruct != struct_instr->opcode()) { + DIAG(i) << "OpGroupMemberDecorate Structure type '" << struct_id + << "' is not a struct type."; + return false; + } + const uint32_t num_struct_members = + static_cast(struct_instr->words().size() - 2); + if (index >= num_struct_members) { + DIAG(i) << "Index " << index + << " provided in OpGroupMemberDecorate for struct " + << struct_id << " is out of bounds. The structure has " + << num_struct_members << " members. Largest valid index is " + << num_struct_members - 1 << "."; + return false; + } + } + return true; +} + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif // 0 + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto entryPointIndex = 2; + auto entryPoint = module_.FindDef(inst->words[entryPointIndex]); + if (!entryPoint || SpvOpFunction != entryPoint->opcode()) { + DIAG(entryPointIndex) << "OpEntryPoint Entry Point '" + << inst->words[entryPointIndex] + << "' is not a function."; + return false; + } + // don't check kernel function signatures + const SpvExecutionModel executionModel = SpvExecutionModel(inst->words[1]); + if (executionModel != SpvExecutionModelKernel) { + // TODO: Check the entry point signature is void main(void), may be subject + // to change + auto entryPointType = module_.FindDef(entryPoint->words()[4]); + if (!entryPointType || 3 != entryPointType->words().size()) { + DIAG(entryPointIndex) + << "OpEntryPoint Entry Point '" << inst->words[entryPointIndex] + << "'s function parameter count is not zero."; + return false; + } + } + + std::stack call_stack; + std::set visited; + call_stack.push(entryPoint->id()); + while (!call_stack.empty()) { + const uint32_t called_func_id = call_stack.top(); + call_stack.pop(); + if (!visited.insert(called_func_id).second) continue; + + const Function* called_func = module_.function(called_func_id); + assert(called_func); + + std::string reason; + if (!called_func->IsCompatibleWithExecutionModel(executionModel, &reason)) { + DIAG(entryPointIndex) + << "OpEntryPoint Entry Point '" << inst->words[entryPointIndex] + << "'s callgraph contains function " << called_func_id + << ", which cannot be used with the current execution model:\n" + << reason; + return false; + } + + for (uint32_t new_call : called_func->function_call_targets()) { + call_stack.push(new_call); + } + } + + auto returnType = module_.FindDef(entryPoint->type_id()); + if (!returnType || SpvOpTypeVoid != returnType->opcode()) { + DIAG(entryPointIndex) << "OpEntryPoint Entry Point '" + << inst->words[entryPointIndex] + << "'s function return type is not void."; + return false; + } + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto entryPointIndex = 1; + auto entryPointID = inst->words[entryPointIndex]; + auto found = + std::find(entry_points_.cbegin(), entry_points_.cend(), entryPointID); + if (found == entry_points_.cend()) { + DIAG(entryPointIndex) << "OpExecutionMode Entry Point '" + << inst->words[entryPointIndex] + << "' is not the Entry Point " + "operand of an OpEntryPoint."; + return false; + } + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto componentIndex = 2; + auto componentType = module_.FindDef(inst->words[componentIndex]); + if (!componentType || !spvOpcodeIsScalarType(componentType->opcode())) { + DIAG(componentIndex) << "OpTypeVector Component Type '" + << inst->words[componentIndex] + << "' is not a scalar type."; + return false; + } + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto columnTypeIndex = 2; + auto columnType = module_.FindDef(inst->words[columnTypeIndex]); + if (!columnType || SpvOpTypeVector != columnType->opcode()) { + DIAG(columnTypeIndex) << "OpTypeMatrix Column Type '" + << inst->words[columnTypeIndex] + << "' is not a vector."; + return false; + } + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t*, + const spv_opcode_desc) { + // OpTypeSampler takes no arguments in Rev31 and beyond. + return true; +} + +// True if the integer constant is > 0. constWords are words of the +// constant-defining instruction (either OpConstant or +// OpSpecConstant). typeWords are the words of the constant's-type-defining +// OpTypeInt. +bool aboveZero(const vector& constWords, + const vector& typeWords) { + const uint32_t width = typeWords[2]; + const bool is_signed = typeWords[3] > 0; + const uint32_t loWord = constWords[3]; + if (width > 32) { + // The spec currently doesn't allow integers wider than 64 bits. + const uint32_t hiWord = constWords[4]; // Must exist, per spec. + if (is_signed && (hiWord >> 31)) return false; + return (loWord | hiWord) > 0; + } else { + if (is_signed && (loWord >> 31)) return false; + return loWord > 0; + } +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto elementTypeIndex = 2; + auto elementType = module_.FindDef(inst->words[elementTypeIndex]); + if (!elementType || !spvOpcodeGeneratesType(elementType->opcode())) { + DIAG(elementTypeIndex) << "OpTypeArray Element Type '" + << inst->words[elementTypeIndex] + << "' is not a type."; + return false; + } + auto lengthIndex = 3; + auto length = module_.FindDef(inst->words[lengthIndex]); + if (!length || !spvOpcodeIsConstant(length->opcode())) { + DIAG(lengthIndex) << "OpTypeArray Length '" << inst->words[lengthIndex] + << "' is not a scalar constant type."; + return false; + } + + // NOTE: Check the initialiser value of the constant + auto constInst = length->words(); + auto constResultTypeIndex = 1; + auto constResultType = module_.FindDef(constInst[constResultTypeIndex]); + if (!constResultType || SpvOpTypeInt != constResultType->opcode()) { + DIAG(lengthIndex) << "OpTypeArray Length '" << inst->words[lengthIndex] + << "' is not a constant integer type."; + return false; + } + + switch (length->opcode()) { + case SpvOpSpecConstant: + case SpvOpConstant: + if (aboveZero(length->words(), constResultType->words())) break; + // Else fall through! + case SpvOpConstantNull: { + DIAG(lengthIndex) << "OpTypeArray Length '" + << inst->words[lengthIndex] + << "' default value must be at least 1."; + return false; + } + case SpvOpSpecConstantOp: + // Assume it's OK, rather than try to evaluate the operation. + break; + default: + assert(0 && "bug in spvOpcodeIsConstant() or result type isn't int"); + } + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto elementTypeIndex = 2; + auto elementType = module_.FindDef(inst->words[elementTypeIndex]); + if (!elementType || !spvOpcodeGeneratesType(elementType->opcode())) { + DIAG(elementTypeIndex) << "OpTypeRuntimeArray Element Type '" + << inst->words[elementTypeIndex] + << "' is not a type."; + return false; + } + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + ValidationState_t& vstate = const_cast(module_); + const uint32_t struct_id = inst->words[1]; + for (size_t memberTypeIndex = 2; memberTypeIndex < inst->words.size(); + ++memberTypeIndex) { + auto memberTypeId = inst->words[memberTypeIndex]; + auto memberType = module_.FindDef(memberTypeId); + if (!memberType || !spvOpcodeGeneratesType(memberType->opcode())) { + DIAG(memberTypeIndex) + << "OpTypeStruct Member Type '" << inst->words[memberTypeIndex] + << "' is not a type."; + return false; + } + if (SpvOpTypeStruct == memberType->opcode() && + module_.IsStructTypeWithBuiltInMember(memberTypeId)) { + DIAG(memberTypeIndex) + << "Structure " << memberTypeId + << " contains members with BuiltIn decoration. Therefore this " + "structure may not be contained as a member of another structure " + "type. Structure " + << struct_id << " contains structure " << memberTypeId << "."; + return false; + } + if (module_.IsForwardPointer(memberTypeId)) { + if (memberType->opcode() != SpvOpTypePointer) { + DIAG(memberTypeIndex) << "Found a forward reference to a non-pointer " + "type in OpTypeStruct instruction."; + return false; + } + // If we're dealing with a forward pointer: + // Find out the type that the pointer is pointing to (must be struct) + // word 3 is the of the type being pointed to. + auto typePointingTo = module_.FindDef(memberType->words()[3]); + if (typePointingTo && typePointingTo->opcode() != SpvOpTypeStruct) { + // Forward declared operands of a struct may only point to a struct. + DIAG(memberTypeIndex) + << "A forward reference operand in an OpTypeStruct must be an " + "OpTypePointer that points to an OpTypeStruct. " + "Found OpTypePointer that points to Op" + << spvOpcodeString(static_cast(typePointingTo->opcode())) + << "."; + return false; + } + } + } + std::unordered_set built_in_members; + for (auto decoration : vstate.id_decorations(struct_id)) { + if (decoration.dec_type() == SpvDecorationBuiltIn && + decoration.struct_member_index() != Decoration::kInvalidMember) { + built_in_members.insert(decoration.struct_member_index()); + } + } + int num_struct_members = static_cast(inst->words.size() - 2); + int num_builtin_members = static_cast(built_in_members.size()); + if (num_builtin_members > 0 && num_builtin_members != num_struct_members) { + DIAG(0) + << "When BuiltIn decoration is applied to a structure-type member, " + "all members of that structure type must also be decorated with " + "BuiltIn (No allowed mixing of built-in variables and " + "non-built-in variables within a single structure). Structure id " + << struct_id << " does not meet this requirement."; + return false; + } + if (num_builtin_members > 0) { + vstate.RegisterStructTypeWithBuiltInMember(struct_id); + } + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto typeIndex = 3; + auto type = module_.FindDef(inst->words[typeIndex]); + if (!type || !spvOpcodeGeneratesType(type->opcode())) { + DIAG(typeIndex) << "OpTypePointer Type '" << inst->words[typeIndex] + << "' is not a type."; + return false; + } + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto returnTypeIndex = 2; + auto returnType = module_.FindDef(inst->words[returnTypeIndex]); + if (!returnType || !spvOpcodeGeneratesType(returnType->opcode())) { + DIAG(returnTypeIndex) << "OpTypeFunction Return Type '" + << inst->words[returnTypeIndex] << "' is not a type."; + return false; + } + size_t num_args = 0; + for (size_t paramTypeIndex = 3; paramTypeIndex < inst->words.size(); + ++paramTypeIndex, ++num_args) { + auto paramType = module_.FindDef(inst->words[paramTypeIndex]); + if (!paramType || !spvOpcodeGeneratesType(paramType->opcode())) { + DIAG(paramTypeIndex) << "OpTypeFunction Parameter Type '" + << inst->words[paramTypeIndex] << "' is not a type."; + return false; + } + } + const uint32_t num_function_args_limit = + module_.options()->universal_limits_.max_function_args; + if (num_args > num_function_args_limit) { + DIAG(returnTypeIndex) << "OpTypeFunction may not take more than " + << num_function_args_limit + << " arguments. OpTypeFunction '" + << inst->words[1] << "' has " << num_args + << " arguments."; + return false; + } + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t*, + const spv_opcode_desc) { + // OpTypePipe has no ID arguments. + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto resultTypeIndex = 1; + auto resultType = module_.FindDef(inst->words[resultTypeIndex]); + if (!resultType || SpvOpTypeBool != resultType->opcode()) { + DIAG(resultTypeIndex) << "OpConstantTrue Result Type '" + << inst->words[resultTypeIndex] + << "' is not a boolean type."; + return false; + } + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto resultTypeIndex = 1; + auto resultType = module_.FindDef(inst->words[resultTypeIndex]); + if (!resultType || SpvOpTypeBool != resultType->opcode()) { + DIAG(resultTypeIndex) << "OpConstantFalse Result Type '" + << inst->words[resultTypeIndex] + << "' is not a boolean type."; + return false; + } + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto resultTypeIndex = 1; + auto resultType = module_.FindDef(inst->words[resultTypeIndex]); + if (!resultType || !spvOpcodeIsComposite(resultType->opcode())) { + DIAG(resultTypeIndex) << "OpConstantComposite Result Type '" + << inst->words[resultTypeIndex] + << "' is not a composite type."; + return false; + } + + auto constituentCount = inst->words.size() - 3; + switch (resultType->opcode()) { + case SpvOpTypeVector: { + auto componentCount = resultType->words()[3]; + if (componentCount != constituentCount) { + // TODO: Output ID's on diagnostic + DIAG(inst->words.size() - 1) + << "OpConstantComposite Constituent count does not match " + "Result Type '" + << resultType->id() << "'s vector component count."; + return false; + } + auto componentType = module_.FindDef(resultType->words()[2]); + assert(componentType); + for (size_t constituentIndex = 3; constituentIndex < inst->words.size(); + constituentIndex++) { + auto constituent = module_.FindDef(inst->words[constituentIndex]); + if (!constituent || + !spvOpcodeIsConstantOrUndef(constituent->opcode())) { + DIAG(constituentIndex) << "OpConstantComposite Constituent '" + << inst->words[constituentIndex] + << "' is not a constant or undef."; + return false; + } + auto constituentResultType = module_.FindDef(constituent->type_id()); + if (!constituentResultType || + componentType->opcode() != constituentResultType->opcode()) { + DIAG(constituentIndex) + << "OpConstantComposite Constituent '" + << inst->words[constituentIndex] + << "'s type does not match Result Type '" << resultType->id() + << "'s vector element type."; + return false; + } + } + } break; + case SpvOpTypeMatrix: { + auto columnCount = resultType->words()[3]; + if (columnCount != constituentCount) { + // TODO: Output ID's on diagnostic + DIAG(inst->words.size() - 1) + << "OpConstantComposite Constituent count does not match " + "Result Type '" + << resultType->id() << "'s matrix column count."; + return false; + } + + auto columnType = module_.FindDef(resultType->words()[2]); + assert(columnType); + auto componentCount = columnType->words()[3]; + auto componentType = module_.FindDef(columnType->words()[2]); + assert(componentType); + + for (size_t constituentIndex = 3; constituentIndex < inst->words.size(); + constituentIndex++) { + auto constituent = module_.FindDef(inst->words[constituentIndex]); + if (!constituent || !(SpvOpConstantComposite == constituent->opcode() || + SpvOpUndef == constituent->opcode())) { + // The message says "... or undef" because the spec does not say + // undef is a constant. + DIAG(constituentIndex) << "OpConstantComposite Constituent '" + << inst->words[constituentIndex] + << "' is not a constant composite or undef."; + return false; + } + auto vector = module_.FindDef(constituent->type_id()); + assert(vector); + if (columnType->opcode() != vector->opcode()) { + DIAG(constituentIndex) + << "OpConstantComposite Constituent '" + << inst->words[constituentIndex] + << "' type does not match Result Type '" << resultType->id() + << "'s matrix column type."; + return false; + } + auto vectorComponentType = module_.FindDef(vector->words()[2]); + assert(vectorComponentType); + if (componentType->id() != vectorComponentType->id()) { + DIAG(constituentIndex) + << "OpConstantComposite Constituent '" + << inst->words[constituentIndex] + << "' component type does not match Result Type '" + << resultType->id() << "'s matrix column component type."; + return false; + } + if (componentCount != vector->words()[3]) { + DIAG(constituentIndex) + << "OpConstantComposite Constituent '" + << inst->words[constituentIndex] + << "' vector component count does not match Result Type '" + << resultType->id() << "'s vector component count."; + return false; + } + } + } break; + case SpvOpTypeArray: { + auto elementType = module_.FindDef(resultType->words()[2]); + assert(elementType); + auto length = module_.FindDef(resultType->words()[3]); + assert(length); + if (length->words()[3] != constituentCount) { + DIAG(inst->words.size() - 1) + << "OpConstantComposite Constituent count does not match " + "Result Type '" + << resultType->id() << "'s array length."; + return false; + } + for (size_t constituentIndex = 3; constituentIndex < inst->words.size(); + constituentIndex++) { + auto constituent = module_.FindDef(inst->words[constituentIndex]); + if (!constituent || + !spvOpcodeIsConstantOrUndef(constituent->opcode())) { + DIAG(constituentIndex) << "OpConstantComposite Constituent '" + << inst->words[constituentIndex] + << "' is not a constant or undef."; + return false; + } + auto constituentType = module_.FindDef(constituent->type_id()); + assert(constituentType); + if (elementType->id() != constituentType->id()) { + DIAG(constituentIndex) + << "OpConstantComposite Constituent '" + << inst->words[constituentIndex] + << "'s type does not match Result Type '" << resultType->id() + << "'s array element type."; + return false; + } + } + } break; + case SpvOpTypeStruct: { + auto memberCount = resultType->words().size() - 2; + if (memberCount != constituentCount) { + DIAG(resultTypeIndex) << "OpConstantComposite Constituent '" + << inst->words[resultTypeIndex] + << "' count does not match Result Type '" + << resultType->id() << "'s struct member count."; + return false; + } + for (uint32_t constituentIndex = 3, memberIndex = 2; + constituentIndex < inst->words.size(); + constituentIndex++, memberIndex++) { + auto constituent = module_.FindDef(inst->words[constituentIndex]); + if (!constituent || + !spvOpcodeIsConstantOrUndef(constituent->opcode())) { + DIAG(constituentIndex) << "OpConstantComposite Constituent '" + << inst->words[constituentIndex] + << "' is not a constant or undef."; + return false; + } + auto constituentType = module_.FindDef(constituent->type_id()); + assert(constituentType); + + auto memberType = module_.FindDef(resultType->words()[memberIndex]); + assert(memberType); + if (memberType->id() != constituentType->id()) { + DIAG(constituentIndex) + << "OpConstantComposite Constituent '" + << inst->words[constituentIndex] + << "' type does not match the Result Type '" + << resultType->id() << "'s member type."; + return false; + } + } + } break; + default: { assert(0 && "Unreachable!"); } break; + } + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto resultTypeIndex = 1; + auto resultType = module_.FindDef(inst->words[resultTypeIndex]); + if (!resultType || SpvOpTypeSampler != resultType->opcode()) { + DIAG(resultTypeIndex) << "OpConstantSampler Result Type '" + << inst->words[resultTypeIndex] + << "' is not a sampler type."; + return false; + } + return true; +} + +// True if instruction defines a type that can have a null value, as defined by +// the SPIR-V spec. Tracks composite-type components through module to check +// nullability transitively. +bool IsTypeNullable(const vector& instruction, + const ValidationState_t& module) { + uint16_t opcode; + uint16_t word_count; + spvOpcodeSplit(instruction[0], &word_count, &opcode); + switch (static_cast(opcode)) { + case SpvOpTypeBool: + case SpvOpTypeInt: + case SpvOpTypeFloat: + case SpvOpTypePointer: + case SpvOpTypeEvent: + case SpvOpTypeDeviceEvent: + case SpvOpTypeReserveId: + case SpvOpTypeQueue: + return true; + case SpvOpTypeArray: + case SpvOpTypeMatrix: + case SpvOpTypeVector: { + auto base_type = module.FindDef(instruction[2]); + return base_type && IsTypeNullable(base_type->words(), module); + } + case SpvOpTypeStruct: { + for (size_t elementIndex = 2; elementIndex < instruction.size(); + ++elementIndex) { + auto element = module.FindDef(instruction[elementIndex]); + if (!element || !IsTypeNullable(element->words(), module)) return false; + } + return true; + } + default: + return false; + } +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto resultTypeIndex = 1; + auto resultType = module_.FindDef(inst->words[resultTypeIndex]); + if (!resultType || !IsTypeNullable(resultType->words(), module_)) { + DIAG(resultTypeIndex) << "OpConstantNull Result Type '" + << inst->words[resultTypeIndex] + << "' cannot have a null value."; + return false; + } + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto resultTypeIndex = 1; + auto resultType = module_.FindDef(inst->words[resultTypeIndex]); + if (!resultType || SpvOpTypeBool != resultType->opcode()) { + DIAG(resultTypeIndex) << "OpSpecConstantTrue Result Type '" + << inst->words[resultTypeIndex] + << "' is not a boolean type."; + return false; + } + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto resultTypeIndex = 1; + auto resultType = module_.FindDef(inst->words[resultTypeIndex]); + if (!resultType || SpvOpTypeBool != resultType->opcode()) { + DIAG(resultTypeIndex) << "OpSpecConstantFalse Result Type '" + << inst->words[resultTypeIndex] + << "' is not a boolean type."; + return false; + } + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto resultTypeIndex = 2; + auto resultID = inst->words[resultTypeIndex]; + auto sampledImageInstr = module_.FindDef(resultID); + // We need to validate 2 things: + // * All OpSampledImage instructions must be in the same block in which their + // Result are consumed. + // * Result from OpSampledImage instructions must not appear as operands + // to OpPhi instructions or OpSelect instructions, or any instructions other + // than the image lookup and query instructions specified to take an operand + // whose type is OpTypeSampledImage. + std::vector consumers = module_.getSampledImageConsumers(resultID); + if (!consumers.empty()) { + for (auto consumer_id : consumers) { + auto consumer_instr = module_.FindDef(consumer_id); + auto consumer_opcode = consumer_instr->opcode(); + if (consumer_instr->block() != sampledImageInstr->block()) { + DIAG(resultTypeIndex) + << "All OpSampledImage instructions must be in the same block in " + "which their Result are consumed. OpSampledImage Result " + "Type '" + << resultID + << "' has a consumer in a different basic " + "block. The consumer instruction is '" + << consumer_id << "'."; + return false; + } + // TODO: The following check is incomplete. We should also check that the + // Sampled Image is not used by instructions that should not take + // SampledImage as an argument. We could find the list of valid + // instructions by scanning for "Sampled Image" in the operand description + // field in the grammar file. + if (consumer_opcode == SpvOpPhi || consumer_opcode == SpvOpSelect) { + DIAG(resultTypeIndex) + << "Result from OpSampledImage instruction must not appear as " + "operands of Op" + << spvOpcodeString(static_cast(consumer_opcode)) << "." + << " Found result '" << resultID << "' as an operand of '" + << consumer_id << "'."; + return false; + } + } + } + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + // The result type must be a composite type. + auto resultTypeIndex = 1; + auto resultType = module_.FindDef(inst->words[resultTypeIndex]); + if (!resultType || !spvOpcodeIsComposite(resultType->opcode())) { + DIAG(resultTypeIndex) << "OpSpecConstantComposite Result Type '" + << inst->words[resultTypeIndex] + << "' is not a composite type."; + return false; + } + // Validation checks differ based on the type of composite type. + auto constituentCount = inst->words.size() - 3; + switch (resultType->opcode()) { + // For Vectors, the following must be met: + // * Number of constituents in the result type and the vector must match. + // * All the components of the vector must have the same type (or specialize + // to the same type). OpConstant and OpSpecConstant are allowed. + // To check that condition, we check each supplied value argument's type + // against the element type of the result type. + case SpvOpTypeVector: { + auto componentCount = resultType->words()[3]; + if (componentCount != constituentCount) { + DIAG(inst->words.size() - 1) + << "OpSpecConstantComposite Constituent count does not match " + "Result Type '" + << resultType->id() << "'s vector component count."; + return false; + } + auto componentType = module_.FindDef(resultType->words()[2]); + assert(componentType); + for (size_t constituentIndex = 3; constituentIndex < inst->words.size(); + constituentIndex++) { + auto constituent = module_.FindDef(inst->words[constituentIndex]); + if (!constituent || + !spvOpcodeIsConstantOrUndef(constituent->opcode())) { + DIAG(constituentIndex) << "OpSpecConstantComposite Constituent '" + << inst->words[constituentIndex] + << "' is not a constant or undef."; + return false; + } + auto constituentResultType = module_.FindDef(constituent->type_id()); + if (!constituentResultType || + componentType->opcode() != constituentResultType->opcode()) { + DIAG(constituentIndex) + << "OpSpecConstantComposite Constituent '" + << inst->words[constituentIndex] + << "'s type does not match Result Type '" << resultType->id() + << "'s vector element type."; + return false; + } + } + break; + } + case SpvOpTypeMatrix: { + auto columnCount = resultType->words()[3]; + if (columnCount != constituentCount) { + DIAG(inst->words.size() - 1) + << "OpSpecConstantComposite Constituent count does not match " + "Result Type '" + << resultType->id() << "'s matrix column count."; + return false; + } + + auto columnType = module_.FindDef(resultType->words()[2]); + assert(columnType); + auto componentCount = columnType->words()[3]; + auto componentType = module_.FindDef(columnType->words()[2]); + assert(componentType); + + for (size_t constituentIndex = 3; constituentIndex < inst->words.size(); + constituentIndex++) { + auto constituent = module_.FindDef(inst->words[constituentIndex]); + auto constituentOpCode = constituent->opcode(); + if (!constituent || !(SpvOpSpecConstantComposite == constituentOpCode || + SpvOpConstantComposite == constituentOpCode || + SpvOpUndef == constituentOpCode)) { + // The message says "... or undef" because the spec does not say + // undef is a constant. + DIAG(constituentIndex) << "OpSpecConstantComposite Constituent '" + << inst->words[constituentIndex] + << "' is not a constant composite or undef."; + return false; + } + auto vector = module_.FindDef(constituent->type_id()); + assert(vector); + if (columnType->opcode() != vector->opcode()) { + DIAG(constituentIndex) + << "OpSpecConstantComposite Constituent '" + << inst->words[constituentIndex] + << "' type does not match Result Type '" << resultType->id() + << "'s matrix column type."; + return false; + } + auto vectorComponentType = module_.FindDef(vector->words()[2]); + assert(vectorComponentType); + if (componentType->id() != vectorComponentType->id()) { + DIAG(constituentIndex) + << "OpSpecConstantComposite Constituent '" + << inst->words[constituentIndex] + << "' component type does not match Result Type '" + << resultType->id() << "'s matrix column component type."; + return false; + } + if (componentCount != vector->words()[3]) { + DIAG(constituentIndex) + << "OpSpecConstantComposite Constituent '" + << inst->words[constituentIndex] + << "' vector component count does not match Result Type '" + << resultType->id() << "'s vector component count."; + return false; + } + } + break; + } + case SpvOpTypeArray: { + auto elementType = module_.FindDef(resultType->words()[2]); + assert(elementType); + auto length = module_.FindDef(resultType->words()[3]); + assert(length); + if (length->words()[3] != constituentCount) { + DIAG(inst->words.size() - 1) + << "OpSpecConstantComposite Constituent count does not match " + "Result Type '" + << resultType->id() << "'s array length."; + return false; + } + for (size_t constituentIndex = 3; constituentIndex < inst->words.size(); + constituentIndex++) { + auto constituent = module_.FindDef(inst->words[constituentIndex]); + if (!constituent || + !spvOpcodeIsConstantOrUndef(constituent->opcode())) { + DIAG(constituentIndex) << "OpSpecConstantComposite Constituent '" + << inst->words[constituentIndex] + << "' is not a constant or undef."; + return false; + } + auto constituentType = module_.FindDef(constituent->type_id()); + assert(constituentType); + if (elementType->id() != constituentType->id()) { + DIAG(constituentIndex) + << "OpSpecConstantComposite Constituent '" + << inst->words[constituentIndex] + << "'s type does not match Result Type '" << resultType->id() + << "'s array element type."; + return false; + } + } + break; + } + case SpvOpTypeStruct: { + auto memberCount = resultType->words().size() - 2; + if (memberCount != constituentCount) { + DIAG(resultTypeIndex) << "OpSpecConstantComposite Constituent '" + << inst->words[resultTypeIndex] + << "' count does not match Result Type '" + << resultType->id() << "'s struct member count."; + return false; + } + for (uint32_t constituentIndex = 3, memberIndex = 2; + constituentIndex < inst->words.size(); + constituentIndex++, memberIndex++) { + auto constituent = module_.FindDef(inst->words[constituentIndex]); + if (!constituent || + !spvOpcodeIsConstantOrUndef(constituent->opcode())) { + DIAG(constituentIndex) << "OpSpecConstantComposite Constituent '" + << inst->words[constituentIndex] + << "' is not a constant or undef."; + return false; + } + auto constituentType = module_.FindDef(constituent->type_id()); + assert(constituentType); + + auto memberType = module_.FindDef(resultType->words()[memberIndex]); + assert(memberType); + if (memberType->id() != constituentType->id()) { + DIAG(constituentIndex) + << "OpSpecConstantComposite Constituent '" + << inst->words[constituentIndex] + << "' type does not match the Result Type '" + << resultType->id() << "'s member type."; + return false; + } + } + break; + } + default: { assert(0 && "Unreachable!"); } break; + } + return true; +} + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst) {} +#endif + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto resultTypeIndex = 1; + auto resultType = module_.FindDef(inst->words[resultTypeIndex]); + if (!resultType || SpvOpTypePointer != resultType->opcode()) { + DIAG(resultTypeIndex) << "OpVariable Result Type '" + << inst->words[resultTypeIndex] + << "' is not a pointer type."; + return false; + } + const auto initialiserIndex = 4; + if (initialiserIndex < inst->words.size()) { + const auto initialiser = module_.FindDef(inst->words[initialiserIndex]); + const auto storageClassIndex = 3; + const auto is_module_scope_var = + initialiser && (initialiser->opcode() == SpvOpVariable) && + (initialiser->word(storageClassIndex) != SpvStorageClassFunction); + const auto is_constant = + initialiser && spvOpcodeIsConstant(initialiser->opcode()); + if (!initialiser || !(is_constant || is_module_scope_var)) { + DIAG(initialiserIndex) + << "OpVariable Initializer '" << inst->words[initialiserIndex] + << "' is not a constant or module-scope variable."; + return false; + } + } + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto resultTypeIndex = 1; + auto resultType = module_.FindDef(inst->words[resultTypeIndex]); + if (!resultType) { + DIAG(resultTypeIndex) << "OpLoad Result Type '" + << inst->words[resultTypeIndex] << "' is not defind."; + return false; + } + const bool uses_variable_pointer = + module_.features().variable_pointers || + module_.features().variable_pointers_storage_buffer; + auto pointerIndex = 3; + auto pointer = module_.FindDef(inst->words[pointerIndex]); + if (!pointer || + (addressingModel == SpvAddressingModelLogical && + ((!uses_variable_pointer && + !spvOpcodeReturnsLogicalPointer(pointer->opcode())) || + (uses_variable_pointer && + !spvOpcodeReturnsLogicalVariablePointer(pointer->opcode()))))) { + DIAG(pointerIndex) << "OpLoad Pointer '" << inst->words[pointerIndex] + << "' is not a logical pointer."; + return false; + } + auto pointerType = module_.FindDef(pointer->type_id()); + if (!pointerType || pointerType->opcode() != SpvOpTypePointer) { + DIAG(pointerIndex) << "OpLoad type for pointer '" + << inst->words[pointerIndex] + << "' is not a pointer type."; + return false; + } + auto pointeeType = module_.FindDef(pointerType->words()[3]); + if (!pointeeType || resultType->id() != pointeeType->id()) { + DIAG(resultTypeIndex) << "OpLoad Result Type '" + << inst->words[resultTypeIndex] + << "' does not match Pointer '" << pointer->id() + << "'s type."; + return false; + } + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + const bool uses_variable_pointer = + module_.features().variable_pointers || + module_.features().variable_pointers_storage_buffer; + const auto pointerIndex = 1; + auto pointer = module_.FindDef(inst->words[pointerIndex]); + if (!pointer || + (addressingModel == SpvAddressingModelLogical && + ((!uses_variable_pointer && + !spvOpcodeReturnsLogicalPointer(pointer->opcode())) || + (uses_variable_pointer && + !spvOpcodeReturnsLogicalVariablePointer(pointer->opcode()))))) { + DIAG(pointerIndex) << "OpStore Pointer '" << inst->words[pointerIndex] + << "' is not a logical pointer."; + return false; + } + auto pointerType = module_.FindDef(pointer->type_id()); + if (!pointer || pointerType->opcode() != SpvOpTypePointer) { + DIAG(pointerIndex) << "OpStore type for pointer '" + << inst->words[pointerIndex] + << "' is not a pointer type."; + return false; + } + auto type = module_.FindDef(pointerType->words()[3]); + assert(type); + if (SpvOpTypeVoid == type->opcode()) { + DIAG(pointerIndex) << "OpStore Pointer '" << inst->words[pointerIndex] + << "'s type is void."; + return false; + } + + // validate storage class + { + uint32_t dataType; + uint32_t storageClass; + if (!module_.GetPointerTypeInfo(pointerType->id(), &dataType, + &storageClass)) { + DIAG(pointerIndex) << "OpStore Pointer '" + << inst->words[pointerIndex] + << "' is not pointer type"; + return false; + } + + if (storageClass == SpvStorageClassUniformConstant || + storageClass == SpvStorageClassInput || + storageClass == SpvStorageClassPushConstant) { + DIAG(pointerIndex) << "OpStore Pointer '" + << inst->words[pointerIndex] + << "' storage class is read-only"; + return false; + } + } + + auto objectIndex = 2; + auto object = module_.FindDef(inst->words[objectIndex]); + if (!object || !object->type_id()) { + DIAG(objectIndex) << "OpStore Object '" << inst->words[objectIndex] + << "' is not an object."; + return false; + } + auto objectType = module_.FindDef(object->type_id()); + assert(objectType); + if (SpvOpTypeVoid == objectType->opcode()) { + DIAG(objectIndex) << "OpStore Object '" << inst->words[objectIndex] + << "'s type is void."; + return false; + } + + if (type->id() != objectType->id()) { + if (!module_.options()->relax_struct_store || + type->opcode() != SpvOpTypeStruct || + objectType->opcode() != SpvOpTypeStruct) { + DIAG(pointerIndex) << "OpStore Pointer '" + << inst->words[pointerIndex] + << "'s type does not match Object '" + << object->id() << "'s type."; + return false; + } + + // TODO: Check for layout compatible matricies and arrays as well. + if (!AreLayoutCompatibleStructs(type, objectType)) { + DIAG(pointerIndex) << "OpStore Pointer '" + << inst->words[pointerIndex] + << "'s layout does not match Object '" + << object->id() << "'s layout."; + return false; + } + } + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto targetIndex = 1; + auto target = module_.FindDef(inst->words[targetIndex]); + if (!target) return false; + auto sourceIndex = 2; + auto source = module_.FindDef(inst->words[sourceIndex]); + if (!source) return false; + auto targetPointerType = module_.FindDef(target->type_id()); + assert(targetPointerType); + auto targetType = module_.FindDef(targetPointerType->words()[3]); + assert(targetType); + auto sourcePointerType = module_.FindDef(source->type_id()); + assert(sourcePointerType); + auto sourceType = module_.FindDef(sourcePointerType->words()[3]); + assert(sourceType); + if (targetType->id() != sourceType->id()) { + DIAG(sourceIndex) << "OpCopyMemory Target '" + << inst->words[sourceIndex] + << "'s type does not match Source '" + << sourceType->id() << "'s type."; + return false; + } + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto targetIndex = 1; + auto target = module_.FindDef(inst->words[targetIndex]); + if (!target) return false; + auto sourceIndex = 2; + auto source = module_.FindDef(inst->words[sourceIndex]); + if (!source) return false; + auto sizeIndex = 3; + auto size = module_.FindDef(inst->words[sizeIndex]); + if (!size) return false; + auto targetPointerType = module_.FindDef(target->type_id()); + if (!targetPointerType || SpvOpTypePointer != targetPointerType->opcode()) { + DIAG(targetIndex) << "OpCopyMemorySized Target '" + << inst->words[targetIndex] << "' is not a pointer."; + return false; + } + auto sourcePointerType = module_.FindDef(source->type_id()); + if (!sourcePointerType || SpvOpTypePointer != sourcePointerType->opcode()) { + DIAG(sourceIndex) << "OpCopyMemorySized Source '" + << inst->words[sourceIndex] << "' is not a pointer."; + return false; + } + switch (size->opcode()) { + // TODO: The following opcode's are assumed to be valid, refer to the + // following bug https://cvs.khronos.org/bugzilla/show_bug.cgi?id=13871 for + // clarification + case SpvOpConstant: + case SpvOpSpecConstant: { + auto sizeType = module_.FindDef(size->type_id()); + assert(sizeType); + if (SpvOpTypeInt != sizeType->opcode()) { + DIAG(sizeIndex) << "OpCopyMemorySized Size '" + << inst->words[sizeIndex] + << "'s type is not an integer type."; + return false; + } + } break; + case SpvOpVariable: { + auto pointerType = module_.FindDef(size->type_id()); + assert(pointerType); + auto sizeType = module_.FindDef(pointerType->type_id()); + if (!sizeType || SpvOpTypeInt != sizeType->opcode()) { + DIAG(sizeIndex) << "OpCopyMemorySized Size '" + << inst->words[sizeIndex] + << "'s variable type is not an integer type."; + return false; + } + } break; + default: + DIAG(sizeIndex) << "OpCopyMemorySized Size '" + << inst->words[sizeIndex] + << "' is not a constant or variable."; + return false; + } + // TODO: Check that consant is a least size 1, see the same bug as above for + // clarification? + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + std::string instr_name = + "Op" + std::string(spvOpcodeString(static_cast(inst->opcode))); + + // The result type must be OpTypePointer. Result Type is at word 1. + auto resultTypeIndex = 1; + auto resultTypeInstr = module_.FindDef(inst->words[resultTypeIndex]); + if (SpvOpTypePointer != resultTypeInstr->opcode()) { + DIAG(resultTypeIndex) << "The Result Type of " << instr_name << " '" + << inst->words[2] + << "' must be OpTypePointer. Found Op" + << spvOpcodeString( + static_cast(resultTypeInstr->opcode())) + << "."; + return false; + } + + // Result type is a pointer. Find out what it's pointing to. + // This will be used to make sure the indexing results in the same type. + // OpTypePointer word 3 is the type being pointed to. + auto resultTypePointedTo = module_.FindDef(resultTypeInstr->word(3)); + + // Base must be a pointer, pointing to the base of a composite object. + auto baseIdIndex = 3; + auto baseInstr = module_.FindDef(inst->words[baseIdIndex]); + auto baseTypeInstr = module_.FindDef(baseInstr->type_id()); + if (!baseTypeInstr || SpvOpTypePointer != baseTypeInstr->opcode()) { + DIAG(baseIdIndex) << "The Base '" << inst->words[baseIdIndex] + << "' in " << instr_name + << " instruction must be a pointer."; + return false; + } + + // The result pointer storage class and base pointer storage class must match. + // Word 2 of OpTypePointer is the Storage Class. + auto resultTypeStorageClass = resultTypeInstr->word(2); + auto baseTypeStorageClass = baseTypeInstr->word(2); + if (resultTypeStorageClass != baseTypeStorageClass) { + DIAG(resultTypeIndex) << "The result pointer storage class and base " + "pointer storage class in " + << instr_name << " do not match."; + return false; + } + + // The type pointed to by OpTypePointer (word 3) must be a composite type. + auto typePointedTo = module_.FindDef(baseTypeInstr->word(3)); + + // Check Universal Limit (SPIR-V Spec. Section 2.17). + // The number of indexes passed to OpAccessChain may not exceed 255 + // The instruction includes 4 words + N words (for N indexes) + const size_t num_indexes = inst->words.size() - 4; + const size_t num_indexes_limit = + module_.options()->universal_limits_.max_access_chain_indexes; + if (num_indexes > num_indexes_limit) { + DIAG(resultTypeIndex) << "The number of indexes in " << instr_name + << " may not exceed " << num_indexes_limit + << ". Found " << num_indexes << " indexes."; + return false; + } + // Indexes walk the type hierarchy to the desired depth, potentially down to + // scalar granularity. The first index in Indexes will select the top-level + // member/element/component/element of the base composite. All composite + // constituents use zero-based numbering, as described by their OpType... + // instruction. The second index will apply similarly to that result, and so + // on. Once any non-composite type is reached, there must be no remaining + // (unused) indexes. + for (size_t i = 4; i < inst->words.size(); ++i) { + const uint32_t cur_word = inst->words[i]; + // Earlier ID checks ensure that cur_word definition exists. + auto cur_word_instr = module_.FindDef(cur_word); + // The index must be a scalar integer type (See OpAccessChain in the Spec.) + auto indexTypeInstr = module_.FindDef(cur_word_instr->type_id()); + if (!indexTypeInstr || SpvOpTypeInt != indexTypeInstr->opcode()) { + DIAG(i) << "Indexes passed to " << instr_name + << " must be of type integer."; + return false; + } + switch (typePointedTo->opcode()) { + case SpvOpTypeMatrix: + case SpvOpTypeVector: + case SpvOpTypeArray: + case SpvOpTypeRuntimeArray: { + // In OpTypeMatrix, OpTypeVector, OpTypeArray, and OpTypeRuntimeArray, + // word 2 is the Element Type. + typePointedTo = module_.FindDef(typePointedTo->word(2)); + break; + } + case SpvOpTypeStruct: { + // In case of structures, there is an additional constraint on the + // index: the index must be an OpConstant. + if (SpvOpConstant != cur_word_instr->opcode()) { + DIAG(i) << "The passed to " << instr_name + << " to index into a " + "structure must be an OpConstant."; + return false; + } + // Get the index value from the OpConstant (word 3 of OpConstant). + // OpConstant could be a signed integer. But it's okay to treat it as + // unsigned because a negative constant int would never be seen as + // correct as a struct offset, since structs can't have more than 2 + // billion members. + const uint32_t cur_index = cur_word_instr->word(3); + // The index points to the struct member we want, therefore, the index + // should be less than the number of struct members. + const uint32_t num_struct_members = + static_cast(typePointedTo->words().size() - 2); + if (cur_index >= num_struct_members) { + DIAG(i) << "Index is out of bounds: " << instr_name + << " can not find index " << cur_index + << " into the structure '" << typePointedTo->id() + << "'. This structure has " << num_struct_members + << " members. Largest valid index is " + << num_struct_members - 1 << "."; + return false; + } + // Struct members IDs start at word 2 of OpTypeStruct. + auto structMemberId = typePointedTo->word(cur_index + 2); + typePointedTo = module_.FindDef(structMemberId); + break; + } + default: { + // Give an error. reached non-composite type while indexes still remain. + DIAG(i) << instr_name + << " reached non-composite type while indexes " + "still remain to be traversed."; + return false; + } + } + } + // At this point, we have fully walked down from the base using the indeces. + // The type being pointed to should be the same as the result type. + if (typePointedTo->id() != resultTypePointedTo->id()) { + DIAG(resultTypeIndex) + << instr_name << " result type (Op" + << spvOpcodeString(static_cast(resultTypePointedTo->opcode())) + << ") does not match the type that results from indexing into the base " + " (Op" + << spvOpcodeString(static_cast(typePointedTo->opcode())) << ")."; + return false; + } + + return true; +} + +template <> +bool idUsage::isValid( + const spv_instruction_t* inst, const spv_opcode_desc opcodeEntry) { + return isValid(inst, opcodeEntry); +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc opcodeEntry) { + // OpPtrAccessChain's validation rules are similar to OpAccessChain, with one + // difference: word 4 must be id of an integer (Element ). + // The grammar guarantees that there are at least 5 words in the instruction + // (i.e. if there are fewer than 5 words, the SPIR-V code will not compile.) + int elem_index = 4; + // We can remove the Element from the instruction words, and simply call + // the validation code of OpAccessChain. + spv_instruction_t new_inst = *inst; + new_inst.words.erase(new_inst.words.begin() + elem_index); + return isValid(&new_inst, opcodeEntry); +} + +template <> +bool idUsage::isValid( + const spv_instruction_t* inst, const spv_opcode_desc opcodeEntry) { + // Has the same validation rules as OpPtrAccessChain + return isValid(inst, opcodeEntry); +} + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto resultTypeIndex = 1; + auto resultType = module_.FindDef(inst->words[resultTypeIndex]); + if (!resultType) return false; + auto functionTypeIndex = 4; + auto functionType = module_.FindDef(inst->words[functionTypeIndex]); + if (!functionType || SpvOpTypeFunction != functionType->opcode()) { + DIAG(functionTypeIndex) + << "OpFunction Function Type '" << inst->words[functionTypeIndex] + << "' is not a function type."; + return false; + } + auto returnType = module_.FindDef(functionType->words()[2]); + assert(returnType); + if (returnType->id() != resultType->id()) { + DIAG(resultTypeIndex) << "OpFunction Result Type '" + << inst->words[resultTypeIndex] + << "' does not match the Function Type '" + << resultType->id() << "'s return type."; + return false; + } + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto resultTypeIndex = 1; + auto resultType = module_.FindDef(inst->words[resultTypeIndex]); + if (!resultType) return false; + // NOTE: Find OpFunction & ensure OpFunctionParameter is not out of place. + size_t paramIndex = 0; + assert(firstInst < inst && "Invalid instruction pointer"); + while (firstInst != --inst) { + if (SpvOpFunction == inst->opcode) { + break; + } else if (SpvOpFunctionParameter == inst->opcode) { + paramIndex++; + } + } + auto functionType = module_.FindDef(inst->words[4]); + assert(functionType); + if (paramIndex >= functionType->words().size() - 3) { + DIAG(0) << "Too many OpFunctionParameters for " << inst->words[2] + << ": expected " << functionType->words().size() - 3 + << " based on the function's type"; + return false; + } + auto paramType = module_.FindDef(functionType->words()[paramIndex + 3]); + assert(paramType); + if (resultType->id() != paramType->id()) { + DIAG(resultTypeIndex) << "OpFunctionParameter Result Type '" + << inst->words[resultTypeIndex] + << "' does not match the OpTypeFunction parameter " + "type of the same index."; + return false; + } + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto resultTypeIndex = 1; + auto resultType = module_.FindDef(inst->words[resultTypeIndex]); + if (!resultType) return false; + auto functionIndex = 3; + auto function = module_.FindDef(inst->words[functionIndex]); + if (!function || SpvOpFunction != function->opcode()) { + DIAG(functionIndex) << "OpFunctionCall Function '" + << inst->words[functionIndex] << "' is not a function."; + return false; + } + auto returnType = module_.FindDef(function->type_id()); + assert(returnType); + if (returnType->id() != resultType->id()) { + DIAG(resultTypeIndex) << "OpFunctionCall Result Type '" + << inst->words[resultTypeIndex] + << "'s type does not match Function '" + << returnType->id() << "'s return type."; + return false; + } + auto functionType = module_.FindDef(function->words()[4]); + assert(functionType); + auto functionCallArgCount = inst->words.size() - 4; + auto functionParamCount = functionType->words().size() - 3; + if (functionParamCount != functionCallArgCount) { + DIAG(inst->words.size() - 1) + << "OpFunctionCall Function 's parameter count does not match " + "the argument count."; + return false; + } + for (size_t argumentIndex = 4, paramIndex = 3; + argumentIndex < inst->words.size(); argumentIndex++, paramIndex++) { + auto argument = module_.FindDef(inst->words[argumentIndex]); + if (!argument) return false; + auto argumentType = module_.FindDef(argument->type_id()); + assert(argumentType); + auto parameterType = module_.FindDef(functionType->words()[paramIndex]); + assert(parameterType); + if (argumentType->id() != parameterType->id()) { + DIAG(argumentIndex) << "OpFunctionCall Argument '" + << inst->words[argumentIndex] + << "'s type does not match Function '" + << parameterType->id() << "'s parameter type."; + return false; + } + } + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto instr_name = [&inst]() { + std::string name = + "Op" + std::string(spvOpcodeString(static_cast(inst->opcode))); + return name; + }; + + // Result Type must be an OpTypeVector. + auto resultTypeIndex = 1; + auto resultType = module_.FindDef(inst->words[resultTypeIndex]); + if (!resultType || resultType->opcode() != SpvOpTypeVector) { + DIAG(resultTypeIndex) << "The Result Type of " << instr_name() + << " must be OpTypeVector. Found Op" + << spvOpcodeString( + static_cast(resultType->opcode())) + << "."; + return false; + } + + // The number of components in Result Type must be the same as the number of + // Component operands. + auto componentCount = inst->words.size() - 5; + auto vectorComponentCountIndex = 3; + auto resultVectorDimension = resultType->words()[vectorComponentCountIndex]; + if (componentCount != resultVectorDimension) { + DIAG(inst->words.size() - 1) + << instr_name() + << " component literals count does not match " + "Result Type '" + << resultType->id() << "'s vector component count."; + return false; + } + + // Vector 1 and Vector 2 must both have vector types, with the same Component + // Type as Result Type. + auto vector1Index = 3; + auto vector1Object = module_.FindDef(inst->words[vector1Index]); + auto vector1Type = module_.FindDef(vector1Object->type_id()); + auto vector2Index = 4; + auto vector2Object = module_.FindDef(inst->words[vector2Index]); + auto vector2Type = module_.FindDef(vector2Object->type_id()); + if (!vector1Type || vector1Type->opcode() != SpvOpTypeVector) { + DIAG(vector1Index) << "The type of Vector 1 must be OpTypeVector."; + return false; + } + if (!vector2Type || vector2Type->opcode() != SpvOpTypeVector) { + DIAG(vector2Index) << "The type of Vector 2 must be OpTypeVector."; + return false; + } + auto vectorComponentTypeIndex = 2; + auto resultComponentType = resultType->words()[vectorComponentTypeIndex]; + auto vector1ComponentType = vector1Type->words()[vectorComponentTypeIndex]; + if (vector1ComponentType != resultComponentType) { + DIAG(vector1Index) << "The Component Type of Vector 1 must be the same " + "as ResultType."; + return false; + } + auto vector2ComponentType = vector2Type->words()[vectorComponentTypeIndex]; + if (vector2ComponentType != resultComponentType) { + DIAG(vector2Index) << "The Component Type of Vector 2 must be the same " + "as ResultType."; + return false; + } + + // All Component literals must either be FFFFFFFF or in [0, N - 1]. + auto vector1ComponentCount = vector1Type->words()[vectorComponentCountIndex]; + auto vector2ComponentCount = vector2Type->words()[vectorComponentCountIndex]; + auto N = vector1ComponentCount + vector2ComponentCount; + auto firstLiteralIndex = 5; + for (size_t i = firstLiteralIndex; i < inst->words.size(); ++i) { + auto literal = inst->words[i]; + if (literal != 0xFFFFFFFF && literal >= N) { + DIAG(i) << "Component literal value " << literal << " is greater than " + << N - 1 << "."; + return false; + } + } + + return true; +} + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc /*opcodeEntry*/) { + auto thisInst = module_.FindDef(inst->words[2]); + SpvOp typeOp = module_.GetIdOpcode(thisInst->type_id()); + if (!spvOpcodeGeneratesType(typeOp)) { + DIAG(0) << "OpPhi's type " << module_.getIdName(thisInst->type_id()) + << " is not a type instruction."; + return false; + } + + auto block = thisInst->block(); + size_t numInOps = inst->words.size() - 3; + if (numInOps % 2 != 0) { + DIAG(0) << "OpPhi does not have an equal number of incoming values and " + "basic blocks."; + return false; + } + + // Create a uniqued vector of predecessor ids for comparison against + // incoming values. OpBranchConditional %cond %label %label produces two + // predecessors in the CFG. + std::vector predIds; + std::transform(block->predecessors()->begin(), block->predecessors()->end(), + std::back_inserter(predIds), + [](const libspirv::BasicBlock* b) { return b->id(); }); + std::sort(predIds.begin(), predIds.end()); + predIds.erase(std::unique(predIds.begin(), predIds.end()), predIds.end()); + + size_t numEdges = numInOps / 2; + if (numEdges != predIds.size()) { + DIAG(0) << "OpPhi's number of incoming blocks (" << numEdges + << ") does not match block's predecessor count (" + << block->predecessors()->size() << ")."; + return false; + } + + for (size_t i = 3; i < inst->words.size(); ++i) { + auto incId = inst->words[i]; + if (i % 2 == 1) { + // Incoming value type must match the phi result type. + auto incTypeId = module_.GetTypeId(incId); + if (thisInst->type_id() != incTypeId) { + DIAG(i) << "OpPhi's result type " + << module_.getIdName(thisInst->type_id()) + << " does not match incoming value " + << module_.getIdName(incId) << " type " + << module_.getIdName(incTypeId) << "."; + return false; + } + } else { + if (module_.GetIdOpcode(incId) != SpvOpLabel) { + DIAG(i) << "OpPhi's incoming basic block " + << module_.getIdName(incId) << " is not an OpLabel."; + return false; + } + + // Incoming basic block must be an immediate predecessor of the phi's + // block. + if (!std::binary_search(predIds.begin(), predIds.end(), incId)) { + DIAG(i) << "OpPhi's incoming basic block " + << module_.getIdName(incId) << " is not a predecessor of " + << module_.getIdName(block->id()) << "."; + return false; + } + } + } + + return true; +} + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + const size_t numOperands = inst->words.size() - 1; + const size_t condOperandIndex = 1; + const size_t targetTrueIndex = 2; + const size_t targetFalseIndex = 3; + + // num_operands is either 3 or 5 --- if 5, the last two need to be literal + // integers + if (numOperands != 3 && numOperands != 5) { + DIAG(0) << "OpBranchConditional requires either 3 or 5 parameters"; + return false; + } + + bool ret = true; + + // grab the condition operand and check that it is a bool + const auto condOp = module_.FindDef(inst->words[condOperandIndex]); + if (!condOp || !module_.IsBoolScalarType(condOp->type_id())) { + DIAG(0) + << "Condition operand for OpBranchConditional must be of boolean type"; + ret = false; + } + + // target operands must be OpLabel + // note that we don't need to check that the target labels are in the same + // function, + // PerformCfgChecks already checks for that + const auto targetOpTrue = module_.FindDef(inst->words[targetTrueIndex]); + if (!targetOpTrue || SpvOpLabel != targetOpTrue->opcode()) { + DIAG(0) << "The 'True Label' operand for OpBranchConditional must be the " + "ID of an OpLabel instruction"; + ret = false; + } + + const auto targetOpFalse = module_.FindDef(inst->words[targetFalseIndex]); + if (!targetOpFalse || SpvOpLabel != targetOpFalse->opcode()) { + DIAG(0) << "The 'False Label' operand for OpBranchConditional must be the " + "ID of an OpLabel instruction"; + ret = false; + } + + return ret; +} + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +template <> +bool idUsage::isValid(const spv_instruction_t* inst, + const spv_opcode_desc) { + auto valueIndex = 1; + auto value = module_.FindDef(inst->words[valueIndex]); + if (!value || !value->type_id()) { + DIAG(valueIndex) << "OpReturnValue Value '" << inst->words[valueIndex] + << "' does not represent a value."; + return false; + } + auto valueType = module_.FindDef(value->type_id()); + if (!valueType || SpvOpTypeVoid == valueType->opcode()) { + DIAG(valueIndex) << "OpReturnValue value's type '" << value->type_id() + << "' is missing or void."; + return false; + } + + const bool uses_variable_pointer = + module_.features().variable_pointers || + module_.features().variable_pointers_storage_buffer; + + if (addressingModel == SpvAddressingModelLogical && + SpvOpTypePointer == valueType->opcode() && !uses_variable_pointer && + !module_.options()->relax_logcial_pointer) { + DIAG(valueIndex) + << "OpReturnValue value's type '" << value->type_id() + << "' is a pointer, which is invalid in the Logical addressing model."; + return false; + } + + // NOTE: Find OpFunction + const spv_instruction_t* function = inst - 1; + while (firstInst != function) { + if (SpvOpFunction == function->opcode) break; + function--; + } + if (SpvOpFunction != function->opcode) { + DIAG(valueIndex) << "OpReturnValue is not in a basic block."; + return false; + } + auto returnType = module_.FindDef(function->words[1]); + if (!returnType || returnType->id() != valueType->id()) { + DIAG(valueIndex) << "OpReturnValue Value '" << inst->words[valueIndex] + << "'s type does not match OpFunction's return type."; + return false; + } + return true; +} + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) { +} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) { +} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) { +} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid(const spv_instruction_t *inst, + const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#if 0 +template <> +bool idUsage::isValid( + const spv_instruction_t *inst, const spv_opcode_desc opcodeEntry) {} +#endif + +#undef DIAG + +bool idUsage::isValid(const spv_instruction_t* inst) { + spv_opcode_desc opcodeEntry = nullptr; + if (spvOpcodeTableValueLookup(targetEnv, opcodeTable, inst->opcode, + &opcodeEntry)) + return false; +#define CASE(OpCode) \ + case Spv##OpCode: \ + return isValid(inst, opcodeEntry); +#define TODO(OpCode) \ + case Spv##OpCode: \ + return true; + switch (inst->opcode) { + TODO(OpUndef) + CASE(OpMemberName) + CASE(OpLine) + CASE(OpDecorate) + CASE(OpMemberDecorate) + CASE(OpDecorationGroup) + CASE(OpGroupDecorate) + CASE(OpGroupMemberDecorate) + TODO(OpExtInst) + CASE(OpEntryPoint) + CASE(OpExecutionMode) + CASE(OpTypeVector) + CASE(OpTypeMatrix) + CASE(OpTypeSampler) + CASE(OpTypeArray) + CASE(OpTypeRuntimeArray) + CASE(OpTypeStruct) + CASE(OpTypePointer) + CASE(OpTypeFunction) + CASE(OpTypePipe) + CASE(OpConstantTrue) + CASE(OpConstantFalse) + CASE(OpConstantComposite) + CASE(OpConstantSampler) + CASE(OpConstantNull) + CASE(OpSpecConstantTrue) + CASE(OpSpecConstantFalse) + CASE(OpSpecConstantComposite) + CASE(OpSampledImage) + TODO(OpSpecConstantOp) + CASE(OpVariable) + CASE(OpLoad) + CASE(OpStore) + CASE(OpCopyMemory) + CASE(OpCopyMemorySized) + CASE(OpAccessChain) + CASE(OpInBoundsAccessChain) + CASE(OpPtrAccessChain) + CASE(OpInBoundsPtrAccessChain) + TODO(OpArrayLength) + TODO(OpGenericPtrMemSemantics) + CASE(OpFunction) + CASE(OpFunctionParameter) + CASE(OpFunctionCall) + // Conversion opcodes are validated in validate_conversion.cpp. + CASE(OpVectorShuffle) + // Other composite opcodes are validated in validate_composites.cpp. + // Arithmetic opcodes are validated in validate_arithmetics.cpp. + // Bitwise opcodes are validated in validate_bitwise.cpp. + // Logical opcodes are validated in validate_logicals.cpp. + // Derivative opcodes are validated in validate_derivatives.cpp. + CASE(OpPhi) + TODO(OpLoopMerge) + TODO(OpSelectionMerge) + TODO(OpBranch) + CASE(OpBranchConditional) + TODO(OpSwitch) + CASE(OpReturnValue) + TODO(OpLifetimeStart) + TODO(OpLifetimeStop) + TODO(OpAtomicLoad) + TODO(OpAtomicStore) + TODO(OpAtomicExchange) + TODO(OpAtomicCompareExchange) + TODO(OpAtomicCompareExchangeWeak) + TODO(OpAtomicIIncrement) + TODO(OpAtomicIDecrement) + TODO(OpAtomicIAdd) + TODO(OpAtomicISub) + TODO(OpAtomicUMin) + TODO(OpAtomicUMax) + TODO(OpAtomicAnd) + TODO(OpAtomicOr) + TODO(OpAtomicSMin) + TODO(OpAtomicSMax) + TODO(OpEmitStreamVertex) + TODO(OpEndStreamPrimitive) + TODO(OpGroupAsyncCopy) + TODO(OpGroupWaitEvents) + TODO(OpGroupAll) + TODO(OpGroupAny) + TODO(OpGroupBroadcast) + TODO(OpGroupIAdd) + TODO(OpGroupFAdd) + TODO(OpGroupFMin) + TODO(OpGroupUMin) + TODO(OpGroupSMin) + TODO(OpGroupFMax) + TODO(OpGroupUMax) + TODO(OpGroupSMax) + TODO(OpEnqueueMarker) + TODO(OpEnqueueKernel) + TODO(OpGetKernelNDrangeSubGroupCount) + TODO(OpGetKernelNDrangeMaxSubGroupSize) + TODO(OpGetKernelWorkGroupSize) + TODO(OpGetKernelPreferredWorkGroupSizeMultiple) + TODO(OpRetainEvent) + TODO(OpReleaseEvent) + TODO(OpCreateUserEvent) + TODO(OpIsValidEvent) + TODO(OpSetUserEventStatus) + TODO(OpCaptureEventProfilingInfo) + TODO(OpGetDefaultQueue) + TODO(OpBuildNDRange) + TODO(OpReadPipe) + TODO(OpWritePipe) + TODO(OpReservedReadPipe) + TODO(OpReservedWritePipe) + TODO(OpReserveReadPipePackets) + TODO(OpReserveWritePipePackets) + TODO(OpCommitReadPipe) + TODO(OpCommitWritePipe) + TODO(OpIsValidReserveId) + TODO(OpGetNumPipePackets) + TODO(OpGetMaxPipePackets) + TODO(OpGroupReserveReadPipePackets) + TODO(OpGroupReserveWritePipePackets) + TODO(OpGroupCommitReadPipe) + TODO(OpGroupCommitWritePipe) + default: + return true; + } +#undef TODO +#undef CASE +} + +bool idUsage::AreLayoutCompatibleStructs(const libspirv::Instruction* type1, + const libspirv::Instruction* type2) { + if (type1->opcode() != SpvOpTypeStruct) { + return false; + } + if (type2->opcode() != SpvOpTypeStruct) { + return false; + } + + if (!HaveLayoutCompatibleMembers(type1, type2)) return false; + + return HaveSameLayoutDecorations(type1, type2); +} + +bool idUsage::HaveLayoutCompatibleMembers(const libspirv::Instruction* type1, + const libspirv::Instruction* type2) { + assert(type1->opcode() == SpvOpTypeStruct && + "type1 must be and OpTypeStruct instruction."); + assert(type2->opcode() == SpvOpTypeStruct && + "type2 must be and OpTypeStruct instruction."); + const auto& type1_operands = type1->operands(); + const auto& type2_operands = type2->operands(); + if (type1_operands.size() != type2_operands.size()) { + return false; + } + + for (size_t operand = 2; operand < type1_operands.size(); ++operand) { + if (type1->word(operand) != type2->word(operand)) { + auto def1 = module_.FindDef(type1->word(operand)); + auto def2 = module_.FindDef(type2->word(operand)); + if (!AreLayoutCompatibleStructs(def1, def2)) { + return false; + } + } + } + return true; +} + +bool idUsage::HaveSameLayoutDecorations(const libspirv::Instruction* type1, + const libspirv::Instruction* type2) { + assert(type1->opcode() == SpvOpTypeStruct && + "type1 must be and OpTypeStruct instruction."); + assert(type2->opcode() == SpvOpTypeStruct && + "type2 must be and OpTypeStruct instruction."); + const std::vector& type1_decorations = + module_.id_decorations(type1->id()); + const std::vector& type2_decorations = + module_.id_decorations(type2->id()); + + // TODO: Will have to add other check for arrays an matricies if we want to + // handle them. + if (HasConflictingMemberOffsets(type1_decorations, type2_decorations)) { + return false; + } + + return true; +} + +bool idUsage::HasConflictingMemberOffsets( + const vector& type1_decorations, + const vector& type2_decorations) const { + { + // We are interested in conflicting decoration. If a decoration is in one + // list but not the other, then we will assume the code is correct. We are + // looking for things we know to be wrong. + // + // We do not have to traverse type2_decoration because, after traversing + // type1_decorations, anything new will not be found in + // type1_decoration. Therefore, it cannot lead to a conflict. + for (const Decoration& decoration : type1_decorations) { + switch (decoration.dec_type()) { + case SpvDecorationOffset: { + // Since these affect the layout of the struct, they must be present + // in both structs. + auto compare = [&decoration](const Decoration& rhs) { + if (rhs.dec_type() != SpvDecorationOffset) return false; + return decoration.struct_member_index() == + rhs.struct_member_index(); + }; + auto i = find_if(type2_decorations.begin(), type2_decorations.end(), + compare); + if (i != type2_decorations.end() && + decoration.params().front() != i->params().front()) { + return true; + } + } break; + default: + // This decoration does not affect the layout of the structure, so + // just moving on. + break; + } + } + } + return false; +} +} // anonymous namespace + +namespace libspirv { + +spv_result_t UpdateIdUse(ValidationState_t& _) { + for (const auto& inst : _.ordered_instructions()) { + for (auto& operand : inst.operands()) { + const spv_operand_type_t& type = operand.type; + const uint32_t operand_id = inst.word(operand.offset); + if (spvIsIdType(type) && type != SPV_OPERAND_TYPE_RESULT_ID) { + if (auto def = _.FindDef(operand_id)) + def->RegisterUse(&inst, operand.offset); + } + } + } + return SPV_SUCCESS; +} + +/// This function checks all ID definitions dominate their use in the CFG. +/// +/// This function will iterate over all ID definitions that are defined in the +/// functions of a module and make sure that the definitions appear in a +/// block that dominates their use. +/// +/// NOTE: This function does NOT check module scoped functions which are +/// checked during the initial binary parse in the IdPass below +spv_result_t CheckIdDefinitionDominateUse(const ValidationState_t& _) { + unordered_set phi_instructions; + for (const auto& definition : _.all_definitions()) { + // Check only those definitions defined in a function + if (const Function* func = definition.second->function()) { + if (const BasicBlock* block = definition.second->block()) { + if (!block->reachable()) continue; + // If the Id is defined within a block then make sure all references to + // that Id appear in a blocks that are dominated by the defining block + for (auto& use_index_pair : definition.second->uses()) { + const Instruction* use = use_index_pair.first; + if (const BasicBlock* use_block = use->block()) { + if (use_block->reachable() == false) continue; + if (use->opcode() == SpvOpPhi) { + phi_instructions.insert(use); + } else if (!block->dominates(*use->block())) { + return _.diag(SPV_ERROR_INVALID_ID) + << "ID " << _.getIdName(definition.first) + << " defined in block " << _.getIdName(block->id()) + << " does not dominate its use in block " + << _.getIdName(use_block->id()); + } + } + } + } else { + // If the Ids defined within a function but not in a block(i.e. function + // parameters, block ids), then make sure all references to that Id + // appear within the same function + for (auto use : definition.second->uses()) { + const Instruction* inst = use.first; + if (inst->function() && inst->function() != func) { + return _.diag(SPV_ERROR_INVALID_ID) + << "ID " << _.getIdName(definition.first) + << " used in function " + << _.getIdName(inst->function()->id()) + << " is used outside of it's defining function " + << _.getIdName(func->id()); + } + } + } + } + // NOTE: Ids defined outside of functions must appear before they are used + // This check is being performed in the IdPass function + } + + // Check all OpPhi parent blocks are dominated by the variable's defining + // blocks + for (const Instruction* phi : phi_instructions) { + if (phi->block()->reachable() == false) continue; + for (size_t i = 3; i < phi->operands().size(); i += 2) { + const Instruction* variable = _.FindDef(phi->word(i)); + const BasicBlock* parent = + phi->function()->GetBlock(phi->word(i + 1)).first; + if (variable->block() && parent->reachable() && + !variable->block()->dominates(*parent)) { + return _.diag(SPV_ERROR_INVALID_ID) + << "In OpPhi instruction " << _.getIdName(phi->id()) << ", ID " + << _.getIdName(variable->id()) + << " definition does not dominate its parent " + << _.getIdName(parent->id()); + } + } + } + + return SPV_SUCCESS; +} + +// Performs SSA validation on the IDs of an instruction. The +// can_have_forward_declared_ids functor should return true if the +// instruction operand's ID can be forward referenced. +spv_result_t IdPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + auto can_have_forward_declared_ids = + spvOperandCanBeForwardDeclaredFunction(static_cast(inst->opcode)); + + // Keep track of a result id defined by this instruction. 0 means it + // does not define an id. + uint32_t result_id = 0; + + for (unsigned i = 0; i < inst->num_operands; i++) { + const spv_parsed_operand_t& operand = inst->operands[i]; + const spv_operand_type_t& type = operand.type; + // We only care about Id operands, which are a single word. + const uint32_t operand_word = inst->words[operand.offset]; + + auto ret = SPV_ERROR_INTERNAL; + switch (type) { + case SPV_OPERAND_TYPE_RESULT_ID: + // NOTE: Multiple Id definitions are being checked by the binary parser. + // + // Defer undefined-forward-reference removal until after we've analyzed + // the remaining operands to this instruction. Deferral only matters + // for + // OpPhi since it's the only case where it defines its own forward + // reference. Other instructions that can have forward references + // either don't define a value or the forward reference is to a function + // Id (and hence defined outside of a function body). + result_id = operand_word; + // NOTE: The result Id is added (in RegisterInstruction) *after* all of + // the other Ids have been checked to avoid premature use in the same + // instruction. + ret = SPV_SUCCESS; + break; + case SPV_OPERAND_TYPE_ID: + case SPV_OPERAND_TYPE_TYPE_ID: + case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID: + case SPV_OPERAND_TYPE_SCOPE_ID: + if (_.IsDefinedId(operand_word)) { + ret = SPV_SUCCESS; + } else if (can_have_forward_declared_ids(i)) { + ret = _.ForwardDeclareId(operand_word); + } else { + ret = _.diag(SPV_ERROR_INVALID_ID) + << "ID " << _.getIdName(operand_word) + << " has not been defined"; + } + break; + default: + ret = SPV_SUCCESS; + break; + } + if (SPV_SUCCESS != ret) { + return ret; + } + } + if (result_id) { + _.RemoveIfForwardDeclared(result_id); + } + _.RegisterInstruction(*inst); + return SPV_SUCCESS; +} +} // namespace libspirv + +spv_result_t spvValidateInstructionIDs(const spv_instruction_t* pInsts, + const uint64_t instCount, + const libspirv::ValidationState_t& state, + spv_position position) { + idUsage idUsage(state.context(), pInsts, instCount, state.memory_model(), + state.addressing_model(), state, state.entry_points(), + position, state.context()->consumer); + for (uint64_t instIndex = 0; instIndex < instCount; ++instIndex) { + if (!idUsage.isValid(&pInsts[instIndex])) return SPV_ERROR_INVALID_ID; + position->index += pInsts[instIndex].words.size(); + } + return SPV_SUCCESS; +} diff --git a/3rdparty/spirv-tools/source/validate_image.cpp b/3rdparty/spirv-tools/source/validate_image.cpp new file mode 100644 index 000000000..1d671b954 --- /dev/null +++ b/3rdparty/spirv-tools/source/validate_image.cpp @@ -0,0 +1,1670 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Validates correctness of image instructions. + +#include "validate.h" + +#include "diagnostic.h" +#include "opcode.h" +#include "spirv_target_env.h" +#include "util/bitutils.h" +#include "val/instruction.h" +#include "val/validation_state.h" + +namespace libspirv { + +namespace { + +// Performs compile time check that all SpvImageOperandsXXX cases are handled in +// this module. If SpvImageOperandsXXX list changes, this function will fail the +// build. +// For all other purposes this is a dummy function. +bool CheckAllImageOperandsHandled() { + SpvImageOperandsMask enum_val = SpvImageOperandsBiasMask; + + // Some improvised code to prevent the compiler from considering enum_val + // constant and optimizing the switch away. + uint32_t stack_var = 0; + if (reinterpret_cast(&stack_var) % 256) + enum_val = SpvImageOperandsLodMask; + + switch (enum_val) { + // Please update the validation rules in this module if you are changing + // the list of image operands, and add new enum values to this switch. + case SpvImageOperandsMaskNone: + return false; + case SpvImageOperandsBiasMask: + case SpvImageOperandsLodMask: + case SpvImageOperandsGradMask: + case SpvImageOperandsConstOffsetMask: + case SpvImageOperandsOffsetMask: + case SpvImageOperandsConstOffsetsMask: + case SpvImageOperandsSampleMask: + case SpvImageOperandsMinLodMask: + return true; + } + return false; +} + +// Used by GetImageTypeInfo. See OpTypeImage spec for more information. +struct ImageTypeInfo { + uint32_t sampled_type = 0; + SpvDim dim = SpvDimMax; + uint32_t depth = 0; + uint32_t arrayed = 0; + uint32_t multisampled = 0; + uint32_t sampled = 0; + SpvImageFormat format = SpvImageFormatMax; + SpvAccessQualifier access_qualifier = SpvAccessQualifierMax; +}; + +// Provides information on image type. |id| should be object of either +// OpTypeImage or OpTypeSampledImage type. Returns false in case of failure +// (not a valid id, failed to parse the instruction, etc). +bool GetImageTypeInfo(const ValidationState_t& _, uint32_t id, + ImageTypeInfo* info) { + if (!id || !info) return false; + + const Instruction* inst = _.FindDef(id); + assert(inst); + + if (inst->opcode() == SpvOpTypeSampledImage) { + inst = _.FindDef(inst->word(2)); + assert(inst); + } + + if (inst->opcode() != SpvOpTypeImage) return false; + + const size_t num_words = inst->words().size(); + if (num_words != 9 && num_words != 10) return false; + + info->sampled_type = inst->word(2); + info->dim = static_cast(inst->word(3)); + info->depth = inst->word(4); + info->arrayed = inst->word(5); + info->multisampled = inst->word(6); + info->sampled = inst->word(7); + info->format = static_cast(inst->word(8)); + info->access_qualifier = num_words < 10 + ? SpvAccessQualifierMax + : static_cast(inst->word(9)); + return true; +} + +bool IsImplicitLod(SpvOp opcode) { + switch (opcode) { + case SpvOpImageSampleImplicitLod: + case SpvOpImageSampleDrefImplicitLod: + case SpvOpImageSampleProjImplicitLod: + case SpvOpImageSampleProjDrefImplicitLod: + case SpvOpImageSparseSampleImplicitLod: + case SpvOpImageSparseSampleDrefImplicitLod: + case SpvOpImageSparseSampleProjImplicitLod: + case SpvOpImageSparseSampleProjDrefImplicitLod: + return true; + default: + break; + }; + return false; +} + +bool IsExplicitLod(SpvOp opcode) { + switch (opcode) { + case SpvOpImageSampleExplicitLod: + case SpvOpImageSampleDrefExplicitLod: + case SpvOpImageSampleProjExplicitLod: + case SpvOpImageSampleProjDrefExplicitLod: + case SpvOpImageSparseSampleExplicitLod: + case SpvOpImageSparseSampleDrefExplicitLod: + case SpvOpImageSparseSampleProjExplicitLod: + case SpvOpImageSparseSampleProjDrefExplicitLod: + return true; + default: + break; + }; + return false; +} + +// Returns true if the opcode is a Image instruction which applies +// homogenous projection to the coordinates. +bool IsProj(SpvOp opcode) { + switch (opcode) { + case SpvOpImageSampleProjImplicitLod: + case SpvOpImageSampleProjDrefImplicitLod: + case SpvOpImageSparseSampleProjImplicitLod: + case SpvOpImageSparseSampleProjDrefImplicitLod: + case SpvOpImageSampleProjExplicitLod: + case SpvOpImageSampleProjDrefExplicitLod: + case SpvOpImageSparseSampleProjExplicitLod: + case SpvOpImageSparseSampleProjDrefExplicitLod: + return true; + default: + break; + }; + return false; +} + +// Returns the number of components in a coordinate used to access a texel in +// a single plane of an image with the given parameters. +uint32_t GetPlaneCoordSize(const ImageTypeInfo& info) { + uint32_t plane_size = 0; + // If this switch breaks your build, please add new values below. + switch (info.dim) { + case SpvDim1D: + case SpvDimBuffer: + plane_size = 1; + break; + case SpvDim2D: + case SpvDimRect: + case SpvDimSubpassData: + plane_size = 2; + break; + case SpvDim3D: + case SpvDimCube: + // For Cube direction vector is used instead of UV. + plane_size = 3; + break; + case SpvDimMax: + assert(0); + break; + } + + return plane_size; +} + +// Returns minimal number of coordinates based on image dim, arrayed and whether +// the instruction uses projection coordinates. +uint32_t GetMinCoordSize(SpvOp opcode, const ImageTypeInfo& info) { + if (info.dim == SpvDimCube && + (opcode == SpvOpImageRead || opcode == SpvOpImageWrite || + opcode == SpvOpImageSparseRead)) { + // These opcodes use UV for Cube, not direction vector. + return 3; + } + + return GetPlaneCoordSize(info) + info.arrayed + (IsProj(opcode) ? 1 : 0); +} + +// Checks ImageOperand bitfield and respective operands. +spv_result_t ValidateImageOperands(ValidationState_t& _, + const spv_parsed_instruction_t& inst, + const ImageTypeInfo& info, uint32_t mask, + uint32_t word_index) { + static const bool kAllImageOperandsHandled = CheckAllImageOperandsHandled(); + (void)kAllImageOperandsHandled; + + const SpvOp opcode = static_cast(inst.opcode); + const uint32_t num_words = inst.num_words; + + size_t expected_num_image_operand_words = spvutils::CountSetBits(mask); + if (mask & SpvImageOperandsGradMask) { + // Grad uses two words. + ++expected_num_image_operand_words; + } + + if (expected_num_image_operand_words != num_words - word_index) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Number of image operand ids doesn't correspond to the bit mask: " + << spvOpcodeString(opcode); + } + + if (spvutils::CountSetBits(mask & (SpvImageOperandsOffsetMask | + SpvImageOperandsConstOffsetMask | + SpvImageOperandsConstOffsetsMask)) > 1) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image Operands Offset, ConstOffset, ConstOffsets cannot be used " + << "together: " << spvOpcodeString(opcode); + }; + + const bool is_implicit_lod = IsImplicitLod(opcode); + const bool is_explicit_lod = IsExplicitLod(opcode); + + // The checks should be done in the order of definition of OperandImage. + + if (mask & SpvImageOperandsBiasMask) { + if (!is_implicit_lod) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image Operand Bias can only be used with ImplicitLod opcodes: " + << spvOpcodeString(opcode); + }; + + const uint32_t type_id = _.GetTypeId(inst.words[word_index++]); + if (!_.IsFloatScalarType(type_id)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image Operand Bias to be float scalar: " + << spvOpcodeString(opcode); + } + + if (info.dim != SpvDim1D && info.dim != SpvDim2D && info.dim != SpvDim3D && + info.dim != SpvDimCube) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image Operand Bias requires 'Dim' parameter to be 1D, 2D, 3D " + "or " + << "Cube: " << spvOpcodeString(opcode); + } + + if (info.multisampled != 0) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image Operand Bias requires 'MS' parameter to be 0: " + << spvOpcodeString(opcode); + } + } + + if (mask & SpvImageOperandsLodMask) { + if (!is_explicit_lod && opcode != SpvOpImageFetch && + opcode != SpvOpImageSparseFetch) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image Operand Lod can only be used with ExplicitLod opcodes " + << "and OpImageFetch: " << spvOpcodeString(opcode); + }; + + if (mask & SpvImageOperandsGradMask) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image Operand bits Lod and Grad cannot be set at the same " + "time: " + << spvOpcodeString(opcode); + } + + const uint32_t type_id = _.GetTypeId(inst.words[word_index++]); + if (is_explicit_lod) { + if (!_.IsFloatScalarType(type_id)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image Operand Lod to be float scalar when used " + << "with ExplicitLod: " << spvOpcodeString(opcode); + } + } else { + if (!_.IsIntScalarType(type_id)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image Operand Lod to be int scalar when used with " + << "OpImageFetch"; + } + } + + if (info.dim != SpvDim1D && info.dim != SpvDim2D && info.dim != SpvDim3D && + info.dim != SpvDimCube) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image Operand Lod requires 'Dim' parameter to be 1D, 2D, 3D " + "or " + << "Cube: " << spvOpcodeString(opcode); + } + + if (info.multisampled != 0) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image Operand Lod requires 'MS' parameter to be 0: " + << spvOpcodeString(opcode); + } + } + + if (mask & SpvImageOperandsGradMask) { + if (!is_explicit_lod) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image Operand Grad can only be used with ExplicitLod opcodes: " + << spvOpcodeString(opcode); + }; + + const uint32_t dx_type_id = _.GetTypeId(inst.words[word_index++]); + const uint32_t dy_type_id = _.GetTypeId(inst.words[word_index++]); + if (!_.IsFloatScalarOrVectorType(dx_type_id) || + !_.IsFloatScalarOrVectorType(dy_type_id)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected both Image Operand Grad ids to be float scalars or " + << "vectors: " << spvOpcodeString(opcode); + } + + const uint32_t plane_size = GetPlaneCoordSize(info); + const uint32_t dx_size = _.GetDimension(dx_type_id); + const uint32_t dy_size = _.GetDimension(dy_type_id); + if (plane_size != dx_size) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image Operand Grad dx to have " << plane_size + << " components, but given " << dx_size << ": " + << spvOpcodeString(opcode); + } + + if (plane_size != dy_size) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image Operand Grad dy to have " << plane_size + << " components, but given " << dy_size << ": " + << spvOpcodeString(opcode); + } + + if (info.multisampled != 0) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image Operand Grad requires 'MS' parameter to be 0: " + << spvOpcodeString(opcode); + } + } + + if (mask & SpvImageOperandsConstOffsetMask) { + if (info.dim == SpvDimCube) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image Operand ConstOffset cannot be used with Cube Image " + "'Dim': " + << spvOpcodeString(opcode); + } + + const uint32_t id = inst.words[word_index++]; + const uint32_t type_id = _.GetTypeId(id); + if (!_.IsIntScalarOrVectorType(type_id)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image Operand ConstOffset to be int scalar or " + << "vector: " << spvOpcodeString(opcode); + } + + if (!spvOpcodeIsConstant(_.GetIdOpcode(id))) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image Operand ConstOffset to be a const object: " + << spvOpcodeString(opcode); + } + + const uint32_t plane_size = GetPlaneCoordSize(info); + const uint32_t offset_size = _.GetDimension(type_id); + if (plane_size != offset_size) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image Operand ConstOffset to have " << plane_size + << " components, but given " << offset_size << ": " + << spvOpcodeString(opcode); + } + } + + if (mask & SpvImageOperandsOffsetMask) { + if (info.dim == SpvDimCube) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image Operand Offset cannot be used with Cube Image 'Dim': " + << spvOpcodeString(opcode); + } + + const uint32_t id = inst.words[word_index++]; + const uint32_t type_id = _.GetTypeId(id); + if (!_.IsIntScalarOrVectorType(type_id)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image Operand Offset to be int scalar or " + << "vector: " << spvOpcodeString(opcode); + } + + const uint32_t plane_size = GetPlaneCoordSize(info); + const uint32_t offset_size = _.GetDimension(type_id); + if (plane_size != offset_size) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image Operand Offset to have " << plane_size + << " components, but given " << offset_size << ": " + << spvOpcodeString(opcode); + } + } + + if (mask & SpvImageOperandsConstOffsetsMask) { + if (opcode != SpvOpImageGather && opcode != SpvOpImageDrefGather && + opcode != SpvOpImageSparseGather && + opcode != SpvOpImageSparseDrefGather) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image Operand ConstOffsets can only be used with " + "OpImageGather " + << "and OpImageDrefGather: " << spvOpcodeString(opcode); + } + + if (info.dim == SpvDimCube) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image Operand ConstOffsets cannot be used with Cube Image " + "'Dim': " + << spvOpcodeString(opcode); + } + + const uint32_t id = inst.words[word_index++]; + const uint32_t type_id = _.GetTypeId(id); + const Instruction* type_inst = _.FindDef(type_id); + assert(type_inst); + + if (type_inst->opcode() != SpvOpTypeArray) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image Operand ConstOffsets to be an array of size 4: " + << spvOpcodeString(opcode); + } + + uint64_t array_size = 0; + if (!_.GetConstantValUint64(type_inst->word(3), &array_size)) { + assert(0 && "Array type definition is corrupt"); + } + + if (array_size != 4) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image Operand ConstOffsets to be an array of size 4: " + << spvOpcodeString(opcode); + } + + const uint32_t component_type = type_inst->word(2); + if (!_.IsIntVectorType(component_type) || + _.GetDimension(component_type) != 2) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image Operand ConstOffsets array componenets to be " + "int " + << "vectors of size 2: " << spvOpcodeString(opcode); + } + + if (!spvOpcodeIsConstant(_.GetIdOpcode(id))) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image Operand ConstOffsets to be a const object: " + << spvOpcodeString(opcode); + } + } + + if (mask & SpvImageOperandsSampleMask) { + if (opcode != SpvOpImageFetch && opcode != SpvOpImageRead && + opcode != SpvOpImageWrite && opcode != SpvOpImageSparseFetch && + opcode != SpvOpImageSparseRead) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image Operand Sample can only be used with OpImageFetch, " + << "OpImageRead, OpImageWrite, OpImageSparseFetch and " + << "OpImageSparseRead: " << spvOpcodeString(opcode); + } + + if (info.multisampled == 0) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image Operand Sample requires non-zero 'MS' parameter: " + << spvOpcodeString(opcode); + } + + const uint32_t type_id = _.GetTypeId(inst.words[word_index++]); + if (!_.IsIntScalarType(type_id)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image Operand Sample to be int scalar: " + << spvOpcodeString(opcode); + } + } + + if (mask & SpvImageOperandsMinLodMask) { + if (!is_implicit_lod && !(mask & SpvImageOperandsGradMask)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image Operand MinLod can only be used with ImplicitLod " + << "opcodes or together with Image Operand Grad: " + << spvOpcodeString(opcode); + }; + + const uint32_t type_id = _.GetTypeId(inst.words[word_index++]); + if (!_.IsFloatScalarType(type_id)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image Operand MinLod to be float scalar: " + << spvOpcodeString(opcode); + } + + if (info.dim != SpvDim1D && info.dim != SpvDim2D && info.dim != SpvDim3D && + info.dim != SpvDimCube) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image Operand MinLod requires 'Dim' parameter to be 1D, 2D, " + "3D " + << "or Cube: " << spvOpcodeString(opcode); + } + + if (info.multisampled != 0) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image Operand MinLod requires 'MS' parameter to be 0: " + << spvOpcodeString(opcode); + } + } + + return SPV_SUCCESS; +} + +// Checks some of the validation rules which are common to multiple opcodes. +spv_result_t ValidateImageCommon(ValidationState_t& _, + const spv_parsed_instruction_t& inst, + const ImageTypeInfo& info) { + const SpvOp opcode = static_cast(inst.opcode); + if (IsProj(opcode)) { + if (info.dim != SpvDim1D && info.dim != SpvDim2D && info.dim != SpvDim3D && + info.dim != SpvDimRect) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image 'Dim' parameter to be 1D, 2D, 3D or Rect: " + << spvOpcodeString(opcode); + } + + if (info.multisampled != 0) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image Image 'MS' parameter to be 0: " + << spvOpcodeString(opcode); + } + + if (info.arrayed != 0) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image Image 'arrayed' parameter to be 0: " + << spvOpcodeString(opcode); + } + } + + if (opcode == SpvOpImageRead || opcode == SpvOpImageSparseRead || + opcode == SpvOpImageWrite) { + if (info.sampled == 0) { + } else if (info.sampled == 2) { + if (info.dim == SpvDim1D && !_.HasCapability(SpvCapabilityImage1D)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Capability Image1D is required to access storage image: " + << spvOpcodeString(opcode); + } else if (info.dim == SpvDimRect && + !_.HasCapability(SpvCapabilityImageRect)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Capability ImageRect is required to access storage image: " + << spvOpcodeString(opcode); + } else if (info.dim == SpvDimBuffer && + !_.HasCapability(SpvCapabilityImageBuffer)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Capability ImageBuffer is required to access storage image: " + << spvOpcodeString(opcode); + } else if (info.dim == SpvDimCube && info.arrayed == 1 && + !_.HasCapability(SpvCapabilityImageCubeArray)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Capability ImageCubeArray is required to access storage " + << "image: " << spvOpcodeString(opcode); + } + + if (info.multisampled == 1 && + !_.HasCapability(SpvCapabilityImageMSArray)) { +#if 0 + // TODO(atgoo@github.com) The description of this rule in the spec + // is unclear and Glslang doesn't declare ImageMSArray. Need to clarify + // and reenable. + return _.diag(SPV_ERROR_INVALID_DATA) + << "Capability ImageMSArray is required to access storage " + << "image: " << spvOpcodeString(opcode); +#endif + } + } else { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image 'Sampled' parameter to be 0 or 2: " + << spvOpcodeString(opcode); + } + } + + return SPV_SUCCESS; +} + +// Returns true if opcode is *ImageSparse*, false otherwise. +bool IsSparse(SpvOp opcode) { + switch (opcode) { + case SpvOpImageSparseSampleImplicitLod: + case SpvOpImageSparseSampleExplicitLod: + case SpvOpImageSparseSampleDrefImplicitLod: + case SpvOpImageSparseSampleDrefExplicitLod: + case SpvOpImageSparseSampleProjImplicitLod: + case SpvOpImageSparseSampleProjExplicitLod: + case SpvOpImageSparseSampleProjDrefImplicitLod: + case SpvOpImageSparseSampleProjDrefExplicitLod: + case SpvOpImageSparseFetch: + case SpvOpImageSparseGather: + case SpvOpImageSparseDrefGather: + case SpvOpImageSparseTexelsResident: + case SpvOpImageSparseRead: { + return true; + } + + default: { return false; } + } + + return false; +} + +// Checks sparse image opcode result type and returns the second struct member. +// Returns inst.type_id for non-sparse image opcodes. +// Not valid for sparse image opcodes which do not return a struct. +spv_result_t GetActualResultType(ValidationState_t& _, + const spv_parsed_instruction_t& inst, + uint32_t* actual_result_type) { + const SpvOp opcode = static_cast(inst.opcode); + + if (IsSparse(opcode)) { + const Instruction* const type_inst = _.FindDef(inst.type_id); + assert(type_inst); + + if (!type_inst || type_inst->opcode() != SpvOpTypeStruct) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Result Type to be OpTypeStruct"; + } + + if (type_inst->words().size() != 4 || + !_.IsIntScalarType(type_inst->word(2))) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Result Type to be a struct containing an int " + "scalar " + << "and a texel"; + } + + *actual_result_type = type_inst->word(3); + } else { + *actual_result_type = inst.type_id; + } + + return SPV_SUCCESS; +} + +// Returns a string describing actual result type of an opcode. +// Not valid for sparse image opcodes which do not return a struct. +const char* GetActualResultTypeStr(SpvOp opcode) { + if (IsSparse(opcode)) return "Result Type's second member"; + return "Result Type"; +} + +} // namespace + +// Validates correctness of image instructions. +spv_result_t ImagePass(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + const SpvOp opcode = static_cast(inst->opcode); + const uint32_t result_type = inst->type_id; + + if (IsImplicitLod(opcode)) { + _.current_function().RegisterExecutionModelLimitation( + SpvExecutionModelFragment, + "ImplicitLod instructions require Fragment execution model"); + } + + switch (opcode) { + case SpvOpTypeImage: { + assert(result_type == 0); + + ImageTypeInfo info; + if (!GetImageTypeInfo(_, inst->words[1], &info)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "OpTypeImage: corrupt definition"; + } + + if (spvIsVulkanEnv(_.context()->target_env)) { + if ((!_.IsFloatScalarType(info.sampled_type) && + !_.IsIntScalarType(info.sampled_type)) || + 32 != _.GetBitWidth(info.sampled_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Sampled Type to be a 32-bit int or float " + "scalar type for Vulkan environment"; + } + } else { + const SpvOp sampled_type_opcode = _.GetIdOpcode(info.sampled_type); + if (sampled_type_opcode != SpvOpTypeVoid && + sampled_type_opcode != SpvOpTypeInt && + sampled_type_opcode != SpvOpTypeFloat) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Sampled Type to be either void or numerical " + << "scalar type"; + } + } + + // Dim is checked elsewhere. + + if (info.depth > 2) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) << ": invalid Depth " << info.depth + << " (must be 0, 1 or 2)"; + } + + if (info.arrayed > 1) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) << ": invalid Arrayed " + << info.arrayed << " (must be 0 or 1)"; + } + + if (info.multisampled > 1) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) << ": invalid MS " + << info.multisampled << " (must be 0 or 1)"; + } + + if (info.sampled > 2) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) << ": invalid Sampled " + << info.sampled << " (must be 0, 1 or 2)"; + } + + if (info.dim == SpvDimSubpassData) { + if (info.sampled != 2) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": Dim SubpassData requires Sampled to be 2"; + } + + if (info.format != SpvImageFormatUnknown) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": Dim SubpassData requires format Unknown"; + } + } + + // Format and Access Qualifier are checked elsewhere. + + break; + } + + case SpvOpTypeSampledImage: { + const uint32_t image_type = inst->words[2]; + if (_.GetIdOpcode(image_type) != SpvOpTypeImage) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Image to be of type OpTypeImage"; + } + + break; + } + + case SpvOpSampledImage: { + if (_.GetIdOpcode(result_type) != SpvOpTypeSampledImage) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Result Type to be OpTypeSampledImage: " + << spvOpcodeString(opcode); + } + + const uint32_t image_type = _.GetOperandTypeId(inst, 2); + if (_.GetIdOpcode(image_type) != SpvOpTypeImage) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image to be of type OpTypeImage: " + << spvOpcodeString(opcode); + } + + ImageTypeInfo info; + if (!GetImageTypeInfo(_, image_type, &info)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Corrupt image type definition"; + } + + // TODO(atgoo@github.com) Check compatibility of result type and received + // image. + + if (spvIsVulkanEnv(_.context()->target_env)) { + if (info.sampled != 1) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image 'Sampled' parameter to be 1 for Vulkan " + "environment: " + << spvOpcodeString(opcode); + } + } else { + if (info.sampled != 0 && info.sampled != 1) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image 'Sampled' parameter to be 0 or 1: " + << spvOpcodeString(opcode); + } + } + + if (info.dim == SpvDimSubpassData) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image 'Dim' parameter to be not SubpassData: " + << spvOpcodeString(opcode); + } + + if (_.GetIdOpcode(_.GetOperandTypeId(inst, 3)) != SpvOpTypeSampler) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Sampler to be of type OpTypeSampler: " + << spvOpcodeString(opcode); + } + + break; + } + + case SpvOpImageSampleImplicitLod: + case SpvOpImageSampleExplicitLod: + case SpvOpImageSampleProjImplicitLod: + case SpvOpImageSampleProjExplicitLod: + case SpvOpImageSparseSampleImplicitLod: + case SpvOpImageSparseSampleExplicitLod: { + uint32_t actual_result_type = 0; + if (spv_result_t error = + GetActualResultType(_, *inst, &actual_result_type)) { + return error; + } + + if (!_.IsIntVectorType(actual_result_type) && + !_.IsFloatVectorType(actual_result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected " << GetActualResultTypeStr(opcode) + << " to be int or float vector type: " + << spvOpcodeString(opcode); + } + + if (_.GetDimension(actual_result_type) != 4) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected " << GetActualResultTypeStr(opcode) + << " to have 4 components: " << spvOpcodeString(opcode); + } + + const uint32_t image_type = _.GetOperandTypeId(inst, 2); + if (_.GetIdOpcode(image_type) != SpvOpTypeSampledImage) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Sampled Image to be of type OpTypeSampledImage: " + << spvOpcodeString(opcode); + } + + ImageTypeInfo info; + if (!GetImageTypeInfo(_, image_type, &info)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Corrupt image type definition"; + } + + if (spv_result_t result = ValidateImageCommon(_, *inst, info)) + return result; + + if (_.GetIdOpcode(info.sampled_type) != SpvOpTypeVoid) { + const uint32_t texel_component_type = + _.GetComponentType(actual_result_type); + if (texel_component_type != info.sampled_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image 'Sampled Type' to be the same as " + << GetActualResultTypeStr(opcode) + << " components: " << spvOpcodeString(opcode); + } + } + + const uint32_t coord_type = _.GetOperandTypeId(inst, 3); + if ((opcode == SpvOpImageSampleExplicitLod || + opcode == SpvOpImageSparseSampleExplicitLod) && + _.HasCapability(SpvCapabilityKernel)) { + if (!_.IsFloatScalarOrVectorType(coord_type) && + !_.IsIntScalarOrVectorType(coord_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Coordinate to be int or float scalar or vector: " + << spvOpcodeString(opcode); + } + } else { + if (!_.IsFloatScalarOrVectorType(coord_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Coordinate to be float scalar or vector: " + << spvOpcodeString(opcode); + } + } + + const uint32_t min_coord_size = GetMinCoordSize(opcode, info); + const uint32_t actual_coord_size = _.GetDimension(coord_type); + if (min_coord_size > actual_coord_size) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Coordinate to have at least " << min_coord_size + << " components, but given only " << actual_coord_size << ": " + << spvOpcodeString(opcode); + } + + if (inst->num_words <= 5) { + assert(IsImplicitLod(opcode)); + break; + } + + const uint32_t mask = inst->words[5]; + if (spv_result_t result = + ValidateImageOperands(_, *inst, info, mask, /* word_index = */ 6)) + return result; + + break; + } + + case SpvOpImageSampleDrefImplicitLod: + case SpvOpImageSampleDrefExplicitLod: + case SpvOpImageSampleProjDrefImplicitLod: + case SpvOpImageSampleProjDrefExplicitLod: + case SpvOpImageSparseSampleDrefImplicitLod: + case SpvOpImageSparseSampleDrefExplicitLod: { + uint32_t actual_result_type = 0; + if (spv_result_t error = + GetActualResultType(_, *inst, &actual_result_type)) { + return error; + } + + if (!_.IsIntScalarType(actual_result_type) && + !_.IsFloatScalarType(actual_result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected " << GetActualResultTypeStr(opcode) + << " to be int or float scalar type: " + << spvOpcodeString(opcode); + } + + const uint32_t image_type = _.GetOperandTypeId(inst, 2); + if (_.GetIdOpcode(image_type) != SpvOpTypeSampledImage) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Sampled Image to be of type OpTypeSampledImage: " + << spvOpcodeString(opcode); + } + + ImageTypeInfo info; + if (!GetImageTypeInfo(_, image_type, &info)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Corrupt image type definition"; + } + + if (spv_result_t result = ValidateImageCommon(_, *inst, info)) + return result; + + if (actual_result_type != info.sampled_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image 'Sampled Type' to be the same as " + << GetActualResultTypeStr(opcode) << ": " + << spvOpcodeString(opcode); + } + + const uint32_t coord_type = _.GetOperandTypeId(inst, 3); + if (!_.IsFloatScalarOrVectorType(coord_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Coordinate to be float scalar or vector: " + << spvOpcodeString(opcode); + } + + const uint32_t min_coord_size = GetMinCoordSize(opcode, info); + const uint32_t actual_coord_size = _.GetDimension(coord_type); + if (min_coord_size > actual_coord_size) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Coordinate to have at least " << min_coord_size + << " components, but given only " << actual_coord_size << ": " + << spvOpcodeString(opcode); + } + + const uint32_t dref_type = _.GetOperandTypeId(inst, 4); + if (!_.IsFloatScalarType(dref_type) || _.GetBitWidth(dref_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": Expected Dref to be of 32-bit float type"; + } + + if (inst->num_words <= 6) { + assert(IsImplicitLod(opcode)); + break; + } + + const uint32_t mask = inst->words[6]; + if (spv_result_t result = + ValidateImageOperands(_, *inst, info, mask, /* word_index = */ 7)) + return result; + + break; + } + + case SpvOpImageFetch: + case SpvOpImageSparseFetch: { + uint32_t actual_result_type = 0; + if (spv_result_t error = + GetActualResultType(_, *inst, &actual_result_type)) { + return error; + } + + if (!_.IsIntVectorType(actual_result_type) && + !_.IsFloatVectorType(actual_result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected " << GetActualResultTypeStr(opcode) + << " to be int or float vector type: " + << spvOpcodeString(opcode); + } + + if (_.GetDimension(actual_result_type) != 4) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected " << GetActualResultTypeStr(opcode) + << " to have 4 components: " << spvOpcodeString(opcode); + } + + const uint32_t image_type = _.GetOperandTypeId(inst, 2); + if (_.GetIdOpcode(image_type) != SpvOpTypeImage) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image to be of type OpTypeImage: " + << spvOpcodeString(opcode); + } + + ImageTypeInfo info; + if (!GetImageTypeInfo(_, image_type, &info)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Corrupt image type definition"; + } + + if (_.GetIdOpcode(info.sampled_type) != SpvOpTypeVoid) { + const uint32_t result_component_type = + _.GetComponentType(actual_result_type); + if (result_component_type != info.sampled_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image 'Sampled Type' to be the same as " + << GetActualResultTypeStr(opcode) + << " components: " << spvOpcodeString(opcode); + } + } + + if (info.dim == SpvDimCube) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image 'Dim' cannot be Cube: " << spvOpcodeString(opcode); + } + + if (info.sampled != 1) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image 'Sampled' parameter to be 1: " + << spvOpcodeString(opcode); + } + + const uint32_t coord_type = _.GetOperandTypeId(inst, 3); + if (!_.IsIntScalarOrVectorType(coord_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Coordinate to be int scalar or vector: " + << spvOpcodeString(opcode); + } + + const uint32_t min_coord_size = GetMinCoordSize(opcode, info); + const uint32_t actual_coord_size = _.GetDimension(coord_type); + if (min_coord_size > actual_coord_size) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Coordinate to have at least " << min_coord_size + << " components, but given only " << actual_coord_size << ": " + << spvOpcodeString(opcode); + } + + if (inst->num_words <= 5) break; + + const uint32_t mask = inst->words[5]; + if (spv_result_t result = + ValidateImageOperands(_, *inst, info, mask, /* word_index = */ 6)) + return result; + + break; + } + + case SpvOpImageGather: + case SpvOpImageDrefGather: + case SpvOpImageSparseGather: + case SpvOpImageSparseDrefGather: { + uint32_t actual_result_type = 0; + if (spv_result_t error = + GetActualResultType(_, *inst, &actual_result_type)) { + return error; + } + + if (!_.IsIntVectorType(actual_result_type) && + !_.IsFloatVectorType(actual_result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected " << GetActualResultTypeStr(opcode) + << " to be int or float vector type: " + << spvOpcodeString(opcode); + } + + if (_.GetDimension(actual_result_type) != 4) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected " << GetActualResultTypeStr(opcode) + << " to have 4 components: " << spvOpcodeString(opcode); + } + + const uint32_t image_type = _.GetOperandTypeId(inst, 2); + if (_.GetIdOpcode(image_type) != SpvOpTypeSampledImage) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Sampled Image to be of type OpTypeSampledImage: " + << spvOpcodeString(opcode); + } + + ImageTypeInfo info; + if (!GetImageTypeInfo(_, image_type, &info)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Corrupt image type definition"; + } + + if (opcode == SpvOpImageDrefGather || + opcode == SpvOpImageSparseDrefGather || + _.GetIdOpcode(info.sampled_type) != SpvOpTypeVoid) { + const uint32_t result_component_type = + _.GetComponentType(actual_result_type); + if (result_component_type != info.sampled_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image 'Sampled Type' to be the same as " + << GetActualResultTypeStr(opcode) + << " components: " << spvOpcodeString(opcode); + } + } + + if (info.dim != SpvDim2D && info.dim != SpvDimCube && + info.dim != SpvDimRect) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image 'Dim' cannot be Cube: " + << spvOpcodeString(opcode); + } + + const uint32_t coord_type = _.GetOperandTypeId(inst, 3); + if (!_.IsFloatScalarOrVectorType(coord_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Coordinate to be float scalar or vector: " + << spvOpcodeString(opcode); + } + + const uint32_t min_coord_size = GetMinCoordSize(opcode, info); + const uint32_t actual_coord_size = _.GetDimension(coord_type); + if (min_coord_size > actual_coord_size) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Coordinate to have at least " << min_coord_size + << " components, but given only " << actual_coord_size << ": " + << spvOpcodeString(opcode); + } + + if (opcode == SpvOpImageGather || opcode == SpvOpImageSparseGather) { + const uint32_t component_index_type = _.GetOperandTypeId(inst, 4); + if (!_.IsIntScalarType(component_index_type) || + _.GetBitWidth(component_index_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Component to be 32-bit int scalar: " + << spvOpcodeString(opcode); + } + } else { + assert(opcode == SpvOpImageDrefGather || + opcode == SpvOpImageSparseDrefGather); + const uint32_t dref_type = _.GetOperandTypeId(inst, 4); + if (!_.IsFloatScalarType(dref_type) || _.GetBitWidth(dref_type) != 32) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": Expected Dref to be of 32-bit float type"; + } + } + + if (inst->num_words <= 6) break; + + const uint32_t mask = inst->words[6]; + if (spv_result_t result = + ValidateImageOperands(_, *inst, info, mask, /* word_index = */ 7)) + return result; + + break; + } + + case SpvOpImageRead: + case SpvOpImageSparseRead: { + uint32_t actual_result_type = 0; + if (spv_result_t error = + GetActualResultType(_, *inst, &actual_result_type)) { + return error; + } + + if (!_.IsIntScalarOrVectorType(actual_result_type) && + !_.IsFloatScalarOrVectorType(actual_result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected " << GetActualResultTypeStr(opcode) + << " to be int or float scalar or vector type: " + << spvOpcodeString(opcode); + } + +#if 0 + // TODO(atgoo@github.com) Disabled until the spec is clarified. + if (_.GetDimension(actual_result_type) != 4) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected " << GetActualResultTypeStr(opcode) + << " to have 4 components: " << spvOpcodeString(opcode); + } +#endif + + const uint32_t image_type = _.GetOperandTypeId(inst, 2); + if (_.GetIdOpcode(image_type) != SpvOpTypeImage) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image to be of type OpTypeImage: " + << spvOpcodeString(opcode); + } + + ImageTypeInfo info; + if (!GetImageTypeInfo(_, image_type, &info)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Corrupt image type definition"; + } + + if (info.dim == SpvDimSubpassData) { + if (opcode == SpvOpImageSparseRead) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image Dim SubpassData cannot be used with " + << spvOpcodeString(opcode); + } + + _.current_function().RegisterExecutionModelLimitation( + SpvExecutionModelFragment, + std::string("Dim SubpassData requires Fragment execution model: ") + + spvOpcodeString(opcode)); + } + + if (_.GetIdOpcode(info.sampled_type) != SpvOpTypeVoid) { + const uint32_t result_component_type = + _.GetComponentType(actual_result_type); + if (result_component_type != info.sampled_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image 'Sampled Type' to be the same as " + << GetActualResultTypeStr(opcode) + << " components: " << spvOpcodeString(opcode); + } + } + + if (spv_result_t result = ValidateImageCommon(_, *inst, info)) + return result; + + const uint32_t coord_type = _.GetOperandTypeId(inst, 3); + if (!_.IsIntScalarOrVectorType(coord_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Coordinate to be int scalar or vector: " + << spvOpcodeString(opcode); + } + + const uint32_t min_coord_size = GetMinCoordSize(opcode, info); + const uint32_t actual_coord_size = _.GetDimension(coord_type); + if (min_coord_size > actual_coord_size) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Coordinate to have at least " << min_coord_size + << " components, but given only " << actual_coord_size << ": " + << spvOpcodeString(opcode); + } + + if (info.format == SpvImageFormatUnknown && + info.dim != SpvDimSubpassData && + !_.HasCapability(SpvCapabilityStorageImageReadWithoutFormat)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Capability StorageImageReadWithoutFormat is required to " + << "read storage image: " << spvOpcodeString(opcode); + } + + if (inst->num_words <= 5) break; + + const uint32_t mask = inst->words[5]; + if (spv_result_t result = + ValidateImageOperands(_, *inst, info, mask, /* word_index = */ 6)) + return result; + + break; + } + + case SpvOpImageWrite: { + const uint32_t image_type = _.GetOperandTypeId(inst, 0); + if (_.GetIdOpcode(image_type) != SpvOpTypeImage) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image to be of type OpTypeImage: " + << spvOpcodeString(opcode); + } + + ImageTypeInfo info; + if (!GetImageTypeInfo(_, image_type, &info)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Corrupt image type definition"; + } + + if (info.dim == SpvDimSubpassData) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image 'Dim' cannot be SubpassData: " + << spvOpcodeString(opcode); + } + + if (spv_result_t result = ValidateImageCommon(_, *inst, info)) + return result; + + const uint32_t coord_type = _.GetOperandTypeId(inst, 1); + if (!_.IsIntScalarOrVectorType(coord_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Coordinate to be int scalar or vector: " + << spvOpcodeString(opcode); + } + + const uint32_t min_coord_size = GetMinCoordSize(opcode, info); + const uint32_t actual_coord_size = _.GetDimension(coord_type); + if (min_coord_size > actual_coord_size) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Coordinate to have at least " << min_coord_size + << " components, but given only " << actual_coord_size << ": " + << spvOpcodeString(opcode); + } + + // TODO(atgoo@github.com) The spec doesn't explicitely say what the type + // of texel should be. + const uint32_t texel_type = _.GetOperandTypeId(inst, 2); + if (!_.IsIntScalarOrVectorType(texel_type) && + !_.IsFloatScalarOrVectorType(texel_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Texel to be int or float vector or scalar: " + << spvOpcodeString(opcode); + } + +#if 0 + // TODO: See above. + if (_.GetDimension(texel_type) != 4) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Texel to have 4 components: " + << spvOpcodeString(opcode); + } +#endif + + if (_.GetIdOpcode(info.sampled_type) != SpvOpTypeVoid) { + const uint32_t texel_component_type = _.GetComponentType(texel_type); + if (texel_component_type != info.sampled_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image 'Sampled Type' to be the same as Texel " + << "components: " << spvOpcodeString(opcode); + } + } + + if (info.format == SpvImageFormatUnknown && + info.dim != SpvDimSubpassData && + !_.HasCapability(SpvCapabilityStorageImageWriteWithoutFormat)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Capability StorageImageWriteWithoutFormat is required to " + "write " + << "to storage image: " << spvOpcodeString(opcode); + } + + if (inst->num_words <= 4) break; + + const uint32_t mask = inst->words[4]; + if (spv_result_t result = + ValidateImageOperands(_, *inst, info, mask, /* word_index = */ 5)) + return result; + + break; + } + + case SpvOpImage: { + if (_.GetIdOpcode(result_type) != SpvOpTypeImage) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Result Type to be OpTypeImage: " + << spvOpcodeString(opcode); + } + + const uint32_t sampled_image_type = _.GetOperandTypeId(inst, 2); + const Instruction* sampled_image_type_inst = + _.FindDef(sampled_image_type); + assert(sampled_image_type_inst); + + if (sampled_image_type_inst->opcode() != SpvOpTypeSampledImage) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Sample Image to be of type OpTypeSampleImage: " + << spvOpcodeString(opcode); + } + + if (sampled_image_type_inst->word(2) != result_type) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Sample Image image type to be equal to Result " + "Type: " + << spvOpcodeString(opcode); + } + + break; + } + + case SpvOpImageQueryFormat: + case SpvOpImageQueryOrder: { + if (!_.IsIntScalarType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Result Type to be int scalar type: " + << spvOpcodeString(opcode); + } + + if (_.GetIdOpcode(_.GetOperandTypeId(inst, 2)) != SpvOpTypeImage) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected operand to be of type OpTypeImage: " + << spvOpcodeString(opcode); + } + break; + } + + case SpvOpImageQuerySizeLod: { + if (!_.IsIntScalarOrVectorType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Result Type to be int scalar or vector type: " + << spvOpcodeString(opcode); + } + + const uint32_t image_type = _.GetOperandTypeId(inst, 2); + if (_.GetIdOpcode(image_type) != SpvOpTypeImage) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image to be of type OpTypeImage: " + << spvOpcodeString(opcode); + } + + ImageTypeInfo info; + if (!GetImageTypeInfo(_, image_type, &info)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Corrupt image type definition"; + } + + uint32_t expected_num_components = info.arrayed; + switch (info.dim) { + case SpvDim1D: + expected_num_components += 1; + break; + case SpvDim2D: + case SpvDimCube: + expected_num_components += 2; + break; + case SpvDim3D: + expected_num_components += 3; + break; + default: + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image 'Dim' must be 1D, 2D, 3D or Cube: " + << spvOpcodeString(opcode); + }; + + if (info.multisampled != 0) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image 'MS' must be 0: " << spvOpcodeString(opcode); + } + + uint32_t result_num_components = _.GetDimension(result_type); + if (result_num_components != expected_num_components) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Result Type has " << result_num_components << " components, " + << "but " << expected_num_components + << " expected: " << spvOpcodeString(opcode); + } + + const uint32_t lod_type = _.GetOperandTypeId(inst, 3); + if (!_.IsIntScalarType(lod_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Level of Detail to be int scalar: " + << spvOpcodeString(opcode); + } + + break; + } + + case SpvOpImageQuerySize: { + if (!_.IsIntScalarOrVectorType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Result Type to be int scalar or vector type: " + << spvOpcodeString(opcode); + } + + const uint32_t image_type = _.GetOperandTypeId(inst, 2); + if (_.GetIdOpcode(image_type) != SpvOpTypeImage) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image to be of type OpTypeImage: " + << spvOpcodeString(opcode); + } + +#if 0 + // TODO(atgoo@github.com) The spec doesn't whitelist all Dims supported by + // GLSL. Need to verify if there is an error and reenable. + ImageTypeInfo info; + if (!GetImageTypeInfo(_, image_type, &info)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Corrupt image type definition"; + } + + uint32_t expected_num_components = info.arrayed; + switch (info.dim) { + case SpvDimBuffer: + expected_num_components += 1; + break; + case SpvDim2D: + if (info.multisampled != 1 && info.sampled != 0 && + info.sampled != 2) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected either 'MS'=1 or 'Sampled'=0 or 'Sampled'=2 " + << "for 2D dim: " << spvOpcodeString(opcode); + } + case SpvDimRect: + expected_num_components += 2; + break; + case SpvDim3D: + expected_num_components += 3; + if (info.sampled != 0 && + info.sampled != 2) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected either 'Sampled'=0 or 'Sampled'=2 " + << "for 3D dim: " << spvOpcodeString(opcode); + } + break; + default: + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image 'Dim' must be Buffer, 2D, 3D or Rect: " + << spvOpcodeString(opcode); + }; + + + if (info.multisampled != 0) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image 'MS' must be 0: " << spvOpcodeString(opcode); + } + + uint32_t result_num_components = _.GetDimension(result_type); + if (result_num_components != expected_num_components) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Result Type has " << result_num_components << " components, " + << "but " << expected_num_components << " expected: " + << spvOpcodeString(opcode); + } +#endif + break; + } + + case SpvOpImageQueryLod: { + _.current_function().RegisterExecutionModelLimitation( + SpvExecutionModelFragment, + "OpImageQueryLod requires Fragment execution model"); + + if (!_.IsFloatVectorType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Result Type to be float vector type: " + << spvOpcodeString(opcode); + } + + if (_.GetDimension(result_type) != 2) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Result Type to have 2 components: " + << spvOpcodeString(opcode); + } + + const uint32_t image_type = _.GetOperandTypeId(inst, 2); + if (_.GetIdOpcode(image_type) != SpvOpTypeSampledImage) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image operand to be of type OpTypeSampledImage: " + << spvOpcodeString(opcode); + } + + ImageTypeInfo info; + if (!GetImageTypeInfo(_, image_type, &info)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Corrupt image type definition"; + } + + if (info.dim != SpvDim1D && info.dim != SpvDim2D && + info.dim != SpvDim3D && info.dim != SpvDimCube) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image 'Dim' must be 1D, 2D, 3D or Cube: " + << spvOpcodeString(opcode); + } + + const uint32_t coord_type = _.GetOperandTypeId(inst, 3); + if (_.HasCapability(SpvCapabilityKernel)) { + if (!_.IsFloatScalarOrVectorType(coord_type) && + !_.IsIntScalarOrVectorType(coord_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Coordinate to be int or float scalar or vector: " + << spvOpcodeString(opcode); + } + } else { + if (!_.IsFloatScalarOrVectorType(coord_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Coordinate to be float scalar or vector: " + << spvOpcodeString(opcode); + } + } + + const uint32_t min_coord_size = GetPlaneCoordSize(info); + const uint32_t actual_coord_size = _.GetDimension(coord_type); + if (min_coord_size > actual_coord_size) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Coordinate to have at least " << min_coord_size + << " components, but given only " << actual_coord_size << ": " + << spvOpcodeString(opcode); + } + break; + } + + case SpvOpImageQueryLevels: + case SpvOpImageQuerySamples: { + if (!_.IsIntScalarType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Result Type to be int scalar type: " + << spvOpcodeString(opcode); + } + + const uint32_t image_type = _.GetOperandTypeId(inst, 2); + if (_.GetIdOpcode(image_type) != SpvOpTypeImage) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected Image to be of type OpTypeImage: " + << spvOpcodeString(opcode); + } + + ImageTypeInfo info; + if (!GetImageTypeInfo(_, image_type, &info)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Corrupt image type definition"; + } + + if (opcode == SpvOpImageQueryLevels) { + if (info.dim != SpvDim1D && info.dim != SpvDim2D && + info.dim != SpvDim3D && info.dim != SpvDimCube) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image 'Dim' must be 1D, 2D, 3D or Cube: " + << spvOpcodeString(opcode); + } + } else { + assert(opcode == SpvOpImageQuerySamples); + if (info.dim != SpvDim2D) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image 'Dim' must be 2D: " << spvOpcodeString(opcode); + } + + if (info.multisampled != 1) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Image 'MS' must be 1: " << spvOpcodeString(opcode); + } + } + + break; + } + + case SpvOpImageSparseSampleProjImplicitLod: + case SpvOpImageSparseSampleProjExplicitLod: + case SpvOpImageSparseSampleProjDrefImplicitLod: + case SpvOpImageSparseSampleProjDrefExplicitLod: { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": instruction reserved for future use, " + << "use of this instruction is invalid"; + } + + case SpvOpImageSparseTexelsResident: { + if (!_.IsBoolScalarType(result_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Result Type to be bool scalar type"; + } + + const uint32_t resident_code_type = _.GetOperandTypeId(inst, 2); + if (!_.IsIntScalarType(resident_code_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Resident Code to be int scalar"; + } + break; + } + + default: + break; + } + + return SPV_SUCCESS; +} + +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/validate_instruction.cpp b/3rdparty/spirv-tools/source/validate_instruction.cpp new file mode 100644 index 000000000..a50e0ea89 --- /dev/null +++ b/3rdparty/spirv-tools/source/validate_instruction.cpp @@ -0,0 +1,552 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Performs validation on instructions that appear inside of a SPIR-V block. + +#include "validate.h" + +#include +#include + +#include +#include + +#include "binary.h" +#include "diagnostic.h" +#include "enum_set.h" +#include "enum_string_mapping.h" +#include "extensions.h" +#include "opcode.h" +#include "operand.h" +#include "spirv_constant.h" +#include "spirv_definition.h" +#include "spirv_target_env.h" +#include "spirv_validator_options.h" +#include "util/string_utils.h" +#include "val/function.h" +#include "val/validation_state.h" + +using libspirv::AssemblyGrammar; +using libspirv::CapabilitySet; +using libspirv::DiagnosticStream; +using libspirv::ExtensionSet; +using libspirv::ValidationState_t; + +namespace { + +std::string ToString(const CapabilitySet& capabilities, + const AssemblyGrammar& grammar) { + std::stringstream ss; + capabilities.ForEach([&grammar, &ss](SpvCapability cap) { + spv_operand_desc desc; + if (SPV_SUCCESS == + grammar.lookupOperand(SPV_OPERAND_TYPE_CAPABILITY, cap, &desc)) + ss << desc->name << " "; + else + ss << cap << " "; + }); + return ss.str(); +} + +// Reports a missing-capability error to _'s diagnostic stream and returns +// SPV_ERROR_INVALID_CAPABILITY. +spv_result_t CapabilityError(ValidationState_t& _, int which_operand, + SpvOp opcode, + const std::string& required_capabilities) { + return _.diag(SPV_ERROR_INVALID_CAPABILITY) + << "Operand " << which_operand << " of " << spvOpcodeString(opcode) + << " requires one of these capabilities: " << required_capabilities; +} + +// Returns capabilities that enable an opcode. An empty result is interpreted +// as no prohibition of use of the opcode. If the result is non-empty, then +// the opcode may only be used if at least one of the capabilities is specified +// by the module. +CapabilitySet EnablingCapabilitiesForOp(const ValidationState_t& state, + SpvOp opcode) { + // Exceptions for SPV_AMD_shader_ballot + switch (opcode) { + // Normally these would require Group capability + case SpvOpGroupIAddNonUniformAMD: + case SpvOpGroupFAddNonUniformAMD: + case SpvOpGroupFMinNonUniformAMD: + case SpvOpGroupUMinNonUniformAMD: + case SpvOpGroupSMinNonUniformAMD: + case SpvOpGroupFMaxNonUniformAMD: + case SpvOpGroupUMaxNonUniformAMD: + case SpvOpGroupSMaxNonUniformAMD: + if (state.HasExtension(libspirv::kSPV_AMD_shader_ballot)) + return CapabilitySet(); + break; + default: + break; + } + // Look it up in the grammar + spv_opcode_desc opcode_desc = {}; + if (SPV_SUCCESS == state.grammar().lookupOpcode(opcode, &opcode_desc)) { + return state.grammar().filterCapsAgainstTargetEnv( + opcode_desc->capabilities, opcode_desc->numCapabilities); + } + return CapabilitySet(); +} + +// Returns an operand's required capabilities. +CapabilitySet RequiredCapabilities(const ValidationState_t& state, + spv_operand_type_t type, uint32_t operand) { + // Mere mention of PointSize, ClipDistance, or CullDistance in a Builtin + // decoration does not require the associated capability. The use of such + // a variable value should trigger the capability requirement, but that's + // not implemented yet. This rule is independent of target environment. + // See https://github.com/KhronosGroup/SPIRV-Tools/issues/365 + if (type == SPV_OPERAND_TYPE_BUILT_IN) { + switch (operand) { + case SpvBuiltInPointSize: + case SpvBuiltInClipDistance: + case SpvBuiltInCullDistance: + return CapabilitySet(); + default: + break; + } + } else if (type == SPV_OPERAND_TYPE_FP_ROUNDING_MODE) { + // Allow all FP rounding modes if requested + if (state.features().free_fp_rounding_mode) { + return CapabilitySet(); + } + } + + spv_operand_desc operand_desc; + const auto ret = state.grammar().lookupOperand(type, operand, &operand_desc); + if (ret == SPV_SUCCESS) { + // Allow FPRoundingMode decoration if requested. + if (type == SPV_OPERAND_TYPE_DECORATION && + operand_desc->value == SpvDecorationFPRoundingMode) { + if (state.features().free_fp_rounding_mode) return CapabilitySet(); + + // Vulkan API requires more capabilities on rounding mode. + if (spvIsVulkanEnv(state.context()->target_env)) { + CapabilitySet cap_set; + cap_set.Add(SpvCapabilityStorageUniformBufferBlock16); + cap_set.Add(SpvCapabilityStorageUniform16); + cap_set.Add(SpvCapabilityStoragePushConstant16); + cap_set.Add(SpvCapabilityStorageInputOutput16); + return cap_set; + } + } + // Allow certain group operations if requested. + if (state.features().group_ops_reduce_and_scans && + type == SPV_OPERAND_TYPE_GROUP_OPERATION && + (operand <= uint32_t(SpvGroupOperationExclusiveScan))) { + return CapabilitySet(); + } + + return state.grammar().filterCapsAgainstTargetEnv( + operand_desc->capabilities, operand_desc->numCapabilities); + } + + return CapabilitySet(); +} + +// Returns operand's required extensions. +ExtensionSet RequiredExtensions(const ValidationState_t& state, + spv_operand_type_t type, uint32_t operand) { + spv_operand_desc operand_desc; + if (state.grammar().lookupOperand(type, operand, &operand_desc) == + SPV_SUCCESS) { + assert(operand_desc); + // If this operand is incorporated into core SPIR-V before or in the current + // target environment, we don't require extensions anymore. + if (spvVersionForTargetEnv(state.grammar().target_env()) >= + operand_desc->minVersion) + return {}; + return {operand_desc->numExtensions, operand_desc->extensions}; + } + + return {}; +} + +} // namespace + +namespace libspirv { + +spv_result_t CapabilityCheck(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + const SpvOp opcode = static_cast(inst->opcode); + CapabilitySet opcode_caps = EnablingCapabilitiesForOp(_, opcode); + if (!_.HasAnyOfCapabilities(opcode_caps)) { + return _.diag(SPV_ERROR_INVALID_CAPABILITY) + << "Opcode " << spvOpcodeString(opcode) + << " requires one of these capabilities: " + << ToString(opcode_caps, _.grammar()); + } + for (int i = 0; i < inst->num_operands; ++i) { + const auto& operand = inst->operands[i]; + const auto word = inst->words[operand.offset]; + if (spvOperandIsConcreteMask(operand.type)) { + // Check for required capabilities for each bit position of the mask. + for (uint32_t mask_bit = 0x80000000; mask_bit; mask_bit >>= 1) { + if (word & mask_bit) { + const auto caps = RequiredCapabilities(_, operand.type, mask_bit); + if (!_.HasAnyOfCapabilities(caps)) { + return CapabilityError(_, i + 1, opcode, + ToString(caps, _.grammar())); + } + } + } + } else if (spvIsIdType(operand.type)) { + // TODO(dneto): Check the value referenced by this Id, if we can compute + // it. For now, just punt, to fix issue 248: + // https://github.com/KhronosGroup/SPIRV-Tools/issues/248 + } else { + // Check the operand word as a whole. + const auto caps = RequiredCapabilities(_, operand.type, word); + if (!_.HasAnyOfCapabilities(caps)) { + return CapabilityError(_, i + 1, opcode, ToString(caps, _.grammar())); + } + } + } + return SPV_SUCCESS; +} + +// Checks that all extensions required by the given instruction's operands were +// declared in the module. +spv_result_t ExtensionCheck(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + const SpvOp opcode = static_cast(inst->opcode); + for (size_t operand_index = 0; operand_index < inst->num_operands; + ++operand_index) { + const auto& operand = inst->operands[operand_index]; + const uint32_t word = inst->words[operand.offset]; + const ExtensionSet required_extensions = + RequiredExtensions(_, operand.type, word); + if (!_.HasAnyOfExtensions(required_extensions)) { + return _.diag(SPV_ERROR_MISSING_EXTENSION) + << spvutils::CardinalToOrdinal(operand_index + 1) << " operand of " + << spvOpcodeString(opcode) << ": operand " << word + << " requires one of these extensions: " + << ExtensionSetToString(required_extensions); + } + } + return SPV_SUCCESS; +} + +// Checks that the instruction can be used in this target environment. +spv_result_t VersionCheck(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + const auto opcode = static_cast(inst->opcode); + spv_opcode_desc inst_desc; + const bool r = _.grammar().lookupOpcode(opcode, &inst_desc); + assert(r == SPV_SUCCESS); + (void)r; + + const auto min_version = inst_desc->minVersion; + + ExtensionSet exts(inst_desc->numExtensions, inst_desc->extensions); + if (exts.IsEmpty()) { + // If no extensions can enable this instruction, then emit error messages + // only concerning core SPIR-V versions if errors happen. + if (min_version == ~0u) { + return _.diag(SPV_ERROR_WRONG_VERSION) + << spvOpcodeString(opcode) << " is reserved for future use."; + } + + if (spvVersionForTargetEnv(_.grammar().target_env()) < min_version) { + return _.diag(SPV_ERROR_WRONG_VERSION) + << spvOpcodeString(opcode) << " requires " + << spvTargetEnvDescription( + static_cast(min_version)) + << " at minimum."; + } + } + // Otherwise, we only error out when no enabling extensions are registered. + else if (!_.HasAnyOfExtensions(exts)) { + if (min_version == ~0u) { + return _.diag(SPV_ERROR_MISSING_EXTENSION) + << spvOpcodeString(opcode) + << " requires one of the following extensions: " + << ExtensionSetToString(exts); + } + + if (static_cast(_.grammar().target_env()) < min_version) { + return _.diag(SPV_ERROR_WRONG_VERSION) + << spvOpcodeString(opcode) << " requires " + << spvTargetEnvDescription( + static_cast(min_version)) + << " at minimum or one of the following extensions: " + << ExtensionSetToString(exts); + } + } + + return SPV_SUCCESS; +} + +// Checks that the Resuld is within the valid bound. +spv_result_t LimitCheckIdBound(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + if (inst->result_id >= _.getIdBound()) { + return _.diag(SPV_ERROR_INVALID_BINARY) + << "Result '" << inst->result_id + << "' must be less than the ID bound '" << _.getIdBound() << "'."; + } + return SPV_SUCCESS; +} + +// Checks that the number of OpTypeStruct members is within the limit. +spv_result_t LimitCheckStruct(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + if (SpvOpTypeStruct != inst->opcode) { + return SPV_SUCCESS; + } + + // Number of members is the number of operands of the instruction minus 1. + // One operand is the result ID. + const uint16_t limit = + static_cast(_.options()->universal_limits_.max_struct_members); + if (inst->num_operands - 1 > limit) { + return _.diag(SPV_ERROR_INVALID_BINARY) + << "Number of OpTypeStruct members (" << inst->num_operands - 1 + << ") has exceeded the limit (" << limit << ")."; + } + + // Section 2.17 of SPIRV Spec specifies that the "Structure Nesting Depth" + // must be less than or equal to 255. + // This is interpreted as structures including other structures as members. + // The code does not follow pointers or look into arrays to see if we reach a + // structure downstream. + // The nesting depth of a struct is 1+(largest depth of any member). + // Scalars are at depth 0. + uint32_t max_member_depth = 0; + // Struct members start at word 2 of OpTypeStruct instruction. + for (size_t word_i = 2; word_i < inst->num_words; ++word_i) { + auto member = inst->words[word_i]; + auto memberTypeInstr = _.FindDef(member); + if (memberTypeInstr && SpvOpTypeStruct == memberTypeInstr->opcode()) { + max_member_depth = std::max( + max_member_depth, _.struct_nesting_depth(memberTypeInstr->id())); + } + } + + const uint32_t depth_limit = _.options()->universal_limits_.max_struct_depth; + const uint32_t cur_depth = 1 + max_member_depth; + _.set_struct_nesting_depth(inst->result_id, cur_depth); + if (cur_depth > depth_limit) { + return _.diag(SPV_ERROR_INVALID_BINARY) + << "Structure Nesting Depth may not be larger than " << depth_limit + << ". Found " << cur_depth << "."; + } + return SPV_SUCCESS; +} + +// Checks that the number of (literal, label) pairs in OpSwitch is within the +// limit. +spv_result_t LimitCheckSwitch(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + if (SpvOpSwitch == inst->opcode) { + // The instruction syntax is as follows: + // OpSwitch literal label literal label ... + // literal,label pairs come after the first 2 operands. + // It is guaranteed at this point that num_operands is an even numner. + unsigned int num_pairs = (inst->num_operands - 2) / 2; + const unsigned int num_pairs_limit = + _.options()->universal_limits_.max_switch_branches; + if (num_pairs > num_pairs_limit) { + return _.diag(SPV_ERROR_INVALID_BINARY) + << "Number of (literal, label) pairs in OpSwitch (" << num_pairs + << ") exceeds the limit (" << num_pairs_limit << ")."; + } + } + return SPV_SUCCESS; +} + +// Ensure the number of variables of the given class does not exceed the limit. +spv_result_t LimitCheckNumVars(ValidationState_t& _, const uint32_t var_id, + const SpvStorageClass storage_class) { + if (SpvStorageClassFunction == storage_class) { + _.registerLocalVariable(var_id); + const uint32_t num_local_vars_limit = + _.options()->universal_limits_.max_local_variables; + if (_.num_local_vars() > num_local_vars_limit) { + return _.diag(SPV_ERROR_INVALID_BINARY) + << "Number of local variables ('Function' Storage Class) " + "exceeded the valid limit (" + << num_local_vars_limit << ")."; + } + } else { + _.registerGlobalVariable(var_id); + const uint32_t num_global_vars_limit = + _.options()->universal_limits_.max_global_variables; + if (_.num_global_vars() > num_global_vars_limit) { + return _.diag(SPV_ERROR_INVALID_BINARY) + << "Number of Global Variables (Storage Class other than " + "'Function') exceeded the valid limit (" + << num_global_vars_limit << ")."; + } + } + return SPV_SUCCESS; +} + +// Registers necessary decoration(s) for the appropriate IDs based on the +// instruction. +spv_result_t RegisterDecorations(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + switch (inst->opcode) { + case SpvOpDecorate: { + const uint32_t target_id = inst->words[1]; + const SpvDecoration dec_type = static_cast(inst->words[2]); + std::vector dec_params; + if (inst->num_words > 3) { + dec_params.insert(dec_params.end(), inst->words + 3, + inst->words + inst->num_words); + } + _.RegisterDecorationForId(target_id, Decoration(dec_type, dec_params)); + break; + } + case SpvOpMemberDecorate: { + const uint32_t struct_id = inst->words[1]; + const uint32_t index = inst->words[2]; + const SpvDecoration dec_type = static_cast(inst->words[3]); + std::vector dec_params; + if (inst->num_words > 4) { + dec_params.insert(dec_params.end(), inst->words + 4, + inst->words + inst->num_words); + } + _.RegisterDecorationForId(struct_id, + Decoration(dec_type, dec_params, index)); + break; + } + case SpvOpDecorationGroup: { + // We don't need to do anything right now. Assigning decorations to groups + // will be taken care of via OpGroupDecorate. + break; + } + case SpvOpGroupDecorate: { + // Word 1 is the group . All subsequent words are target s that + // are going to be decorated with the decorations. + const uint32_t decoration_group_id = inst->words[1]; + std::vector& group_decorations = + _.id_decorations(decoration_group_id); + for (int i = 2; i < inst->num_words; ++i) { + const uint32_t target_id = inst->words[i]; + _.RegisterDecorationsForId(target_id, group_decorations.begin(), + group_decorations.end()); + } + break; + } + case SpvOpGroupMemberDecorate: { + // Word 1 is the Decoration Group followed by (struct,literal) + // pairs. All decorations of the group should be applied to all the struct + // members that are specified in the instructions. + const uint32_t decoration_group_id = inst->words[1]; + std::vector& group_decorations = + _.id_decorations(decoration_group_id); + // Grammar checks ensures that the number of arguments to this instruction + // is an odd number: 1 decoration group + (id,literal) pairs. + for (int i = 2; i + 1 < inst->num_words; i = i + 2) { + const uint32_t struct_id = inst->words[i]; + const uint32_t index = inst->words[i + 1]; + // ID validation phase ensures this is in fact a struct instruction and + // that the index is not out of bound. + _.RegisterDecorationsForStructMember(struct_id, index, + group_decorations.begin(), + group_decorations.end()); + } + break; + } + default: + break; + } + return SPV_SUCCESS; +} + +// Parses OpExtension instruction and logs warnings if unsuccessful. +void CheckIfKnownExtension(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + const std::string extension_str = GetExtensionString(inst); + Extension extension; + if (!GetExtensionFromString(extension_str.c_str(), &extension)) { + _.diag(SPV_SUCCESS) << "Found unrecognized extension " << extension_str; + return; + } +} + +spv_result_t InstructionPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + const SpvOp opcode = static_cast(inst->opcode); + if (opcode == SpvOpExtension) { + CheckIfKnownExtension(_, inst); + } else if (opcode == SpvOpCapability) { + _.RegisterCapability( + static_cast(inst->words[inst->operands[0].offset])); + } else if (opcode == SpvOpMemoryModel) { + _.set_addressing_model( + static_cast(inst->words[inst->operands[0].offset])); + _.set_memory_model( + static_cast(inst->words[inst->operands[1].offset])); + } else if (opcode == SpvOpExecutionMode) { + const uint32_t entry_point = inst->words[1]; + _.RegisterExecutionModeForEntryPoint(entry_point, + SpvExecutionMode(inst->words[2])); + } else if (opcode == SpvOpVariable) { + const auto storage_class = + static_cast(inst->words[inst->operands[2].offset]); + if (auto error = LimitCheckNumVars(_, inst->result_id, storage_class)) { + return error; + } + if (storage_class == SpvStorageClassGeneric) + return _.diag(SPV_ERROR_INVALID_BINARY) + << "OpVariable storage class cannot be Generic"; + if (_.current_layout_section() == kLayoutFunctionDefinitions) { + if (storage_class != SpvStorageClassFunction) { + return _.diag(SPV_ERROR_INVALID_LAYOUT) + << "Variables must have a function[7] storage class inside" + " of a function"; + } + if (_.current_function().IsFirstBlock( + _.current_function().current_block()->id()) == false) { + return _.diag(SPV_ERROR_INVALID_CFG) << "Variables can only be defined " + "in the first block of a " + "function"; + } + } else { + if (storage_class == SpvStorageClassFunction) { + return _.diag(SPV_ERROR_INVALID_LAYOUT) + << "Variables can not have a function[7] storage class " + "outside of a function"; + } + } + } + + // SPIR-V Spec 2.16.3: Validation Rules for Kernel Capabilities: The + // Signedness in OpTypeInt must always be 0. + if (SpvOpTypeInt == inst->opcode && _.HasCapability(SpvCapabilityKernel) && + inst->words[inst->operands[2].offset] != 0u) { + return _.diag(SPV_ERROR_INVALID_BINARY) << "The Signedness in OpTypeInt " + "must always be 0 when Kernel " + "capability is used."; + } + + // In order to validate decoration rules, we need to know all the decorations + // that are applied to any given . + RegisterDecorations(_, inst); + + if (auto error = ExtensionCheck(_, inst)) return error; + if (auto error = CapabilityCheck(_, inst)) return error; + if (auto error = LimitCheckIdBound(_, inst)) return error; + if (auto error = LimitCheckStruct(_, inst)) return error; + if (auto error = LimitCheckSwitch(_, inst)) return error; + if (auto error = VersionCheck(_, inst)) return error; + + // All instruction checks have passed. + return SPV_SUCCESS; +} +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/validate_layout.cpp b/3rdparty/spirv-tools/source/validate_layout.cpp new file mode 100644 index 000000000..37c49be55 --- /dev/null +++ b/3rdparty/spirv-tools/source/validate_layout.cpp @@ -0,0 +1,207 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Source code for logical layout validation as described in section 2.4 + +#include "validate.h" + +#include + +#include "diagnostic.h" +#include "opcode.h" +#include "operand.h" +#include "spirv-tools/libspirv.h" +#include "val/function.h" +#include "val/validation_state.h" + +using libspirv::FunctionDecl; +using libspirv::kLayoutFunctionDeclarations; +using libspirv::kLayoutFunctionDefinitions; +using libspirv::kLayoutMemoryModel; +using libspirv::ValidationState_t; + +namespace { +// Module scoped instructions are processed by determining if the opcode +// is part of the current layout section. If it is not then the next sections is +// checked. +spv_result_t ModuleScopedInstructions(ValidationState_t& _, + const spv_parsed_instruction_t* inst, + SpvOp opcode) { + while (_.IsOpcodeInCurrentLayoutSection(opcode) == false) { + _.ProgressToNextLayoutSectionOrder(); + + switch (_.current_layout_section()) { + case kLayoutMemoryModel: + if (opcode != SpvOpMemoryModel) { + return _.diag(SPV_ERROR_INVALID_LAYOUT) + << spvOpcodeString(opcode) + << " cannot appear before the memory model instruction"; + } + break; + case kLayoutFunctionDeclarations: + // All module sections have been processed. Recursively call + // ModuleLayoutPass to process the next section of the module + return libspirv::ModuleLayoutPass(_, inst); + default: + break; + } + } + return SPV_SUCCESS; +} + +// Function declaration validation is performed by making sure that the +// FunctionParameter and FunctionEnd instructions only appear inside of +// functions. It also ensures that the Function instruction does not appear +// inside of another function. This stage ends when the first label is +// encountered inside of a function. +spv_result_t FunctionScopedInstructions(ValidationState_t& _, + const spv_parsed_instruction_t* inst, + SpvOp opcode) { + if (_.IsOpcodeInCurrentLayoutSection(opcode)) { + switch (opcode) { + case SpvOpFunction: { + if (_.in_function_body()) { + return _.diag(SPV_ERROR_INVALID_LAYOUT) + << "Cannot declare a function in a function body"; + } + auto control_mask = static_cast( + inst->words[inst->operands[2].offset]); + if (auto error = + _.RegisterFunction(inst->result_id, inst->type_id, control_mask, + inst->words[inst->operands[3].offset])) + return error; + if (_.current_layout_section() == kLayoutFunctionDefinitions) { + if (auto error = _.current_function().RegisterSetFunctionDeclType( + FunctionDecl::kFunctionDeclDefinition)) + return error; + } + } break; + + case SpvOpFunctionParameter: + if (_.in_function_body() == false) { + return _.diag(SPV_ERROR_INVALID_LAYOUT) << "Function parameter " + "instructions must be in " + "a function body"; + } + if (_.current_function().block_count() != 0) { + return _.diag(SPV_ERROR_INVALID_LAYOUT) + << "Function parameters must only appear immediately after " + "the function definition"; + } + if (auto error = _.current_function().RegisterFunctionParameter( + inst->result_id, inst->type_id)) + return error; + break; + + case SpvOpFunctionEnd: + if (_.in_function_body() == false) { + return _.diag(SPV_ERROR_INVALID_LAYOUT) + << "Function end instructions must be in a function body"; + } + if (_.in_block()) { + return _.diag(SPV_ERROR_INVALID_LAYOUT) + << "Function end cannot be called in blocks"; + } + if (_.current_function().block_count() == 0 && + _.current_layout_section() == kLayoutFunctionDefinitions) { + return _.diag(SPV_ERROR_INVALID_LAYOUT) << "Function declarations " + "must appear before " + "function definitions."; + } + if (_.current_layout_section() == kLayoutFunctionDeclarations) { + if (auto error = _.current_function().RegisterSetFunctionDeclType( + FunctionDecl::kFunctionDeclDeclaration)) + return error; + } + if (auto error = _.RegisterFunctionEnd()) return error; + break; + + case SpvOpLine: + case SpvOpNoLine: + break; + case SpvOpLabel: + // If the label is encountered then the current function is a + // definition so set the function to a declaration and update the + // module section + if (_.in_function_body() == false) { + return _.diag(SPV_ERROR_INVALID_LAYOUT) + << "Label instructions must be in a function body"; + } + if (_.in_block()) { + return _.diag(SPV_ERROR_INVALID_LAYOUT) + << "A block must end with a branch instruction."; + } + if (_.current_layout_section() == kLayoutFunctionDeclarations) { + _.ProgressToNextLayoutSectionOrder(); + if (auto error = _.current_function().RegisterSetFunctionDeclType( + FunctionDecl::kFunctionDeclDefinition)) + return error; + } + break; + + default: + if (_.current_layout_section() == kLayoutFunctionDeclarations && + _.in_function_body()) { + return _.diag(SPV_ERROR_INVALID_LAYOUT) + << "A function must begin with a label"; + } else { + if (_.in_block() == false) { + return _.diag(SPV_ERROR_INVALID_LAYOUT) + << spvOpcodeString(opcode) << " must appear in a block"; + } + } + break; + } + } else { + return _.diag(SPV_ERROR_INVALID_LAYOUT) + << spvOpcodeString(opcode) + << " cannot appear in a function declaration"; + } + return SPV_SUCCESS; +} +} // namespace + +namespace libspirv { +// TODO(umar): Check linkage capabilities for function declarations +// TODO(umar): Better error messages +// NOTE: This function does not handle CFG related validation +// Performs logical layout validation. See Section 2.4 +spv_result_t ModuleLayoutPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + const SpvOp opcode = static_cast(inst->opcode); + + switch (_.current_layout_section()) { + case kLayoutCapabilities: + case kLayoutExtensions: + case kLayoutExtInstImport: + case kLayoutMemoryModel: + case kLayoutEntryPoint: + case kLayoutExecutionMode: + case kLayoutDebug1: + case kLayoutDebug2: + case kLayoutDebug3: + case kLayoutAnnotations: + case kLayoutTypes: + if (auto error = ModuleScopedInstructions(_, inst, opcode)) return error; + break; + case kLayoutFunctionDeclarations: + case kLayoutFunctionDefinitions: + if (auto error = FunctionScopedInstructions(_, inst, opcode)) { + return error; + } + break; + } + return SPV_SUCCESS; +} +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/validate_literals.cpp b/3rdparty/spirv-tools/source/validate_literals.cpp new file mode 100644 index 000000000..5ced89ef5 --- /dev/null +++ b/3rdparty/spirv-tools/source/validate_literals.cpp @@ -0,0 +1,99 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Validates literal numbers. + +#include "validate.h" + +#include + +#include "diagnostic.h" +#include "opcode.h" +#include "val/instruction.h" +#include "val/validation_state.h" + +namespace libspirv { + +namespace { + +// Returns true if the operand holds a literal number +bool IsLiteralNumber(const spv_parsed_operand_t* operand) { + switch (operand->number_kind) { + case SPV_NUMBER_SIGNED_INT: + case SPV_NUMBER_UNSIGNED_INT: + case SPV_NUMBER_FLOATING: + return true; + default: + return false; + } +} + +// Verifies that the upper bits of the given upper |word| with given +// lower |width| are zero- or sign-extended when |signed_int| is true +bool VerifyUpperBits(uint32_t word, uint32_t width, bool signed_int) { + assert(width < 32); + assert(0 < width); + const uint32_t upper_mask = 0xFFFFFFFFu << width; + const uint32_t upper_bits = word & upper_mask; + + bool result = false; + if (signed_int) { + const uint32_t sign_bit = word & (1u << (width - 1)); + if (sign_bit) { + result = upper_bits == upper_mask; + } else { + result = upper_bits == 0; + } + } else { + result = upper_bits == 0; + } + return result; +} + +} // namespace + +// Validates that literal numbers are represented according to the spec +spv_result_t LiteralsPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + // For every operand that is a literal number + for (uint16_t i = 0; i < inst->num_operands; i++) { + const spv_parsed_operand_t* operand = inst->operands + i; + if (!IsLiteralNumber(operand)) continue; + + // The upper bits are always in the last word (little-endian) + int last_index = operand->offset + operand->num_words - 1; + const uint32_t upper_word = inst->words[last_index]; + + // TODO(jcaraban): is the |word size| defined in some header? + const uint32_t word_size = 32; + uint32_t bit_width = operand->number_bit_width; + + // Bit widths that are a multiple of the word size have no upper bits + const auto remaining_value_bits = bit_width % word_size; + if (remaining_value_bits == 0) continue; + + const bool signedness = operand->number_kind == SPV_NUMBER_SIGNED_INT; + + if (!VerifyUpperBits(upper_word, remaining_value_bits, signedness)) { + return _.diag(SPV_ERROR_INVALID_VALUE) + << "The high-order bits of a literal number in instruction " + << inst->result_id << " must be 0 for a floating-point type, " + << "or 0 for an integer type with Signedness of 0, " + << "or sign extended when Signedness is 1"; + } + } + return SPV_SUCCESS; +} + +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/validate_logicals.cpp b/3rdparty/spirv-tools/source/validate_logicals.cpp new file mode 100644 index 000000000..5a6c034f0 --- /dev/null +++ b/3rdparty/spirv-tools/source/validate_logicals.cpp @@ -0,0 +1,284 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Validates correctness of logical SPIR-V instructions. + +#include "validate.h" + +#include "diagnostic.h" +#include "opcode.h" +#include "val/instruction.h" +#include "val/validation_state.h" + +namespace libspirv { + +namespace { + +// Returns operand word for given instruction and operand index. +// The operand is expected to only have one word. +inline uint32_t GetOperandWord(const spv_parsed_instruction_t* inst, + size_t operand_index) { + assert(operand_index < inst->num_operands); + const spv_parsed_operand_t& operand = inst->operands[operand_index]; + assert(operand.num_words == 1); + return inst->words[operand.offset]; +} + +// Returns the type id of instruction operand at |operand_index|. +// The operand is expected to be an id. +inline uint32_t GetOperandTypeId(ValidationState_t& _, + const spv_parsed_instruction_t* inst, + size_t operand_index) { + return _.GetTypeId(GetOperandWord(inst, operand_index)); +} +} // namespace + +// Validates correctness of logical instructions. +spv_result_t LogicalsPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + const SpvOp opcode = static_cast(inst->opcode); + const uint32_t result_type = inst->type_id; + + switch (opcode) { + case SpvOpAny: + case SpvOpAll: { + if (!_.IsBoolScalarType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected bool scalar type as Result Type: " + << spvOpcodeString(opcode); + + const uint32_t vector_type = GetOperandTypeId(_, inst, 2); + if (!vector_type || !_.IsBoolVectorType(vector_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected operand to be vector bool: " + << spvOpcodeString(opcode); + + break; + } + + case SpvOpIsNan: + case SpvOpIsInf: + case SpvOpIsFinite: + case SpvOpIsNormal: + case SpvOpSignBitSet: { + if (!_.IsBoolScalarType(result_type) && !_.IsBoolVectorType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected bool scalar or vector type as Result Type: " + << spvOpcodeString(opcode); + + const uint32_t operand_type = GetOperandTypeId(_, inst, 2); + if (!operand_type || (!_.IsFloatScalarType(operand_type) && + !_.IsFloatVectorType(operand_type))) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected operand to be scalar or vector float: " + << spvOpcodeString(opcode); + + if (_.GetDimension(result_type) != _.GetDimension(operand_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected vector sizes of Result Type and the operand to be " + "equal: " + << spvOpcodeString(opcode); + + break; + } + + case SpvOpFOrdEqual: + case SpvOpFUnordEqual: + case SpvOpFOrdNotEqual: + case SpvOpFUnordNotEqual: + case SpvOpFOrdLessThan: + case SpvOpFUnordLessThan: + case SpvOpFOrdGreaterThan: + case SpvOpFUnordGreaterThan: + case SpvOpFOrdLessThanEqual: + case SpvOpFUnordLessThanEqual: + case SpvOpFOrdGreaterThanEqual: + case SpvOpFUnordGreaterThanEqual: + case SpvOpLessOrGreater: + case SpvOpOrdered: + case SpvOpUnordered: { + if (!_.IsBoolScalarType(result_type) && !_.IsBoolVectorType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected bool scalar or vector type as Result Type: " + << spvOpcodeString(opcode); + + const uint32_t left_operand_type = GetOperandTypeId(_, inst, 2); + if (!left_operand_type || (!_.IsFloatScalarType(left_operand_type) && + !_.IsFloatVectorType(left_operand_type))) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected operands to be scalar or vector float: " + << spvOpcodeString(opcode); + + if (_.GetDimension(result_type) != _.GetDimension(left_operand_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected vector sizes of Result Type and the operands to be " + "equal: " + << spvOpcodeString(opcode); + + if (left_operand_type != GetOperandTypeId(_, inst, 3)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected left and right operands to have the same type: " + << spvOpcodeString(opcode); + + break; + } + + case SpvOpLogicalEqual: + case SpvOpLogicalNotEqual: + case SpvOpLogicalOr: + case SpvOpLogicalAnd: { + if (!_.IsBoolScalarType(result_type) && !_.IsBoolVectorType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected bool scalar or vector type as Result Type: " + << spvOpcodeString(opcode); + + if (result_type != GetOperandTypeId(_, inst, 2) || + result_type != GetOperandTypeId(_, inst, 3)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected both operands to be of Result Type: " + << spvOpcodeString(opcode); + + break; + } + + case SpvOpLogicalNot: { + if (!_.IsBoolScalarType(result_type) && !_.IsBoolVectorType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected bool scalar or vector type as Result Type: " + << spvOpcodeString(opcode); + + if (result_type != GetOperandTypeId(_, inst, 2)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected operand to be of Result Type: " + << spvOpcodeString(opcode); + + break; + } + + case SpvOpSelect: { + uint32_t dimension = 1; + { + const Instruction* type_inst = _.FindDef(result_type); + assert(type_inst); + + const SpvOp type_opcode = type_inst->opcode(); + switch (type_opcode) { + case SpvOpTypePointer: { + if (!_.features().variable_pointers && + !_.features().variable_pointers_storage_buffer) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Using pointers with OpSelect requires capability " + << "VariablePointers or VariablePointersStorageBuffer"; + break; + } + + case SpvOpTypeVector: { + dimension = type_inst->word(3); + break; + } + + case SpvOpTypeBool: + case SpvOpTypeInt: + case SpvOpTypeFloat: { + break; + } + + default: { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected scalar or vector type as Result Type: " + << spvOpcodeString(opcode); + } + } + } + + const uint32_t condition_type = GetOperandTypeId(_, inst, 2); + const uint32_t left_type = GetOperandTypeId(_, inst, 3); + const uint32_t right_type = GetOperandTypeId(_, inst, 4); + + if (!condition_type || (!_.IsBoolScalarType(condition_type) && + !_.IsBoolVectorType(condition_type))) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected bool scalar or vector type as condition: " + << spvOpcodeString(opcode); + + if (_.GetDimension(condition_type) != dimension) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected vector sizes of Result Type and the condition to be" + << " equal: " << spvOpcodeString(opcode); + + if (result_type != left_type || result_type != right_type) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected both objects to be of Result Type: " + << spvOpcodeString(opcode); + + break; + } + + case SpvOpIEqual: + case SpvOpINotEqual: + case SpvOpUGreaterThan: + case SpvOpUGreaterThanEqual: + case SpvOpULessThan: + case SpvOpULessThanEqual: + case SpvOpSGreaterThan: + case SpvOpSGreaterThanEqual: + case SpvOpSLessThan: + case SpvOpSLessThanEqual: { + if (!_.IsBoolScalarType(result_type) && !_.IsBoolVectorType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected bool scalar or vector type as Result Type: " + << spvOpcodeString(opcode); + + const uint32_t left_type = GetOperandTypeId(_, inst, 2); + const uint32_t right_type = GetOperandTypeId(_, inst, 3); + + if (!left_type || + (!_.IsIntScalarType(left_type) && !_.IsIntVectorType(left_type))) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected operands to be scalar or vector int: " + << spvOpcodeString(opcode); + + if (_.GetDimension(result_type) != _.GetDimension(left_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected vector sizes of Result Type and the operands to be" + << " equal: " << spvOpcodeString(opcode); + + if (!right_type || + (!_.IsIntScalarType(right_type) && !_.IsIntVectorType(right_type))) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected operands to be scalar or vector int: " + << spvOpcodeString(opcode); + + if (_.GetDimension(result_type) != _.GetDimension(right_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected vector sizes of Result Type and the operands to be" + << " equal: " << spvOpcodeString(opcode); + + if (_.GetBitWidth(left_type) != _.GetBitWidth(right_type)) + return _.diag(SPV_ERROR_INVALID_DATA) + << "Expected both operands to have the same component bit " + "width: " + << spvOpcodeString(opcode); + + break; + } + + default: + break; + } + + return SPV_SUCCESS; +} + +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/validate_primitives.cpp b/3rdparty/spirv-tools/source/validate_primitives.cpp new file mode 100644 index 000000000..de5bc2c30 --- /dev/null +++ b/3rdparty/spirv-tools/source/validate_primitives.cpp @@ -0,0 +1,73 @@ +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Validates correctness of primitive SPIR-V instructions. + +#include "validate.h" + +#include + +#include "diagnostic.h" +#include "opcode.h" +#include "val/instruction.h" +#include "val/validation_state.h" + +namespace libspirv { + +// Validates correctness of primitive instructions. +spv_result_t PrimitivesPass(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + const SpvOp opcode = static_cast(inst->opcode); + + switch (opcode) { + case SpvOpEmitVertex: + case SpvOpEndPrimitive: + case SpvOpEmitStreamVertex: + case SpvOpEndStreamPrimitive: + _.current_function().RegisterExecutionModelLimitation( + SpvExecutionModelGeometry, + std::string(spvOpcodeString(opcode)) + + " instructions require Geometry execution model"); + break; + default: + break; + } + + switch (opcode) { + case SpvOpEmitStreamVertex: + case SpvOpEndStreamPrimitive: { + const uint32_t stream_id = inst->words[1]; + const uint32_t stream_type = _.GetTypeId(stream_id); + if (!_.IsIntScalarType(stream_type)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Stream to be int scalar"; + } + + const SpvOp stream_opcode = _.GetIdOpcode(stream_id); + if (!spvOpcodeIsConstant(stream_opcode)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << spvOpcodeString(opcode) + << ": expected Stream to be constant instruction"; + } + } + + default: + break; + } + + return SPV_SUCCESS; +} + +} // namespace libspirv diff --git a/3rdparty/spirv-tools/source/validate_type_unique.cpp b/3rdparty/spirv-tools/source/validate_type_unique.cpp new file mode 100644 index 000000000..b7f77ed6c --- /dev/null +++ b/3rdparty/spirv-tools/source/validate_type_unique.cpp @@ -0,0 +1,62 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Ensures type declarations are unique unless allowed by the specification. + +#include "validate.h" + +#include "diagnostic.h" +#include "opcode.h" +#include "val/instruction.h" +#include "val/validation_state.h" + +namespace libspirv { + +// Validates that type declarations are unique, unless multiple declarations +// of the same data type are allowed by the specification. +// (see section 2.8 Types and Variables) +// Doesn't do anything if SPV_VAL_ignore_type_decl_unique was declared in the +// module. +spv_result_t TypeUniquePass(ValidationState_t& _, + const spv_parsed_instruction_t* inst) { + if (_.HasExtension(Extension::kSPV_VALIDATOR_ignore_type_decl_unique)) + return SPV_SUCCESS; + + const SpvOp opcode = static_cast(inst->opcode); + + if (spvOpcodeGeneratesType(opcode)) { + if (opcode == SpvOpTypeArray || opcode == SpvOpTypeRuntimeArray || + opcode == SpvOpTypeStruct) { + // Duplicate declarations of aggregates are allowed. + return SPV_SUCCESS; + } + + if (inst->opcode == SpvOpTypePointer && + _.HasExtension(Extension::kSPV_KHR_variable_pointers)) { + // Duplicate pointer types are allowed with this extension. + return SPV_SUCCESS; + } + + if (!_.RegisterUniqueTypeDeclaration(*inst)) { + return _.diag(SPV_ERROR_INVALID_DATA) + << "Duplicate non-aggregate type declarations are not allowed." + << " Opcode: " << spvOpcodeString(SpvOp(inst->opcode)) + << " id: " << inst->result_id; + } + } + + return SPV_SUCCESS; +} + +} // namespace libspirv diff --git a/3rdparty/spirv-tools/syntax.md b/3rdparty/spirv-tools/syntax.md new file mode 100644 index 000000000..be3a5d5ad --- /dev/null +++ b/3rdparty/spirv-tools/syntax.md @@ -0,0 +1,238 @@ +# SPIR-V Assembly language syntax + +## Overview + +The assembly attempts to adhere to the binary form from Section 3 of the SPIR-V +spec as closely as possible, with one exception aiming at improving the text's +readability. The `` generated by an instruction is moved to the +beginning of that instruction and followed by an `=` sign. This allows us to +distinguish between variable definitions and uses and locate value definitions +more easily. + +Here is an example: + +``` + OpCapability Shader + OpMemoryModel Logical Simple + OpEntryPoint GLCompute %3 "main" + OpExecutionMode %3 LocalSize 64 64 1 +%1 = OpTypeVoid +%2 = OpTypeFunction %1 +%3 = OpFunction %1 None %2 +%4 = OpLabel + OpReturn + OpFunctionEnd +``` + +A module is a sequence of instructions, separated by whitespace. +An instruction is an opcode name followed by operands, separated by +whitespace. Typically each instruction is presented on its own line, +but the assembler does not enforce this rule. + +The opcode names and expected operands are described in Section 3 of +the SPIR-V specification. An operand is one of: +* a literal integer: A decimal integer, or a hexadecimal integer. + A hexadecimal integer is indicated by a leading `0x` or `0X`. A hex + integer supplied for a signed integer value will be sign-extended. + For example, `0xffff` supplied as the literal for an `OpConstant` + on a signed 16-bit integer type will be interpreted as the value `-1`. +* a literal floating point number, in decimal or hexadecimal form. + See [below](#floats). +* a literal string. + * A literal string is everything following a double-quote `"` until the + following un-escaped double-quote. This includes special characters such + as newlines. + * A backslash `\` may be used to escape characters in the string. The `\` + may be used to escape a double-quote or a `\` but is simply ignored when + preceding any other character. +* a named enumerated value, specific to that operand position. For example, + the `OpMemoryModel` takes a named Addressing Model operand (e.g. `Logical` or + `Physical32`), and a named Memory Model operand (e.g. `Simple` or `OpenCL`). + Named enumerated values are only meaningful in specific positions, and will + otherwise generate an error. +* a mask expression, consisting of one or more mask enum names separated + by `|`. For example, the expression `NotNaN|NotInf|NSZ` denotes the mask + which is the combination of the `NotNaN`, `NotInf`, and `NSZ` flags. +* an injected immediate integer: `!`. See [below](#immediate). +* an ID, e.g. `%foo`. See [below](#id). +* the name of an extended instruction. For example, `sqrt` in an extended + instruction such as `%f = OpExtInst %f32 %OpenCLImport sqrt %arg` +* the name of an opcode for OpSpecConstantOp, but where the `Op` prefix + is removed. For example, the following indicates the use of an integer + addition in a specialization constant computation: + `%sum = OpSpecConstantOp %i32 IAdd %a %b` + +## ID Definitions & Usage + + +An ID _definition_ pertains to the `` of an instruction, and ID +_usage_ is a use of an ID as an input to an instruction. + +An ID in the assembly language begins with `%` and must be followed by a name +consisting of one or more letters, numbers or underscore characters. + +For every ID in the assembly program, the assembler generates a unique number +called the ID's internal number. Then each ID reference translates into its +internal number in the SPIR-V output. Internal numbers are unique within the +compilation unit: no two IDs in the same unit will share internal numbers. + +The disassembler generates IDs where the name is always a decimal number +greater than 0. + +So the example can be rewritten using more user-friendly names, as follows: +``` + OpCapability Shader + OpMemoryModel Logical Simple + OpEntryPoint GLCompute %main "main" + OpExecutionMode %main LocalSize 64 64 1 + %void = OpTypeVoid +%fnMain = OpTypeFunction %void + %main = OpFunction %void None %fnMain +%lbMain = OpLabel + OpReturn + OpFunctionEnd +``` + +## Floating point literals + + +The assembler and disassembler support floating point literals in both +decimal and hexadecimal form. + +The syntax for a floating point literal is the same as floating point +constants in the C programming language, except: +* An optional leading minus (`-`) is part of the literal. +* An optional type specifier suffix is not allowed. +Infinity and NaN values are expressed in hexadecimal float literals +by using the maximum representable exponent for the bit width. + +For example, in 32-bit floating point, 8 bits are used for the exponent, and the +exponent bias is 127. So the maximum representable unbiased exponent is 128. +Therefore, we represent the infinities and some NaNs as follows: + +``` +%float32 = OpTypeFloat 32 +%inf = OpConstant %float32 0x1p+128 +%neginf = OpConstant %float32 -0x1p+128 +%aNaN = OpConstant %float32 0x1.8p+128 +%moreNaN = OpConstant %float32 -0x1.0002p+128 +``` +The assembler preserves all the bits of a NaN value. For example, the encoding +of `%aNaN` in the previous example is the same as the word with bits +`0x7fc00000`, and `%moreNaN` is encoded as `0xff800100`. + +The disassembler prints infinite, NaN, and subnormal values in hexadecimal form. +Zero and normal values are printed in decimal form with enough digits +to preserve all significand bits. + +## Arbitrary Integers + + +When writing tests it can be useful to emit an invalid 32 bit word into the +binary stream at arbitrary positions within the assembly. To specify an +arbitrary word into the stream the prefix `!` is used, this takes the form +`!`. Here is an example. + +``` +OpCapability !0x0000FF00 +``` + +Any token in a valid assembly program may be replaced by `!` -- even +tokens that dictate how the rest of the instruction is parsed. Consider, for +example, the following assembly program: + +``` +%4 = OpConstant %1 123 456 789 OpExecutionMode %2 LocalSize 11 22 33 +OpExecutionMode %3 InputLines +``` + +The tokens `OpConstant`, `LocalSize`, and `InputLines` may be replaced by random +`!` values, and the assembler will still assemble an output binary with +three instructions. It will not necessarily be valid SPIR-V, but it will +faithfully reflect the input text. + +You may wonder how the assembler recognizes the instruction structure (including +instruction boundaries) in the text with certain crucial tokens replaced by +arbitrary integers. If, say, `OpConstant` becomes a `!` whose value +differs from the binary representation of `OpConstant` (remember that this +feature is intended for fine-grain control in SPIR-V testing), the assembler +generally has no idea what that value stands for. So how does it know there is +exactly one `` and three number literals following in that instruction, +before the next one begins? And if `LocalSize` is replaced by an arbitrary +`!`, how does it know to take the next three tokens (instead of zero or +one, both of which are possible in the absence of certainty that `LocalSize` +provided)? The answer is a simple rule governing the parsing of instructions +with `!` in them: + +When a token in the assembly program is a `!`, that integer value is +emitted into the binary output, and parsing proceeds differently than before: +each subsequent token not recognized as an OpCode or a is emitted +into the binary output without any checking; when a recognizable OpCode or a + is eventually encountered, it begins a new instruction and parsing +returns to normal. (If a subsequent OpCode is never found, then this alternate +parsing mode handles all the remaining tokens in the program.) + +The assembler processes the tokens encountered in alternate parsing mode as +follows: + +* If the token is a number literal, since context may be lost, the number + is interpreted as a 32-bit value and output as a single word. In order to + specify multiple-word literals in alternate-parsing mode, further uses of + `!` tokens may be required. + All formats supported by `strtoul()` are accepted. +* If the token is a string literal, it outputs a sequence of words representing + the string as defined in the SPIR-V specification for Literal String. +* If the token is an ID, it outputs the ID's internal number. +* If the token is another `!`, it outputs that integer. +* Any other token causes the assembler to quit with an error. + +Note that this has some interesting consequences, including: + +* When an OpCode is replaced by `!`, the integer value should encode + the instruction's word count, as specified in the physical-layout section of + the SPIR-V specification. + +* Consecutive instructions may have their OpCode replaced by `!` and + still produce valid SPIR-V. For example, `!262187 %1 %2 "abc" !327739 %1 %3 6 + %2` will successfully assemble into SPIR-V declaring a constant and a + PrivateGlobal variable. + +* Enums (such as `DontInline` or `SubgroupMemory`, for instance) are not handled + by the alternate parsing mode. They must be replaced by `!` for + successful assembly. + +* The `` on the left-hand side of an assignment cannot be a + `!`. The `` can be still be manually controlled if desired + by expressing the entire instruction as `!` tokens for its opcode and + operands. + +* The `=` sign cannot be processed by the alternate parsing mode if the OpCode + following it is a `!`. + +* When replacing a named ID with `!`, it is possible to generate + unintentionally valid SPIR-V. If the integer provided happens to equal a + number generated for an existing named ID, it will result in a reference to + that named ID being output. This may be valid SPIR-V, contrary to the + presumed intention of the writer. + +## Notes + +* Some enumerants cannot be used by name, because the target instruction +in which they are meaningful take an ID reference instead of a literal value. +For example: + * Named enumerated value `CmdExecTime` from section 3.30 Kernel + Profiling Info is used in constructing a mask value supplied as + an ID for `OpCaptureEventProfilingInfo`. But no other instruction + has enough context to bring the enumerant names from section 3.30 + into scope. + * Similarly, the names in section 3.29 Kernel Enqueue Flags are used to + construct a value supplied as an ID to the Flags argument of + OpEnqueueKernel. + * Similarly for the names in section 3.25 Memory Semantics. + * Similarly for the names in section 3.27 Scope. +* Some enumerants cannot be used by name, because they only name values +returned by an instruction: + * Enumerants from 3.12 Image Channel Order name possible values returned + by the `OpImageQueryOrder` instruction. + * Enumerants from 3.13 Image Channel Data Type name possible values + returned by the `OpImageQueryFormat` instruction. diff --git a/3rdparty/spirv-tools/test/CMakeLists.txt b/3rdparty/spirv-tools/test/CMakeLists.txt new file mode 100644 index 000000000..5317e1f1f --- /dev/null +++ b/3rdparty/spirv-tools/test/CMakeLists.txt @@ -0,0 +1,218 @@ +# Copyright (c) 2015-2016 The Khronos Group Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Add a SPIR-V Tools unit test. Signature: +# add_spvtools_unittest( +# TARGET target_name +# SRCS src_file.h src_file.cpp +# LIBS lib1 lib2 +# ) + +if (NOT "${SPIRV_SKIP_TESTS}") + if (TARGET gmock_main) + message(STATUS "Found Google Mock, building tests.") + else() + message(STATUS "Did not find googletest, tests will not be built. " + "To enable tests place googletest in '/external/googletest'.") + endif() +endif() + +function(add_spvtools_unittest) + if (NOT "${SPIRV_SKIP_TESTS}" AND TARGET gmock_main) + set(one_value_args TARGET) + set(multi_value_args SRCS LIBS ENVIRONMENT) + cmake_parse_arguments( + ARG "" "${one_value_args}" "${multi_value_args}" ${ARGN}) + set(target test_${ARG_TARGET}) + add_executable(${target} ${ARG_SRCS}) + spvtools_default_compile_options(${target}) + if(${COMPILER_IS_LIKE_GNU}) + target_compile_options(${target} PRIVATE -Wno-undef) + # Effcee and RE2 headers exhibit shadowing. + target_compile_options(${target} PRIVATE -Wno-shadow) + endif() + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") + # Disable C4503 "decorated name length exceeded" warning, + # triggered by some heavily templated types. + # We don't care much about that in test code. + # Important to do since we have warnings-as-errors. + target_compile_options(${target} PRIVATE /wd4503) + # Googletest accidentally turns off support for ::testing::Combine + # in VS 2017. See https://github.com/google/googletest/issues/1352 + # Forcibly turn it on again. + target_compile_options(${target} PRIVATE /DGTEST_HAS_COMBINE=1) + endif() + target_include_directories(${target} PRIVATE + ${SPIRV_HEADER_INCLUDE_DIR} + ${spirv-tools_SOURCE_DIR} + ${spirv-tools_SOURCE_DIR}/include + ${spirv-tools_SOURCE_DIR}/test + ${spirv-tools_BINARY_DIR} + ${gtest_SOURCE_DIR}/include + ${gmock_SOURCE_DIR}/include + ) + if (TARGET effcee) + # If using Effcee for testing, then add its include directory. + target_include_directories(${target} PRIVATE ${effcee_SOURCE_DIR}) + endif() + target_link_libraries(${target} PRIVATE ${ARG_LIBS}) + if (TARGET effcee) + target_link_libraries(${target} PRIVATE effcee) + endif() + target_link_libraries(${target} PRIVATE gmock_main) + add_test(NAME spirv-tools-${target} COMMAND ${target}) + if (DEFINED ARG_ENVIRONMENT) + set_tests_properties(spirv-tools-${target} PROPERTIES ENVIRONMENT ${ARG_ENVIRONMENT}) + endif() + set_property(TARGET ${target} PROPERTY FOLDER "SPIRV-Tools tests") + endif() +endfunction() + +set(TEST_SOURCES + test_fixture.h + unit_spirv.h + + assembly_context_test.cpp + assembly_format_test.cpp + binary_destroy_test.cpp + binary_endianness_test.cpp + binary_header_get_test.cpp + binary_parse_test.cpp + binary_strnlen_s_test.cpp + binary_to_text_test.cpp + binary_to_text.literal_test.cpp + comment_test.cpp + enum_string_mapping_test.cpp + enum_set_test.cpp + ext_inst.debuginfo_test.cpp + ext_inst.glsl_test.cpp + ext_inst.opencl_test.cpp + fix_word_test.cpp + generator_magic_number_test.cpp + hex_float_test.cpp + immediate_int_test.cpp + libspirv_macros_test.cpp + named_id_test.cpp + name_mapper_test.cpp + opcode_make_test.cpp + opcode_require_capabilities_test.cpp + opcode_split_test.cpp + opcode_table_get_test.cpp + operand_capabilities_test.cpp + operand_test.cpp + operand_pattern_test.cpp + software_version_test.cpp + target_env_test.cpp + text_advance_test.cpp + text_destroy_test.cpp + text_literal_test.cpp + text_start_new_inst_test.cpp + text_to_binary.annotation_test.cpp + text_to_binary.barrier_test.cpp + text_to_binary.constant_test.cpp + text_to_binary.control_flow_test.cpp + text_to_binary_test.cpp + text_to_binary.debug_test.cpp + text_to_binary.device_side_enqueue_test.cpp + text_to_binary.extension_test.cpp + text_to_binary.function_test.cpp + text_to_binary.group_test.cpp + text_to_binary.image_test.cpp + text_to_binary.literal_test.cpp + text_to_binary.memory_test.cpp + text_to_binary.misc_test.cpp + text_to_binary.mode_setting_test.cpp + text_to_binary.pipe_storage_test.cpp + text_to_binary.type_declaration_test.cpp + text_to_binary.subgroup_dispatch_test.cpp + text_to_binary.reserved_sampling_test.cpp + text_word_get_test.cpp + + unit_spirv.cpp +) + +add_spvtools_unittest( + TARGET spirv_unit_tests + SRCS ${TEST_SOURCES} + LIBS ${SPIRV_TOOLS}) + +add_spvtools_unittest( + TARGET diagnostic + SRCS diagnostic_test.cpp + LIBS ${SPIRV_TOOLS}) + +add_spvtools_unittest( + TARGET c_interface + SRCS c_interface_test.cpp + LIBS ${SPIRV_TOOLS}) + +add_spvtools_unittest( + TARGET c_interface_shared + SRCS c_interface_test.cpp + LIBS ${SPIRV_TOOLS}-shared + ENVIRONMENT PATH=$) + +add_spvtools_unittest( + TARGET cpp_interface + SRCS cpp_interface_test.cpp + LIBS SPIRV-Tools-opt) + +add_spvtools_unittest( + TARGET parse_number + SRCS parse_number_test.cpp + LIBS ${SPIRV_TOOLS}) + +add_spvtools_unittest( + TARGET string_utils + SRCS string_utils_test.cpp + LIBS ${SPIRV_TOOLS}) + +if (${SPIRV_TIMER_ENABLED}) +add_spvtools_unittest( + TARGET timer + SRCS timer_test.cpp + LIBS ${SPIRV_TOOLS}) +endif() + +add_spvtools_unittest( + TARGET log + SRCS log_test.cpp + LIBS ${SPIRV_TOOLS}) + +add_spvtools_unittest( + TARGET preserve_numeric_ids + SRCS preserve_numeric_ids_test.cpp + LIBS ${SPIRV_TOOLS}) + +add_spvtools_unittest( + TARGET bit_stream + SRCS bit_stream.cpp + LIBS ${SPIRV_TOOLS}) + +add_spvtools_unittest( + TARGET huffman_codec + SRCS huffman_codec.cpp + LIBS ${SPIRV_TOOLS}) + +add_spvtools_unittest( + TARGET move_to_front + SRCS move_to_front_test.cpp + LIBS ${SPIRV_TOOLS}) + +add_subdirectory(comp) +add_subdirectory(link) +add_subdirectory(opt) +add_subdirectory(stats) +add_subdirectory(util) +add_subdirectory(val) diff --git a/3rdparty/spirv-tools/test/assembly_context_test.cpp b/3rdparty/spirv-tools/test/assembly_context_test.cpp new file mode 100644 index 000000000..65a40aad2 --- /dev/null +++ b/3rdparty/spirv-tools/test/assembly_context_test.cpp @@ -0,0 +1,76 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "unit_spirv.h" + +#include +#include + +#include "source/instruction.h" + +using libspirv::AssemblyContext; +using spvtest::AutoText; +using spvtest::Concatenate; +using ::testing::Eq; + +namespace { + +struct EncodeStringCase { + std::string str; + std::vector initial_contents; +}; + +using EncodeStringTest = ::testing::TestWithParam; + +TEST_P(EncodeStringTest, Sample) { + AssemblyContext context(AutoText(""), nullptr); + spv_instruction_t inst; + inst.words = GetParam().initial_contents; + ASSERT_EQ(SPV_SUCCESS, + context.binaryEncodeString(GetParam().str.c_str(), &inst)); + // We already trust MakeVector + EXPECT_THAT(inst.words, + Eq(Concatenate({GetParam().initial_contents, + spvtest::MakeVector(GetParam().str)}))); +} + +// clang-format off +INSTANTIATE_TEST_CASE_P( + BinaryEncodeString, EncodeStringTest, + ::testing::ValuesIn(std::vector{ + // Use cases that exercise at least one to two words, + // and both empty and non-empty initial contents. + {"", {}}, + {"", {1,2,3}}, + {"a", {}}, + {"a", {4}}, + {"ab", {4}}, + {"abc", {}}, + {"abc", {18}}, + {"abcd", {}}, + {"abcd", {22}}, + {"abcde", {4}}, + {"abcdef", {}}, + {"abcdef", {99,42}}, + {"abcdefg", {}}, + {"abcdefg", {101}}, + {"abcdefgh", {}}, + {"abcdefgh", {102, 103, 104}}, + // A very long string, encoded after an initial word. + // SPIR-V limits strings to 65535 characters. + {std::string(65535, 'a'), {1}}, + }),); +// clang-format on + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/assembly_format_test.cpp b/3rdparty/spirv-tools/test/assembly_format_test.cpp new file mode 100644 index 000000000..953fb8a33 --- /dev/null +++ b/3rdparty/spirv-tools/test/assembly_format_test.cpp @@ -0,0 +1,35 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "test_fixture.h" + +namespace { + +using spvtest::ScopedContext; +using spvtest::TextToBinaryTest; + +TEST_F(TextToBinaryTest, NotPlacingResultIDAtTheBeginning) { + SetText("OpTypeMatrix %1 %2 1000"); + EXPECT_EQ(SPV_ERROR_INVALID_TEXT, + spvTextToBinary(ScopedContext().context, text.str, text.length, + &binary, &diagnostic)); + ASSERT_NE(nullptr, diagnostic); + EXPECT_STREQ( + "Expected at the beginning of an instruction, found " + "'OpTypeMatrix'.", + diagnostic->error); + EXPECT_EQ(0u, diagnostic->position.line); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/binary_destroy_test.cpp b/3rdparty/spirv-tools/test/binary_destroy_test.cpp new file mode 100644 index 000000000..2df8379c8 --- /dev/null +++ b/3rdparty/spirv-tools/test/binary_destroy_test.cpp @@ -0,0 +1,42 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "unit_spirv.h" + +#include "test_fixture.h" + +namespace { + +using spvtest::ScopedContext; + +TEST(BinaryDestroy, Null) { + // There is no state or return value to check. Just check + // for the ability to call the API without abnormal termination. + spvBinaryDestroy(nullptr); +} + +using BinaryDestroySomething = spvtest::TextToBinaryTest; + +// Checks safety of destroying a validly constructed binary. +TEST_F(BinaryDestroySomething, Default) { + // Use a binary object constructed by the API instead of rolling our own. + SetText("OpSource OpenCL_C 120"); + spv_binary my_binary = nullptr; + ASSERT_EQ(SPV_SUCCESS, spvTextToBinary(ScopedContext().context, text.str, + text.length, &my_binary, &diagnostic)); + ASSERT_NE(nullptr, my_binary); + spvBinaryDestroy(my_binary); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/binary_endianness_test.cpp b/3rdparty/spirv-tools/test/binary_endianness_test.cpp new file mode 100644 index 000000000..343c17e2e --- /dev/null +++ b/3rdparty/spirv-tools/test/binary_endianness_test.cpp @@ -0,0 +1,52 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "unit_spirv.h" + +namespace { + +TEST(BinaryEndianness, InvalidCode) { + uint32_t invalidMagicNumber[] = {0}; + spv_const_binary_t binary = {invalidMagicNumber, 1}; + spv_endianness_t endian; + ASSERT_EQ(SPV_ERROR_INVALID_BINARY, spvBinaryEndianness(&binary, &endian)); +} + +TEST(BinaryEndianness, Little) { + uint32_t magicNumber; + if (I32_ENDIAN_HOST == I32_ENDIAN_LITTLE) { + magicNumber = 0x07230203; + } else { + magicNumber = 0x03022307; + } + spv_const_binary_t binary = {&magicNumber, 1}; + spv_endianness_t endian; + ASSERT_EQ(SPV_SUCCESS, spvBinaryEndianness(&binary, &endian)); + ASSERT_EQ(SPV_ENDIANNESS_LITTLE, endian); +} + +TEST(BinaryEndianness, Big) { + uint32_t magicNumber; + if (I32_ENDIAN_HOST == I32_ENDIAN_BIG) { + magicNumber = 0x07230203; + } else { + magicNumber = 0x03022307; + } + spv_const_binary_t binary = {&magicNumber, 1}; + spv_endianness_t endian; + ASSERT_EQ(SPV_SUCCESS, spvBinaryEndianness(&binary, &endian)); + ASSERT_EQ(SPV_ENDIANNESS_BIG, endian); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/binary_header_get_test.cpp b/3rdparty/spirv-tools/test/binary_header_get_test.cpp new file mode 100644 index 000000000..d6efe5af0 --- /dev/null +++ b/3rdparty/spirv-tools/test/binary_header_get_test.cpp @@ -0,0 +1,82 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/spirv_constant.h" +#include "unit_spirv.h" + +namespace { + +class BinaryHeaderGet : public ::testing::Test { + public: + BinaryHeaderGet() { memset(code, 0, sizeof(code)); } + + virtual void SetUp() { + code[0] = SpvMagicNumber; + code[1] = SpvVersion; + code[2] = SPV_GENERATOR_CODEPLAY; + code[3] = 1; // NOTE: Bound + code[4] = 0; // NOTE: Schema; reserved + code[5] = 0; // NOTE: Instructions + + binary.code = code; + binary.wordCount = 6; + } + spv_const_binary_t get_const_binary() { + return spv_const_binary_t{binary.code, binary.wordCount}; + } + virtual void TearDown() {} + + uint32_t code[6]; + spv_binary_t binary; +}; + +TEST_F(BinaryHeaderGet, Default) { + spv_endianness_t endian; + spv_const_binary_t const_bin = get_const_binary(); + ASSERT_EQ(SPV_SUCCESS, spvBinaryEndianness(&const_bin, &endian)); + + spv_header_t header; + ASSERT_EQ(SPV_SUCCESS, spvBinaryHeaderGet(&const_bin, endian, &header)); + + ASSERT_EQ(static_cast(SpvMagicNumber), header.magic); + ASSERT_EQ(0x00010300u, header.version); + ASSERT_EQ(static_cast(SPV_GENERATOR_CODEPLAY), header.generator); + ASSERT_EQ(1u, header.bound); + ASSERT_EQ(0u, header.schema); + ASSERT_EQ(&code[5], header.instructions); +} + +TEST_F(BinaryHeaderGet, InvalidCode) { + spv_const_binary_t my_binary = {nullptr, 0}; + spv_header_t header; + ASSERT_EQ(SPV_ERROR_INVALID_BINARY, + spvBinaryHeaderGet(&my_binary, SPV_ENDIANNESS_LITTLE, &header)); +} + +TEST_F(BinaryHeaderGet, InvalidPointerHeader) { + spv_const_binary_t const_bin = get_const_binary(); + ASSERT_EQ(SPV_ERROR_INVALID_POINTER, + spvBinaryHeaderGet(&const_bin, SPV_ENDIANNESS_LITTLE, nullptr)); +} + +TEST_F(BinaryHeaderGet, TruncatedHeader) { + for (uint8_t i = 1; i < SPV_INDEX_INSTRUCTION; i++) { + binary.wordCount = i; + spv_const_binary_t const_bin = get_const_binary(); + ASSERT_EQ(SPV_ERROR_INVALID_BINARY, + spvBinaryHeaderGet(&const_bin, SPV_ENDIANNESS_LITTLE, nullptr)); + } +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/binary_parse_test.cpp b/3rdparty/spirv-tools/test/binary_parse_test.cpp new file mode 100644 index 000000000..bb1d6ffd1 --- /dev/null +++ b/3rdparty/spirv-tools/test/binary_parse_test.cpp @@ -0,0 +1,898 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "gmock/gmock.h" +#include "latest_version_opencl_std_header.h" +#include "source/message.h" +#include "source/table.h" +#include "test_fixture.h" +#include "unit_spirv.h" + +// Returns true if two spv_parsed_operand_t values are equal. +// To use this operator, this definition must appear in the same namespace +// as spv_parsed_operand_t. +static bool operator==(const spv_parsed_operand_t& a, + const spv_parsed_operand_t& b) { + return a.offset == b.offset && a.num_words == b.num_words && + a.type == b.type && a.number_kind == b.number_kind && + a.number_bit_width == b.number_bit_width; +} + +namespace { + +using ::libspirv::SetContextMessageConsumer; +using ::spvtest::Concatenate; +using ::spvtest::MakeInstruction; +using ::spvtest::MakeVector; +using ::spvtest::ScopedContext; +using ::testing::_; +using ::testing::AnyOf; +using ::testing::Eq; +using ::testing::InSequence; +using ::testing::Return; + +// An easily-constructible and comparable object for the contents of an +// spv_parsed_instruction_t. Unlike spv_parsed_instruction_t, owns the memory +// of its components. +struct ParsedInstruction { + explicit ParsedInstruction(const spv_parsed_instruction_t& inst) + : words(inst.words, inst.words + inst.num_words), + opcode(static_cast(inst.opcode)), + ext_inst_type(inst.ext_inst_type), + type_id(inst.type_id), + result_id(inst.result_id), + operands(inst.operands, inst.operands + inst.num_operands) {} + + std::vector words; + SpvOp opcode; + spv_ext_inst_type_t ext_inst_type; + uint32_t type_id; + uint32_t result_id; + std::vector operands; + + bool operator==(const ParsedInstruction& b) const { + return words == b.words && opcode == b.opcode && + ext_inst_type == b.ext_inst_type && type_id == b.type_id && + result_id == b.result_id && operands == b.operands; + } +}; + +// Prints a ParsedInstruction object to the given output stream, and returns +// the stream. +std::ostream& operator<<(std::ostream& os, const ParsedInstruction& inst) { + os << "\nParsedInstruction( {"; + spvtest::PrintTo(spvtest::WordVector(inst.words), &os); + os << "}, opcode: " << int(inst.opcode) + << " ext_inst_type: " << int(inst.ext_inst_type) + << " type_id: " << inst.type_id << " result_id: " << inst.result_id; + for (const auto& operand : inst.operands) { + os << " { offset: " << operand.offset << " num_words: " << operand.num_words + << " type: " << int(operand.type) + << " number_kind: " << int(operand.number_kind) + << " number_bit_width: " << int(operand.number_bit_width) << "}"; + } + os << ")"; + return os; +} + +// Sanity check for the equality operator on ParsedInstruction. +TEST(ParsedInstruction, ZeroInitializedAreEqual) { + spv_parsed_instruction_t pi = {}; + ParsedInstruction a(pi); + ParsedInstruction b(pi); + EXPECT_THAT(a, ::testing::TypedEq(b)); +} + +// Googlemock class receiving Header/Instruction calls from spvBinaryParse(). +class MockParseClient { + public: + MOCK_METHOD6(Header, spv_result_t(spv_endianness_t endian, uint32_t magic, + uint32_t version, uint32_t generator, + uint32_t id_bound, uint32_t reserved)); + MOCK_METHOD1(Instruction, spv_result_t(const ParsedInstruction&)); +}; + +// Casts user_data as MockParseClient and invokes its Header(). +spv_result_t invoke_header(void* user_data, spv_endianness_t endian, + uint32_t magic, uint32_t version, uint32_t generator, + uint32_t id_bound, uint32_t reserved) { + return static_cast(user_data)->Header( + endian, magic, version, generator, id_bound, reserved); +} + +// Casts user_data as MockParseClient and invokes its Instruction(). +spv_result_t invoke_instruction( + void* user_data, const spv_parsed_instruction_t* parsed_instruction) { + return static_cast(user_data)->Instruction( + ParsedInstruction(*parsed_instruction)); +} + +// The SPIR-V module header words for the Khronos Assembler generator, +// for a module with an ID bound of 1. +const uint32_t kHeaderForBound1[] = { + SpvMagicNumber, SpvVersion, + SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_ASSEMBLER, 0), 1 /*bound*/, + 0 /*schema*/}; + +// Returns the expected SPIR-V module header words for the Khronos +// Assembler generator, and with a given Id bound. +std::vector ExpectedHeaderForBound(uint32_t bound) { + return {SpvMagicNumber, 0x10000, + SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_ASSEMBLER, 0), bound, 0}; +} + +// Returns a parsed operand for a non-number value at the given word offset +// within an instruction. +spv_parsed_operand_t MakeSimpleOperand(uint16_t offset, + spv_operand_type_t type) { + return {offset, 1, type, SPV_NUMBER_NONE, 0}; +} + +// Returns a parsed operand for a literal unsigned integer value at the given +// word offset within an instruction. +spv_parsed_operand_t MakeLiteralNumberOperand(uint16_t offset) { + return {offset, 1, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_NUMBER_UNSIGNED_INT, + 32}; +} + +// Returns a parsed operand for a literal string value at the given +// word offset within an instruction. +spv_parsed_operand_t MakeLiteralStringOperand(uint16_t offset, + uint16_t length) { + return {offset, length, SPV_OPERAND_TYPE_LITERAL_STRING, SPV_NUMBER_NONE, 0}; +} + +// Returns a ParsedInstruction for an OpTypeVoid instruction that would +// generate the given result Id. +ParsedInstruction MakeParsedVoidTypeInstruction(uint32_t result_id) { + const auto void_inst = MakeInstruction(SpvOpTypeVoid, {result_id}); + const auto void_operands = std::vector{ + MakeSimpleOperand(1, SPV_OPERAND_TYPE_RESULT_ID)}; + const spv_parsed_instruction_t parsed_void_inst = { + void_inst.data(), + static_cast(void_inst.size()), + SpvOpTypeVoid, + SPV_EXT_INST_TYPE_NONE, + 0, // type id + result_id, + void_operands.data(), + static_cast(void_operands.size())}; + return ParsedInstruction(parsed_void_inst); +} + +// Returns a ParsedInstruction for an OpTypeInt instruction that generates +// the given result Id for a 32-bit signed integer scalar type. +ParsedInstruction MakeParsedInt32TypeInstruction(uint32_t result_id) { + const auto i32_inst = MakeInstruction(SpvOpTypeInt, {result_id, 32, 1}); + const auto i32_operands = std::vector{ + MakeSimpleOperand(1, SPV_OPERAND_TYPE_RESULT_ID), + MakeLiteralNumberOperand(2), MakeLiteralNumberOperand(3)}; + spv_parsed_instruction_t parsed_i32_inst = { + i32_inst.data(), + static_cast(i32_inst.size()), + SpvOpTypeInt, + SPV_EXT_INST_TYPE_NONE, + 0, // type id + result_id, + i32_operands.data(), + static_cast(i32_operands.size())}; + return ParsedInstruction(parsed_i32_inst); +} + +class BinaryParseTest : public spvtest::TextToBinaryTestBase<::testing::Test> { + protected: + void Parse(const SpirvVector& words, spv_result_t expected_result, + bool flip_words = false) { + SpirvVector flipped_words(words); + SCOPED_TRACE(flip_words ? "Flipped Endianness" : "Normal Endianness"); + if (flip_words) { + std::transform(flipped_words.begin(), flipped_words.end(), + flipped_words.begin(), [](const uint32_t raw_word) { + return spvFixWord(raw_word, + I32_ENDIAN_HOST == I32_ENDIAN_BIG + ? SPV_ENDIANNESS_LITTLE + : SPV_ENDIANNESS_BIG); + }); + } + EXPECT_EQ(expected_result, + spvBinaryParse(ScopedContext().context, &client_, + flipped_words.data(), flipped_words.size(), + invoke_header, invoke_instruction, &diagnostic_)); + } + + spv_diagnostic diagnostic_ = nullptr; + MockParseClient client_; +}; + +// Adds an EXPECT_CALL to client_->Header() with appropriate parameters, +// including bound. Returns the EXPECT_CALL result. +#define EXPECT_HEADER(bound) \ + EXPECT_CALL( \ + client_, \ + Header(AnyOf(SPV_ENDIANNESS_LITTLE, SPV_ENDIANNESS_BIG), SpvMagicNumber, \ + 0x10000, SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_ASSEMBLER, 0), \ + bound, 0 /*reserved*/)) + +static const bool kSwapEndians[] = {false, true}; + +TEST_F(BinaryParseTest, EmptyModuleHasValidHeaderAndNoInstructionCallbacks) { + for (bool endian_swap : kSwapEndians) { + const auto words = CompileSuccessfully(""); + EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS)); + EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback. + Parse(words, SPV_SUCCESS, endian_swap); + EXPECT_EQ(nullptr, diagnostic_); + } +} + +TEST_F(BinaryParseTest, NullDiagnosticsIsOkForGoodParse) { + const auto words = CompileSuccessfully(""); + EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS)); + EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback. + EXPECT_EQ( + SPV_SUCCESS, + spvBinaryParse(ScopedContext().context, &client_, words.data(), + words.size(), invoke_header, invoke_instruction, nullptr)); +} + +TEST_F(BinaryParseTest, NullDiagnosticsIsOkForBadParse) { + auto words = CompileSuccessfully(""); + words.push_back(0xffffffff); // Certainly invalid instruction header. + EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS)); + EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback. + EXPECT_EQ( + SPV_ERROR_INVALID_BINARY, + spvBinaryParse(ScopedContext().context, &client_, words.data(), + words.size(), invoke_header, invoke_instruction, nullptr)); +} + +// Make sure that we don't blow up when both the consumer and the diagnostic are +// null. +TEST_F(BinaryParseTest, NullConsumerNullDiagnosticsForBadParse) { + auto words = CompileSuccessfully(""); + + auto ctx = spvContextCreate(SPV_ENV_UNIVERSAL_1_1); + SetContextMessageConsumer(ctx, nullptr); + + words.push_back(0xffffffff); // Certainly invalid instruction header. + EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS)); + EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback. + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, + spvBinaryParse(ctx, &client_, words.data(), words.size(), + invoke_header, invoke_instruction, nullptr)); + + spvContextDestroy(ctx); +} + +TEST_F(BinaryParseTest, SpecifyConsumerNullDiagnosticsForGoodParse) { + const auto words = CompileSuccessfully(""); + + auto ctx = spvContextCreate(SPV_ENV_UNIVERSAL_1_1); + int invocation = 0; + SetContextMessageConsumer( + ctx, [&invocation](spv_message_level_t, const char*, + const spv_position_t&, const char*) { ++invocation; }); + + EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS)); + EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback. + EXPECT_EQ(SPV_SUCCESS, + spvBinaryParse(ctx, &client_, words.data(), words.size(), + invoke_header, invoke_instruction, nullptr)); + EXPECT_EQ(0, invocation); + + spvContextDestroy(ctx); +} + +TEST_F(BinaryParseTest, SpecifyConsumerNullDiagnosticsForBadParse) { + auto words = CompileSuccessfully(""); + + auto ctx = spvContextCreate(SPV_ENV_UNIVERSAL_1_1); + int invocation = 0; + SetContextMessageConsumer( + ctx, [&invocation](spv_message_level_t level, const char* source, + const spv_position_t& position, const char* message) { + ++invocation; + EXPECT_EQ(SPV_MSG_ERROR, level); + EXPECT_STREQ("input", source); + EXPECT_EQ(0u, position.line); + EXPECT_EQ(0u, position.column); + EXPECT_EQ(5u, position.index); + EXPECT_STREQ("Invalid opcode: 65535", message); + }); + + words.push_back(0xffffffff); // Certainly invalid instruction header. + EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS)); + EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback. + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, + spvBinaryParse(ctx, &client_, words.data(), words.size(), + invoke_header, invoke_instruction, nullptr)); + EXPECT_EQ(1, invocation); + + spvContextDestroy(ctx); +} + +TEST_F(BinaryParseTest, SpecifyConsumerSpecifyDiagnosticsForGoodParse) { + const auto words = CompileSuccessfully(""); + + auto ctx = spvContextCreate(SPV_ENV_UNIVERSAL_1_1); + int invocation = 0; + SetContextMessageConsumer( + ctx, [&invocation](spv_message_level_t, const char*, + const spv_position_t&, const char*) { ++invocation; }); + + EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS)); + EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback. + EXPECT_EQ(SPV_SUCCESS, + spvBinaryParse(ctx, &client_, words.data(), words.size(), + invoke_header, invoke_instruction, &diagnostic_)); + EXPECT_EQ(0, invocation); + EXPECT_EQ(nullptr, diagnostic_); + + spvContextDestroy(ctx); +} + +TEST_F(BinaryParseTest, SpecifyConsumerSpecifyDiagnosticsForBadParse) { + auto words = CompileSuccessfully(""); + + auto ctx = spvContextCreate(SPV_ENV_UNIVERSAL_1_1); + int invocation = 0; + SetContextMessageConsumer( + ctx, [&invocation](spv_message_level_t, const char*, + const spv_position_t&, const char*) { ++invocation; }); + + words.push_back(0xffffffff); // Certainly invalid instruction header. + EXPECT_HEADER(1).WillOnce(Return(SPV_SUCCESS)); + EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback. + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, + spvBinaryParse(ctx, &client_, words.data(), words.size(), + invoke_header, invoke_instruction, &diagnostic_)); + EXPECT_EQ(0, invocation); + EXPECT_STREQ("Invalid opcode: 65535", diagnostic_->error); + + spvContextDestroy(ctx); +} + +TEST_F(BinaryParseTest, + ModuleWithSingleInstructionHasValidHeaderAndInstructionCallback) { + for (bool endian_swap : kSwapEndians) { + const auto words = CompileSuccessfully("%1 = OpTypeVoid"); + InSequence calls_expected_in_specific_order; + EXPECT_HEADER(2).WillOnce(Return(SPV_SUCCESS)); + EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1))) + .WillOnce(Return(SPV_SUCCESS)); + Parse(words, SPV_SUCCESS, endian_swap); + EXPECT_EQ(nullptr, diagnostic_); + } +} + +TEST_F(BinaryParseTest, NullHeaderCallbackIsIgnored) { + const auto words = CompileSuccessfully("%1 = OpTypeVoid"); + EXPECT_CALL(client_, Header(_, _, _, _, _, _)) + .Times(0); // No header callback. + EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1))) + .WillOnce(Return(SPV_SUCCESS)); + EXPECT_EQ(SPV_SUCCESS, spvBinaryParse(ScopedContext().context, &client_, + words.data(), words.size(), nullptr, + invoke_instruction, &diagnostic_)); + EXPECT_EQ(nullptr, diagnostic_); +} + +TEST_F(BinaryParseTest, NullInstructionCallbackIsIgnored) { + const auto words = CompileSuccessfully("%1 = OpTypeVoid"); + EXPECT_HEADER((2)).WillOnce(Return(SPV_SUCCESS)); + EXPECT_CALL(client_, Instruction(_)).Times(0); // No instruction callback. + EXPECT_EQ(SPV_SUCCESS, + spvBinaryParse(ScopedContext().context, &client_, words.data(), + words.size(), invoke_header, nullptr, &diagnostic_)); + EXPECT_EQ(nullptr, diagnostic_); +} + +// Check the result of multiple instruction callbacks. +// +// This test exercises non-default values for the following members of the +// spv_parsed_instruction_t struct: words, num_words, opcode, result_id, +// operands, num_operands. +TEST_F(BinaryParseTest, TwoScalarTypesGenerateTwoInstructionCallbacks) { + for (bool endian_swap : kSwapEndians) { + const auto words = CompileSuccessfully( + "%1 = OpTypeVoid " + "%2 = OpTypeInt 32 1"); + InSequence calls_expected_in_specific_order; + EXPECT_HEADER(3).WillOnce(Return(SPV_SUCCESS)); + EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1))) + .WillOnce(Return(SPV_SUCCESS)); + EXPECT_CALL(client_, Instruction(MakeParsedInt32TypeInstruction(2))) + .WillOnce(Return(SPV_SUCCESS)); + Parse(words, SPV_SUCCESS, endian_swap); + EXPECT_EQ(nullptr, diagnostic_); + } +} + +TEST_F(BinaryParseTest, EarlyReturnWithZeroPassingCallbacks) { + for (bool endian_swap : kSwapEndians) { + const auto words = CompileSuccessfully( + "%1 = OpTypeVoid " + "%2 = OpTypeInt 32 1"); + InSequence calls_expected_in_specific_order; + EXPECT_HEADER(3).WillOnce(Return(SPV_ERROR_INVALID_BINARY)); + // Early exit means no calls to Instruction(). + EXPECT_CALL(client_, Instruction(_)).Times(0); + Parse(words, SPV_ERROR_INVALID_BINARY, endian_swap); + // On error, the binary parser doesn't generate its own diagnostics. + EXPECT_EQ(nullptr, diagnostic_); + } +} + +TEST_F(BinaryParseTest, + EarlyReturnWithZeroPassingCallbacksAndSpecifiedResultCode) { + for (bool endian_swap : kSwapEndians) { + const auto words = CompileSuccessfully( + "%1 = OpTypeVoid " + "%2 = OpTypeInt 32 1"); + InSequence calls_expected_in_specific_order; + EXPECT_HEADER(3).WillOnce(Return(SPV_REQUESTED_TERMINATION)); + // Early exit means no calls to Instruction(). + EXPECT_CALL(client_, Instruction(_)).Times(0); + Parse(words, SPV_REQUESTED_TERMINATION, endian_swap); + // On early termination, the binary parser doesn't generate its own + // diagnostics. + EXPECT_EQ(nullptr, diagnostic_); + } +} + +TEST_F(BinaryParseTest, EarlyReturnWithOnePassingCallback) { + for (bool endian_swap : kSwapEndians) { + const auto words = CompileSuccessfully( + "%1 = OpTypeVoid " + "%2 = OpTypeInt 32 1 " + "%3 = OpTypeFloat 32"); + InSequence calls_expected_in_specific_order; + EXPECT_HEADER(4).WillOnce(Return(SPV_SUCCESS)); + EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1))) + .WillOnce(Return(SPV_REQUESTED_TERMINATION)); + Parse(words, SPV_REQUESTED_TERMINATION, endian_swap); + // On early termination, the binary parser doesn't generate its own + // diagnostics. + EXPECT_EQ(nullptr, diagnostic_); + } +} + +TEST_F(BinaryParseTest, EarlyReturnWithTwoPassingCallbacks) { + for (bool endian_swap : kSwapEndians) { + const auto words = CompileSuccessfully( + "%1 = OpTypeVoid " + "%2 = OpTypeInt 32 1 " + "%3 = OpTypeFloat 32"); + InSequence calls_expected_in_specific_order; + EXPECT_HEADER(4).WillOnce(Return(SPV_SUCCESS)); + EXPECT_CALL(client_, Instruction(MakeParsedVoidTypeInstruction(1))) + .WillOnce(Return(SPV_SUCCESS)); + EXPECT_CALL(client_, Instruction(MakeParsedInt32TypeInstruction(2))) + .WillOnce(Return(SPV_REQUESTED_TERMINATION)); + Parse(words, SPV_REQUESTED_TERMINATION, endian_swap); + // On early termination, the binary parser doesn't generate its own + // diagnostics. + EXPECT_EQ(nullptr, diagnostic_); + } +} + +TEST_F(BinaryParseTest, InstructionWithStringOperand) { + const std::string str = + "the future is already here, it's just not evenly distributed"; + const auto str_words = MakeVector(str); + const auto instruction = MakeInstruction(SpvOpName, {99}, str_words); + const auto words = Concatenate({ExpectedHeaderForBound(100), instruction}); + InSequence calls_expected_in_specific_order; + EXPECT_HEADER(100).WillOnce(Return(SPV_SUCCESS)); + const auto operands = std::vector{ + MakeSimpleOperand(1, SPV_OPERAND_TYPE_ID), + MakeLiteralStringOperand(2, static_cast(str_words.size()))}; + EXPECT_CALL(client_, + Instruction(ParsedInstruction(spv_parsed_instruction_t{ + instruction.data(), static_cast(instruction.size()), + SpvOpName, SPV_EXT_INST_TYPE_NONE, 0 /*type id*/, + 0 /* No result id for OpName*/, operands.data(), + static_cast(operands.size())}))) + .WillOnce(Return(SPV_SUCCESS)); + // Since we are actually checking the output, don't test the + // endian-swapped version. + Parse(words, SPV_SUCCESS, false); + EXPECT_EQ(nullptr, diagnostic_); +} + +// Checks for non-zero values for the result_id and ext_inst_type members +// spv_parsed_instruction_t. +TEST_F(BinaryParseTest, ExtendedInstruction) { + const auto words = CompileSuccessfully( + "%extcl = OpExtInstImport \"OpenCL.std\" " + "%result = OpExtInst %float %extcl sqrt %x"); + EXPECT_HEADER(5).WillOnce(Return(SPV_SUCCESS)); + EXPECT_CALL(client_, Instruction(_)).WillOnce(Return(SPV_SUCCESS)); + // We're only interested in the second call to Instruction(): + const auto operands = std::vector{ + MakeSimpleOperand(1, SPV_OPERAND_TYPE_TYPE_ID), + MakeSimpleOperand(2, SPV_OPERAND_TYPE_RESULT_ID), + MakeSimpleOperand(3, + SPV_OPERAND_TYPE_ID), // Extended instruction set Id + MakeSimpleOperand(4, SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER), + MakeSimpleOperand(5, SPV_OPERAND_TYPE_ID), // Id of the argument + }; + const auto instruction = MakeInstruction( + SpvOpExtInst, + {2, 3, 1, static_cast(OpenCLLIB::Entrypoints::Sqrt), 4}); + EXPECT_CALL(client_, + Instruction(ParsedInstruction(spv_parsed_instruction_t{ + instruction.data(), static_cast(instruction.size()), + SpvOpExtInst, SPV_EXT_INST_TYPE_OPENCL_STD, 2 /*type id*/, + 3 /*result id*/, operands.data(), + static_cast(operands.size())}))) + .WillOnce(Return(SPV_SUCCESS)); + // Since we are actually checking the output, don't test the + // endian-swapped version. + Parse(words, SPV_SUCCESS, false); + EXPECT_EQ(nullptr, diagnostic_); +} + +// A binary parser diagnostic test case where we provide the words array +// pointer and word count explicitly. +struct WordsAndCountDiagnosticCase { + const uint32_t* words; + size_t num_words; + std::string expected_diagnostic; +}; + +using BinaryParseWordsAndCountDiagnosticTest = spvtest::TextToBinaryTestBase< + ::testing::TestWithParam>; + +TEST_P(BinaryParseWordsAndCountDiagnosticTest, WordAndCountCases) { + EXPECT_EQ( + SPV_ERROR_INVALID_BINARY, + spvBinaryParse(ScopedContext().context, nullptr, GetParam().words, + GetParam().num_words, nullptr, nullptr, &diagnostic)); + ASSERT_NE(nullptr, diagnostic); + EXPECT_THAT(diagnostic->error, Eq(GetParam().expected_diagnostic)); +} + +INSTANTIATE_TEST_CASE_P( + BinaryParseDiagnostic, BinaryParseWordsAndCountDiagnosticTest, + ::testing::ValuesIn(std::vector{ + {nullptr, 0, "Missing module."}, + {kHeaderForBound1, 0, + "Module has incomplete header: only 0 words instead of 5"}, + {kHeaderForBound1, 1, + "Module has incomplete header: only 1 words instead of 5"}, + {kHeaderForBound1, 2, + "Module has incomplete header: only 2 words instead of 5"}, + {kHeaderForBound1, 3, + "Module has incomplete header: only 3 words instead of 5"}, + {kHeaderForBound1, 4, + "Module has incomplete header: only 4 words instead of 5"}, + }), ); + +// A binary parser diagnostic test case where a vector of words is +// provided. We'll use this to express cases that can't be created +// via the assembler. Either we want to make a malformed instruction, +// or an invalid case the assembler would reject. +struct WordVectorDiagnosticCase { + std::vector words; + std::string expected_diagnostic; +}; + +using BinaryParseWordVectorDiagnosticTest = spvtest::TextToBinaryTestBase< + ::testing::TestWithParam>; + +TEST_P(BinaryParseWordVectorDiagnosticTest, WordVectorCases) { + const auto& words = GetParam().words; + EXPECT_THAT(spvBinaryParse(ScopedContext().context, nullptr, words.data(), + words.size(), nullptr, nullptr, &diagnostic), + AnyOf(SPV_ERROR_INVALID_BINARY, SPV_ERROR_INVALID_ID)); + ASSERT_NE(nullptr, diagnostic); + EXPECT_THAT(diagnostic->error, Eq(GetParam().expected_diagnostic)); +} + +INSTANTIATE_TEST_CASE_P( + BinaryParseDiagnostic, BinaryParseWordVectorDiagnosticTest, + ::testing::ValuesIn(std::vector{ + {Concatenate({ExpectedHeaderForBound(1), {spvOpcodeMake(0, SpvOpNop)}}), + "Invalid instruction word count: 0"}, + {Concatenate( + {ExpectedHeaderForBound(1), + {spvOpcodeMake(1, static_cast( + std::numeric_limits::max()))}}), + "Invalid opcode: 65535"}, + {Concatenate({ExpectedHeaderForBound(1), + MakeInstruction(SpvOpNop, {42})}), + "Invalid instruction OpNop starting at word 5: expected " + "no more operands after 1 words, but stated word count is 2."}, + // Supply several more unexpectd words. + {Concatenate({ExpectedHeaderForBound(1), + MakeInstruction(SpvOpNop, {42, 43, 44, 45, 46, 47})}), + "Invalid instruction OpNop starting at word 5: expected " + "no more operands after 1 words, but stated word count is 7."}, + {Concatenate({ExpectedHeaderForBound(1), + MakeInstruction(SpvOpTypeVoid, {1, 2})}), + "Invalid instruction OpTypeVoid starting at word 5: expected " + "no more operands after 2 words, but stated word count is 3."}, + {Concatenate({ExpectedHeaderForBound(1), + MakeInstruction(SpvOpTypeVoid, {1, 2, 5, 9, 10})}), + "Invalid instruction OpTypeVoid starting at word 5: expected " + "no more operands after 2 words, but stated word count is 6."}, + {Concatenate({ExpectedHeaderForBound(1), + MakeInstruction(SpvOpTypeInt, {1, 32, 1, 9})}), + "Invalid instruction OpTypeInt starting at word 5: expected " + "no more operands after 4 words, but stated word count is 5."}, + {Concatenate({ExpectedHeaderForBound(1), + MakeInstruction(SpvOpTypeInt, {1})}), + "End of input reached while decoding OpTypeInt starting at word 5:" + " expected more operands after 2 words."}, + + // Check several cases for running off the end of input. + + // Detect a missing single word operand. + {Concatenate({ExpectedHeaderForBound(1), + {spvOpcodeMake(2, SpvOpTypeStruct)}}), + "End of input reached while decoding OpTypeStruct starting at word" + " 5: missing result ID operand at word offset 1."}, + // Detect this a missing a multi-word operand to OpConstant. + // We also lie and say the OpConstant instruction has 5 words when + // it only has 3. Corresponds to something like this: + // %1 = OpTypeInt 64 0 + // %2 = OpConstant %1 + {Concatenate({ExpectedHeaderForBound(3), + {MakeInstruction(SpvOpTypeInt, {1, 64, 0})}, + {spvOpcodeMake(5, SpvOpConstant), 1, 2}}), + "End of input reached while decoding OpConstant starting at word" + " 9: missing possibly multi-word literal number operand at word " + "offset 3."}, + // Detect when we provide only one word from the 64-bit literal, + // and again lie about the number of words in the instruction. + {Concatenate({ExpectedHeaderForBound(3), + {MakeInstruction(SpvOpTypeInt, {1, 64, 0})}, + {spvOpcodeMake(5, SpvOpConstant), 1, 2, 42}}), + "End of input reached while decoding OpConstant starting at word" + " 9: truncated possibly multi-word literal number operand at word " + "offset 3."}, + // Detect when a required string operand is missing. + // Also, lie about the length of the instruction. + {Concatenate({ExpectedHeaderForBound(3), + {spvOpcodeMake(3, SpvOpString), 1}}), + "End of input reached while decoding OpString starting at word" + " 5: missing literal string operand at word offset 2."}, + // Detect when a required string operand is truncated: it's missing + // a null terminator. Catching the error avoids a buffer overrun. + {Concatenate({ExpectedHeaderForBound(3), + {spvOpcodeMake(4, SpvOpString), 1, 0x41414141, + 0x41414141}}), + "End of input reached while decoding OpString starting at word" + " 5: truncated literal string operand at word offset 2."}, + // Detect when an optional string operand is truncated: it's missing + // a null terminator. Catching the error avoids a buffer overrun. + // (It is valid for an optional string operand to be absent.) + {Concatenate({ExpectedHeaderForBound(3), + {spvOpcodeMake(6, SpvOpSource), + static_cast(SpvSourceLanguageOpenCL_C), 210, + 1 /* file id */, + /*start of string*/ 0x41414141, 0x41414141}}), + "End of input reached while decoding OpSource starting at word" + " 5: truncated literal string operand at word offset 4."}, + + // (End of input exhaustion test cases.) + + // In this case the instruction word count is too small, where + // it would truncate a multi-word operand to OpConstant. + {Concatenate({ExpectedHeaderForBound(3), + {MakeInstruction(SpvOpTypeInt, {1, 64, 0})}, + {spvOpcodeMake(4, SpvOpConstant), 1, 2, 44, 44}}), + "Invalid word count: OpConstant starting at word 9 says it has 4" + " words, but found 5 words instead."}, + // Word count is to small, where it would truncate a literal string. + {Concatenate({ExpectedHeaderForBound(2), + {spvOpcodeMake(3, SpvOpString), 1, 0x41414141, 0}}), + "Invalid word count: OpString starting at word 5 says it has 3" + " words, but found 4 words instead."}, + // Word count is too large. The string terminates before the last + // word. + {Concatenate({ExpectedHeaderForBound(2), + {spvOpcodeMake(4, SpvOpString), 1 /* result id */}, + MakeVector("abc"), + {0 /* this word does not belong*/}}), + "Invalid instruction OpString starting at word 5: expected no more" + " operands after 3 words, but stated word count is 4."}, + // Word count is too large. There are too many words after the string + // literal. A linkage attribute decoration is the only case in SPIR-V + // where a string operand is followed by another operand. + {Concatenate({ExpectedHeaderForBound(2), + {spvOpcodeMake(6, SpvOpDecorate), 1 /* target id */, + static_cast(SpvDecorationLinkageAttributes)}, + MakeVector("abc"), + {static_cast(SpvLinkageTypeImport), + 0 /* does not belong */}}), + "Invalid instruction OpDecorate starting at word 5: expected no more" + " operands after 5 words, but stated word count is 6."}, + // Like the previous case, but with 5 extra words. + {Concatenate({ExpectedHeaderForBound(2), + {spvOpcodeMake(10, SpvOpDecorate), 1 /* target id */, + static_cast(SpvDecorationLinkageAttributes)}, + MakeVector("abc"), + {static_cast(SpvLinkageTypeImport), + /* don't belong */ 0, 1, 2, 3, 4}}), + "Invalid instruction OpDecorate starting at word 5: expected no more" + " operands after 5 words, but stated word count is 10."}, + // Like the previous two cases, but with OpMemberDecorate. + {Concatenate({ExpectedHeaderForBound(2), + {spvOpcodeMake(7, SpvOpMemberDecorate), 1 /* target id */, + 42 /* member index */, + static_cast(SpvDecorationLinkageAttributes)}, + MakeVector("abc"), + {static_cast(SpvLinkageTypeImport), + 0 /* does not belong */}}), + "Invalid instruction OpMemberDecorate starting at word 5: expected no" + " more operands after 6 words, but stated word count is 7."}, + {Concatenate({ExpectedHeaderForBound(2), + {spvOpcodeMake(11, SpvOpMemberDecorate), + 1 /* target id */, 42 /* member index */, + static_cast(SpvDecorationLinkageAttributes)}, + MakeVector("abc"), + {static_cast(SpvLinkageTypeImport), + /* don't belong */ 0, 1, 2, 3, 4}}), + "Invalid instruction OpMemberDecorate starting at word 5: expected no" + " more operands after 6 words, but stated word count is 11."}, + // Word count is too large. There should be no more words + // after the RelaxedPrecision decoration. + {Concatenate({ExpectedHeaderForBound(2), + {spvOpcodeMake(4, SpvOpDecorate), 1 /* target id */, + static_cast(SpvDecorationRelaxedPrecision), + 0 /* does not belong */}}), + "Invalid instruction OpDecorate starting at word 5: expected no" + " more operands after 3 words, but stated word count is 4."}, + // Word count is too large. There should be only one word after + // the SpecId decoration enum word. + {Concatenate({ExpectedHeaderForBound(2), + {spvOpcodeMake(5, SpvOpDecorate), 1 /* target id */, + static_cast(SpvDecorationSpecId), + 42 /* the spec id */, 0 /* does not belong */}}), + "Invalid instruction OpDecorate starting at word 5: expected no" + " more operands after 4 words, but stated word count is 5."}, + {Concatenate({ExpectedHeaderForBound(2), + {spvOpcodeMake(2, SpvOpTypeVoid), 0}}), + "Error: Result Id is 0"}, + {Concatenate({ + ExpectedHeaderForBound(2), + {spvOpcodeMake(2, SpvOpTypeVoid), 1}, + {spvOpcodeMake(2, SpvOpTypeBool), 1}, + }), + "Id 1 is defined more than once"}, + {Concatenate({ExpectedHeaderForBound(3), + MakeInstruction(SpvOpExtInst, {2, 3, 100, 4, 5})}), + "OpExtInst set Id 100 does not reference an OpExtInstImport result " + "Id"}, + {Concatenate({ExpectedHeaderForBound(101), + MakeInstruction(SpvOpExtInstImport, {100}, + MakeVector("OpenCL.std")), + // OpenCL cos is #14 + MakeInstruction(SpvOpExtInst, {2, 3, 100, 14, 5, 999})}), + "Invalid instruction OpExtInst starting at word 10: expected no " + "more operands after 6 words, but stated word count is 7."}, + // In this case, the OpSwitch selector refers to an invalid ID. + {Concatenate({ExpectedHeaderForBound(3), + MakeInstruction(SpvOpSwitch, {1, 2, 42, 3})}), + "Invalid OpSwitch: selector id 1 has no type"}, + // In this case, the OpSwitch selector refers to an ID that has + // no type. + {Concatenate({ExpectedHeaderForBound(3), + MakeInstruction(SpvOpLabel, {1}), + MakeInstruction(SpvOpSwitch, {1, 2, 42, 3})}), + "Invalid OpSwitch: selector id 1 has no type"}, + {Concatenate({ExpectedHeaderForBound(3), + MakeInstruction(SpvOpTypeInt, {1, 32, 0}), + MakeInstruction(SpvOpSwitch, {1, 3, 42, 3})}), + "Invalid OpSwitch: selector id 1 is a type, not a value"}, + {Concatenate({ExpectedHeaderForBound(3), + MakeInstruction(SpvOpTypeFloat, {1, 32}), + MakeInstruction(SpvOpConstant, {1, 2, 0x78f00000}), + MakeInstruction(SpvOpSwitch, {2, 3, 42, 3})}), + "Invalid OpSwitch: selector id 2 is not a scalar integer"}, + {Concatenate({ExpectedHeaderForBound(3), + MakeInstruction(SpvOpExtInstImport, {1}, + MakeVector("invalid-import"))}), + "Invalid extended instruction import 'invalid-import'"}, + {Concatenate({ + ExpectedHeaderForBound(3), + MakeInstruction(SpvOpTypeInt, {1, 32, 0}), + MakeInstruction(SpvOpConstant, {2, 2, 42}), + }), + "Type Id 2 is not a type"}, + {Concatenate({ + ExpectedHeaderForBound(3), + MakeInstruction(SpvOpTypeBool, {1}), + MakeInstruction(SpvOpConstant, {1, 2, 42}), + }), + "Type Id 1 is not a scalar numeric type"}, + }), ); + +// A binary parser diagnostic case generated from an assembly text input. +struct AssemblyDiagnosticCase { + std::string assembly; + std::string expected_diagnostic; +}; + +using BinaryParseAssemblyDiagnosticTest = spvtest::TextToBinaryTestBase< + ::testing::TestWithParam>; + +TEST_P(BinaryParseAssemblyDiagnosticTest, AssemblyCases) { + auto words = CompileSuccessfully(GetParam().assembly); + EXPECT_THAT(spvBinaryParse(ScopedContext().context, nullptr, words.data(), + words.size(), nullptr, nullptr, &diagnostic), + AnyOf(SPV_ERROR_INVALID_BINARY, SPV_ERROR_INVALID_ID)); + ASSERT_NE(nullptr, diagnostic); + EXPECT_THAT(diagnostic->error, Eq(GetParam().expected_diagnostic)); +} + +INSTANTIATE_TEST_CASE_P( + BinaryParseDiagnostic, BinaryParseAssemblyDiagnosticTest, + ::testing::ValuesIn(std::vector{ + {"%1 = OpConstant !0 42", "Error: Type Id is 0"}, + // A required id is 0. + {"OpName !0 \"foo\"", "Id is 0"}, + // An optional id is 0, in this case the optional + // initializer. + {"%2 = OpVariable %1 CrossWorkgroup !0", "Id is 0"}, + {"OpControlBarrier !0 %1 %2", "scope ID is 0"}, + {"OpControlBarrier %1 !0 %2", "scope ID is 0"}, + {"OpControlBarrier %1 %2 !0", "memory semantics ID is 0"}, + {"%import = OpExtInstImport \"GLSL.std.450\" " + "%result = OpExtInst %type %import !999999 %x", + "Invalid extended instruction number: 999999"}, + {"%2 = OpSpecConstantOp %1 !1000 %2", + "Invalid OpSpecConstantOp opcode: 1000"}, + {"OpCapability !9999", "Invalid capability operand: 9999"}, + {"OpSource !9999 100", "Invalid source language operand: 9999"}, + {"OpEntryPoint !9999", "Invalid execution model operand: 9999"}, + {"OpMemoryModel !9999", "Invalid addressing model operand: 9999"}, + {"OpMemoryModel Logical !9999", "Invalid memory model operand: 9999"}, + {"OpExecutionMode %1 !9999", "Invalid execution mode operand: 9999"}, + {"OpTypeForwardPointer %1 !9999", + "Invalid storage class operand: 9999"}, + {"%2 = OpTypeImage %1 !9999", "Invalid dimensionality operand: 9999"}, + {"%2 = OpTypeImage %1 1D 0 0 0 0 !9999", + "Invalid image format operand: 9999"}, + {"OpDecorate %1 FPRoundingMode !9999", + "Invalid floating-point rounding mode operand: 9999"}, + {"OpDecorate %1 LinkageAttributes \"C\" !9999", + "Invalid linkage type operand: 9999"}, + {"%1 = OpTypePipe !9999", "Invalid access qualifier operand: 9999"}, + {"OpDecorate %1 FuncParamAttr !9999", + "Invalid function parameter attribute operand: 9999"}, + {"OpDecorate %1 !9999", "Invalid decoration operand: 9999"}, + {"OpDecorate %1 BuiltIn !9999", "Invalid built-in operand: 9999"}, + {"%2 = OpGroupIAdd %1 %3 !9999", + "Invalid group operation operand: 9999"}, + {"OpDecorate %1 FPFastMathMode !63", + "Invalid floating-point fast math mode operand: 63 has invalid mask " + "component 32"}, + {"%2 = OpFunction %2 !31", + "Invalid function control operand: 31 has invalid mask component 16"}, + {"OpLoopMerge %1 %2 !7", + "Invalid loop control operand: 7 has invalid mask component 4"}, + {"%2 = OpImageFetch %1 %image %coord !511", + "Invalid image operand: 511 has invalid mask component 256"}, + {"OpSelectionMerge %1 !7", + "Invalid selection control operand: 7 has invalid mask component 4"}, + }), ); + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/binary_strnlen_s_test.cpp b/3rdparty/spirv-tools/test/binary_strnlen_s_test.cpp new file mode 100644 index 000000000..2d2170b09 --- /dev/null +++ b/3rdparty/spirv-tools/test/binary_strnlen_s_test.cpp @@ -0,0 +1,30 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "unit_spirv.h" + +namespace { + +TEST(Strnlen, Samples) { + EXPECT_EQ(0u, spv_strnlen_s(nullptr, 0)); + EXPECT_EQ(0u, spv_strnlen_s(nullptr, 5)); + EXPECT_EQ(0u, spv_strnlen_s("abc", 0)); + EXPECT_EQ(1u, spv_strnlen_s("abc", 1)); + EXPECT_EQ(3u, spv_strnlen_s("abc", 3)); + EXPECT_EQ(3u, spv_strnlen_s("abc\0", 5)); + EXPECT_EQ(0u, spv_strnlen_s("\0", 5)); + EXPECT_EQ(1u, spv_strnlen_s("a\0c", 5)); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/binary_to_text.literal_test.cpp b/3rdparty/spirv-tools/test/binary_to_text.literal_test.cpp new file mode 100644 index 000000000..9201f4a06 --- /dev/null +++ b/3rdparty/spirv-tools/test/binary_to_text.literal_test.cpp @@ -0,0 +1,72 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "unit_spirv.h" + +#include "gmock/gmock.h" +#include "test_fixture.h" + +using ::testing::Eq; + +namespace { + +using RoundTripLiteralsTest = + spvtest::TextToBinaryTestBase<::testing::TestWithParam>; + +TEST_P(RoundTripLiteralsTest, Sample) { + EXPECT_THAT(EncodeAndDecodeSuccessfully(GetParam()), Eq(GetParam())); +} + +// clang-format off +INSTANTIATE_TEST_CASE_P( + StringLiterals, RoundTripLiteralsTest, + ::testing::ValuesIn(std::vector{ + "OpName %1 \"\"\n", // empty + "OpName %1 \"foo\"\n", // normal + "OpName %1 \"foo bar\"\n", // string with spaces + "OpName %1 \"foo\tbar\"\n", // string with tab + "OpName %1 \"\tfoo\"\n", // starts with tab + "OpName %1 \" foo\"\n", // starts with space + "OpName %1 \"foo \"\n", // ends with space + "OpName %1 \"foo\t\"\n", // ends with tab + "OpName %1 \"foo\nbar\"\n", // contains newline + "OpName %1 \"\nfoo\nbar\"\n", // starts with newline + "OpName %1 \"\n\n\nfoo\nbar\"\n", // multiple newlines + "OpName %1 \"\\\"foo\nbar\\\"\"\n", // escaped quote + "OpName %1 \"\\\\foo\nbar\\\\\"\n", // escaped backslash + "OpName %1 \"\xE4\xBA\xB2\"\n", // UTF-8 + }),); +// clang-format on + +using RoundTripSpecialCaseLiteralsTest = spvtest::TextToBinaryTestBase< + ::testing::TestWithParam>>; + +// Test case where the generated disassembly is not the same as the +// assembly passed in. +TEST_P(RoundTripSpecialCaseLiteralsTest, Sample) { + EXPECT_THAT(EncodeAndDecodeSuccessfully(std::get<0>(GetParam())), + Eq(std::get<1>(GetParam()))); +} + +// clang-format off +INSTANTIATE_TEST_CASE_P( + StringLiterals, RoundTripSpecialCaseLiteralsTest, + ::testing::ValuesIn(std::vector>{ + {"OpName %1 \"\\foo\"\n", "OpName %1 \"foo\"\n"}, // Escape f + {"OpName %1 \"\\\nfoo\"\n", "OpName %1 \"\nfoo\"\n"}, // Escape newline + {"OpName %1 \"\\\xE4\xBA\xB2\"\n", "OpName %1 \"\xE4\xBA\xB2\"\n"}, // Escape utf-8 + }),); +// clang-format on + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/binary_to_text_test.cpp b/3rdparty/spirv-tools/test/binary_to_text_test.cpp new file mode 100644 index 000000000..1ec13db76 --- /dev/null +++ b/3rdparty/spirv-tools/test/binary_to_text_test.cpp @@ -0,0 +1,553 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "unit_spirv.h" + +#include + +#include "gmock/gmock.h" + +#include "source/spirv_constant.h" +#include "test_fixture.h" + +namespace { + +using spvtest::AutoText; +using spvtest::ScopedContext; +using spvtest::TextToBinaryTest; +using std::get; +using std::tuple; +using ::testing::Combine; +using ::testing::Eq; +using ::testing::HasSubstr; + +class BinaryToText : public ::testing::Test { + public: + BinaryToText() : context(spvContextCreate(SPV_ENV_UNIVERSAL_1_0)) {} + ~BinaryToText() { spvContextDestroy(context); } + + virtual void SetUp() { + const char* textStr = R"( + OpSource OpenCL_C 12 + OpMemoryModel Physical64 OpenCL + OpSourceExtension "PlaceholderExtensionName" + OpEntryPoint Kernel %1 "foo" + OpExecutionMode %1 LocalSizeHint 1 1 1 + %2 = OpTypeVoid + %3 = OpTypeBool + %4 = OpTypeInt 8 0 + %5 = OpTypeInt 8 1 + %6 = OpTypeInt 16 0 + %7 = OpTypeInt 16 1 + %8 = OpTypeInt 32 0 + %9 = OpTypeInt 32 1 +%10 = OpTypeInt 64 0 +%11 = OpTypeInt 64 1 +%12 = OpTypeFloat 16 +%13 = OpTypeFloat 32 +%14 = OpTypeFloat 64 +%15 = OpTypeVector %4 2 +)"; + spv_text_t text = {textStr, strlen(textStr)}; + spv_diagnostic diagnostic = nullptr; + spv_result_t error = + spvTextToBinary(context, text.str, text.length, &binary, &diagnostic); + if (error) { + spvDiagnosticPrint(diagnostic); + spvDiagnosticDestroy(diagnostic); + ASSERT_EQ(SPV_SUCCESS, error); + } + } + + virtual void TearDown() { spvBinaryDestroy(binary); } + + // Compiles the given assembly text, and saves it into 'binary'. + void CompileSuccessfully(std::string text) { + spv_diagnostic diagnostic = nullptr; + EXPECT_EQ(SPV_SUCCESS, spvTextToBinary(context, text.c_str(), text.size(), + &binary, &diagnostic)); + } + + spv_context context; + spv_binary binary; +}; + +TEST_F(BinaryToText, Default) { + spv_text text = nullptr; + spv_diagnostic diagnostic = nullptr; + ASSERT_EQ( + SPV_SUCCESS, + spvBinaryToText(context, binary->code, binary->wordCount, + SPV_BINARY_TO_TEXT_OPTION_NONE, &text, &diagnostic)); + printf("%s", text->str); + spvTextDestroy(text); +} + +TEST_F(BinaryToText, MissingModule) { + spv_text text; + spv_diagnostic diagnostic = nullptr; + EXPECT_EQ( + SPV_ERROR_INVALID_BINARY, + spvBinaryToText(context, nullptr, 42, SPV_BINARY_TO_TEXT_OPTION_NONE, + &text, &diagnostic)); + EXPECT_THAT(diagnostic->error, Eq(std::string("Missing module."))); + if (diagnostic) { + spvDiagnosticPrint(diagnostic); + spvDiagnosticDestroy(diagnostic); + } +} + +TEST_F(BinaryToText, TruncatedModule) { + // Make a valid module with zero instructions. + CompileSuccessfully(""); + EXPECT_EQ(SPV_INDEX_INSTRUCTION, binary->wordCount); + + for (size_t length = 0; length < SPV_INDEX_INSTRUCTION; length++) { + spv_text text = nullptr; + spv_diagnostic diagnostic = nullptr; + EXPECT_EQ( + SPV_ERROR_INVALID_BINARY, + spvBinaryToText(context, binary->code, length, + SPV_BINARY_TO_TEXT_OPTION_NONE, &text, &diagnostic)); + ASSERT_NE(nullptr, diagnostic); + std::stringstream expected; + expected << "Module has incomplete header: only " << length + << " words instead of " << SPV_INDEX_INSTRUCTION; + EXPECT_THAT(diagnostic->error, Eq(expected.str())); + spvDiagnosticDestroy(diagnostic); + } +} + +TEST_F(BinaryToText, InvalidMagicNumber) { + CompileSuccessfully(""); + std::vector damaged_binary(binary->code, + binary->code + binary->wordCount); + damaged_binary[SPV_INDEX_MAGIC_NUMBER] ^= 123; + + spv_diagnostic diagnostic = nullptr; + spv_text text; + EXPECT_EQ( + SPV_ERROR_INVALID_BINARY, + spvBinaryToText(context, damaged_binary.data(), damaged_binary.size(), + SPV_BINARY_TO_TEXT_OPTION_NONE, &text, &diagnostic)); + ASSERT_NE(nullptr, diagnostic); + std::stringstream expected; + expected << "Invalid SPIR-V magic number '" << std::hex + << damaged_binary[SPV_INDEX_MAGIC_NUMBER] << "'."; + EXPECT_THAT(diagnostic->error, Eq(expected.str())); + spvDiagnosticDestroy(diagnostic); +} + +struct FailedDecodeCase { + std::string source_text; + std::vector appended_instruction; + std::string expected_error_message; +}; + +using BinaryToTextFail = + spvtest::TextToBinaryTestBase<::testing::TestWithParam>; + +TEST_P(BinaryToTextFail, EncodeSuccessfullyDecodeFailed) { + EXPECT_THAT(EncodeSuccessfullyDecodeFailed(GetParam().source_text, + GetParam().appended_instruction), + Eq(GetParam().expected_error_message)); +} + +INSTANTIATE_TEST_CASE_P( + InvalidIds, BinaryToTextFail, + ::testing::ValuesIn(std::vector{ + {"", spvtest::MakeInstruction(SpvOpTypeVoid, {0}), + "Error: Result Id is 0"}, + {"", spvtest::MakeInstruction(SpvOpConstant, {0, 1, 42}), + "Error: Type Id is 0"}, + {"%1 = OpTypeVoid", spvtest::MakeInstruction(SpvOpTypeVoid, {1}), + "Id 1 is defined more than once"}, + {"%1 = OpTypeVoid\n" + "%2 = OpNot %1 %foo", + spvtest::MakeInstruction(SpvOpNot, {1, 2, 3}), + "Id 2 is defined more than once"}, + {"%1 = OpTypeVoid\n" + "%2 = OpNot %1 %foo", + spvtest::MakeInstruction(SpvOpNot, {1, 1, 3}), + "Id 1 is defined more than once"}, + // The following are the two failure cases for + // Parser::setNumericTypeInfoForType. + {"", spvtest::MakeInstruction(SpvOpConstant, {500, 1, 42}), + "Type Id 500 is not a type"}, + {"%1 = OpTypeInt 32 0\n" + "%2 = OpTypeVector %1 4", + spvtest::MakeInstruction(SpvOpConstant, {2, 3, 999}), + "Type Id 2 is not a scalar numeric type"}, + }), ); + +INSTANTIATE_TEST_CASE_P( + InvalidIdsCheckedDuringLiteralCaseParsing, BinaryToTextFail, + ::testing::ValuesIn(std::vector{ + {"", spvtest::MakeInstruction(SpvOpSwitch, {1, 2, 3, 4}), + "Invalid OpSwitch: selector id 1 has no type"}, + {"%1 = OpTypeVoid\n", + spvtest::MakeInstruction(SpvOpSwitch, {1, 2, 3, 4}), + "Invalid OpSwitch: selector id 1 is a type, not a value"}, + {"%1 = OpConstantTrue !500", + spvtest::MakeInstruction(SpvOpSwitch, {1, 2, 3, 4}), + "Type Id 500 is not a type"}, + {"%1 = OpTypeFloat 32\n%2 = OpConstant %1 1.5", + spvtest::MakeInstruction(SpvOpSwitch, {2, 3, 4, 5}), + "Invalid OpSwitch: selector id 2 is not a scalar integer"}, + }), ); + +TEST_F(TextToBinaryTest, OneInstruction) { + const std::string input = "OpSource OpenCL_C 12\n"; + EXPECT_EQ(input, EncodeAndDecodeSuccessfully(input)); +} + +// Exercise the case where an operand itself has operands. +// This could detect problems in updating the expected-set-of-operands +// list. +TEST_F(TextToBinaryTest, OperandWithOperands) { + const std::string input = R"(OpEntryPoint Kernel %1 "foo" +OpExecutionMode %1 LocalSizeHint 100 200 300 +%2 = OpTypeVoid +%3 = OpTypeFunction %2 +%1 = OpFunction %1 None %3 +)"; + EXPECT_EQ(input, EncodeAndDecodeSuccessfully(input)); +} + +using RoundTripInstructionsTest = spvtest::TextToBinaryTestBase< + ::testing::TestWithParam>>; + +TEST_P(RoundTripInstructionsTest, Sample) { + EXPECT_THAT(EncodeAndDecodeSuccessfully(get<1>(GetParam()), + SPV_BINARY_TO_TEXT_OPTION_NONE, + get<0>(GetParam())), + Eq(get<1>(GetParam()))); +} + +// clang-format off +INSTANTIATE_TEST_CASE_P( + NumericLiterals, RoundTripInstructionsTest, + // This test is independent of environment, so just test the one. + Combine(::testing::Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, + SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_3), + ::testing::ValuesIn(std::vector{ + "%1 = OpTypeInt 12 0\n%2 = OpConstant %1 1867\n", + "%1 = OpTypeInt 12 1\n%2 = OpConstant %1 1867\n", + "%1 = OpTypeInt 12 1\n%2 = OpConstant %1 -1867\n", + "%1 = OpTypeInt 32 0\n%2 = OpConstant %1 1867\n", + "%1 = OpTypeInt 32 1\n%2 = OpConstant %1 1867\n", + "%1 = OpTypeInt 32 1\n%2 = OpConstant %1 -1867\n", + "%1 = OpTypeInt 64 0\n%2 = OpConstant %1 18446744073709551615\n", + "%1 = OpTypeInt 64 1\n%2 = OpConstant %1 9223372036854775807\n", + "%1 = OpTypeInt 64 1\n%2 = OpConstant %1 -9223372036854775808\n", + // 16-bit floats print as hex floats. + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1.ff4p+16\n", + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1.d2cp-10\n", + // 32-bit floats + "%1 = OpTypeFloat 32\n%2 = OpConstant %1 -3.125\n", + "%1 = OpTypeFloat 32\n%2 = OpConstant %1 0x1.8p+128\n", // NaN + "%1 = OpTypeFloat 32\n%2 = OpConstant %1 -0x1.0002p+128\n", // NaN + "%1 = OpTypeFloat 32\n%2 = OpConstant %1 0x1p+128\n", // Inf + "%1 = OpTypeFloat 32\n%2 = OpConstant %1 -0x1p+128\n", // -Inf + // 64-bit floats + "%1 = OpTypeFloat 64\n%2 = OpConstant %1 -3.125\n", + "%1 = OpTypeFloat 64\n%2 = OpConstant %1 0x1.ffffffffffffap-1023\n", // small normal + "%1 = OpTypeFloat 64\n%2 = OpConstant %1 -0x1.ffffffffffffap-1023\n", + "%1 = OpTypeFloat 64\n%2 = OpConstant %1 0x1.8p+1024\n", // NaN + "%1 = OpTypeFloat 64\n%2 = OpConstant %1 -0x1.0002p+1024\n", // NaN + "%1 = OpTypeFloat 64\n%2 = OpConstant %1 0x1p+1024\n", // Inf + "%1 = OpTypeFloat 64\n%2 = OpConstant %1 -0x1p+1024\n", // -Inf + })), ); +// clang-format on + +INSTANTIATE_TEST_CASE_P( + MemoryAccessMasks, RoundTripInstructionsTest, + Combine(::testing::Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, + SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_3), + ::testing::ValuesIn(std::vector{ + "OpStore %1 %2\n", // 3 words long. + "OpStore %1 %2 None\n", // 4 words long, explicit final 0. + "OpStore %1 %2 Volatile\n", + "OpStore %1 %2 Aligned 8\n", + "OpStore %1 %2 Nontemporal\n", + // Combinations show the names from LSB to MSB + "OpStore %1 %2 Volatile|Aligned 16\n", + "OpStore %1 %2 Volatile|Nontemporal\n", + "OpStore %1 %2 Volatile|Aligned|Nontemporal 32\n", + })), ); + +INSTANTIATE_TEST_CASE_P( + FPFastMathModeMasks, RoundTripInstructionsTest, + Combine( + ::testing::Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, + SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_3), + ::testing::ValuesIn(std::vector{ + "OpDecorate %1 FPFastMathMode None\n", + "OpDecorate %1 FPFastMathMode NotNaN\n", + "OpDecorate %1 FPFastMathMode NotInf\n", + "OpDecorate %1 FPFastMathMode NSZ\n", + "OpDecorate %1 FPFastMathMode AllowRecip\n", + "OpDecorate %1 FPFastMathMode Fast\n", + // Combinations show the names from LSB to MSB + "OpDecorate %1 FPFastMathMode NotNaN|NotInf\n", + "OpDecorate %1 FPFastMathMode NSZ|AllowRecip\n", + "OpDecorate %1 FPFastMathMode NotNaN|NotInf|NSZ|AllowRecip|Fast\n", + })), ); + +INSTANTIATE_TEST_CASE_P( + LoopControlMasks, RoundTripInstructionsTest, + Combine(::testing::Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, + SPV_ENV_UNIVERSAL_1_3, SPV_ENV_UNIVERSAL_1_2), + ::testing::ValuesIn(std::vector{ + "OpLoopMerge %1 %2 None\n", + "OpLoopMerge %1 %2 Unroll\n", + "OpLoopMerge %1 %2 DontUnroll\n", + "OpLoopMerge %1 %2 Unroll|DontUnroll\n", + })), ); + +INSTANTIATE_TEST_CASE_P(LoopControlMasksV11, RoundTripInstructionsTest, + Combine(::testing::Values(SPV_ENV_UNIVERSAL_1_1, + SPV_ENV_UNIVERSAL_1_2, + SPV_ENV_UNIVERSAL_1_3), + ::testing::ValuesIn(std::vector{ + "OpLoopMerge %1 %2 DependencyInfinite\n", + "OpLoopMerge %1 %2 DependencyLength 8\n", + })), ); + +INSTANTIATE_TEST_CASE_P( + SelectionControlMasks, RoundTripInstructionsTest, + Combine(::testing::Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, + SPV_ENV_UNIVERSAL_1_3, SPV_ENV_UNIVERSAL_1_2), + ::testing::ValuesIn(std::vector{ + "OpSelectionMerge %1 None\n", + "OpSelectionMerge %1 Flatten\n", + "OpSelectionMerge %1 DontFlatten\n", + "OpSelectionMerge %1 Flatten|DontFlatten\n", + })), ); + +INSTANTIATE_TEST_CASE_P( + FunctionControlMasks, RoundTripInstructionsTest, + Combine(::testing::Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, + SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_3), + ::testing::ValuesIn(std::vector{ + "%2 = OpFunction %1 None %3\n", + "%2 = OpFunction %1 Inline %3\n", + "%2 = OpFunction %1 DontInline %3\n", + "%2 = OpFunction %1 Pure %3\n", + "%2 = OpFunction %1 Const %3\n", + "%2 = OpFunction %1 Inline|Pure|Const %3\n", + "%2 = OpFunction %1 DontInline|Const %3\n", + })), ); + +INSTANTIATE_TEST_CASE_P( + ImageMasks, RoundTripInstructionsTest, + Combine(::testing::Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, + SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_3), + ::testing::ValuesIn(std::vector{ + "%2 = OpImageFetch %1 %3 %4\n", + "%2 = OpImageFetch %1 %3 %4 None\n", + "%2 = OpImageFetch %1 %3 %4 Bias %5\n", + "%2 = OpImageFetch %1 %3 %4 Lod %5\n", + "%2 = OpImageFetch %1 %3 %4 Grad %5 %6\n", + "%2 = OpImageFetch %1 %3 %4 ConstOffset %5\n", + "%2 = OpImageFetch %1 %3 %4 Offset %5\n", + "%2 = OpImageFetch %1 %3 %4 ConstOffsets %5\n", + "%2 = OpImageFetch %1 %3 %4 Sample %5\n", + "%2 = OpImageFetch %1 %3 %4 MinLod %5\n", + "%2 = OpImageFetch %1 %3 %4 Bias|Lod|Grad %5 %6 %7 %8\n", + "%2 = OpImageFetch %1 %3 %4 ConstOffset|Offset|ConstOffsets" + " %5 %6 %7\n", + "%2 = OpImageFetch %1 %3 %4 Sample|MinLod %5 %6\n", + "%2 = OpImageFetch %1 %3 %4" + " Bias|Lod|Grad|ConstOffset|Offset|ConstOffsets|Sample|MinLod" + " %5 %6 %7 %8 %9 %10 %11 %12 %13\n"})), ); + +INSTANTIATE_TEST_CASE_P( + NewInstructionsInSPIRV1_2, RoundTripInstructionsTest, + Combine(::testing::Values(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_3), + ::testing::ValuesIn(std::vector{ + "OpExecutionModeId %1 SubgroupsPerWorkgroupId %2\n", + "OpExecutionModeId %1 LocalSizeId %2 %3 %4\n", + "OpExecutionModeId %1 LocalSizeHintId %2\n", + "OpDecorateId %1 AlignmentId %2\n", + "OpDecorateId %1 MaxByteOffsetId %2\n", + })), ); + +using MaskSorting = TextToBinaryTest; + +TEST_F(MaskSorting, MasksAreSortedFromLSBToMSB) { + EXPECT_THAT(EncodeAndDecodeSuccessfully( + "OpStore %1 %2 Nontemporal|Aligned|Volatile 32"), + Eq("OpStore %1 %2 Volatile|Aligned|Nontemporal 32\n")); + EXPECT_THAT( + EncodeAndDecodeSuccessfully( + "OpDecorate %1 FPFastMathMode NotInf|Fast|AllowRecip|NotNaN|NSZ"), + Eq("OpDecorate %1 FPFastMathMode NotNaN|NotInf|NSZ|AllowRecip|Fast\n")); + EXPECT_THAT( + EncodeAndDecodeSuccessfully("OpLoopMerge %1 %2 DontUnroll|Unroll"), + Eq("OpLoopMerge %1 %2 Unroll|DontUnroll\n")); + EXPECT_THAT( + EncodeAndDecodeSuccessfully("OpSelectionMerge %1 DontFlatten|Flatten"), + Eq("OpSelectionMerge %1 Flatten|DontFlatten\n")); + EXPECT_THAT(EncodeAndDecodeSuccessfully( + "%2 = OpFunction %1 DontInline|Const|Pure|Inline %3"), + Eq("%2 = OpFunction %1 Inline|DontInline|Pure|Const %3\n")); + EXPECT_THAT(EncodeAndDecodeSuccessfully( + "%2 = OpImageFetch %1 %3 %4" + " MinLod|Sample|Offset|Lod|Grad|ConstOffsets|ConstOffset|Bias" + " %5 %6 %7 %8 %9 %10 %11 %12 %13\n"), + Eq("%2 = OpImageFetch %1 %3 %4" + " Bias|Lod|Grad|ConstOffset|Offset|ConstOffsets|Sample|MinLod" + " %5 %6 %7 %8 %9 %10 %11 %12 %13\n")); +} + +using OperandTypeTest = TextToBinaryTest; + +TEST_F(OperandTypeTest, OptionalTypedLiteralNumber) { + const std::string input = + "%1 = OpTypeInt 32 0\n" + "%2 = OpConstant %1 42\n" + "OpSwitch %2 %3 100 %4\n"; + EXPECT_EQ(input, EncodeAndDecodeSuccessfully(input)); +} + +using IndentTest = spvtest::TextToBinaryTest; + +TEST_F(IndentTest, Sample) { + const std::string input = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +%1 = OpTypeInt 32 0 +%2 = OpTypeStruct %1 %3 %4 %5 %6 %7 %8 %9 %10 ; force IDs into double digits +%11 = OpConstant %1 42 +OpStore %2 %3 Aligned|Volatile 4 ; bogus, but not indented +)"; + const std::string expected = + R"( OpCapability Shader + OpMemoryModel Logical GLSL450 + %1 = OpTypeInt 32 0 + %2 = OpTypeStruct %1 %3 %4 %5 %6 %7 %8 %9 %10 + %11 = OpConstant %1 42 + OpStore %2 %3 Volatile|Aligned 4 +)"; + EXPECT_THAT( + EncodeAndDecodeSuccessfully(input, SPV_BINARY_TO_TEXT_OPTION_INDENT), + expected); +} + +using FriendlyNameDisassemblyTest = spvtest::TextToBinaryTest; + +TEST_F(FriendlyNameDisassemblyTest, Sample) { + const std::string input = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +%1 = OpTypeInt 32 0 +%2 = OpTypeStruct %1 %3 %4 %5 %6 %7 %8 %9 %10 ; force IDs into double digits +%11 = OpConstant %1 42 +)"; + const std::string expected = + R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +%uint = OpTypeInt 32 0 +%_struct_2 = OpTypeStruct %uint %3 %4 %5 %6 %7 %8 %9 %10 +%uint_42 = OpConstant %uint 42 +)"; + EXPECT_THAT(EncodeAndDecodeSuccessfully( + input, SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES), + expected); +} + +TEST_F(TextToBinaryTest, ShowByteOffsetsWhenRequested) { + const std::string input = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +%1 = OpTypeInt 32 0 +%2 = OpTypeVoid +)"; + const std::string expected = + R"(OpCapability Shader ; 0x00000014 +OpMemoryModel Logical GLSL450 ; 0x0000001c +%1 = OpTypeInt 32 0 ; 0x00000028 +%2 = OpTypeVoid ; 0x00000038 +)"; + EXPECT_THAT(EncodeAndDecodeSuccessfully( + input, SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET), + expected); +} + +// Test version string. +TEST_F(TextToBinaryTest, VersionString) { + auto words = CompileSuccessfully(""); + spv_text decoded_text = nullptr; + EXPECT_THAT(spvBinaryToText(ScopedContext().context, words.data(), + words.size(), SPV_BINARY_TO_TEXT_OPTION_NONE, + &decoded_text, &diagnostic), + Eq(SPV_SUCCESS)); + EXPECT_EQ(nullptr, diagnostic); + + EXPECT_THAT(decoded_text->str, HasSubstr("Version: 1.0\n")) + << EncodeAndDecodeSuccessfully(""); + spvTextDestroy(decoded_text); +} + +// Test generator string. + +// A test case for the generator string. This allows us to +// test both of the 16-bit components of the generator word. +struct GeneratorStringCase { + uint16_t generator; + uint16_t misc; + std::string expected; +}; + +using GeneratorStringTest = spvtest::TextToBinaryTestBase< + ::testing::TestWithParam>; + +TEST_P(GeneratorStringTest, Sample) { + auto words = CompileSuccessfully(""); + EXPECT_EQ(2u, SPV_INDEX_GENERATOR_NUMBER); + words[SPV_INDEX_GENERATOR_NUMBER] = + SPV_GENERATOR_WORD(GetParam().generator, GetParam().misc); + + spv_text decoded_text = nullptr; + EXPECT_THAT(spvBinaryToText(ScopedContext().context, words.data(), + words.size(), SPV_BINARY_TO_TEXT_OPTION_NONE, + &decoded_text, &diagnostic), + Eq(SPV_SUCCESS)); + EXPECT_THAT(diagnostic, Eq(nullptr)); + EXPECT_THAT(std::string(decoded_text->str), HasSubstr(GetParam().expected)); + spvTextDestroy(decoded_text); +} + +INSTANTIATE_TEST_CASE_P(GeneratorStrings, GeneratorStringTest, + ::testing::ValuesIn(std::vector{ + {SPV_GENERATOR_KHRONOS, 12, "Khronos; 12"}, + {SPV_GENERATOR_LUNARG, 99, "LunarG; 99"}, + {SPV_GENERATOR_VALVE, 1, "Valve; 1"}, + {SPV_GENERATOR_CODEPLAY, 65535, "Codeplay; 65535"}, + {SPV_GENERATOR_NVIDIA, 19, "NVIDIA; 19"}, + {SPV_GENERATOR_ARM, 1000, "ARM; 1000"}, + {SPV_GENERATOR_KHRONOS_LLVM_TRANSLATOR, 38, + "Khronos LLVM/SPIR-V Translator; 38"}, + {SPV_GENERATOR_KHRONOS_ASSEMBLER, 2, + "Khronos SPIR-V Tools Assembler; 2"}, + {SPV_GENERATOR_KHRONOS_GLSLANG, 1, + "Khronos Glslang Reference Front End; 1"}, + {1000, 18, "Unknown(1000); 18"}, + {65535, 32767, "Unknown(65535); 32767"}, + }), ); + +// TODO(dneto): Test new instructions and enums in SPIR-V 1.3 + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/bit_stream.cpp b/3rdparty/spirv-tools/test/bit_stream.cpp new file mode 100644 index 000000000..d30a79f7f --- /dev/null +++ b/3rdparty/spirv-tools/test/bit_stream.cpp @@ -0,0 +1,1424 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "gmock/gmock.h" +#include "util/bit_stream.h" + +namespace { + +using spvutils::BitReaderInterface; +using spvutils::BitReaderWord64; +using spvutils::BitsetToStream; +using spvutils::BitsToStream; +using spvutils::BitWriterInterface; +using spvutils::BitWriterWord64; +using spvutils::BufferToStream; +using spvutils::DecodeZigZag; +using spvutils::EncodeZigZag; +using spvutils::GetLowerBits; +using spvutils::Log2U64; +using spvutils::NumBitsToNumWords; +using spvutils::PadToWord; +using spvutils::StreamToBits; +using spvutils::StreamToBitset; +using spvutils::StreamToBuffer; + +// A simple and inefficient implementatition of BitWriterInterface, +// using std::stringstream. Intended for tests only. +class BitWriterStringStream : public BitWriterInterface { + public: + void WriteStream(const std::string& bits) override { ss_ << bits; } + + void WriteBits(uint64_t bits, size_t num_bits) override { + assert(num_bits <= 64); + ss_ << BitsToStream(bits, num_bits); + } + + size_t GetNumBits() const override { return ss_.str().size(); } + + std::vector GetDataCopy() const override { + return StreamToBuffer(ss_.str()); + } + + std::string GetStreamRaw() const { return ss_.str(); } + + private: + std::stringstream ss_; +}; + +// A simple and inefficient implementatition of BitReaderInterface. +// Intended for tests only. +class BitReaderFromString : public BitReaderInterface { + public: + explicit BitReaderFromString(std::string&& str) + : str_(std::move(str)), pos_(0) {} + + explicit BitReaderFromString(const std::vector& buffer) + : str_(BufferToStream(buffer)), pos_(0) {} + + explicit BitReaderFromString(const std::vector& buffer) + : str_(PadToWord<64>(BufferToStream(buffer))), pos_(0) {} + + size_t ReadBits(uint64_t* bits, size_t num_bits) override { + if (ReachedEnd()) return 0; + std::string sub = str_.substr(pos_, num_bits); + *bits = StreamToBits(sub); + pos_ += sub.length(); + return sub.length(); + } + + size_t GetNumReadBits() const override { return pos_; } + + bool ReachedEnd() const override { return pos_ >= str_.length(); } + + const std::string& GetStreamPadded64() const { return str_; } + + private: + std::string str_; + size_t pos_; +}; + +TEST(Log2U16, Test) { + EXPECT_EQ(0u, Log2U64(0)); + EXPECT_EQ(0u, Log2U64(1)); + EXPECT_EQ(1u, Log2U64(2)); + EXPECT_EQ(1u, Log2U64(3)); + EXPECT_EQ(2u, Log2U64(4)); + EXPECT_EQ(2u, Log2U64(5)); + EXPECT_EQ(2u, Log2U64(6)); + EXPECT_EQ(2u, Log2U64(7)); + EXPECT_EQ(3u, Log2U64(8)); + EXPECT_EQ(3u, Log2U64(9)); + EXPECT_EQ(3u, Log2U64(10)); + EXPECT_EQ(3u, Log2U64(11)); + EXPECT_EQ(3u, Log2U64(12)); + EXPECT_EQ(3u, Log2U64(13)); + EXPECT_EQ(3u, Log2U64(14)); + EXPECT_EQ(3u, Log2U64(15)); + EXPECT_EQ(4u, Log2U64(16)); + EXPECT_EQ(4u, Log2U64(17)); + EXPECT_EQ(5u, Log2U64(35)); + EXPECT_EQ(6u, Log2U64(72)); + EXPECT_EQ(7u, Log2U64(255)); + EXPECT_EQ(8u, Log2U64(256)); + EXPECT_EQ(15u, Log2U64(65535)); + EXPECT_EQ(16u, Log2U64(65536)); + EXPECT_EQ(19u, Log2U64(0xFFFFF)); + EXPECT_EQ(23u, Log2U64(0xFFFFFF)); + EXPECT_EQ(27u, Log2U64(0xFFFFFFF)); + EXPECT_EQ(31u, Log2U64(0xFFFFFFFF)); + EXPECT_EQ(35u, Log2U64(0xFFFFFFFFF)); + EXPECT_EQ(39u, Log2U64(0xFFFFFFFFFF)); + EXPECT_EQ(43u, Log2U64(0xFFFFFFFFFFF)); + EXPECT_EQ(47u, Log2U64(0xFFFFFFFFFFFF)); + EXPECT_EQ(51u, Log2U64(0xFFFFFFFFFFFFF)); + EXPECT_EQ(55u, Log2U64(0xFFFFFFFFFFFFFF)); + EXPECT_EQ(59u, Log2U64(0xFFFFFFFFFFFFFFF)); + EXPECT_EQ(63u, Log2U64(0xFFFFFFFFFFFFFFFF)); +} + +TEST(NumBitsToNumWords, Word8) { + EXPECT_EQ(0u, NumBitsToNumWords<8>(0)); + EXPECT_EQ(1u, NumBitsToNumWords<8>(1)); + EXPECT_EQ(1u, NumBitsToNumWords<8>(7)); + EXPECT_EQ(1u, NumBitsToNumWords<8>(8)); + EXPECT_EQ(2u, NumBitsToNumWords<8>(9)); + EXPECT_EQ(2u, NumBitsToNumWords<8>(16)); + EXPECT_EQ(3u, NumBitsToNumWords<8>(17)); + EXPECT_EQ(3u, NumBitsToNumWords<8>(23)); + EXPECT_EQ(3u, NumBitsToNumWords<8>(24)); + EXPECT_EQ(4u, NumBitsToNumWords<8>(25)); +} + +TEST(NumBitsToNumWords, Word64) { + EXPECT_EQ(0u, NumBitsToNumWords<64>(0)); + EXPECT_EQ(1u, NumBitsToNumWords<64>(1)); + EXPECT_EQ(1u, NumBitsToNumWords<64>(64)); + EXPECT_EQ(2u, NumBitsToNumWords<64>(65)); + EXPECT_EQ(2u, NumBitsToNumWords<64>(128)); + EXPECT_EQ(3u, NumBitsToNumWords<64>(129)); +} + +TEST(ZigZagCoding, Encode) { + EXPECT_EQ(0u, EncodeZigZag(0)); + EXPECT_EQ(1u, EncodeZigZag(-1)); + EXPECT_EQ(2u, EncodeZigZag(1)); + EXPECT_EQ(3u, EncodeZigZag(-2)); + EXPECT_EQ(4u, EncodeZigZag(2)); + EXPECT_EQ(5u, EncodeZigZag(-3)); + EXPECT_EQ(6u, EncodeZigZag(3)); + EXPECT_EQ(std::numeric_limits::max() - 1, + EncodeZigZag(std::numeric_limits::max())); + EXPECT_EQ(std::numeric_limits::max(), + EncodeZigZag(std::numeric_limits::min())); +} + +TEST(ZigZagCoding, Decode) { + EXPECT_EQ(0, DecodeZigZag(0)); + EXPECT_EQ(-1, DecodeZigZag(1)); + EXPECT_EQ(1, DecodeZigZag(2)); + EXPECT_EQ(-2, DecodeZigZag(3)); + EXPECT_EQ(2, DecodeZigZag(4)); + EXPECT_EQ(-3, DecodeZigZag(5)); + EXPECT_EQ(3, DecodeZigZag(6)); + EXPECT_EQ(std::numeric_limits::min(), + DecodeZigZag(std::numeric_limits::max())); + EXPECT_EQ(std::numeric_limits::max(), + DecodeZigZag(std::numeric_limits::max() - 1)); +} + +TEST(ZigZagCoding, Encode0) { + EXPECT_EQ(0u, EncodeZigZag(0, 0)); + EXPECT_EQ(1u, EncodeZigZag(-1, 0)); + EXPECT_EQ(2u, EncodeZigZag(1, 0)); + EXPECT_EQ(3u, EncodeZigZag(-2, 0)); + EXPECT_EQ(std::numeric_limits::max() - 1, + EncodeZigZag(std::numeric_limits::max(), 0)); + EXPECT_EQ(std::numeric_limits::max(), + EncodeZigZag(std::numeric_limits::min(), 0)); +} + +TEST(ZigZagCoding, Decode0) { + EXPECT_EQ(0, DecodeZigZag(0, 0)); + EXPECT_EQ(-1, DecodeZigZag(1, 0)); + EXPECT_EQ(1, DecodeZigZag(2, 0)); + EXPECT_EQ(-2, DecodeZigZag(3, 0)); + EXPECT_EQ(std::numeric_limits::min(), + DecodeZigZag(std::numeric_limits::max(), 0)); + EXPECT_EQ(std::numeric_limits::max(), + DecodeZigZag(std::numeric_limits::max() - 1, 0)); +} + +TEST(ZigZagCoding, Decode0SameAsNormalZigZag) { + for (int32_t i = -10000; i < 10000; i += 123) { + ASSERT_EQ(DecodeZigZag(i), DecodeZigZag(i, 0)); + } +} + +TEST(ZigZagCoding, Encode0SameAsNormalZigZag) { + for (uint32_t i = 0; i < 10000; i += 123) { + ASSERT_EQ(EncodeZigZag(i), EncodeZigZag(i, 0)); + } +} + +TEST(ZigZagCoding, Encode1) { + EXPECT_EQ(0u, EncodeZigZag(0, 1)); + EXPECT_EQ(1u, EncodeZigZag(1, 1)); + EXPECT_EQ(2u, EncodeZigZag(-1, 1)); + EXPECT_EQ(3u, EncodeZigZag(-2, 1)); + EXPECT_EQ(4u, EncodeZigZag(2, 1)); + EXPECT_EQ(5u, EncodeZigZag(3, 1)); + EXPECT_EQ(6u, EncodeZigZag(-3, 1)); + EXPECT_EQ(7u, EncodeZigZag(-4, 1)); + EXPECT_EQ(std::numeric_limits::max() - 2, + EncodeZigZag(std::numeric_limits::max(), 1)); + EXPECT_EQ(std::numeric_limits::max() - 1, + EncodeZigZag(std::numeric_limits::min() + 1, 1)); + EXPECT_EQ(std::numeric_limits::max(), + EncodeZigZag(std::numeric_limits::min(), 1)); +} + +TEST(ZigZagCoding, Decode1) { + EXPECT_EQ(0, DecodeZigZag(0, 1)); + EXPECT_EQ(1, DecodeZigZag(1, 1)); + EXPECT_EQ(-1, DecodeZigZag(2, 1)); + EXPECT_EQ(-2, DecodeZigZag(3, 1)); + EXPECT_EQ(2, DecodeZigZag(4, 1)); + EXPECT_EQ(3, DecodeZigZag(5, 1)); + EXPECT_EQ(-3, DecodeZigZag(6, 1)); + EXPECT_EQ(-4, DecodeZigZag(7, 1)); + EXPECT_EQ(std::numeric_limits::min(), + DecodeZigZag(std::numeric_limits::max(), 1)); + EXPECT_EQ(std::numeric_limits::min() + 1, + DecodeZigZag(std::numeric_limits::max() - 1, 1)); + EXPECT_EQ(std::numeric_limits::max(), + DecodeZigZag(std::numeric_limits::max() - 2, 1)); +} + +TEST(ZigZagCoding, Encode2) { + EXPECT_EQ(0u, EncodeZigZag(0, 2)); + EXPECT_EQ(1u, EncodeZigZag(1, 2)); + EXPECT_EQ(2u, EncodeZigZag(2, 2)); + EXPECT_EQ(3u, EncodeZigZag(3, 2)); + EXPECT_EQ(4u, EncodeZigZag(-1, 2)); + EXPECT_EQ(5u, EncodeZigZag(-2, 2)); + EXPECT_EQ(6u, EncodeZigZag(-3, 2)); + EXPECT_EQ(7u, EncodeZigZag(-4, 2)); + EXPECT_EQ(8u, EncodeZigZag(4, 2)); + EXPECT_EQ(9u, EncodeZigZag(5, 2)); + EXPECT_EQ(10u, EncodeZigZag(6, 2)); + EXPECT_EQ(11u, EncodeZigZag(7, 2)); + EXPECT_EQ(12u, EncodeZigZag(-5, 2)); + EXPECT_EQ(13u, EncodeZigZag(-6, 2)); + EXPECT_EQ(14u, EncodeZigZag(-7, 2)); + EXPECT_EQ(15u, EncodeZigZag(-8, 2)); + EXPECT_EQ(std::numeric_limits::max() - 4, + EncodeZigZag(std::numeric_limits::max(), 2)); + EXPECT_EQ(std::numeric_limits::max() - 3, + EncodeZigZag(std::numeric_limits::min() + 3, 2)); + EXPECT_EQ(std::numeric_limits::max() - 2, + EncodeZigZag(std::numeric_limits::min() + 2, 2)); + EXPECT_EQ(std::numeric_limits::max() - 1, + EncodeZigZag(std::numeric_limits::min() + 1, 2)); + EXPECT_EQ(std::numeric_limits::max(), + EncodeZigZag(std::numeric_limits::min(), 2)); +} + +TEST(ZigZagCoding, Decode2) { + EXPECT_EQ(0, DecodeZigZag(0, 2)); + EXPECT_EQ(1, DecodeZigZag(1, 2)); + EXPECT_EQ(2, DecodeZigZag(2, 2)); + EXPECT_EQ(3, DecodeZigZag(3, 2)); + EXPECT_EQ(-1, DecodeZigZag(4, 2)); + EXPECT_EQ(-2, DecodeZigZag(5, 2)); + EXPECT_EQ(-3, DecodeZigZag(6, 2)); + EXPECT_EQ(-4, DecodeZigZag(7, 2)); + EXPECT_EQ(4, DecodeZigZag(8, 2)); + EXPECT_EQ(5, DecodeZigZag(9, 2)); + EXPECT_EQ(6, DecodeZigZag(10, 2)); + EXPECT_EQ(7, DecodeZigZag(11, 2)); + EXPECT_EQ(-5, DecodeZigZag(12, 2)); + EXPECT_EQ(-6, DecodeZigZag(13, 2)); + EXPECT_EQ(-7, DecodeZigZag(14, 2)); + EXPECT_EQ(-8, DecodeZigZag(15, 2)); + EXPECT_EQ(std::numeric_limits::min(), + DecodeZigZag(std::numeric_limits::max(), 2)); + EXPECT_EQ(std::numeric_limits::min() + 1, + DecodeZigZag(std::numeric_limits::max() - 1, 2)); + EXPECT_EQ(std::numeric_limits::min() + 2, + DecodeZigZag(std::numeric_limits::max() - 2, 2)); + EXPECT_EQ(std::numeric_limits::min() + 3, + DecodeZigZag(std::numeric_limits::max() - 3, 2)); + EXPECT_EQ(std::numeric_limits::max(), + DecodeZigZag(std::numeric_limits::max() - 4, 2)); +} + +TEST(ZigZagCoding, Encode63) { + EXPECT_EQ(0u, EncodeZigZag(0, 63)); + + for (int64_t i = 0; i < 0xFFFFFFFF; i += 1234567) { + const int64_t positive_val = GetLowerBits(i * i * i + i * i, 63) | 1UL; + ASSERT_EQ(static_cast(positive_val), + EncodeZigZag(positive_val, 63)); + ASSERT_EQ((1ULL << 63) - 1 + positive_val, EncodeZigZag(-positive_val, 63)); + } + + EXPECT_EQ((1ULL << 63) - 1, + EncodeZigZag(std::numeric_limits::max(), 63)); + EXPECT_EQ(std::numeric_limits::max() - 1, + EncodeZigZag(std::numeric_limits::min() + 1, 63)); + EXPECT_EQ(std::numeric_limits::max(), + EncodeZigZag(std::numeric_limits::min(), 63)); +} + +TEST(BufToStream, UInt8_Empty) { + const std::string expected_bits = ""; + std::vector buffer = StreamToBuffer(expected_bits); + EXPECT_TRUE(buffer.empty()); + const std::string result_bits = BufferToStream(buffer); + EXPECT_EQ(expected_bits, result_bits); +} + +TEST(BufToStream, UInt8_OneWord) { + const std::string expected_bits = "00101100"; + std::vector buffer = StreamToBuffer(expected_bits); + EXPECT_EQ(std::vector({static_cast( + StreamToBitset<8>(expected_bits).to_ulong())}), + buffer); + const std::string result_bits = BufferToStream(buffer); + EXPECT_EQ(expected_bits, result_bits); +} + +TEST(BufToStream, UInt8_MultipleWords) { + const std::string expected_bits = + "00100010" + "01101010" + "01111101" + "00100010"; + std::vector buffer = StreamToBuffer(expected_bits); + EXPECT_EQ(std::vector({ + static_cast(StreamToBitset<8>("00100010").to_ulong()), + static_cast(StreamToBitset<8>("01101010").to_ulong()), + static_cast(StreamToBitset<8>("01111101").to_ulong()), + static_cast(StreamToBitset<8>("00100010").to_ulong()), + }), + buffer); + const std::string result_bits = BufferToStream(buffer); + EXPECT_EQ(expected_bits, result_bits); +} + +TEST(BufToStream, UInt64_Empty) { + const std::string expected_bits = ""; + std::vector buffer = StreamToBuffer(expected_bits); + EXPECT_TRUE(buffer.empty()); + const std::string result_bits = BufferToStream(buffer); + EXPECT_EQ(expected_bits, result_bits); +} + +TEST(BufToStream, UInt64_OneWord) { + const std::string expected_bits = + "0001000111101110011001101010101000100010110011000100010010001000"; + std::vector buffer = StreamToBuffer(expected_bits); + ASSERT_EQ(1u, buffer.size()); + EXPECT_EQ(0x1122334455667788u, buffer[0]); + const std::string result_bits = BufferToStream(buffer); + EXPECT_EQ(expected_bits, result_bits); +} + +TEST(BufToStream, UInt64_Unaligned) { + const std::string expected_bits = + "0010001001101010011111010010001001001010000111110010010010010101" + "0010001001101010011111111111111111111111"; + std::vector buffer = StreamToBuffer(expected_bits); + EXPECT_EQ(std::vector({ + StreamToBits(expected_bits.substr(0, 64)), + StreamToBits(expected_bits.substr(64, 64)), + }), + buffer); + const std::string result_bits = BufferToStream(buffer); + EXPECT_EQ(PadToWord<64>(expected_bits), result_bits); +} + +TEST(BufToStream, UInt64_MultipleWords) { + const std::string expected_bits = + "0010001001101010011111010010001001001010000111110010010010010101" + "0010001001101010011111111111111111111111000111110010010010010111" + "0000000000000000000000000000000000000000000000000010010011111111"; + std::vector buffer = StreamToBuffer(expected_bits); + EXPECT_EQ(std::vector({ + StreamToBits(expected_bits.substr(0, 64)), + StreamToBits(expected_bits.substr(64, 64)), + StreamToBits(expected_bits.substr(128, 64)), + }), + buffer); + const std::string result_bits = BufferToStream(buffer); + EXPECT_EQ(expected_bits, result_bits); +} + +TEST(PadToWord, Test) { + EXPECT_EQ("10100000", PadToWord<8>("101")); + EXPECT_EQ( + "10100000" + "00000000", + PadToWord<16>("101")); + EXPECT_EQ( + "10100000" + "00000000" + "00000000" + "00000000", + PadToWord<32>("101")); + EXPECT_EQ( + "10100000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000", + PadToWord<64>("101")); +} + +TEST(BitWriterStringStream, Empty) { + BitWriterStringStream writer; + EXPECT_EQ(0u, writer.GetNumBits()); + EXPECT_EQ(0u, writer.GetDataSizeBytes()); + EXPECT_EQ("", writer.GetStreamRaw()); +} + +TEST(BitWriterStringStream, WriteStream) { + BitWriterStringStream writer; + const std::string bits1 = "1011111111111111111"; + writer.WriteStream(bits1); + EXPECT_EQ(19u, writer.GetNumBits()); + EXPECT_EQ(3u, writer.GetDataSizeBytes()); + EXPECT_EQ(bits1, writer.GetStreamRaw()); + + const std::string bits2 = "10100001010101010000111111111111111111111111111"; + writer.WriteStream(bits2); + EXPECT_EQ(66u, writer.GetNumBits()); + EXPECT_EQ(9u, writer.GetDataSizeBytes()); + EXPECT_EQ(bits1 + bits2, writer.GetStreamRaw()); +} + +TEST(BitWriterStringStream, WriteBitSet) { + BitWriterStringStream writer; + const std::string bits1 = "10101"; + writer.WriteBitset(StreamToBitset<16>(bits1)); + EXPECT_EQ(16u, writer.GetNumBits()); + EXPECT_EQ(2u, writer.GetDataSizeBytes()); + EXPECT_EQ(PadToWord<16>(bits1), writer.GetStreamRaw()); +} + +TEST(BitWriterStringStream, WriteBits) { + BitWriterStringStream writer; + const uint64_t bits1 = 0x1 | 0x2 | 0x10; + writer.WriteBits(bits1, 5); + EXPECT_EQ(5u, writer.GetNumBits()); + EXPECT_EQ(1u, writer.GetDataSizeBytes()); + EXPECT_EQ("11001", writer.GetStreamRaw()); +} + +TEST(BitWriterStringStream, WriteUnencodedU8) { + BitWriterStringStream writer; + const uint8_t bits = 127; + writer.WriteUnencoded(bits); + EXPECT_EQ(8u, writer.GetNumBits()); + EXPECT_EQ("11111110", writer.GetStreamRaw()); +} + +TEST(BitWriterStringStream, WriteUnencodedS64) { + BitWriterStringStream writer; + const int64_t bits = std::numeric_limits::min() + 7; + writer.WriteUnencoded(bits); + EXPECT_EQ(64u, writer.GetNumBits()); + EXPECT_EQ("1110000000000000000000000000000000000000000000000000000000000001", + writer.GetStreamRaw()); +} + +TEST(BitWriterStringStream, WriteMultiple) { + BitWriterStringStream writer; + + std::string expected_result; + const std::string bits1 = "101001111111001100010000001110001111111100"; + writer.WriteStream(bits1); + + const std::string bits2 = "10100011000010010101"; + writer.WriteBitset(StreamToBitset<20>(bits2)); + + const uint64_t val = 0x1 | 0x2 | 0x10; + const std::string bits3 = BitsToStream(val, 8); + writer.WriteBits(val, 8); + + const std::string expected = bits1 + bits2 + bits3; + + EXPECT_EQ(expected.length(), writer.GetNumBits()); + EXPECT_EQ(9u, writer.GetDataSizeBytes()); + EXPECT_EQ(expected, writer.GetStreamRaw()); + + EXPECT_EQ(PadToWord<8>(expected), BufferToStream(writer.GetDataCopy())); +} + +TEST(BitWriterWord64, Empty) { + BitWriterWord64 writer; + EXPECT_EQ(0u, writer.GetNumBits()); + EXPECT_EQ(0u, writer.GetDataSizeBytes()); + EXPECT_EQ("", writer.GetStreamPadded64()); +} + +TEST(BitWriterWord64, WriteStream) { + BitWriterWord64 writer; + std::string expected; + + { + const std::string bits = "101"; + expected += bits; + writer.WriteStream(bits); + EXPECT_EQ(expected.length(), writer.GetNumBits()); + EXPECT_EQ(1u, writer.GetDataSizeBytes()); + EXPECT_EQ(PadToWord<64>(expected), writer.GetStreamPadded64()); + } + + { + const std::string bits = "10000111111111110000000"; + expected += bits; + writer.WriteStream(bits); + EXPECT_EQ(expected.length(), writer.GetNumBits()); + EXPECT_EQ(PadToWord<64>(expected), writer.GetStreamPadded64()); + } + + { + const std::string bits = "101001111111111100000111111111111100"; + expected += bits; + writer.WriteStream(bits); + EXPECT_EQ(expected.length(), writer.GetNumBits()); + EXPECT_EQ(PadToWord<64>(expected), writer.GetStreamPadded64()); + } +} + +TEST(BitWriterWord64, WriteBitset) { + BitWriterWord64 writer; + const std::string bits1 = "10101"; + writer.WriteBitset(StreamToBitset<16>(bits1), 12); + EXPECT_EQ(12u, writer.GetNumBits()); + EXPECT_EQ(2u, writer.GetDataSizeBytes()); + EXPECT_EQ(PadToWord<64>(bits1), writer.GetStreamPadded64()); +} + +TEST(BitWriterWord64, WriteBits) { + BitWriterWord64 writer; + const uint64_t bits1 = 0x1 | 0x2 | 0x10; + writer.WriteBits(bits1, 5); + writer.WriteBits(bits1, 5); + writer.WriteBits(bits1, 5); + EXPECT_EQ(15u, writer.GetNumBits()); + EXPECT_EQ(2u, writer.GetDataSizeBytes()); + EXPECT_EQ(PadToWord<64>("110011100111001"), writer.GetStreamPadded64()); +} + +TEST(BitWriterWord64, WriteZeroBits) { + BitWriterWord64 writer; + writer.WriteBits(0, 0); + writer.WriteBits(1, 0); + EXPECT_EQ(0u, writer.GetNumBits()); + writer.WriteBits(1, 1); + writer.WriteBits(0, 0); + EXPECT_EQ(PadToWord<64>("1"), writer.GetStreamPadded64()); + writer.WriteBits(0, 63); + EXPECT_EQ(64u, writer.GetNumBits()); + writer.WriteBits(0, 0); + writer.WriteBits(7, 3); + writer.WriteBits(0, 0); + EXPECT_EQ( + PadToWord<64>( + "1" + "000000000000000000000000000000000000000000000000000000000000000" + "111"), + writer.GetStreamPadded64()); +} + +TEST(BitWriterWord64, ComparisonTestWriteLotsOfBits) { + BitWriterStringStream writer1; + BitWriterWord64 writer2(16384); + + for (uint64_t i = 0; i < 65000; i += 25) { + writer1.WriteBits(i, 16); + writer2.WriteBits(i, 16); + ASSERT_EQ(writer1.GetNumBits(), writer2.GetNumBits()); + } + + EXPECT_EQ(PadToWord<64>(writer1.GetStreamRaw()), writer2.GetStreamPadded64()); +} + +TEST(BitWriterWord64, ComparisonTestWriteLotsOfStreams) { + BitWriterStringStream writer1; + BitWriterWord64 writer2(16384); + + for (int i = 0; i < 1000; ++i) { + std::string bits = "1111100000"; + if (i % 2) bits += "101010"; + if (i % 3) bits += "1110100"; + if (i % 5) bits += "1110100111111111111"; + writer1.WriteStream(bits); + writer2.WriteStream(bits); + ASSERT_EQ(writer1.GetNumBits(), writer2.GetNumBits()); + } + + EXPECT_EQ(PadToWord<64>(writer1.GetStreamRaw()), writer2.GetStreamPadded64()); +} + +TEST(BitWriterWord64, ComparisonTestWriteLotsOfBitsets) { + BitWriterStringStream writer1; + BitWriterWord64 writer2(16384); + + for (uint64_t i = 0; i < 65000; i += 25) { + std::bitset<16> bits1(i); + std::bitset<24> bits2(i); + writer1.WriteBitset(bits1); + writer1.WriteBitset(bits2); + writer2.WriteBitset(bits1); + writer2.WriteBitset(bits2); + ASSERT_EQ(writer1.GetNumBits(), writer2.GetNumBits()); + } + + EXPECT_EQ(PadToWord<64>(writer1.GetStreamRaw()), writer2.GetStreamPadded64()); +} + +TEST(GetLowerBits, Test) { + EXPECT_EQ(0u, GetLowerBits(255, 0)); + EXPECT_EQ(1u, GetLowerBits(255, 1)); + EXPECT_EQ(3u, GetLowerBits(255, 2)); + EXPECT_EQ(7u, GetLowerBits(255, 3)); + EXPECT_EQ(15u, GetLowerBits(255, 4)); + EXPECT_EQ(31u, GetLowerBits(255, 5)); + EXPECT_EQ(63u, GetLowerBits(255, 6)); + EXPECT_EQ(127u, GetLowerBits(255, 7)); + EXPECT_EQ(255u, GetLowerBits(255, 8)); + EXPECT_EQ(0xFFu, GetLowerBits(0xFFFFFFFF, 8)); + EXPECT_EQ(0xFFFFu, GetLowerBits(0xFFFFFFFF, 16)); + EXPECT_EQ(0xFFFFFFu, GetLowerBits(0xFFFFFFFF, 24)); + EXPECT_EQ(0xFFFFFFu, GetLowerBits(0xFFFFFFFFFFFF, 24)); + EXPECT_EQ(0xFFFFFFFFFFFFFFFFu, + GetLowerBits(0xFFFFFFFFFFFFFFFFu, 64)); + EXPECT_EQ(StreamToBits("1010001110"), + GetLowerBits(StreamToBits("1010001110111101111111"), 10)); +} + +TEST(BitReaderFromString, FromU8) { + std::vector buffer = { + 0xAA, + 0xBB, + 0xCC, + 0xDD, + }; + + const std::string total_stream = + "01010101" + "11011101" + "00110011" + "10111011"; + + BitReaderFromString reader(buffer); + EXPECT_EQ(PadToWord<64>(total_stream), reader.GetStreamPadded64()); + + uint64_t bits = 0; + EXPECT_EQ(2u, reader.ReadBits(&bits, 2)); + EXPECT_EQ(PadToWord<64>("01"), BitsToStream(bits)); + EXPECT_EQ(20u, reader.ReadBits(&bits, 20)); + EXPECT_EQ(PadToWord<64>("01010111011101001100"), BitsToStream(bits)); + EXPECT_EQ(20u, reader.ReadBits(&bits, 20)); + EXPECT_EQ(PadToWord<64>("11101110110000000000"), BitsToStream(bits)); + EXPECT_EQ(22u, reader.ReadBits(&bits, 30)); + EXPECT_EQ(PadToWord<64>("0000000000000000000000"), BitsToStream(bits)); + EXPECT_TRUE(reader.ReachedEnd()); +} + +TEST(BitReaderFromString, FromU64) { + std::vector buffer = { + 0xAAAAAAAAAAAAAAAA, + 0xBBBBBBBBBBBBBBBB, + 0xCCCCCCCCCCCCCCCC, + 0xDDDDDDDDDDDDDDDD, + }; + + const std::string total_stream = + "0101010101010101010101010101010101010101010101010101010101010101" + "1101110111011101110111011101110111011101110111011101110111011101" + "0011001100110011001100110011001100110011001100110011001100110011" + "1011101110111011101110111011101110111011101110111011101110111011"; + + BitReaderFromString reader(buffer); + EXPECT_EQ(total_stream, reader.GetStreamPadded64()); + + uint64_t bits = 0; + size_t pos = 0; + size_t to_read = 5; + while (reader.ReadBits(&bits, to_read) > 0) { + EXPECT_EQ(BitsToStream(bits), + PadToWord<64>(total_stream.substr(pos, to_read))); + pos += to_read; + to_read = (to_read + 35) % 64 + 1; + } + EXPECT_TRUE(reader.ReachedEnd()); +} + +TEST(BitReaderWord64, ReadBitsSingleByte) { + BitReaderWord64 reader(std::vector({uint8_t(0xF0)})); + EXPECT_FALSE(reader.ReachedEnd()); + + uint64_t bits = 0; + EXPECT_EQ(1u, reader.ReadBits(&bits, 1)); + EXPECT_EQ(0u, bits); + EXPECT_EQ(2u, reader.ReadBits(&bits, 2)); + EXPECT_EQ(0u, bits); + EXPECT_EQ(2u, reader.ReadBits(&bits, 2)); + EXPECT_EQ(2u, bits); + EXPECT_EQ(2u, reader.ReadBits(&bits, 2)); + EXPECT_EQ(3u, bits); + EXPECT_FALSE(reader.OnlyZeroesLeft()); + EXPECT_FALSE(reader.ReachedEnd()); + EXPECT_EQ(2u, reader.ReadBits(&bits, 2)); + EXPECT_EQ(1u, bits); + EXPECT_TRUE(reader.OnlyZeroesLeft()); + EXPECT_FALSE(reader.ReachedEnd()); + EXPECT_EQ(55u, reader.ReadBits(&bits, 64)); + EXPECT_EQ(0u, bits); + EXPECT_TRUE(reader.ReachedEnd()); +} + +TEST(BitReaderWord64, ReadBitsetSingleByte) { + BitReaderWord64 reader(std::vector({uint8_t(0xCC)})); + std::bitset<4> bits; + EXPECT_EQ(2u, reader.ReadBitset(&bits, 2)); + EXPECT_EQ(0u, bits.to_ullong()); + EXPECT_EQ(2u, reader.ReadBitset(&bits, 2)); + EXPECT_EQ(3u, bits.to_ullong()); + EXPECT_FALSE(reader.OnlyZeroesLeft()); + EXPECT_EQ(4u, reader.ReadBitset(&bits, 4)); + EXPECT_EQ(12u, bits.to_ullong()); + EXPECT_TRUE(reader.OnlyZeroesLeft()); +} + +TEST(BitReaderWord64, ReadStreamSingleByte) { + BitReaderWord64 reader(std::vector({uint8_t(0xAA)})); + EXPECT_EQ("", reader.ReadStream(0)); + EXPECT_EQ("0", reader.ReadStream(1)); + EXPECT_EQ("101", reader.ReadStream(3)); + EXPECT_EQ("01010000", reader.ReadStream(8)); + EXPECT_TRUE(reader.OnlyZeroesLeft()); + EXPECT_EQ("0000000000000000000000000000000000000000000000000000", + reader.ReadStream(64)); + EXPECT_TRUE(reader.ReachedEnd()); +} + +TEST(BitReaderWord64, ReadStreamEmpty) { + std::vector buffer; + BitReaderWord64 reader(std::move(buffer)); + EXPECT_TRUE(reader.OnlyZeroesLeft()); + EXPECT_TRUE(reader.ReachedEnd()); + EXPECT_EQ("", reader.ReadStream(10)); + EXPECT_TRUE(reader.ReachedEnd()); +} + +TEST(BitReaderWord64, ReadBitsTwoWords) { + std::vector buffer = {0x0000000000000001, 0x0000000000FFFFFF}; + + BitReaderWord64 reader(std::move(buffer)); + + uint64_t bits = 0; + EXPECT_EQ(1u, reader.ReadBits(&bits, 1)); + EXPECT_EQ(1u, bits); + EXPECT_EQ(62u, reader.ReadBits(&bits, 62)); + EXPECT_EQ(0u, bits); + EXPECT_EQ(2u, reader.ReadBits(&bits, 2)); + EXPECT_EQ(2u, bits); + EXPECT_EQ(3u, reader.ReadBits(&bits, 3)); + EXPECT_EQ(7u, bits); + EXPECT_FALSE(reader.OnlyZeroesLeft()); + EXPECT_EQ(32u, reader.ReadBits(&bits, 32)); + EXPECT_EQ(0xFFFFFu, bits); + EXPECT_TRUE(reader.OnlyZeroesLeft()); + EXPECT_FALSE(reader.ReachedEnd()); + EXPECT_EQ(28u, reader.ReadBits(&bits, 32)); + EXPECT_EQ(0u, bits); + EXPECT_TRUE(reader.ReachedEnd()); +} + +TEST(BitReaderFromString, ReadUnencodedU8) { + BitReaderFromString reader("11111110"); + uint8_t val = 0; + ASSERT_TRUE(reader.ReadUnencoded(&val)); + EXPECT_EQ(8u, reader.GetNumReadBits()); + EXPECT_EQ(127, val); +} + +TEST(BitReaderFromString, ReadUnencodedU16Fail) { + BitReaderFromString reader("11111110"); + uint16_t val = 0; + ASSERT_FALSE(reader.ReadUnencoded(&val)); +} + +TEST(BitReaderFromString, ReadUnencodedS64) { + BitReaderFromString reader( + "1110000000000000000000000000000000000000000000000000000000000001"); + int64_t val = 0; + ASSERT_TRUE(reader.ReadUnencoded(&val)); + EXPECT_EQ(64u, reader.GetNumReadBits()); + EXPECT_EQ(std::numeric_limits::min() + 7, val); +} + +TEST(BitReaderWord64, FromU8) { + std::vector buffer = { + 0xAA, + 0xBB, + 0xCC, + 0xDD, + }; + + BitReaderWord64 reader(std::move(buffer)); + + uint64_t bits = 0; + EXPECT_EQ(2u, reader.ReadBits(&bits, 2)); + EXPECT_EQ(PadToWord<64>("01"), BitsToStream(bits)); + EXPECT_EQ(20u, reader.ReadBits(&bits, 20)); + EXPECT_EQ(PadToWord<64>("01010111011101001100"), BitsToStream(bits)); + EXPECT_EQ(20u, reader.ReadBits(&bits, 20)); + EXPECT_EQ(PadToWord<64>("11101110110000000000"), BitsToStream(bits)); + EXPECT_EQ(22u, reader.ReadBits(&bits, 30)); + EXPECT_EQ(PadToWord<64>("0000000000000000000000"), BitsToStream(bits)); + EXPECT_TRUE(reader.ReachedEnd()); +} + +TEST(BitReaderWord64, FromU64) { + std::vector buffer = { + 0xAAAAAAAAAAAAAAAA, + 0xBBBBBBBBBBBBBBBB, + 0xCCCCCCCCCCCCCCCC, + 0xDDDDDDDDDDDDDDDD, + }; + + const std::string total_stream = + "0101010101010101010101010101010101010101010101010101010101010101" + "1101110111011101110111011101110111011101110111011101110111011101" + "0011001100110011001100110011001100110011001100110011001100110011" + "1011101110111011101110111011101110111011101110111011101110111011"; + + BitReaderWord64 reader(std::move(buffer)); + + uint64_t bits = 0; + size_t pos = 0; + size_t to_read = 5; + while (reader.ReadBits(&bits, to_read) > 0) { + EXPECT_EQ(BitsToStream(bits), + PadToWord<64>(total_stream.substr(pos, to_read))); + pos += to_read; + to_read = (to_read + 35) % 64 + 1; + } + EXPECT_TRUE(reader.ReachedEnd()); +} + +TEST(BitReaderWord64, ComparisonLotsOfU8) { + std::vector buffer; + for (uint32_t i = 0; i < 10003; ++i) { + buffer.push_back(static_cast(i % 255)); + } + + BitReaderFromString reader1(buffer); + BitReaderWord64 reader2(std::move(buffer)); + + uint64_t bits1 = 0, bits2 = 0; + size_t to_read = 5; + while (reader1.ReadBits(&bits1, to_read) > 0) { + reader2.ReadBits(&bits2, to_read); + EXPECT_EQ(bits1, bits2); + to_read = (to_read + 35) % 64 + 1; + } + + EXPECT_EQ(0u, reader2.ReadBits(&bits2, 1)); +} + +TEST(BitReaderWord64, ComparisonLotsOfU64) { + std::vector buffer; + for (uint64_t i = 0; i < 1000; ++i) { + buffer.push_back(i); + } + + BitReaderFromString reader1(buffer); + BitReaderWord64 reader2(std::move(buffer)); + + uint64_t bits1 = 0, bits2 = 0; + size_t to_read = 5; + while (reader1.ReadBits(&bits1, to_read) > 0) { + reader2.ReadBits(&bits2, to_read); + EXPECT_EQ(bits1, bits2); + to_read = (to_read + 35) % 64 + 1; + } + + EXPECT_EQ(0u, reader2.ReadBits(&bits2, 1)); +} + +TEST(ReadWriteWord64, ReadWriteLotsOfBits) { + BitWriterWord64 writer(16384); + for (uint64_t i = 0; i < 65000; i += 25) { + const uint64_t num_bits = i % 64 + 1; + const uint64_t bits = i >> (64 - num_bits); + writer.WriteBits(bits, size_t(num_bits)); + } + + BitReaderWord64 reader(writer.GetDataCopy()); + for (uint64_t i = 0; i < 65000; i += 25) { + const uint64_t num_bits = i % 64 + 1; + const uint64_t expected_bits = i >> (64 - num_bits); + uint64_t bits = 0; + reader.ReadBits(&bits, size_t(num_bits)); + EXPECT_EQ(expected_bits, bits); + } + + EXPECT_TRUE(reader.OnlyZeroesLeft()); +} + +TEST(VariableWidthWrite, Write0U) { + BitWriterStringStream writer; + writer.WriteVariableWidthU64(0, 2); + EXPECT_EQ("000", writer.GetStreamRaw()); + writer.WriteVariableWidthU32(0, 2); + EXPECT_EQ( + "000" + "000", + writer.GetStreamRaw()); + writer.WriteVariableWidthU16(0, 2); + EXPECT_EQ( + "000" + "000" + "000", + writer.GetStreamRaw()); + writer.WriteVariableWidthU8(0, 2); + EXPECT_EQ( + "000" + "000" + "000" + "000", + writer.GetStreamRaw()); +} + +TEST(VariableWidthWrite, Write0S) { + BitWriterStringStream writer; + writer.WriteVariableWidthS64(0, 2, 0); + EXPECT_EQ("000", writer.GetStreamRaw()); + writer.WriteVariableWidthS32(0, 2, 0); + EXPECT_EQ( + "000" + "000", + writer.GetStreamRaw()); + writer.WriteVariableWidthS16(0, 2, 0); + EXPECT_EQ( + "000" + "000" + "000", + writer.GetStreamRaw()); + writer.WriteVariableWidthS8(0, 2, 0); + EXPECT_EQ( + "000" + "000" + "000" + "000", + writer.GetStreamRaw()); +} + +TEST(VariableWidthWrite, WriteSmallUnsigned) { + BitWriterStringStream writer; + writer.WriteVariableWidthU64(1, 2); + EXPECT_EQ("100", writer.GetStreamRaw()); + writer.WriteVariableWidthU32(2, 2); + EXPECT_EQ( + "100" + "010", + writer.GetStreamRaw()); + writer.WriteVariableWidthU16(3, 2); + EXPECT_EQ( + "100" + "010" + "110", + writer.GetStreamRaw()); + writer.WriteVariableWidthU8(4, 2); + EXPECT_EQ( + "100" + "010" + "110" + "001100", + writer.GetStreamRaw()); +} + +TEST(VariableWidthWrite, WriteSmallSigned) { + BitWriterStringStream writer; + writer.WriteVariableWidthS64(1, 2, 0); + EXPECT_EQ("010", writer.GetStreamRaw()); + writer.WriteVariableWidthS64(-1, 2, 0); + EXPECT_EQ( + "010" + "100", + writer.GetStreamRaw()); + writer.WriteVariableWidthS16(3, 2, 0); + EXPECT_EQ( + "010" + "100" + "011100", + writer.GetStreamRaw()); + writer.WriteVariableWidthS8(-4, 2, 0); + EXPECT_EQ( + "010" + "100" + "011100" + "111100", + writer.GetStreamRaw()); +} + +TEST(VariableWidthWrite, U64Val127ChunkLength7) { + BitWriterStringStream writer; + writer.WriteVariableWidthU64(127, 7); + EXPECT_EQ( + "1111111" + "0", + writer.GetStreamRaw()); +} + +TEST(VariableWidthWrite, U32Val255ChunkLength7) { + BitWriterStringStream writer; + writer.WriteVariableWidthU32(255, 7); + EXPECT_EQ( + "1111111" + "1" + "1000000" + "0", + writer.GetStreamRaw()); +} + +TEST(VariableWidthWrite, U16Val2ChunkLength4) { + BitWriterStringStream writer; + writer.WriteVariableWidthU16(2, 4); + EXPECT_EQ( + "0100" + "0", + writer.GetStreamRaw()); +} + +TEST(VariableWidthWrite, U8Val128ChunkLength7) { + BitWriterStringStream writer; + writer.WriteVariableWidthU8(128, 7); + EXPECT_EQ( + "0000000" + "1" + "1", + writer.GetStreamRaw()); +} + +TEST(VariableWidthWrite, U64ValAAAAChunkLength2) { + BitWriterStringStream writer; + writer.WriteVariableWidthU64(0xAAAA, 2); + EXPECT_EQ( + "01" + "1" + "01" + "1" + "01" + "1" + "01" + "1" + "01" + "1" + "01" + "1" + "01" + "1" + "01" + "0", + writer.GetStreamRaw()); +} + +TEST(VariableWidthWrite, S8ValM128ChunkLength7) { + BitWriterStringStream writer; + writer.WriteVariableWidthS8(-128, 7, 0); + EXPECT_EQ( + "1111111" + "1" + "1", + writer.GetStreamRaw()); +} + +TEST(VariableWidthRead, U64Val127ChunkLength7) { + BitReaderFromString reader( + "1111111" + "0"); + uint64_t val = 0; + ASSERT_TRUE(reader.ReadVariableWidthU64(&val, 7)); + EXPECT_EQ(127u, val); +} + +TEST(VariableWidthRead, U32Val255ChunkLength7) { + BitReaderFromString reader( + "1111111" + "1" + "1000000" + "0"); + uint32_t val = 0; + ASSERT_TRUE(reader.ReadVariableWidthU32(&val, 7)); + EXPECT_EQ(255u, val); +} + +TEST(VariableWidthRead, U16Val2ChunkLength4) { + BitReaderFromString reader( + "0100" + "0"); + uint16_t val = 0; + ASSERT_TRUE(reader.ReadVariableWidthU16(&val, 4)); + EXPECT_EQ(2u, val); +} + +TEST(VariableWidthRead, U8Val128ChunkLength7) { + BitReaderFromString reader( + "0000000" + "1" + "1"); + uint8_t val = 0; + ASSERT_TRUE(reader.ReadVariableWidthU8(&val, 7)); + EXPECT_EQ(128u, val); +} + +TEST(VariableWidthRead, U64ValAAAAChunkLength2) { + BitReaderFromString reader( + "01" + "1" + "01" + "1" + "01" + "1" + "01" + "1" + "01" + "1" + "01" + "1" + "01" + "1" + "01" + "0"); + uint64_t val = 0; + ASSERT_TRUE(reader.ReadVariableWidthU64(&val, 2)); + EXPECT_EQ(0xAAAAu, val); +} + +TEST(VariableWidthRead, S8ValM128ChunkLength7) { + BitReaderFromString reader( + "1111111" + "1" + "1"); + int8_t val = 0; + ASSERT_TRUE(reader.ReadVariableWidthS8(&val, 7, 0)); + EXPECT_EQ(-128, val); +} + +TEST(VariableWidthRead, FailTooShort) { + BitReaderFromString reader("00000001100000"); + uint64_t val = 0; + ASSERT_FALSE(reader.ReadVariableWidthU64(&val, 7)); +} + +TEST(VariableWidthWriteRead, SingleWriteReadU64) { + for (uint64_t i = 0; i < 1000000; i += 1234) { + const uint64_t val = i * i * i; + const size_t chunk_length = size_t(i % 16 + 1); + + BitWriterWord64 writer; + writer.WriteVariableWidthU64(val, chunk_length); + + BitReaderWord64 reader(writer.GetDataCopy()); + uint64_t read_val = 0; + ASSERT_TRUE(reader.ReadVariableWidthU64(&read_val, chunk_length)); + + ASSERT_EQ(val, read_val) << "Chunk length " << chunk_length; + } +} + +TEST(VariableWidthWriteRead, SingleWriteReadS64) { + for (int64_t i = 0; i < 1000000; i += 4321) { + const int64_t val = i * i * (i % 2 ? -i : i); + const size_t chunk_length = size_t(i % 16 + 1); + const size_t zigzag_exponent = size_t(i % 13); + + BitWriterWord64 writer; + writer.WriteVariableWidthS64(val, chunk_length, zigzag_exponent); + + BitReaderWord64 reader(writer.GetDataCopy()); + int64_t read_val = 0; + ASSERT_TRUE( + reader.ReadVariableWidthS64(&read_val, chunk_length, zigzag_exponent)); + + ASSERT_EQ(val, read_val) << "Chunk length " << chunk_length; + } +} + +TEST(VariableWidthWriteRead, SingleWriteReadU32) { + for (uint32_t i = 0; i < 100000; i += 123) { + const uint32_t val = i * i; + const size_t chunk_length = i % 16 + 1; + + BitWriterWord64 writer; + writer.WriteVariableWidthU32(val, chunk_length); + + BitReaderWord64 reader(writer.GetDataCopy()); + uint32_t read_val = 0; + ASSERT_TRUE(reader.ReadVariableWidthU32(&read_val, chunk_length)); + + ASSERT_EQ(val, read_val) << "Chunk length " << chunk_length; + } +} + +TEST(VariableWidthWriteRead, SingleWriteReadS32) { + for (int32_t i = 0; i < 100000; i += 123) { + const int32_t val = i * (i % 2 ? -i : i); + const size_t chunk_length = i % 16 + 1; + const size_t zigzag_exponent = i % 11; + + BitWriterWord64 writer; + writer.WriteVariableWidthS32(val, chunk_length, zigzag_exponent); + + BitReaderWord64 reader(writer.GetDataCopy()); + int32_t read_val = 0; + ASSERT_TRUE( + reader.ReadVariableWidthS32(&read_val, chunk_length, zigzag_exponent)); + + ASSERT_EQ(val, read_val) << "Chunk length " << chunk_length; + } +} + +TEST(VariableWidthWriteRead, SingleWriteReadU16) { + for (int i = 0; i < 65536; i += 123) { + const uint16_t val = static_cast(i); + const size_t chunk_length = val % 10 + 1; + + BitWriterWord64 writer; + writer.WriteVariableWidthU16(val, chunk_length); + + BitReaderWord64 reader(writer.GetDataCopy()); + uint16_t read_val = 0; + ASSERT_TRUE(reader.ReadVariableWidthU16(&read_val, chunk_length)); + + ASSERT_EQ(val, read_val) << "Chunk length " << chunk_length; + } +} + +TEST(VariableWidthWriteRead, SingleWriteReadS16) { + for (int i = -32768; i < 32768; i += 123) { + const int16_t val = static_cast(i); + const size_t chunk_length = std::abs(i) % 10 + 1; + const size_t zigzag_exponent = std::abs(i) % 7; + + BitWriterWord64 writer; + writer.WriteVariableWidthS16(val, chunk_length, zigzag_exponent); + + BitReaderWord64 reader(writer.GetDataCopy()); + int16_t read_val = 0; + ASSERT_TRUE( + reader.ReadVariableWidthS16(&read_val, chunk_length, zigzag_exponent)); + + ASSERT_EQ(val, read_val) << "Chunk length " << chunk_length; + } +} + +TEST(VariableWidthWriteRead, SingleWriteReadU8) { + for (int i = 0; i < 256; ++i) { + const uint8_t val = static_cast(i); + const size_t chunk_length = val % 5 + 1; + + BitWriterWord64 writer; + writer.WriteVariableWidthU8(val, chunk_length); + + BitReaderWord64 reader(writer.GetDataCopy()); + uint8_t read_val = 0; + ASSERT_TRUE(reader.ReadVariableWidthU8(&read_val, chunk_length)); + + ASSERT_EQ(val, read_val) << "Chunk length " << chunk_length; + } +} + +TEST(VariableWidthWriteRead, SingleWriteReadS8) { + for (int i = -128; i < 128; ++i) { + const int8_t val = static_cast(i); + const size_t chunk_length = std::abs(i) % 5 + 1; + const size_t zigzag_exponent = std::abs(i) % 3; + + BitWriterWord64 writer; + writer.WriteVariableWidthS8(val, chunk_length, zigzag_exponent); + + BitReaderWord64 reader(writer.GetDataCopy()); + int8_t read_val = 0; + ASSERT_TRUE( + reader.ReadVariableWidthS8(&read_val, chunk_length, zigzag_exponent)); + + ASSERT_EQ(val, read_val) << "Chunk length " << chunk_length; + } +} + +TEST(VariableWidthWriteRead, SmallNumbersChunkLength4) { + const std::vector expected_values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + + BitWriterWord64 writer; + for (uint64_t val : expected_values) { + writer.WriteVariableWidthU64(val, 4); + } + + EXPECT_EQ(50u, writer.GetNumBits()); + + std::vector actual_values; + BitReaderWord64 reader(writer.GetDataCopy()); + while (!reader.OnlyZeroesLeft()) { + uint64_t val = 0; + ASSERT_TRUE(reader.ReadVariableWidthU64(&val, 4)); + actual_values.push_back(val); + } + + EXPECT_EQ(expected_values, actual_values); +} + +TEST(VariableWidthWriteRead, VariedNumbersChunkLength8) { + const std::vector expected_values = {1000, 0, 255, 4294967296}; + const size_t kExpectedNumBits = 9 * (2 + 1 + 1 + 5); + + BitWriterWord64 writer; + for (uint64_t val : expected_values) { + writer.WriteVariableWidthU64(val, 8); + } + + EXPECT_EQ(kExpectedNumBits, writer.GetNumBits()); + + std::vector actual_values; + BitReaderWord64 reader(writer.GetDataCopy()); + while (!reader.OnlyZeroesLeft()) { + uint64_t val = 0; + ASSERT_TRUE(reader.ReadVariableWidthU64(&val, 8)); + actual_values.push_back(val); + } + + EXPECT_EQ(expected_values, actual_values); +} + +TEST(FixedWidthWrite, Val0Max3) { + BitWriterStringStream writer; + writer.WriteFixedWidth(0, 3); + EXPECT_EQ("00", writer.GetStreamRaw()); +} + +TEST(FixedWidthWrite, Val0Max5) { + BitWriterStringStream writer; + writer.WriteFixedWidth(0, 5); + EXPECT_EQ("000", writer.GetStreamRaw()); +} + +TEST(FixedWidthWrite, Val0Max255) { + BitWriterStringStream writer; + writer.WriteFixedWidth(0, 255); + EXPECT_EQ("00000000", writer.GetStreamRaw()); +} + +TEST(FixedWidthWrite, Val3Max8) { + BitWriterStringStream writer; + writer.WriteFixedWidth(3, 8); + EXPECT_EQ("1100", writer.GetStreamRaw()); +} + +TEST(FixedWidthWrite, Val15Max127) { + BitWriterStringStream writer; + writer.WriteFixedWidth(15, 127); + EXPECT_EQ("1111000", writer.GetStreamRaw()); +} + +TEST(FixedWidthRead, Val0Max3) { + BitReaderFromString reader("0011111"); + uint64_t val = 0; + ASSERT_TRUE(reader.ReadFixedWidth(&val, 3)); + EXPECT_EQ(0u, val); +} + +TEST(FixedWidthRead, Val0Max5) { + BitReaderFromString reader("0001010101"); + uint64_t val = 0; + ASSERT_TRUE(reader.ReadFixedWidth(&val, 5)); + EXPECT_EQ(0u, val); +} + +TEST(FixedWidthRead, Val3Max8) { + BitReaderFromString reader("11001010101"); + uint64_t val = 0; + ASSERT_TRUE(reader.ReadFixedWidth(&val, 8)); + EXPECT_EQ(3u, val); +} + +TEST(FixedWidthRead, Val15Max127) { + BitReaderFromString reader("111100010101"); + uint64_t val = 0; + ASSERT_TRUE(reader.ReadFixedWidth(&val, 127)); + EXPECT_EQ(15u, val); +} + +TEST(FixedWidthRead, Fail) { + BitReaderFromString reader("111100"); + uint64_t val = 0; + ASSERT_FALSE(reader.ReadFixedWidth(&val, 127)); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/c_interface_test.cpp b/3rdparty/spirv-tools/test/c_interface_test.cpp new file mode 100644 index 000000000..9260549d6 --- /dev/null +++ b/3rdparty/spirv-tools/test/c_interface_test.cpp @@ -0,0 +1,298 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "message.h" +#include "spirv-tools/libspirv.h" +#include "table.h" + +namespace { + +using namespace spvtools; + +// TODO(antiagainst): Use public C API for setting the consumer once exists. +#ifndef SPIRV_TOOLS_SHAREDLIB +void SetContextMessageConsumer(spv_context context, + spvtools::MessageConsumer consumer) { + libspirv::SetContextMessageConsumer(context, consumer); +} +#else +void SetContextMessageConsumer(spv_context, spvtools::MessageConsumer) {} +#endif + +// The default consumer is a null std::function. +TEST(CInterface, DefaultConsumerNullDiagnosticForValidInput) { + auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1); + const char input_text[] = + "OpCapability Shader\n" + "OpCapability Linkage\n" + "OpMemoryModel Logical GLSL450"; + + spv_binary binary = nullptr; + EXPECT_EQ(SPV_SUCCESS, spvTextToBinary(context, input_text, + sizeof(input_text), &binary, nullptr)); + + { + // Sadly the compiler don't allow me to feed binary directly to + // spvValidate(). + spv_const_binary_t b{binary->code, binary->wordCount}; + EXPECT_EQ(SPV_SUCCESS, spvValidate(context, &b, nullptr)); + } + + spv_text text = nullptr; + EXPECT_EQ(SPV_SUCCESS, spvBinaryToText(context, binary->code, + binary->wordCount, 0, &text, nullptr)); + + spvTextDestroy(text); + spvBinaryDestroy(binary); + spvContextDestroy(context); +} + +// The default consumer is a null std::function. +TEST(CInterface, DefaultConsumerNullDiagnosticForInvalidAssembling) { + auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1); + const char input_text[] = "%1 = OpName"; + + spv_binary binary = nullptr; + EXPECT_EQ(SPV_ERROR_INVALID_TEXT, + spvTextToBinary(context, input_text, sizeof(input_text), &binary, + nullptr)); + spvBinaryDestroy(binary); + spvContextDestroy(context); +} + +// The default consumer is a null std::function. +TEST(CInterface, DefaultConsumerNullDiagnosticForInvalidDiassembling) { + auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1); + const char input_text[] = "OpNop"; + + spv_binary binary = nullptr; + ASSERT_EQ(SPV_SUCCESS, spvTextToBinary(context, input_text, + sizeof(input_text), &binary, nullptr)); + // Change OpNop to an invalid (wordcount|opcode) word. + binary->code[binary->wordCount - 1] = 0xffffffff; + + spv_text text = nullptr; + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, + spvBinaryToText(context, binary->code, binary->wordCount, 0, &text, + nullptr)); + + spvTextDestroy(text); + spvBinaryDestroy(binary); + spvContextDestroy(context); +} + +// The default consumer is a null std::function. +TEST(CInterface, DefaultConsumerNullDiagnosticForInvalidValidating) { + auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1); + const char input_text[] = "OpNop"; + + spv_binary binary = nullptr; + ASSERT_EQ(SPV_SUCCESS, spvTextToBinary(context, input_text, + sizeof(input_text), &binary, nullptr)); + + spv_const_binary_t b{binary->code, binary->wordCount}; + EXPECT_EQ(SPV_ERROR_INVALID_LAYOUT, spvValidate(context, &b, nullptr)); + + spvBinaryDestroy(binary); + spvContextDestroy(context); +} + +TEST(CInterface, SpecifyConsumerNullDiagnosticForAssembling) { + const char input_text[] = "%1 = OpName\n"; + + auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1); + int invocation = 0; + SetContextMessageConsumer( + context, + [&invocation](spv_message_level_t level, const char* source, + const spv_position_t& position, const char* message) { + ++invocation; + EXPECT_EQ(SPV_MSG_ERROR, level); + // The error happens at scanning the begining of second line. + EXPECT_STREQ("input", source); + EXPECT_EQ(1u, position.line); + EXPECT_EQ(0u, position.column); + EXPECT_EQ(12u, position.index); + EXPECT_STREQ("Expected operand, found end of stream.", message); + }); + + spv_binary binary = nullptr; + EXPECT_EQ(SPV_ERROR_INVALID_TEXT, + spvTextToBinary(context, input_text, sizeof(input_text), &binary, + nullptr)); +#ifndef SPIRV_TOOLS_SHAREDLIB + EXPECT_EQ(1, invocation); +#endif + spvBinaryDestroy(binary); + spvContextDestroy(context); +} + +TEST(CInterface, SpecifyConsumerNullDiagnosticForDisassembling) { + const char input_text[] = "OpNop"; + + auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1); + int invocation = 0; + SetContextMessageConsumer( + context, + [&invocation](spv_message_level_t level, const char* source, + const spv_position_t& position, const char* message) { + ++invocation; + EXPECT_EQ(SPV_MSG_ERROR, level); + EXPECT_STREQ("input", source); + EXPECT_EQ(0u, position.line); + EXPECT_EQ(0u, position.column); + EXPECT_EQ(5u, position.index); + EXPECT_STREQ("Invalid opcode: 65535", message); + }); + + spv_binary binary = nullptr; + ASSERT_EQ(SPV_SUCCESS, spvTextToBinary(context, input_text, + sizeof(input_text), &binary, nullptr)); + // Change OpNop to an invalid (wordcount|opcode) word. + binary->code[binary->wordCount - 1] = 0xffffffff; + + spv_text text = nullptr; + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, + spvBinaryToText(context, binary->code, binary->wordCount, 0, &text, + nullptr)); +#ifndef SPIRV_TOOLS_SHAREDLIB + EXPECT_EQ(1, invocation); +#endif + + spvTextDestroy(text); + spvBinaryDestroy(binary); + spvContextDestroy(context); +} + +TEST(CInterface, SpecifyConsumerNullDiagnosticForValidating) { + const char input_text[] = "OpNop"; + + auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1); + int invocation = 0; + SetContextMessageConsumer( + context, + [&invocation](spv_message_level_t level, const char* source, + const spv_position_t& position, const char* message) { + ++invocation; + EXPECT_EQ(SPV_MSG_ERROR, level); + EXPECT_STREQ("input", source); + EXPECT_EQ(0u, position.line); + EXPECT_EQ(0u, position.column); + // TODO(antiagainst): what validation reports is not a word offset here. + // It is inconsistent with diassembler. Should be fixed. + EXPECT_EQ(1u, position.index); + EXPECT_STREQ("Nop cannot appear before the memory model instruction", + message); + }); + + spv_binary binary = nullptr; + ASSERT_EQ(SPV_SUCCESS, spvTextToBinary(context, input_text, + sizeof(input_text), &binary, nullptr)); + + spv_const_binary_t b{binary->code, binary->wordCount}; + EXPECT_EQ(SPV_ERROR_INVALID_LAYOUT, spvValidate(context, &b, nullptr)); +#ifndef SPIRV_TOOLS_SHAREDLIB + EXPECT_EQ(1, invocation); +#endif + + spvBinaryDestroy(binary); + spvContextDestroy(context); +} + +// When having both a consumer and an diagnostic object, the diagnostic object +// should take priority. +TEST(CInterface, SpecifyConsumerSpecifyDiagnosticForAssembling) { + const char input_text[] = "%1 = OpName"; + + auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1); + int invocation = 0; + SetContextMessageConsumer( + context, + [&invocation](spv_message_level_t, const char*, const spv_position_t&, + const char*) { ++invocation; }); + + spv_binary binary = nullptr; + spv_diagnostic diagnostic = nullptr; + EXPECT_EQ(SPV_ERROR_INVALID_TEXT, + spvTextToBinary(context, input_text, sizeof(input_text), &binary, + &diagnostic)); + EXPECT_EQ(0, invocation); // Consumer should not be invoked at all. + EXPECT_STREQ("Expected operand, found end of stream.", diagnostic->error); + + spvDiagnosticDestroy(diagnostic); + spvBinaryDestroy(binary); + spvContextDestroy(context); +} + +TEST(CInterface, SpecifyConsumerSpecifyDiagnosticForDisassembling) { + const char input_text[] = "OpNop"; + + auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1); + int invocation = 0; + SetContextMessageConsumer( + context, + [&invocation](spv_message_level_t, const char*, const spv_position_t&, + const char*) { ++invocation; }); + + spv_binary binary = nullptr; + ASSERT_EQ(SPV_SUCCESS, spvTextToBinary(context, input_text, + sizeof(input_text), &binary, nullptr)); + // Change OpNop to an invalid (wordcount|opcode) word. + binary->code[binary->wordCount - 1] = 0xffffffff; + + spv_diagnostic diagnostic = nullptr; + spv_text text = nullptr; + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, + spvBinaryToText(context, binary->code, binary->wordCount, 0, &text, + &diagnostic)); + + EXPECT_EQ(0, invocation); // Consumer should not be invoked at all. + EXPECT_STREQ("Invalid opcode: 65535", diagnostic->error); + + spvTextDestroy(text); + spvDiagnosticDestroy(diagnostic); + spvBinaryDestroy(binary); + spvContextDestroy(context); +} + +TEST(CInterface, SpecifyConsumerSpecifyDiagnosticForValidating) { + const char input_text[] = "OpNop"; + + auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_1); + int invocation = 0; + SetContextMessageConsumer( + context, + [&invocation](spv_message_level_t, const char*, const spv_position_t&, + const char*) { ++invocation; }); + + spv_binary binary = nullptr; + ASSERT_EQ(SPV_SUCCESS, spvTextToBinary(context, input_text, + sizeof(input_text), &binary, nullptr)); + + spv_diagnostic diagnostic = nullptr; + spv_const_binary_t b{binary->code, binary->wordCount}; + EXPECT_EQ(SPV_ERROR_INVALID_LAYOUT, spvValidate(context, &b, &diagnostic)); + + EXPECT_EQ(0, invocation); // Consumer should not be invoked at all. + EXPECT_STREQ("Nop cannot appear before the memory model instruction", + diagnostic->error); + + spvDiagnosticDestroy(diagnostic); + spvBinaryDestroy(binary); + spvContextDestroy(context); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/comment_test.cpp b/3rdparty/spirv-tools/test/comment_test.cpp new file mode 100644 index 000000000..f60b7918b --- /dev/null +++ b/3rdparty/spirv-tools/test/comment_test.cpp @@ -0,0 +1,46 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "gmock/gmock.h" +#include "test_fixture.h" +#include "unit_spirv.h" + +namespace { + +using spvtest::Concatenate; +using spvtest::MakeInstruction; +using spvtest::MakeVector; +using spvtest::TextToBinaryTest; +using testing::Eq; + +TEST_F(TextToBinaryTest, Whitespace) { + std::string input = R"( +; I'm a proud comment at the beginning of the file +; I hide: OpCapability Shader + OpMemoryModel Logical Simple ; comment after instruction +;;;;;;;; many ;'s + %glsl450 = OpExtInstImport "GLSL.std.450" + ; comment indented +)"; + + EXPECT_THAT( + CompiledInstructions(input), + Eq(Concatenate({MakeInstruction(SpvOpMemoryModel, + {uint32_t(SpvAddressingModelLogical), + uint32_t(SpvMemoryModelSimple)}), + MakeInstruction(SpvOpExtInstImport, {1}, + MakeVector("GLSL.std.450"))}))); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/comp/CMakeLists.txt b/3rdparty/spirv-tools/test/comp/CMakeLists.txt new file mode 100644 index 000000000..c947fde0c --- /dev/null +++ b/3rdparty/spirv-tools/test/comp/CMakeLists.txt @@ -0,0 +1,29 @@ +# Copyright (c) 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(VAL_TEST_COMMON_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/../test_fixture.h + ${CMAKE_CURRENT_SOURCE_DIR}/../unit_spirv.h +) + +if(SPIRV_BUILD_COMPRESSION) + add_spvtools_unittest(TARGET markv_codec + SRCS + markv_codec_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../tools/comp/markv_model_factory.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../tools/comp/markv_model_shader.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS SPIRV-Tools-comp ${SPIRV_TOOLS} + ) +endif(SPIRV_BUILD_COMPRESSION) diff --git a/3rdparty/spirv-tools/test/comp/markv_codec_test.cpp b/3rdparty/spirv-tools/test/comp/markv_codec_test.cpp new file mode 100644 index 000000000..a313d6ee3 --- /dev/null +++ b/3rdparty/spirv-tools/test/comp/markv_codec_test.cpp @@ -0,0 +1,828 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Tests for unique type declaration rules validator. + +#include +#include +#include + +#include "gmock/gmock.h" +#include "source/comp/markv.h" +#include "test_fixture.h" +#include "tools/comp/markv_model_factory.h" +#include "unit_spirv.h" + +namespace { + +using libspirv::SetContextMessageConsumer; +using spvtest::ScopedContext; +using spvtools::MarkvModelType; +using MarkvTest = ::testing::TestWithParam; + +void DiagnosticsMessageHandler(spv_message_level_t level, const char*, + const spv_position_t& position, + const char* message) { + switch (level) { + case SPV_MSG_FATAL: + case SPV_MSG_INTERNAL_ERROR: + case SPV_MSG_ERROR: + std::cerr << "error: " << position.index << ": " << message << std::endl; + break; + case SPV_MSG_WARNING: + std::cout << "warning: " << position.index << ": " << message + << std::endl; + break; + case SPV_MSG_INFO: + std::cout << "info: " << position.index << ": " << message << std::endl; + break; + default: + break; + } +} + +// Compiles |code| to SPIR-V |words|. +void Compile(const std::string& code, std::vector* words, + uint32_t options = SPV_TEXT_TO_BINARY_OPTION_NONE, + spv_target_env env = SPV_ENV_UNIVERSAL_1_2) { + ScopedContext ctx(env); + SetContextMessageConsumer(ctx.context, DiagnosticsMessageHandler); + + spv_binary spirv_binary; + ASSERT_EQ(SPV_SUCCESS, + spvTextToBinaryWithOptions(ctx.context, code.c_str(), code.size(), + options, &spirv_binary, nullptr)); + + *words = std::vector(spirv_binary->code, + spirv_binary->code + spirv_binary->wordCount); + + spvBinaryDestroy(spirv_binary); +} + +// Disassembles SPIR-V |words| to |out_text|. +void Disassemble(const std::vector& words, std::string* out_text, + spv_target_env env = SPV_ENV_UNIVERSAL_1_2) { + ScopedContext ctx(env); + SetContextMessageConsumer(ctx.context, DiagnosticsMessageHandler); + + spv_text text = nullptr; + ASSERT_EQ(SPV_SUCCESS, spvBinaryToText(ctx.context, words.data(), + words.size(), 0, &text, nullptr)); + assert(text); + + *out_text = std::string(text->str, text->length); + spvTextDestroy(text); +} + +// Encodes/decodes |original|, assembles/dissasembles |original|, then compares +// the results of the two operations. +void TestEncodeDecode(MarkvModelType model_type, + const std::string& original_text) { + ScopedContext ctx(SPV_ENV_UNIVERSAL_1_2); + std::unique_ptr model = + spvtools::CreateMarkvModel(model_type); + spvtools::MarkvCodecOptions options; + + std::vector expected_binary; + Compile(original_text, &expected_binary); + ASSERT_FALSE(expected_binary.empty()); + + std::string expected_text; + Disassemble(expected_binary, &expected_text); + ASSERT_FALSE(expected_text.empty()); + + std::vector binary_to_encode; + Compile(original_text, &binary_to_encode, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ASSERT_FALSE(binary_to_encode.empty()); + + std::stringstream encoder_comments; + const auto output_to_string_stream = + [&encoder_comments](const std::string& str) { encoder_comments << str; }; + + std::vector markv; + ASSERT_EQ(SPV_SUCCESS, spvtools::SpirvToMarkv( + ctx.context, binary_to_encode, options, *model, + DiagnosticsMessageHandler, output_to_string_stream, + spvtools::MarkvDebugConsumer(), &markv)); + ASSERT_FALSE(markv.empty()); + + std::vector decoded_binary; + ASSERT_EQ(SPV_SUCCESS, + spvtools::MarkvToSpirv( + ctx.context, markv, options, *model, DiagnosticsMessageHandler, + spvtools::MarkvLogConsumer(), spvtools::MarkvDebugConsumer(), + &decoded_binary)); + ASSERT_FALSE(decoded_binary.empty()); + + EXPECT_EQ(expected_binary, decoded_binary) << encoder_comments.str(); + + std::string decoded_text; + Disassemble(decoded_binary, &decoded_text); + ASSERT_FALSE(decoded_text.empty()); + + EXPECT_EQ(expected_text, decoded_text) << encoder_comments.str(); +} + +void TestEncodeDecodeShaderMainBody(MarkvModelType model_type, + const std::string& body) { + const std::string prefix = + R"( +OpCapability Shader +OpCapability Int64 +OpCapability Float64 +%ext_inst = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +%void = OpTypeVoid +%func = OpTypeFunction %void +%bool = OpTypeBool +%f32 = OpTypeFloat 32 +%u32 = OpTypeInt 32 0 +%s32 = OpTypeInt 32 1 +%f64 = OpTypeFloat 64 +%u64 = OpTypeInt 64 0 +%s64 = OpTypeInt 64 1 +%boolvec2 = OpTypeVector %bool 2 +%s32vec2 = OpTypeVector %s32 2 +%u32vec2 = OpTypeVector %u32 2 +%f32vec2 = OpTypeVector %f32 2 +%f64vec2 = OpTypeVector %f64 2 +%boolvec3 = OpTypeVector %bool 3 +%u32vec3 = OpTypeVector %u32 3 +%s32vec3 = OpTypeVector %s32 3 +%f32vec3 = OpTypeVector %f32 3 +%f64vec3 = OpTypeVector %f64 3 +%boolvec4 = OpTypeVector %bool 4 +%u32vec4 = OpTypeVector %u32 4 +%s32vec4 = OpTypeVector %s32 4 +%f32vec4 = OpTypeVector %f32 4 +%f64vec4 = OpTypeVector %f64 4 + +%f32_0 = OpConstant %f32 0 +%f32_1 = OpConstant %f32 1 +%f32_2 = OpConstant %f32 2 +%f32_3 = OpConstant %f32 3 +%f32_4 = OpConstant %f32 4 +%f32_pi = OpConstant %f32 3.14159 + +%s32_0 = OpConstant %s32 0 +%s32_1 = OpConstant %s32 1 +%s32_2 = OpConstant %s32 2 +%s32_3 = OpConstant %s32 3 +%s32_4 = OpConstant %s32 4 +%s32_m1 = OpConstant %s32 -1 + +%u32_0 = OpConstant %u32 0 +%u32_1 = OpConstant %u32 1 +%u32_2 = OpConstant %u32 2 +%u32_3 = OpConstant %u32 3 +%u32_4 = OpConstant %u32 4 + +%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1 +%u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2 +%u32vec3_012 = OpConstantComposite %u32vec3 %u32_0 %u32_1 %u32_2 +%u32vec3_123 = OpConstantComposite %u32vec3 %u32_1 %u32_2 %u32_3 +%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3 +%u32vec4_1234 = OpConstantComposite %u32vec4 %u32_1 %u32_2 %u32_3 %u32_4 + +%s32vec2_01 = OpConstantComposite %s32vec2 %s32_0 %s32_1 +%s32vec2_12 = OpConstantComposite %s32vec2 %s32_1 %s32_2 +%s32vec3_012 = OpConstantComposite %s32vec3 %s32_0 %s32_1 %s32_2 +%s32vec3_123 = OpConstantComposite %s32vec3 %s32_1 %s32_2 %s32_3 +%s32vec4_0123 = OpConstantComposite %s32vec4 %s32_0 %s32_1 %s32_2 %s32_3 +%s32vec4_1234 = OpConstantComposite %s32vec4 %s32_1 %s32_2 %s32_3 %s32_4 + +%f32vec2_01 = OpConstantComposite %f32vec2 %f32_0 %f32_1 +%f32vec2_12 = OpConstantComposite %f32vec2 %f32_1 %f32_2 +%f32vec3_012 = OpConstantComposite %f32vec3 %f32_0 %f32_1 %f32_2 +%f32vec3_123 = OpConstantComposite %f32vec3 %f32_1 %f32_2 %f32_3 +%f32vec4_0123 = OpConstantComposite %f32vec4 %f32_0 %f32_1 %f32_2 %f32_3 +%f32vec4_1234 = OpConstantComposite %f32vec4 %f32_1 %f32_2 %f32_3 %f32_4 + +%main = OpFunction %void None %func +%main_entry = OpLabel)"; + + const std::string suffix = + R"( +OpReturn +OpFunctionEnd)"; + + TestEncodeDecode(model_type, prefix + body + suffix); +} + +TEST_P(MarkvTest, U32Literal) { + TestEncodeDecode(GetParam(), R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +%u32 = OpTypeInt 32 0 +%100 = OpConstant %u32 0 +%200 = OpConstant %u32 1 +%300 = OpConstant %u32 4294967295 +)"); +} + +TEST_P(MarkvTest, S32Literal) { + TestEncodeDecode(GetParam(), R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +%s32 = OpTypeInt 32 1 +%100 = OpConstant %s32 0 +%200 = OpConstant %s32 1 +%300 = OpConstant %s32 -1 +%400 = OpConstant %s32 2147483647 +%500 = OpConstant %s32 -2147483648 +)"); +} + +TEST_P(MarkvTest, U64Literal) { + TestEncodeDecode(GetParam(), R"( +OpCapability Shader +OpCapability Linkage +OpCapability Int64 +OpMemoryModel Logical GLSL450 +%u64 = OpTypeInt 64 0 +%100 = OpConstant %u64 0 +%200 = OpConstant %u64 1 +%300 = OpConstant %u64 18446744073709551615 +)"); +} + +TEST_P(MarkvTest, S64Literal) { + TestEncodeDecode(GetParam(), R"( +OpCapability Shader +OpCapability Linkage +OpCapability Int64 +OpMemoryModel Logical GLSL450 +%s64 = OpTypeInt 64 1 +%100 = OpConstant %s64 0 +%200 = OpConstant %s64 1 +%300 = OpConstant %s64 -1 +%400 = OpConstant %s64 9223372036854775807 +%500 = OpConstant %s64 -9223372036854775808 +)"); +} + +TEST_P(MarkvTest, U16Literal) { + TestEncodeDecode(GetParam(), R"( +OpCapability Shader +OpCapability Linkage +OpCapability Int16 +OpMemoryModel Logical GLSL450 +%u16 = OpTypeInt 16 0 +%100 = OpConstant %u16 0 +%200 = OpConstant %u16 1 +%300 = OpConstant %u16 65535 +)"); +} + +TEST_P(MarkvTest, S16Literal) { + TestEncodeDecode(GetParam(), R"( +OpCapability Shader +OpCapability Linkage +OpCapability Int16 +OpMemoryModel Logical GLSL450 +%s16 = OpTypeInt 16 1 +%100 = OpConstant %s16 0 +%200 = OpConstant %s16 1 +%300 = OpConstant %s16 -1 +%400 = OpConstant %s16 32767 +%500 = OpConstant %s16 -32768 +)"); +} + +TEST_P(MarkvTest, F32Literal) { + TestEncodeDecode(GetParam(), R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +%f32 = OpTypeFloat 32 +%100 = OpConstant %f32 0 +%200 = OpConstant %f32 1 +%300 = OpConstant %f32 0.1 +%400 = OpConstant %f32 -0.1 +)"); +} + +TEST_P(MarkvTest, F64Literal) { + TestEncodeDecode(GetParam(), R"( +OpCapability Shader +OpCapability Linkage +OpCapability Float64 +OpMemoryModel Logical GLSL450 +%f64 = OpTypeFloat 64 +%100 = OpConstant %f64 0 +%200 = OpConstant %f64 1 +%300 = OpConstant %f64 0.1 +%400 = OpConstant %f64 -0.1 +)"); +} + +TEST_P(MarkvTest, F16Literal) { + TestEncodeDecode(GetParam(), R"( +OpCapability Shader +OpCapability Linkage +OpCapability Float16 +OpMemoryModel Logical GLSL450 +%f16 = OpTypeFloat 16 +%100 = OpConstant %f16 0 +%200 = OpConstant %f16 1 +%300 = OpConstant %f16 0.1 +%400 = OpConstant %f16 -0.1 +)"); +} + +TEST_P(MarkvTest, StringLiteral) { + TestEncodeDecode(GetParam(), R"( +OpCapability Shader +OpCapability Linkage +OpExtension "SPV_KHR_16bit_storage" +OpExtension "xxx" +OpExtension "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +OpExtension "" +OpMemoryModel Logical GLSL450 +)"); +} + +TEST_P(MarkvTest, WithFunction) { + TestEncodeDecode(GetParam(), R"( +OpCapability Addresses +OpCapability Kernel +OpCapability GenericPointer +OpCapability Linkage +OpExtension "SPV_KHR_16bit_storage" +OpMemoryModel Physical32 OpenCL +%f32 = OpTypeFloat 32 +%u32 = OpTypeInt 32 0 +%void = OpTypeVoid +%void_func = OpTypeFunction %void +%100 = OpConstant %u32 1 +%200 = OpConstant %u32 2 +%main = OpFunction %void None %void_func +%entry_main = OpLabel +%300 = OpIAdd %u32 %100 %200 +OpReturn +OpFunctionEnd +)"); +} + +TEST_P(MarkvTest, WithMultipleFunctions) { + TestEncodeDecode(GetParam(), R"( +OpCapability Addresses +OpCapability Kernel +OpCapability GenericPointer +OpCapability Linkage +OpMemoryModel Physical32 OpenCL +%f32 = OpTypeFloat 32 +%one = OpConstant %f32 1 +%void = OpTypeVoid +%void_func = OpTypeFunction %void +%f32_func = OpTypeFunction %f32 %f32 +%sqr_plus_one = OpFunction %f32 None %f32_func +%x = OpFunctionParameter %f32 +%100 = OpLabel +%x2 = OpFMul %f32 %x %x +%x2p1 = OpFunctionCall %f32 %plus_one %x2 +OpReturnValue %x2p1 +OpFunctionEnd +%plus_one = OpFunction %f32 None %f32_func +%y = OpFunctionParameter %f32 +%200 = OpLabel +%yp1 = OpFAdd %f32 %y %one +OpReturnValue %yp1 +OpFunctionEnd +%main = OpFunction %void None %void_func +%entry_main = OpLabel +%1p1 = OpFunctionCall %f32 %sqr_plus_one %one +OpReturn +OpFunctionEnd +)"); +} + +TEST_P(MarkvTest, ForwardDeclaredId) { + TestEncodeDecode(GetParam(), R"( +OpCapability Addresses +OpCapability Kernel +OpCapability GenericPointer +OpCapability Linkage +OpMemoryModel Physical32 OpenCL +OpEntryPoint Kernel %1 "simple_kernel" +%2 = OpTypeInt 32 0 +%3 = OpTypeVector %2 2 +%4 = OpConstant %2 2 +%5 = OpTypeArray %2 %4 +%6 = OpTypeVoid +%7 = OpTypeFunction %6 +%1 = OpFunction %6 None %7 +%8 = OpLabel +OpReturn +OpFunctionEnd +)"); +} + +TEST_P(MarkvTest, WithSwitch) { + TestEncodeDecode(GetParam(), R"( +OpCapability Addresses +OpCapability Kernel +OpCapability GenericPointer +OpCapability Linkage +OpCapability Int64 +OpMemoryModel Physical32 OpenCL +%u64 = OpTypeInt 64 0 +%void = OpTypeVoid +%void_func = OpTypeFunction %void +%val = OpConstant %u64 1 +%main = OpFunction %void None %void_func +%entry_main = OpLabel +OpSwitch %val %default 1 %case1 1000000000000 %case2 +%case1 = OpLabel +OpNop +OpBranch %after_switch +%case2 = OpLabel +OpNop +OpBranch %after_switch +%default = OpLabel +OpNop +OpBranch %after_switch +%after_switch = OpLabel +OpReturn +OpFunctionEnd +)"); +} + +TEST_P(MarkvTest, WithLoop) { + TestEncodeDecode(GetParam(), R"( +OpCapability Addresses +OpCapability Kernel +OpCapability GenericPointer +OpCapability Linkage +OpMemoryModel Physical32 OpenCL +%void = OpTypeVoid +%void_func = OpTypeFunction %void +%main = OpFunction %void None %void_func +%entry_main = OpLabel +OpLoopMerge %merge %continue DontUnroll|DependencyLength 10 +OpBranch %begin_loop +%begin_loop = OpLabel +OpNop +OpBranch %continue +%continue = OpLabel +OpNop +OpBranch %begin_loop +%merge = OpLabel +OpReturn +OpFunctionEnd +)"); +} + +TEST_P(MarkvTest, WithDecorate) { + TestEncodeDecode(GetParam(), R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 ArrayStride 4 +OpDecorate %1 Uniform +%2 = OpTypeFloat 32 +%1 = OpTypeRuntimeArray %2 +)"); +} + +TEST_P(MarkvTest, WithExtInst) { + TestEncodeDecode(GetParam(), R"( +OpCapability Addresses +OpCapability Kernel +OpCapability GenericPointer +OpCapability Linkage +%opencl = OpExtInstImport "OpenCL.std" +OpMemoryModel Physical32 OpenCL +%f32 = OpTypeFloat 32 +%void = OpTypeVoid +%void_func = OpTypeFunction %void +%100 = OpConstant %f32 1.1 +%main = OpFunction %void None %void_func +%entry_main = OpLabel +%200 = OpExtInst %f32 %opencl cos %100 +OpReturn +OpFunctionEnd +)"); +} + +TEST_P(MarkvTest, F32Mul) { + TestEncodeDecodeShaderMainBody(GetParam(), R"( +%val1 = OpFMul %f32 %f32_0 %f32_1 +%val2 = OpFMul %f32 %f32_2 %f32_0 +%val3 = OpFMul %f32 %f32_pi %f32_2 +%val4 = OpFMul %f32 %f32_1 %f32_1 +)"); +} + +TEST_P(MarkvTest, U32Mul) { + TestEncodeDecodeShaderMainBody(GetParam(), R"( +%val1 = OpIMul %u32 %u32_0 %u32_1 +%val2 = OpIMul %u32 %u32_2 %u32_0 +%val3 = OpIMul %u32 %u32_3 %u32_2 +%val4 = OpIMul %u32 %u32_1 %u32_1 +)"); +} + +TEST_P(MarkvTest, S32Mul) { + TestEncodeDecodeShaderMainBody(GetParam(), R"( +%val1 = OpIMul %s32 %s32_0 %s32_1 +%val2 = OpIMul %s32 %s32_2 %s32_0 +%val3 = OpIMul %s32 %s32_m1 %s32_2 +%val4 = OpIMul %s32 %s32_1 %s32_1 +)"); +} + +TEST_P(MarkvTest, F32Add) { + TestEncodeDecodeShaderMainBody(GetParam(), R"( +%val1 = OpFAdd %f32 %f32_0 %f32_1 +%val2 = OpFAdd %f32 %f32_2 %f32_0 +%val3 = OpFAdd %f32 %f32_pi %f32_2 +%val4 = OpFAdd %f32 %f32_1 %f32_1 +)"); +} + +TEST_P(MarkvTest, U32Add) { + TestEncodeDecodeShaderMainBody(GetParam(), R"( +%val1 = OpIAdd %u32 %u32_0 %u32_1 +%val2 = OpIAdd %u32 %u32_2 %u32_0 +%val3 = OpIAdd %u32 %u32_3 %u32_2 +%val4 = OpIAdd %u32 %u32_1 %u32_1 +)"); +} + +TEST_P(MarkvTest, S32Add) { + TestEncodeDecodeShaderMainBody(GetParam(), R"( +%val1 = OpIAdd %s32 %s32_0 %s32_1 +%val2 = OpIAdd %s32 %s32_2 %s32_0 +%val3 = OpIAdd %s32 %s32_m1 %s32_2 +%val4 = OpIAdd %s32 %s32_1 %s32_1 +)"); +} + +TEST_P(MarkvTest, F32Dot) { + TestEncodeDecodeShaderMainBody(GetParam(), R"( +%dot2_1 = OpDot %f32 %f32vec2_01 %f32vec2_12 +%dot2_2 = OpDot %f32 %f32vec2_01 %f32vec2_01 +%dot2_3 = OpDot %f32 %f32vec2_12 %f32vec2_12 +%dot3_1 = OpDot %f32 %f32vec3_012 %f32vec3_123 +%dot3_2 = OpDot %f32 %f32vec3_012 %f32vec3_012 +%dot3_3 = OpDot %f32 %f32vec3_123 %f32vec3_123 +%dot4_1 = OpDot %f32 %f32vec4_0123 %f32vec4_1234 +%dot4_2 = OpDot %f32 %f32vec4_0123 %f32vec4_0123 +%dot4_3 = OpDot %f32 %f32vec4_1234 %f32vec4_1234 +)"); +} + +TEST_P(MarkvTest, F32VectorCompositeConstruct) { + TestEncodeDecodeShaderMainBody(GetParam(), R"( +%cc1 = OpCompositeConstruct %f32vec4 %f32vec2_01 %f32vec2_12 +%cc2 = OpCompositeConstruct %f32vec3 %f32vec2_01 %f32_2 +%cc3 = OpCompositeConstruct %f32vec2 %f32_1 %f32_2 +%cc4 = OpCompositeConstruct %f32vec4 %f32_1 %f32_2 %cc3 +)"); +} + +TEST_P(MarkvTest, U32VectorCompositeConstruct) { + TestEncodeDecodeShaderMainBody(GetParam(), R"( +%cc1 = OpCompositeConstruct %u32vec4 %u32vec2_01 %u32vec2_12 +%cc2 = OpCompositeConstruct %u32vec3 %u32vec2_01 %u32_2 +%cc3 = OpCompositeConstruct %u32vec2 %u32_1 %u32_2 +%cc4 = OpCompositeConstruct %u32vec4 %u32_1 %u32_2 %cc3 +)"); +} + +TEST_P(MarkvTest, S32VectorCompositeConstruct) { + TestEncodeDecodeShaderMainBody(GetParam(), R"( +%cc1 = OpCompositeConstruct %u32vec4 %u32vec2_01 %u32vec2_12 +%cc2 = OpCompositeConstruct %u32vec3 %u32vec2_01 %u32_2 +%cc3 = OpCompositeConstruct %u32vec2 %u32_1 %u32_2 +%cc4 = OpCompositeConstruct %u32vec4 %u32_1 %u32_2 %cc3 +)"); +} + +TEST_P(MarkvTest, F32VectorCompositeExtract) { + TestEncodeDecodeShaderMainBody(GetParam(), R"( +%f32vec4_3210 = OpCompositeConstruct %f32vec4 %f32_3 %f32_2 %f32_1 %f32_0 +%f32vec3_013 = OpCompositeExtract %f32vec3 %f32vec4_0123 0 1 3 +)"); +} + +TEST_P(MarkvTest, F32VectorComparison) { + TestEncodeDecodeShaderMainBody(GetParam(), R"( +%f32vec4_3210 = OpCompositeConstruct %f32vec4 %f32_3 %f32_2 %f32_1 %f32_0 +%c1 = OpFOrdEqual %boolvec4 %f32vec4_0123 %f32vec4_3210 +%c2 = OpFUnordEqual %boolvec4 %f32vec4_0123 %f32vec4_3210 +%c3 = OpFOrdNotEqual %boolvec4 %f32vec4_0123 %f32vec4_3210 +%c4 = OpFUnordNotEqual %boolvec4 %f32vec4_0123 %f32vec4_3210 +%c5 = OpFOrdLessThan %boolvec4 %f32vec4_0123 %f32vec4_3210 +%c6 = OpFUnordLessThan %boolvec4 %f32vec4_0123 %f32vec4_3210 +%c7 = OpFOrdGreaterThan %boolvec4 %f32vec4_0123 %f32vec4_3210 +%c8 = OpFUnordGreaterThan %boolvec4 %f32vec4_0123 %f32vec4_3210 +%c9 = OpFOrdLessThanEqual %boolvec4 %f32vec4_0123 %f32vec4_3210 +%c10 = OpFUnordLessThanEqual %boolvec4 %f32vec4_0123 %f32vec4_3210 +%c11 = OpFOrdGreaterThanEqual %boolvec4 %f32vec4_0123 %f32vec4_3210 +%c12 = OpFUnordGreaterThanEqual %boolvec4 %f32vec4_0123 %f32vec4_3210 +)"); +} + +TEST_P(MarkvTest, VectorShuffle) { + TestEncodeDecodeShaderMainBody(GetParam(), R"( +%f32vec4_3210 = OpCompositeConstruct %f32vec4 %f32_3 %f32_2 %f32_1 %f32_0 +%sh1 = OpVectorShuffle %f32vec2 %f32vec4_0123 %f32vec4_3210 3 6 +%sh2 = OpVectorShuffle %f32vec3 %f32vec2_01 %f32vec4_3210 0 3 4 +)"); +} + +TEST_P(MarkvTest, VectorTimesScalar) { + TestEncodeDecodeShaderMainBody(GetParam(), R"( +%f32vec4_3210 = OpCompositeConstruct %f32vec4 %f32_3 %f32_2 %f32_1 %f32_0 +%res1 = OpVectorTimesScalar %f32vec4 %f32vec4_0123 %f32_2 +%res2 = OpVectorTimesScalar %f32vec4 %f32vec4_3210 %f32_2 +)"); +} + +TEST_P(MarkvTest, SpirvSpecSample) { + TestEncodeDecode(GetParam(), R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %31 %33 %42 %57 + OpExecutionMode %4 OriginLowerLeft + +; Debug information + OpSource GLSL 450 + OpName %4 "main" + OpName %9 "scale" + OpName %17 "S" + OpMemberName %17 0 "b" + OpMemberName %17 1 "v" + OpMemberName %17 2 "i" + OpName %18 "blockName" + OpMemberName %18 0 "s" + OpMemberName %18 1 "cond" + OpName %20 "" + OpName %31 "color" + OpName %33 "color1" + OpName %42 "color2" + OpName %48 "i" + OpName %57 "multiplier" + +; Annotations (non-debug) + OpDecorate %15 ArrayStride 16 + OpMemberDecorate %17 0 Offset 0 + OpMemberDecorate %17 1 Offset 16 + OpMemberDecorate %17 2 Offset 96 + OpMemberDecorate %18 0 Offset 0 + OpMemberDecorate %18 1 Offset 112 + OpDecorate %18 Block + OpDecorate %20 DescriptorSet 0 + OpDecorate %42 NoPerspective + +; All types, variables, and constants + %2 = OpTypeVoid + %3 = OpTypeFunction %2 ; void () + %6 = OpTypeFloat 32 ; 32-bit float + %7 = OpTypeVector %6 4 ; vec4 + %8 = OpTypePointer Function %7 ; function-local vec4* + %10 = OpConstant %6 1 + %11 = OpConstant %6 2 + %12 = OpConstantComposite %7 %10 %10 %11 %10 ; vec4(1.0, 1.0, 2.0, 1.0) + %13 = OpTypeInt 32 0 ; 32-bit int, sign-less + %14 = OpConstant %13 5 + %15 = OpTypeArray %7 %14 + %16 = OpTypeInt 32 1 + %17 = OpTypeStruct %13 %15 %16 + %18 = OpTypeStruct %17 %13 + %19 = OpTypePointer Uniform %18 + %20 = OpVariable %19 Uniform + %21 = OpConstant %16 1 + %22 = OpTypePointer Uniform %13 + %25 = OpTypeBool + %26 = OpConstant %13 0 + %30 = OpTypePointer Output %7 + %31 = OpVariable %30 Output + %32 = OpTypePointer Input %7 + %33 = OpVariable %32 Input + %35 = OpConstant %16 0 + %36 = OpConstant %16 2 + %37 = OpTypePointer Uniform %7 + %42 = OpVariable %32 Input + %47 = OpTypePointer Function %16 + %55 = OpConstant %16 4 + %57 = OpVariable %32 Input + +; All functions + %4 = OpFunction %2 None %3 ; main() + %5 = OpLabel + %9 = OpVariable %8 Function + %48 = OpVariable %47 Function + OpStore %9 %12 + %23 = OpAccessChain %22 %20 %21 ; location of cond + %24 = OpLoad %13 %23 ; load 32-bit int from cond + %27 = OpINotEqual %25 %24 %26 ; convert to bool + OpSelectionMerge %29 None ; structured if + OpBranchConditional %27 %28 %41 ; if cond + %28 = OpLabel ; then + %34 = OpLoad %7 %33 + %38 = OpAccessChain %37 %20 %35 %21 %36 ; s.v[2] + %39 = OpLoad %7 %38 + %40 = OpFAdd %7 %34 %39 + OpStore %31 %40 + OpBranch %29 + %41 = OpLabel ; else + %43 = OpLoad %7 %42 + %44 = OpExtInst %7 %1 Sqrt %43 ; extended instruction sqrt + %45 = OpLoad %7 %9 + %46 = OpFMul %7 %44 %45 + OpStore %31 %46 + OpBranch %29 + %29 = OpLabel ; endif + OpStore %48 %35 + OpBranch %49 + %49 = OpLabel + OpLoopMerge %51 %52 None ; structured loop + OpBranch %53 + %53 = OpLabel + %54 = OpLoad %16 %48 + %56 = OpSLessThan %25 %54 %55 ; i < 4 ? + OpBranchConditional %56 %50 %51 ; body or break + %50 = OpLabel ; body + %58 = OpLoad %7 %57 + %59 = OpLoad %7 %31 + %60 = OpFMul %7 %59 %58 + OpStore %31 %60 + OpBranch %52 + %52 = OpLabel ; continue target + %61 = OpLoad %16 %48 + %62 = OpIAdd %16 %61 %21 ; ++i + OpStore %48 %62 + OpBranch %49 ; loop back + %51 = OpLabel ; loop merge point + OpReturn + OpFunctionEnd +)"); +} + +TEST_P(MarkvTest, SampleFromDeadBranchEliminationTest) { + TestEncodeDecode(GetParam(), R"( +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%5 = OpTypeFunction %void +%bool = OpTypeBool +%true = OpConstantTrue %bool +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%float_0 = OpConstant %float 0 +%12 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 +%float_1 = OpConstant %float 1 +%14 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +%_ptr_Input_v4float = OpTypePointer Input %v4float +%main = OpFunction %void None %5 +%17 = OpLabel +OpSelectionMerge %18 None +OpBranchConditional %true %19 %20 +%19 = OpLabel +OpBranch %18 +%20 = OpLabel +OpBranch %18 +%18 = OpLabel +%21 = OpPhi %v4float %12 %19 %14 %20 +OpStore %gl_FragColor %21 +OpReturn +OpFunctionEnd +)"); +} + +INSTANTIATE_TEST_CASE_P(AllMarkvModels, MarkvTest, + ::testing::ValuesIn(std::vector{ + spvtools::kMarkvModelShaderLite, + spvtools::kMarkvModelShaderMid, + spvtools::kMarkvModelShaderMax, + }), ); + +} // namespace diff --git a/3rdparty/spirv-tools/test/cpp_interface_test.cpp b/3rdparty/spirv-tools/test/cpp_interface_test.cpp new file mode 100644 index 000000000..2bab430b2 --- /dev/null +++ b/3rdparty/spirv-tools/test/cpp_interface_test.cpp @@ -0,0 +1,365 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "spirv-tools/optimizer.hpp" +#include "spirv/1.1/spirv.h" + +namespace { + +using namespace spvtools; +using ::testing::ContainerEq; +using ::testing::HasSubstr; + +TEST(CppInterface, SuccessfulRoundTrip) { + const std::string input_text = "%2 = OpSizeOf %1 %3\n"; + SpirvTools t(SPV_ENV_UNIVERSAL_1_1); + + std::vector binary; + EXPECT_TRUE(t.Assemble(input_text, &binary)); + EXPECT_TRUE(binary.size() > 5u); + EXPECT_EQ(SpvMagicNumber, binary[0]); + EXPECT_EQ(SpvVersion, binary[1]); + + // This cannot pass validation since %1 is not defined. + t.SetMessageConsumer([](spv_message_level_t level, const char* source, + const spv_position_t& position, const char* message) { + EXPECT_EQ(SPV_MSG_ERROR, level); + EXPECT_STREQ("input", source); + EXPECT_EQ(0u, position.line); + EXPECT_EQ(0u, position.column); + EXPECT_EQ(1u, position.index); + EXPECT_STREQ("ID 1 has not been defined", message); + }); + EXPECT_FALSE(t.Validate(binary)); + + std::string output_text; + EXPECT_TRUE(t.Disassemble(binary, &output_text)); + EXPECT_EQ(input_text, output_text); +} + +TEST(CppInterface, AssembleEmptyModule) { + std::vector binary(10, 42); + SpirvTools t(SPV_ENV_UNIVERSAL_1_1); + EXPECT_TRUE(t.Assemble("", &binary)); + // We only have the header. + EXPECT_EQ(5u, binary.size()); + EXPECT_EQ(SpvMagicNumber, binary[0]); + EXPECT_EQ(SpvVersion, binary[1]); +} + +TEST(CppInterface, AssembleOverloads) { + const std::string input_text = "%2 = OpSizeOf %1 %3\n"; + SpirvTools t(SPV_ENV_UNIVERSAL_1_1); + { + std::vector binary; + EXPECT_TRUE(t.Assemble(input_text, &binary)); + EXPECT_TRUE(binary.size() > 5u); + EXPECT_EQ(SpvMagicNumber, binary[0]); + EXPECT_EQ(SpvVersion, binary[1]); + } + { + std::vector binary; + EXPECT_TRUE(t.Assemble(input_text.data(), input_text.size(), &binary)); + EXPECT_TRUE(binary.size() > 5u); + EXPECT_EQ(SpvMagicNumber, binary[0]); + EXPECT_EQ(SpvVersion, binary[1]); + } + { // Ignore the last newline. + std::vector binary; + EXPECT_TRUE(t.Assemble(input_text.data(), input_text.size() - 1, &binary)); + EXPECT_TRUE(binary.size() > 5u); + EXPECT_EQ(SpvMagicNumber, binary[0]); + EXPECT_EQ(SpvVersion, binary[1]); + } +} + +TEST(CppInterface, AssembleWithWrongTargetEnv) { + const std::string input_text = "%r = OpSizeOf %type %pointer"; + SpirvTools t(SPV_ENV_UNIVERSAL_1_0); + int invocation_count = 0; + t.SetMessageConsumer( + [&invocation_count](spv_message_level_t level, const char* source, + const spv_position_t& position, const char* message) { + ++invocation_count; + EXPECT_EQ(SPV_MSG_ERROR, level); + EXPECT_STREQ("input", source); + EXPECT_EQ(0u, position.line); + EXPECT_EQ(5u, position.column); + EXPECT_EQ(5u, position.index); + EXPECT_STREQ("Invalid Opcode name 'OpSizeOf'", message); + }); + + std::vector binary = {42, 42}; + EXPECT_FALSE(t.Assemble(input_text, &binary)); + EXPECT_THAT(binary, ContainerEq(std::vector{42, 42})); + EXPECT_EQ(1, invocation_count); +} + +TEST(CppInterface, DisassembleEmptyModule) { + std::string text(10, 'x'); + SpirvTools t(SPV_ENV_UNIVERSAL_1_1); + int invocation_count = 0; + t.SetMessageConsumer( + [&invocation_count](spv_message_level_t level, const char* source, + const spv_position_t& position, const char* message) { + ++invocation_count; + EXPECT_EQ(SPV_MSG_ERROR, level); + EXPECT_STREQ("input", source); + EXPECT_EQ(0u, position.line); + EXPECT_EQ(0u, position.column); + EXPECT_EQ(0u, position.index); + EXPECT_STREQ("Missing module.", message); + }); + EXPECT_FALSE(t.Disassemble({}, &text)); + EXPECT_EQ("xxxxxxxxxx", text); // The original string is unmodified. + EXPECT_EQ(1, invocation_count); +} + +TEST(CppInterface, DisassembleOverloads) { + const std::string input_text = "%2 = OpSizeOf %1 %3\n"; + SpirvTools t(SPV_ENV_UNIVERSAL_1_1); + + std::vector binary; + EXPECT_TRUE(t.Assemble(input_text, &binary)); + + { + std::string output_text; + EXPECT_TRUE(t.Disassemble(binary, &output_text)); + EXPECT_EQ(input_text, output_text); + } + { + std::string output_text; + EXPECT_TRUE(t.Disassemble(binary.data(), binary.size(), &output_text)); + EXPECT_EQ(input_text, output_text); + } +} + +TEST(CppInterface, DisassembleWithWrongTargetEnv) { + const std::string input_text = "%r = OpSizeOf %type %pointer"; + SpirvTools t11(SPV_ENV_UNIVERSAL_1_1); + SpirvTools t10(SPV_ENV_UNIVERSAL_1_0); + int invocation_count = 0; + t10.SetMessageConsumer( + [&invocation_count](spv_message_level_t level, const char* source, + const spv_position_t& position, const char* message) { + ++invocation_count; + EXPECT_EQ(SPV_MSG_ERROR, level); + EXPECT_STREQ("input", source); + EXPECT_EQ(0u, position.line); + EXPECT_EQ(0u, position.column); + EXPECT_EQ(5u, position.index); + EXPECT_STREQ("Invalid opcode: 321", message); + }); + + std::vector binary; + EXPECT_TRUE(t11.Assemble(input_text, &binary)); + + std::string output_text(10, 'x'); + EXPECT_FALSE(t10.Disassemble(binary, &output_text)); + EXPECT_EQ("xxxxxxxxxx", output_text); // The original string is unmodified. +} + +TEST(CppInterface, SuccessfulValidation) { + const std::string input_text = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450)"; + SpirvTools t(SPV_ENV_UNIVERSAL_1_1); + int invocation_count = 0; + t.SetMessageConsumer([&invocation_count](spv_message_level_t, const char*, + const spv_position_t&, const char*) { + ++invocation_count; + }); + + std::vector binary; + EXPECT_TRUE(t.Assemble(input_text, &binary)); + EXPECT_TRUE(t.Validate(binary)); + EXPECT_EQ(0, invocation_count); +} + +TEST(CppInterface, ValidateOverloads) { + const std::string input_text = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450)"; + SpirvTools t(SPV_ENV_UNIVERSAL_1_1); + std::vector binary; + EXPECT_TRUE(t.Assemble(input_text, &binary)); + + { EXPECT_TRUE(t.Validate(binary)); } + { EXPECT_TRUE(t.Validate(binary.data(), binary.size())); } +} + +TEST(CppInterface, ValidateEmptyModule) { + SpirvTools t(SPV_ENV_UNIVERSAL_1_1); + int invocation_count = 0; + t.SetMessageConsumer( + [&invocation_count](spv_message_level_t level, const char* source, + const spv_position_t& position, const char* message) { + ++invocation_count; + EXPECT_EQ(SPV_MSG_ERROR, level); + EXPECT_STREQ("input", source); + EXPECT_EQ(0u, position.line); + EXPECT_EQ(0u, position.column); + EXPECT_EQ(0u, position.index); + EXPECT_STREQ("Invalid SPIR-V magic number.", message); + }); + EXPECT_FALSE(t.Validate({})); + EXPECT_EQ(1, invocation_count); +} + +// Returns the assembly for a SPIR-V module with a struct declaration +// with the given number of members. +std::string MakeModuleHavingStruct(int num_members) { + std::stringstream os; + os << R"(OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + %1 = OpTypeInt 32 0 + %2 = OpTypeStruct)"; + for (int i = 0; i < num_members; i++) os << " %1"; + return os.str(); +} + +TEST(CppInterface, ValidateWithOptionsPass) { + SpirvTools t(SPV_ENV_UNIVERSAL_1_1); + std::vector binary; + EXPECT_TRUE(t.Assemble(MakeModuleHavingStruct(10), &binary)); + const spvtools::ValidatorOptions opts; + + EXPECT_TRUE(t.Validate(binary.data(), binary.size(), opts)); +} + +TEST(CppInterface, ValidateWithOptionsFail) { + SpirvTools t(SPV_ENV_UNIVERSAL_1_1); + std::vector binary; + EXPECT_TRUE(t.Assemble(MakeModuleHavingStruct(10), &binary)); + spvtools::ValidatorOptions opts; + opts.SetUniversalLimit(spv_validator_limit_max_struct_members, 9); + std::stringstream os; + t.SetMessageConsumer([&os](spv_message_level_t, const char*, + const spv_position_t&, + const char* message) { os << message; }); + + EXPECT_FALSE(t.Validate(binary.data(), binary.size(), opts)); + EXPECT_THAT( + os.str(), + HasSubstr( + "Number of OpTypeStruct members (10) has exceeded the limit (9)")); +} + +// Checks that after running the given optimizer |opt| on the given |original| +// source code, we can get the given |optimized| source code. +void CheckOptimization(const char* original, const char* optimized, + const Optimizer& opt) { + SpirvTools t(SPV_ENV_UNIVERSAL_1_1); + std::vector original_binary; + ASSERT_TRUE(t.Assemble(original, &original_binary)); + + std::vector optimized_binary; + EXPECT_TRUE(opt.Run(original_binary.data(), original_binary.size(), + &optimized_binary)); + + std::string optimized_text; + EXPECT_TRUE(t.Disassemble(optimized_binary, &optimized_text)); + EXPECT_EQ(optimized, optimized_text); +} + +TEST(CppInterface, OptimizeEmptyModule) { + SpirvTools t(SPV_ENV_UNIVERSAL_1_1); + std::vector binary; + EXPECT_TRUE(t.Assemble("", &binary)); + + Optimizer o(SPV_ENV_UNIVERSAL_1_1); + o.RegisterPass(CreateStripDebugInfoPass()); + EXPECT_TRUE(o.Run(binary.data(), binary.size(), &binary)); +} + +TEST(CppInterface, OptimizeModifiedModule) { + Optimizer o(SPV_ENV_UNIVERSAL_1_1); + o.RegisterPass(CreateStripDebugInfoPass()); + CheckOptimization("OpSource GLSL 450", "", o); +} + +TEST(CppInterface, OptimizeMulitplePasses) { + const char* original_text = + "OpSource GLSL 450 " + "OpDecorate %true SpecId 1 " + "%bool = OpTypeBool " + "%true = OpSpecConstantTrue %bool"; + + Optimizer o(SPV_ENV_UNIVERSAL_1_1); + o.RegisterPass(CreateStripDebugInfoPass()) + .RegisterPass(CreateFreezeSpecConstantValuePass()); + + const char* expected_text = + "%bool = OpTypeBool\n" + "%true = OpConstantTrue %bool\n"; + + CheckOptimization(original_text, expected_text, o); +} + +TEST(CppInterface, OptimizeDoNothingWithPassToken) { + CreateFreezeSpecConstantValuePass(); + auto token = CreateUnifyConstantPass(); +} + +TEST(CppInterface, OptimizeReassignPassToken) { + auto token = CreateNullPass(); + token = CreateStripDebugInfoPass(); + + CheckOptimization( + "OpSource GLSL 450", "", + Optimizer(SPV_ENV_UNIVERSAL_1_1).RegisterPass(std::move(token))); +} + +TEST(CppInterface, OptimizeMoveConstructPassToken) { + auto token1 = CreateStripDebugInfoPass(); + Optimizer::PassToken token2(std::move(token1)); + + CheckOptimization( + "OpSource GLSL 450", "", + Optimizer(SPV_ENV_UNIVERSAL_1_1).RegisterPass(std::move(token2))); +} + +TEST(CppInterface, OptimizeMoveAssignPassToken) { + auto token1 = CreateStripDebugInfoPass(); + auto token2 = CreateNullPass(); + token2 = std::move(token1); + + CheckOptimization( + "OpSource GLSL 450", "", + Optimizer(SPV_ENV_UNIVERSAL_1_1).RegisterPass(std::move(token2))); +} + +TEST(CppInterface, OptimizeSameAddressForOriginalOptimizedBinary) { + SpirvTools t(SPV_ENV_UNIVERSAL_1_1); + std::vector binary; + ASSERT_TRUE(t.Assemble("OpSource GLSL 450", &binary)); + + EXPECT_TRUE(Optimizer(SPV_ENV_UNIVERSAL_1_1) + .RegisterPass(CreateStripDebugInfoPass()) + .Run(binary.data(), binary.size(), &binary)); + + std::string optimized_text; + EXPECT_TRUE(t.Disassemble(binary, &optimized_text)); + EXPECT_EQ("", optimized_text); +} + +// TODO(antiagainst): tests for SetMessageConsumer(). + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/diagnostic_test.cpp b/3rdparty/spirv-tools/test/diagnostic_test.cpp new file mode 100644 index 000000000..8b8dbbe34 --- /dev/null +++ b/3rdparty/spirv-tools/test/diagnostic_test.cpp @@ -0,0 +1,106 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "gmock/gmock.h" +#include "unit_spirv.h" + +namespace { + +using libspirv::DiagnosticStream; +using ::testing::Eq; + +// Returns a newly created diagnostic value. +spv_diagnostic MakeValidDiagnostic() { + spv_position_t position = {}; + spv_diagnostic diagnostic = spvDiagnosticCreate(&position, ""); + EXPECT_NE(nullptr, diagnostic); + return diagnostic; +} + +TEST(Diagnostic, DestroyNull) { spvDiagnosticDestroy(nullptr); } + +TEST(Diagnostic, DestroyValidDiagnostic) { + spv_diagnostic diagnostic = MakeValidDiagnostic(); + spvDiagnosticDestroy(diagnostic); + // We aren't allowed to use the diagnostic pointer anymore. + // So we can't test its behaviour. +} + +TEST(Diagnostic, DestroyValidDiagnosticAfterReassignment) { + spv_diagnostic diagnostic = MakeValidDiagnostic(); + spv_diagnostic second_diagnostic = MakeValidDiagnostic(); + EXPECT_TRUE(diagnostic != second_diagnostic); + spvDiagnosticDestroy(diagnostic); + diagnostic = second_diagnostic; + spvDiagnosticDestroy(diagnostic); +} + +TEST(Diagnostic, PrintDefault) { + char message[] = "Test Diagnostic!"; + spv_diagnostic_t diagnostic = {{2, 3, 5}, message}; + // TODO: Redirect stderr + ASSERT_EQ(SPV_SUCCESS, spvDiagnosticPrint(&diagnostic)); + // TODO: Validate the output of spvDiagnosticPrint() + // TODO: Remove the redirection of stderr +} + +TEST(Diagnostic, PrintInvalidDiagnostic) { + ASSERT_EQ(SPV_ERROR_INVALID_DIAGNOSTIC, spvDiagnosticPrint(nullptr)); +} + +// TODO(dneto): We should be able to redirect the diagnostic printing. +// Once we do that, we can test diagnostic corner cases. + +TEST(DiagnosticStream, ConversionToResultType) { + // Check after the DiagnosticStream object is destroyed. + spv_result_t value; + { value = DiagnosticStream({}, nullptr, SPV_ERROR_INVALID_TEXT); } + EXPECT_EQ(SPV_ERROR_INVALID_TEXT, value); + + // Check implicit conversion via plain assignment. + value = DiagnosticStream({}, nullptr, SPV_SUCCESS); + EXPECT_EQ(SPV_SUCCESS, value); + + // Check conversion via constructor. + EXPECT_EQ(SPV_FAILED_MATCH, + spv_result_t(DiagnosticStream({}, nullptr, SPV_FAILED_MATCH))); +} + +TEST( + DiagnosticStream, + MoveConstructorPreservesPreviousMessagesAndPreventsOutputFromExpiringValue) { + std::ostringstream messages; + int message_count = 0; + auto consumer = [&messages, &message_count](spv_message_level_t, const char*, + const spv_position_t&, + const char* msg) { + message_count++; + messages << msg; + }; + + // Enclose the DiagnosticStream variables in a scope to force destruction. + { + DiagnosticStream ds0({}, consumer, SPV_ERROR_INVALID_BINARY); + ds0 << "First"; + DiagnosticStream ds1(std::move(ds0)); + ds1 << "Second"; + } + EXPECT_THAT(message_count, Eq(1)); + EXPECT_THAT(messages.str(), Eq("FirstSecond")); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/enum_set_test.cpp b/3rdparty/spirv-tools/test/enum_set_test.cpp new file mode 100644 index 000000000..81671e637 --- /dev/null +++ b/3rdparty/spirv-tools/test/enum_set_test.cpp @@ -0,0 +1,288 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include "gmock/gmock.h" + +#include "enum_set.h" +#include "unit_spirv.h" + +namespace { + +using libspirv::CapabilitySet; +using libspirv::EnumSet; +using spvtest::ElementsIn; +using ::testing::Eq; +using ::testing::ValuesIn; + +TEST(EnumSet, IsEmpty1) { + EnumSet set; + EXPECT_TRUE(set.IsEmpty()); + set.Add(0); + EXPECT_FALSE(set.IsEmpty()); +} + +TEST(EnumSet, IsEmpty2) { + EnumSet set; + EXPECT_TRUE(set.IsEmpty()); + set.Add(150); + EXPECT_FALSE(set.IsEmpty()); +} + +TEST(EnumSet, IsEmpty3) { + EnumSet set(4); + EXPECT_FALSE(set.IsEmpty()); +} + +TEST(EnumSet, IsEmpty4) { + EnumSet set(300); + EXPECT_FALSE(set.IsEmpty()); +} + +TEST(EnumSetHasAnyOf, EmptySetEmptyQuery) { + const EnumSet set; + const EnumSet empty; + EXPECT_TRUE(set.HasAnyOf(empty)); + EXPECT_TRUE(EnumSet().HasAnyOf(EnumSet())); +} + +TEST(EnumSetHasAnyOf, MaskSetEmptyQuery) { + EnumSet set; + const EnumSet empty; + set.Add(5); + set.Add(8); + EXPECT_TRUE(set.HasAnyOf(empty)); +} + +TEST(EnumSetHasAnyOf, OverflowSetEmptyQuery) { + EnumSet set; + const EnumSet empty; + set.Add(200); + set.Add(300); + EXPECT_TRUE(set.HasAnyOf(empty)); +} + +TEST(EnumSetHasAnyOf, EmptyQuery) { + EnumSet set; + const EnumSet empty; + set.Add(5); + set.Add(8); + set.Add(200); + set.Add(300); + EXPECT_TRUE(set.HasAnyOf(empty)); +} + +TEST(EnumSetHasAnyOf, EmptyQueryAlwaysTrue) { + EnumSet set; + const EnumSet empty; + EXPECT_TRUE(set.HasAnyOf(empty)); + set.Add(5); + EXPECT_TRUE(set.HasAnyOf(empty)); + + EXPECT_TRUE(EnumSet(100).HasAnyOf(EnumSet())); +} + +TEST(EnumSetHasAnyOf, ReflexiveMask) { + EnumSet set(3); + set.Add(24); + set.Add(30); + EXPECT_TRUE(set.HasAnyOf(set)); +} + +TEST(EnumSetHasAnyOf, ReflexiveOverflow) { + EnumSet set(200); + set.Add(300); + set.Add(400); + EXPECT_TRUE(set.HasAnyOf(set)); +} + +TEST(EnumSetHasAnyOf, Reflexive) { + EnumSet set(3); + set.Add(24); + set.Add(300); + set.Add(400); + EXPECT_TRUE(set.HasAnyOf(set)); +} + +TEST(EnumSetHasAnyOf, EmptySetHasNone) { + EnumSet set; + EnumSet items; + for (uint32_t i = 0; i < 200; ++i) { + items.Add(i); + EXPECT_FALSE(set.HasAnyOf(items)); + EXPECT_FALSE(set.HasAnyOf(EnumSet(i))); + } +} + +TEST(EnumSetHasAnyOf, MaskSetMaskQuery) { + EnumSet set(0); + EnumSet items(1); + EXPECT_FALSE(set.HasAnyOf(items)); + set.Add(2); + items.Add(3); + EXPECT_FALSE(set.HasAnyOf(items)); + set.Add(3); + EXPECT_TRUE(set.HasAnyOf(items)); + set.Add(4); + EXPECT_TRUE(set.HasAnyOf(items)); +} + +TEST(EnumSetHasAnyOf, OverflowSetOverflowQuery) { + EnumSet set(100); + EnumSet items(200); + EXPECT_FALSE(set.HasAnyOf(items)); + set.Add(300); + items.Add(400); + EXPECT_FALSE(set.HasAnyOf(items)); + set.Add(200); + EXPECT_TRUE(set.HasAnyOf(items)); + set.Add(500); + EXPECT_TRUE(set.HasAnyOf(items)); +} + +TEST(EnumSetHasAnyOf, GeneralCase) { + EnumSet set(0); + EnumSet items(100); + EXPECT_FALSE(set.HasAnyOf(items)); + set.Add(300); + items.Add(4); + EXPECT_FALSE(set.HasAnyOf(items)); + set.Add(5); + items.Add(500); + EXPECT_FALSE(set.HasAnyOf(items)); + set.Add(500); + EXPECT_TRUE(set.HasAnyOf(items)); + EXPECT_FALSE(set.HasAnyOf(EnumSet(20))); + EXPECT_FALSE(set.HasAnyOf(EnumSet(600))); + EXPECT_TRUE(set.HasAnyOf(EnumSet(5))); + EXPECT_TRUE(set.HasAnyOf(EnumSet(300))); + EXPECT_TRUE(set.HasAnyOf(EnumSet(0))); +} + +TEST(EnumSet, DefaultIsEmpty) { + EnumSet set; + for (uint32_t i = 0; i < 1000; ++i) { + EXPECT_FALSE(set.Contains(i)); + } +} + +TEST(CapabilitySet, ConstructSingleMemberMatrix) { + CapabilitySet s(SpvCapabilityMatrix); + EXPECT_TRUE(s.Contains(SpvCapabilityMatrix)); + EXPECT_FALSE(s.Contains(SpvCapabilityShader)); + EXPECT_FALSE(s.Contains(static_cast(1000))); +} + +TEST(CapabilitySet, ConstructSingleMemberMaxInMask) { + CapabilitySet s(static_cast(63)); + EXPECT_FALSE(s.Contains(SpvCapabilityMatrix)); + EXPECT_FALSE(s.Contains(SpvCapabilityShader)); + EXPECT_TRUE(s.Contains(static_cast(63))); + EXPECT_FALSE(s.Contains(static_cast(64))); + EXPECT_FALSE(s.Contains(static_cast(1000))); +} + +TEST(CapabilitySet, ConstructSingleMemberMinOverflow) { + // Check the first one that forces overflow beyond the mask. + CapabilitySet s(static_cast(64)); + EXPECT_FALSE(s.Contains(SpvCapabilityMatrix)); + EXPECT_FALSE(s.Contains(SpvCapabilityShader)); + EXPECT_FALSE(s.Contains(static_cast(63))); + EXPECT_TRUE(s.Contains(static_cast(64))); + EXPECT_FALSE(s.Contains(static_cast(1000))); +} + +TEST(CapabilitySet, ConstructSingleMemberMaxOverflow) { + // Check the max 32-bit signed int. + CapabilitySet s(static_cast(0x7fffffffu)); + EXPECT_FALSE(s.Contains(SpvCapabilityMatrix)); + EXPECT_FALSE(s.Contains(SpvCapabilityShader)); + EXPECT_FALSE(s.Contains(static_cast(1000))); + EXPECT_TRUE(s.Contains(static_cast(0x7fffffffu))); +} + +TEST(CapabilitySet, AddEnum) { + CapabilitySet s(SpvCapabilityShader); + s.Add(SpvCapabilityKernel); + s.Add(static_cast(42)); + EXPECT_FALSE(s.Contains(SpvCapabilityMatrix)); + EXPECT_TRUE(s.Contains(SpvCapabilityShader)); + EXPECT_TRUE(s.Contains(SpvCapabilityKernel)); + EXPECT_TRUE(s.Contains(static_cast(42))); +} + +TEST(CapabilitySet, InitializerListEmpty) { + CapabilitySet s{}; + for (uint32_t i = 0; i < 1000; i++) { + EXPECT_FALSE(s.Contains(static_cast(i))); + } +} + +struct ForEachCase { + CapabilitySet capabilities; + std::vector expected; +}; + +using CapabilitySetForEachTest = ::testing::TestWithParam; + +TEST_P(CapabilitySetForEachTest, CallsAsExpected) { + EXPECT_THAT(ElementsIn(GetParam().capabilities), Eq(GetParam().expected)); +} + +TEST_P(CapabilitySetForEachTest, CopyConstructor) { + CapabilitySet copy(GetParam().capabilities); + EXPECT_THAT(ElementsIn(copy), Eq(GetParam().expected)); +} + +TEST_P(CapabilitySetForEachTest, MoveConstructor) { + // We need a writable copy to move from. + CapabilitySet copy(GetParam().capabilities); + CapabilitySet moved(std::move(copy)); + EXPECT_THAT(ElementsIn(moved), Eq(GetParam().expected)); + + // The moved-from set is empty. + EXPECT_THAT(ElementsIn(copy), Eq(std::vector{})); +} + +TEST_P(CapabilitySetForEachTest, OperatorEquals) { + CapabilitySet assigned = GetParam().capabilities; + EXPECT_THAT(ElementsIn(assigned), Eq(GetParam().expected)); +} + +TEST_P(CapabilitySetForEachTest, OperatorEqualsSelfAssign) { + CapabilitySet assigned{GetParam().capabilities}; + assigned = assigned; + EXPECT_THAT(ElementsIn(assigned), Eq(GetParam().expected)); +} + +INSTANTIATE_TEST_CASE_P(Samples, CapabilitySetForEachTest, + ValuesIn(std::vector{ + {{}, {}}, + {{SpvCapabilityMatrix}, {SpvCapabilityMatrix}}, + {{SpvCapabilityKernel, SpvCapabilityShader}, + {SpvCapabilityShader, SpvCapabilityKernel}}, + {{static_cast(999)}, + {static_cast(999)}}, + {{static_cast(0x7fffffff)}, + {static_cast(0x7fffffff)}}, + // Mixture and out of order + {{static_cast(0x7fffffff), + static_cast(100), + SpvCapabilityShader, SpvCapabilityMatrix}, + {SpvCapabilityMatrix, SpvCapabilityShader, + static_cast(100), + static_cast(0x7fffffff)}}, + }), ); + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/enum_string_mapping_test.cpp b/3rdparty/spirv-tools/test/enum_string_mapping_test.cpp new file mode 100644 index 000000000..f7f1ef505 --- /dev/null +++ b/3rdparty/spirv-tools/test/enum_string_mapping_test.cpp @@ -0,0 +1,194 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Tests for OpExtension validator rules. + +#include + +#include "enum_string_mapping.h" +#include "extensions.h" +#include "gtest/gtest.h" + +namespace { + +using ::libspirv::Extension; + +using ::testing::Values; +using ::testing::ValuesIn; + +using ExtensionTest = + ::testing::TestWithParam>; +using UnknownExtensionTest = ::testing::TestWithParam; +using CapabilityTest = + ::testing::TestWithParam>; + +TEST_P(ExtensionTest, TestExtensionFromString) { + const std::pair& param = GetParam(); + const Extension extension = param.first; + const std::string extension_str = param.second; + Extension result_extension; + ASSERT_TRUE(libspirv::GetExtensionFromString(extension_str.c_str(), + &result_extension)); + EXPECT_EQ(extension, result_extension); +} + +TEST_P(ExtensionTest, TestExtensionToString) { + const std::pair& param = GetParam(); + const Extension extension = param.first; + const std::string extension_str = param.second; + const std::string result_str = libspirv::ExtensionToString(extension); + EXPECT_EQ(extension_str, result_str); +} + +TEST_P(UnknownExtensionTest, TestExtensionFromStringFails) { + Extension result_extension; + ASSERT_FALSE( + libspirv::GetExtensionFromString(GetParam().c_str(), &result_extension)); +} + +TEST_P(CapabilityTest, TestCapabilityToString) { + const std::pair& param = GetParam(); + const SpvCapability capability = param.first; + const std::string capability_str = param.second; + const std::string result_str = libspirv::CapabilityToString(capability); + EXPECT_EQ(capability_str, result_str); +} + +INSTANTIATE_TEST_CASE_P( + AllExtensions, ExtensionTest, + ValuesIn(std::vector>({ + {Extension::kSPV_KHR_16bit_storage, "SPV_KHR_16bit_storage"}, + {Extension::kSPV_KHR_device_group, "SPV_KHR_device_group"}, + {Extension::kSPV_KHR_multiview, "SPV_KHR_multiview"}, + {Extension::kSPV_KHR_shader_ballot, "SPV_KHR_shader_ballot"}, + {Extension::kSPV_KHR_shader_draw_parameters, + "SPV_KHR_shader_draw_parameters"}, + {Extension::kSPV_KHR_subgroup_vote, "SPV_KHR_subgroup_vote"}, + {Extension::kSPV_NVX_multiview_per_view_attributes, + "SPV_NVX_multiview_per_view_attributes"}, + {Extension::kSPV_NV_geometry_shader_passthrough, + "SPV_NV_geometry_shader_passthrough"}, + {Extension::kSPV_NV_sample_mask_override_coverage, + "SPV_NV_sample_mask_override_coverage"}, + {Extension::kSPV_NV_stereo_view_rendering, + "SPV_NV_stereo_view_rendering"}, + {Extension::kSPV_NV_viewport_array2, "SPV_NV_viewport_array2"}, + {Extension::kSPV_GOOGLE_decorate_string, "SPV_GOOGLE_decorate_string"}, + {Extension::kSPV_GOOGLE_hlsl_functionality1, + "SPV_GOOGLE_hlsl_functionality1"}, + }))); + +INSTANTIATE_TEST_CASE_P(UnknownExtensions, UnknownExtensionTest, + Values("", "SPV_KHR_", "SPV_KHR_device_group_ERROR", + /*alphabetically before all extensions*/ "A", + /*alphabetically after all extensions*/ "Z", + "SPV_ERROR_random_string_hfsdklhlktherh")); + +INSTANTIATE_TEST_CASE_P( + AllCapabilities, CapabilityTest, + ValuesIn(std::vector>( + {{SpvCapabilityMatrix, "Matrix"}, + {SpvCapabilityShader, "Shader"}, + {SpvCapabilityGeometry, "Geometry"}, + {SpvCapabilityTessellation, "Tessellation"}, + {SpvCapabilityAddresses, "Addresses"}, + {SpvCapabilityLinkage, "Linkage"}, + {SpvCapabilityKernel, "Kernel"}, + {SpvCapabilityVector16, "Vector16"}, + {SpvCapabilityFloat16Buffer, "Float16Buffer"}, + {SpvCapabilityFloat16, "Float16"}, + {SpvCapabilityFloat64, "Float64"}, + {SpvCapabilityInt64, "Int64"}, + {SpvCapabilityInt64Atomics, "Int64Atomics"}, + {SpvCapabilityImageBasic, "ImageBasic"}, + {SpvCapabilityImageReadWrite, "ImageReadWrite"}, + {SpvCapabilityImageMipmap, "ImageMipmap"}, + {SpvCapabilityPipes, "Pipes"}, + {SpvCapabilityGroups, "Groups"}, + {SpvCapabilityDeviceEnqueue, "DeviceEnqueue"}, + {SpvCapabilityLiteralSampler, "LiteralSampler"}, + {SpvCapabilityAtomicStorage, "AtomicStorage"}, + {SpvCapabilityInt16, "Int16"}, + {SpvCapabilityTessellationPointSize, "TessellationPointSize"}, + {SpvCapabilityGeometryPointSize, "GeometryPointSize"}, + {SpvCapabilityImageGatherExtended, "ImageGatherExtended"}, + {SpvCapabilityStorageImageMultisample, "StorageImageMultisample"}, + {SpvCapabilityUniformBufferArrayDynamicIndexing, + "UniformBufferArrayDynamicIndexing"}, + {SpvCapabilitySampledImageArrayDynamicIndexing, + "SampledImageArrayDynamicIndexing"}, + {SpvCapabilityStorageBufferArrayDynamicIndexing, + "StorageBufferArrayDynamicIndexing"}, + {SpvCapabilityStorageImageArrayDynamicIndexing, + "StorageImageArrayDynamicIndexing"}, + {SpvCapabilityClipDistance, "ClipDistance"}, + {SpvCapabilityCullDistance, "CullDistance"}, + {SpvCapabilityImageCubeArray, "ImageCubeArray"}, + {SpvCapabilitySampleRateShading, "SampleRateShading"}, + {SpvCapabilityImageRect, "ImageRect"}, + {SpvCapabilitySampledRect, "SampledRect"}, + {SpvCapabilityGenericPointer, "GenericPointer"}, + {SpvCapabilityInt8, "Int8"}, + {SpvCapabilityInputAttachment, "InputAttachment"}, + {SpvCapabilitySparseResidency, "SparseResidency"}, + {SpvCapabilityMinLod, "MinLod"}, + {SpvCapabilitySampled1D, "Sampled1D"}, + {SpvCapabilityImage1D, "Image1D"}, + {SpvCapabilitySampledCubeArray, "SampledCubeArray"}, + {SpvCapabilitySampledBuffer, "SampledBuffer"}, + {SpvCapabilityImageBuffer, "ImageBuffer"}, + {SpvCapabilityImageMSArray, "ImageMSArray"}, + {SpvCapabilityStorageImageExtendedFormats, + "StorageImageExtendedFormats"}, + {SpvCapabilityImageQuery, "ImageQuery"}, + {SpvCapabilityDerivativeControl, "DerivativeControl"}, + {SpvCapabilityInterpolationFunction, "InterpolationFunction"}, + {SpvCapabilityTransformFeedback, "TransformFeedback"}, + {SpvCapabilityGeometryStreams, "GeometryStreams"}, + {SpvCapabilityStorageImageReadWithoutFormat, + "StorageImageReadWithoutFormat"}, + {SpvCapabilityStorageImageWriteWithoutFormat, + "StorageImageWriteWithoutFormat"}, + {SpvCapabilityMultiViewport, "MultiViewport"}, + {SpvCapabilitySubgroupDispatch, "SubgroupDispatch"}, + {SpvCapabilityNamedBarrier, "NamedBarrier"}, + {SpvCapabilityPipeStorage, "PipeStorage"}, + {SpvCapabilitySubgroupBallotKHR, "SubgroupBallotKHR"}, + {SpvCapabilityDrawParameters, "DrawParameters"}, + {SpvCapabilitySubgroupVoteKHR, "SubgroupVoteKHR"}, + {SpvCapabilityStorageBuffer16BitAccess, "StorageBuffer16BitAccess"}, + {SpvCapabilityStorageUniformBufferBlock16, + "StorageBuffer16BitAccess"}, // Preferred name + {SpvCapabilityUniformAndStorageBuffer16BitAccess, + "UniformAndStorageBuffer16BitAccess"}, + {SpvCapabilityStorageUniform16, + "UniformAndStorageBuffer16BitAccess"}, // Preferred name + {SpvCapabilityStoragePushConstant16, "StoragePushConstant16"}, + {SpvCapabilityStorageInputOutput16, "StorageInputOutput16"}, + {SpvCapabilityDeviceGroup, "DeviceGroup"}, + {SpvCapabilityMultiView, "MultiView"}, + {SpvCapabilitySampleMaskOverrideCoverageNV, + "SampleMaskOverrideCoverageNV"}, + {SpvCapabilityGeometryShaderPassthroughNV, + "GeometryShaderPassthroughNV"}, + // The next two are different names for the same token. + {SpvCapabilityShaderViewportIndexLayerNV, + "ShaderViewportIndexLayerEXT"}, + {SpvCapabilityShaderViewportIndexLayerEXT, + "ShaderViewportIndexLayerEXT"}, + {SpvCapabilityShaderViewportMaskNV, "ShaderViewportMaskNV"}, + {SpvCapabilityShaderStereoViewNV, "ShaderStereoViewNV"}, + {SpvCapabilityPerViewAttributesNV, "PerViewAttributesNV"}})), ); + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/ext_inst.debuginfo_test.cpp b/3rdparty/spirv-tools/test/ext_inst.debuginfo_test.cpp new file mode 100644 index 000000000..608d11992 --- /dev/null +++ b/3rdparty/spirv-tools/test/ext_inst.debuginfo_test.cpp @@ -0,0 +1,808 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "unit_spirv.h" + +#include +#include "DebugInfo.h" +#include "test_fixture.h" + +// This file tests the correctness of encoding and decoding of instructions +// involving the DebugInfo extended instruction set. +// Semantic correctness should be the responsibility of validator. +// +// See https://www.khronos.org/registry/spir-v/specs/1.0/DebugInfo.html + +namespace { + +using spvtest::Concatenate; +using spvtest::MakeInstruction; +using spvtest::MakeVector; +using testing::Eq; + +struct InstructionCase { + uint32_t opcode; + std::string name; + std::string operands; + std::vector expected_operands; +}; + +using ExtInstDebugInfoRoundTripTest = + spvtest::TextToBinaryTestBase<::testing::TestWithParam>; +using ExtInstDebugInfoRoundTripTestExplicit = spvtest::TextToBinaryTest; + +TEST_P(ExtInstDebugInfoRoundTripTest, ParameterizedExtInst) { + const std::string input = + "%1 = OpExtInstImport \"DebugInfo\"\n" + "%3 = OpExtInst %2 %1 " + + GetParam().name + GetParam().operands + "\n"; + // First make sure it assembles correctly. + EXPECT_THAT( + CompiledInstructions(input), + Eq(Concatenate( + {MakeInstruction(SpvOpExtInstImport, {1}, MakeVector("DebugInfo")), + MakeInstruction(SpvOpExtInst, {2, 3, 1, GetParam().opcode}, + GetParam().expected_operands)}))) + << input; + // Now check the round trip through the disassembler. + EXPECT_THAT(EncodeAndDecodeSuccessfully(input), input) << input; +} + +#define CASE_0(Enum) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, "", {} \ + } + +#define CASE_ILL(Enum, L0, L1) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, " %4 " #L0 " " #L1, { \ + 4, L0, L1 \ + } \ + } + +#define CASE_IL(Enum, L0) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, " %4 " #L0, { 4, L0 } \ + } + +#define CASE_I(Enum) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, " %4", { 4 } \ + } + +#define CASE_II(Enum) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, " %4 %5", { 4, 5 } \ + } + +#define CASE_III(Enum) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, " %4 %5 %6", { 4, 5, 6 } \ + } + +#define CASE_IIII(Enum) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, " %4 %5 %6 %7", { \ + 4, 5, 6, 7 \ + } \ + } + +#define CASE_IIIII(Enum) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, " %4 %5 %6 %7 %8", { \ + 4, 5, 6, 7, 8 \ + } \ + } + +#define CASE_IIIIII(Enum) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, " %4 %5 %6 %7 %8 %9", { \ + 4, 5, 6, 7, 8, 9 \ + } \ + } + +#define CASE_IIIIIII(Enum) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, " %4 %5 %6 %7 %8 %9 %10", { \ + 4, 5, 6, 7, 8, 9, 10 \ + } \ + } + +#define CASE_IIILLI(Enum, L0, L1) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, \ + " %4 %5 %6 " #L0 " " #L1 " %7", { \ + 4, 5, 6, L0, L1, 7 \ + } \ + } + +#define CASE_IIILLIL(Enum, L0, L1, L2) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, \ + " %4 %5 %6 " #L0 " " #L1 " %7 " #L2, { \ + 4, 5, 6, L0, L1, 7, L2 \ + } \ + } + +#define CASE_IE(Enum, E0) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, " %4 " #E0, { \ + 4, uint32_t(DebugInfo##E0) \ + } \ + } + +#define CASE_IIE(Enum, E0) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, " %4 %5 " #E0, { \ + 4, 5, uint32_t(DebugInfo##E0) \ + } \ + } + +#define CASE_ISF(Enum, S0, Fstr, Fnum) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, " %4 " #S0 " " Fstr, { \ + 4, uint32_t(SpvStorageClass##S0), Fnum \ + } \ + } + +#define CASE_LII(Enum, L0) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, " " #L0 " %4 %5", { \ + L0, 4, 5 \ + } \ + } + +#define CASE_ILI(Enum, L0) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, " %4 " #L0 " %5", { \ + 4, L0, 5 \ + } \ + } + +#define CASE_ILII(Enum, L0) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, " %4 " #L0 " %5 %6", { \ + 4, L0, 5, 6 \ + } \ + } + +#define CASE_ILLII(Enum, L0, L1) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, \ + " %4 " #L0 " " #L1 " %5 %6", { \ + 4, L0, L1, 5, 6 \ + } \ + } + +#define CASE_IIILLIIF(Enum, L0, L1, Fstr, Fnum) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, \ + " %4 %5 %6 " #L0 " " #L1 " %7 %8 " Fstr, { \ + 4, 5, 6, L0, L1, 7, 8, Fnum \ + } \ + } + +#define CASE_IIILLIIFII(Enum, L0, L1, Fstr, Fnum) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, \ + " %4 %5 %6 " #L0 " " #L1 " %7 %8 " Fstr " %9 %10", { \ + 4, 5, 6, L0, L1, 7, 8, Fnum, 9, 10 \ + } \ + } + +#define CASE_IIILLIIFIIII(Enum, L0, L1, Fstr, Fnum) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, \ + " %4 %5 %6 " #L0 " " #L1 " %7 %8 " Fstr " %9 %10 %11 %12", { \ + 4, 5, 6, L0, L1, 7, 8, Fnum, 9, 10, 11, 12 \ + } \ + } + +#define CASE_IIILLIIFIIIIII(Enum, L0, L1, Fstr, Fnum) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, \ + " %4 %5 %6 " #L0 " " #L1 " %7 %8 " Fstr " %9 %10 %11 %12 %13 %14", { \ + 4, 5, 6, L0, L1, 7, 8, Fnum, 9, 10, 11, 12, 13, 14 \ + } \ + } + +#define CASE_IEILLIIF(Enum, E0, L0, L1, Fstr, Fnum) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, \ + " %4 " #E0 " %5 " #L0 " " #L1 " %6 %7 " Fstr, { \ + 4, uint32_t(DebugInfo##E0), 5, L0, L1, 6, 7, Fnum \ + } \ + } + +#define CASE_IEILLIIFI(Enum, E0, L0, L1, Fstr, Fnum) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, \ + " %4 " #E0 " %5 " #L0 " " #L1 " %6 %7 " Fstr " %8", { \ + 4, uint32_t(DebugInfo##E0), 5, L0, L1, 6, 7, Fnum, 8 \ + } \ + } + +#define CASE_IEILLIIFII(Enum, E0, L0, L1, Fstr, Fnum) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, \ + " %4 " #E0 " %5 " #L0 " " #L1 " %6 %7 " Fstr " %8 %9", { \ + 4, uint32_t(DebugInfo##E0), 5, L0, L1, 6, 7, Fnum, 8, 9 \ + } \ + } + +#define CASE_IEILLIIFIII(Enum, E0, L0, L1, Fstr, Fnum) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, \ + " %4 " #E0 " %5 " #L0 " " #L1 " %6 %7 " Fstr " %8 %9 %10", { \ + 4, uint32_t(DebugInfo##E0), 5, L0, L1, 6, 7, Fnum, 8, 9, 10 \ + } \ + } + +#define CASE_IEILLIIFIIII(Enum, E0, L0, L1, Fstr, Fnum) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, \ + " %4 " #E0 " %5 " #L0 " " #L1 " %6 %7 " Fstr " %8 %9 %10 %11", { \ + 4, uint32_t(DebugInfo##E0), 5, L0, L1, 6, 7, Fnum, 8, 9, 10, 11 \ + } \ + } + +#define CASE_IIILLIIIF(Enum, L0, L1, Fstr, Fnum) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, \ + " %4 %5 %6 " #L0 " " #L1 " %7 %8 %9 " Fstr, { \ + 4, 5, 6, L0, L1, 7, 8, 9, Fnum \ + } \ + } + +#define CASE_IIILLIIIFI(Enum, L0, L1, Fstr, Fnum) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, \ + " %4 %5 %6 " #L0 " " #L1 " %7 %8 %9 " Fstr " %10", { \ + 4, 5, 6, L0, L1, 7, 8, 9, Fnum, 10 \ + } \ + } + +#define CASE_IIIIF(Enum, Fstr, Fnum) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, " %4 %5 %6 %7 " Fstr, { \ + 4, 5, 6, 7, Fnum \ + } \ + } + +#define CASE_IIILL(Enum, L0, L1) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, " %4 %5 %6 " #L0 " " #L1, { \ + 4, 5, 6, L0, L1 \ + } \ + } + +#define CASE_IIIILL(Enum, L0, L1) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, \ + " %4 %5 %6 %7 " #L0 " " #L1, { \ + 4, 5, 6, 7, L0, L1 \ + } \ + } + +#define CASE_IILLI(Enum, L0, L1) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, \ + " %4 %5 " #L0 " " #L1 " %6", { \ + 4, 5, L0, L1, 6 \ + } \ + } + +#define CASE_IILLII(Enum, L0, L1) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, \ + " %4 %5 " #L0 " " #L1 " %6 %7", { \ + 4, 5, L0, L1, 6, 7 \ + } \ + } + +#define CASE_IILLIII(Enum, L0, L1) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, \ + " %4 %5 " #L0 " " #L1 " %6 %7 %8", { \ + 4, 5, L0, L1, 6, 7, 8 \ + } \ + } + +#define CASE_IILLIIII(Enum, L0, L1) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, \ + " %4 %5 " #L0 " " #L1 " %6 %7 %8 %9", { \ + 4, 5, L0, L1, 6, 7, 8, 9 \ + } \ + } + +#define CASE_IIILLIIFLI(Enum, L0, L1, Fstr, Fnum, L2) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, \ + " %4 %5 %6 " #L0 " " #L1 " %7 %8 " Fstr " " #L2 " %9", { \ + 4, 5, 6, L0, L1, 7, 8, Fnum, L2, 9 \ + } \ + } + +#define CASE_IIILLIIFLII(Enum, L0, L1, Fstr, Fnum, L2) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, \ + " %4 %5 %6 " #L0 " " #L1 " %7 %8 " Fstr " " #L2 " %9 %10", { \ + 4, 5, 6, L0, L1, 7, 8, Fnum, L2, 9, 10 \ + } \ + } + +#define CASE_E(Enum, E0) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, " " #E0, { \ + uint32_t(DebugInfo##E0) \ + } \ + } + +#define CASE_EL(Enum, E0, L0) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, " " #E0 " " #L0, { \ + uint32_t(DebugInfo##E0), L0 \ + } \ + } + +#define CASE_ELL(Enum, E0, L0, L1) \ + { \ + uint32_t(DebugInfoDebug##Enum), "Debug" #Enum, " " #E0 " " #L0 " " #L1, { \ + uint32_t(DebugInfo##E0), L0, L1 \ + } \ + } + +// DebugInfo 4.1 Absent Debugging Information +INSTANTIATE_TEST_CASE_P(DebugInfoDebugInfoNone, ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_0(InfoNone), // enum value 0 + })), ); + +// DebugInfo 4.2 Compilation Unit +INSTANTIATE_TEST_CASE_P(DebugInfoDebugCompilationUnit, + ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_ILL(CompilationUnit, 100, 42), + })), ); + +// DebugInfo 4.3 Type instructions +INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeBasic, ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_IIE(TypeBasic, Unspecified), + CASE_IIE(TypeBasic, Address), + CASE_IIE(TypeBasic, Boolean), + CASE_IIE(TypeBasic, Float), + CASE_IIE(TypeBasic, Signed), + CASE_IIE(TypeBasic, SignedChar), + CASE_IIE(TypeBasic, Unsigned), + CASE_IIE(TypeBasic, UnsignedChar), + })), ); + +// The FlagIsPublic is value is (1 << 0) | (1 << 2) which is the same +// as the bitwise-OR of FlagIsProtected and FlagIsPrivate. +// The disassembler will emit the compound expression instead. +// There is no simple fix for this. This enum is not really a mask +// for the bottom two bits. +TEST_F(ExtInstDebugInfoRoundTripTestExplicit, FlagIsPublic) { + const std::string prefix = + "%1 = OpExtInstImport \"DebugInfo\"\n" + "%3 = OpExtInst %2 %1 DebugTypePointer %4 Private "; + const std::string input = prefix + "FlagIsPublic\n"; + const std::string expected = prefix + "FlagIsProtected|FlagIsPrivate\n"; + // First make sure it assembles correctly. + EXPECT_THAT( + CompiledInstructions(input), + Eq(Concatenate( + {MakeInstruction(SpvOpExtInstImport, {1}, MakeVector("DebugInfo")), + MakeInstruction(SpvOpExtInst, {2, 3, 1, DebugInfoDebugTypePointer, 4, + uint32_t(SpvStorageClassPrivate), + DebugInfoFlagIsPublic})}))) + << input; + // Now check the round trip through the disassembler. + EXPECT_THAT(EncodeAndDecodeSuccessfully(input), Eq(expected)) << input; +} + +INSTANTIATE_TEST_CASE_P( + DebugInfoDebugTypePointer, ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + + //// Use each flag independently. + CASE_ISF(TypePointer, Private, "FlagIsProtected", + uint32_t(DebugInfoFlagIsProtected)), + CASE_ISF(TypePointer, Private, "FlagIsPrivate", + uint32_t(DebugInfoFlagIsPrivate)), + + // FlagIsPublic is tested above. + + CASE_ISF(TypePointer, Private, "FlagIsLocal", + uint32_t(DebugInfoFlagIsLocal)), + CASE_ISF(TypePointer, Private, "FlagIsDefinition", + uint32_t(DebugInfoFlagIsDefinition)), + CASE_ISF(TypePointer, Private, "FlagFwdDecl", + uint32_t(DebugInfoFlagFwdDecl)), + CASE_ISF(TypePointer, Private, "FlagArtificial", + uint32_t(DebugInfoFlagArtificial)), + CASE_ISF(TypePointer, Private, "FlagExplicit", + uint32_t(DebugInfoFlagExplicit)), + CASE_ISF(TypePointer, Private, "FlagPrototyped", + uint32_t(DebugInfoFlagPrototyped)), + CASE_ISF(TypePointer, Private, "FlagObjectPointer", + uint32_t(DebugInfoFlagObjectPointer)), + CASE_ISF(TypePointer, Private, "FlagStaticMember", + uint32_t(DebugInfoFlagStaticMember)), + CASE_ISF(TypePointer, Private, "FlagIndirectVariable", + uint32_t(DebugInfoFlagIndirectVariable)), + CASE_ISF(TypePointer, Private, "FlagLValueReference", + uint32_t(DebugInfoFlagLValueReference)), + CASE_ISF(TypePointer, Private, "FlagIsOptimized", + uint32_t(DebugInfoFlagIsOptimized)), + + //// Use flags in combination, and try different storage classes. + CASE_ISF(TypePointer, Function, "FlagIsProtected|FlagIsPrivate", + uint32_t(DebugInfoFlagIsProtected) | + uint32_t(DebugInfoFlagIsPrivate)), + CASE_ISF( + TypePointer, Workgroup, + "FlagIsPrivate|FlagFwdDecl|FlagIndirectVariable|FlagIsOptimized", + uint32_t(DebugInfoFlagIsPrivate) | uint32_t(DebugInfoFlagFwdDecl) | + uint32_t(DebugInfoFlagIndirectVariable) | + uint32_t(DebugInfoFlagIsOptimized)), + + })), ); + +INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeQualifier, + ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_IE(TypeQualifier, ConstType), + CASE_IE(TypeQualifier, VolatileType), + CASE_IE(TypeQualifier, RestrictType), + })), ); + +INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeArray, ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_II(TypeArray), + CASE_III(TypeArray), + CASE_IIII(TypeArray), + CASE_IIIII(TypeArray), + })), ); + +INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeVector, ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_IL(TypeVector, 2), + CASE_IL(TypeVector, 3), + CASE_IL(TypeVector, 4), + CASE_IL(TypeVector, 16), + })), ); + +INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypedef, ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_IIILLI(Typedef, 12, 13), + CASE_IIILLI(Typedef, 14, 99), + })), ); + +INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeFunction, + ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_I(TypeFunction), + CASE_II(TypeFunction), + CASE_III(TypeFunction), + CASE_IIII(TypeFunction), + CASE_IIIII(TypeFunction), + })), ); + +INSTANTIATE_TEST_CASE_P( + DebugInfoDebugTypeEnum, ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_IIILLIIFII( + TypeEnum, 12, 13, + "FlagIsPrivate|FlagFwdDecl|FlagIndirectVariable|FlagIsOptimized", + uint32_t(DebugInfoFlagIsPrivate) | uint32_t(DebugInfoFlagFwdDecl) | + uint32_t(DebugInfoFlagIndirectVariable) | + uint32_t(DebugInfoFlagIsOptimized)), + CASE_IIILLIIFIIII(TypeEnum, 17, 18, "FlagStaticMember", + uint32_t(DebugInfoFlagStaticMember)), + CASE_IIILLIIFIIIIII(TypeEnum, 99, 1, "FlagStaticMember", + uint32_t(DebugInfoFlagStaticMember)), + })), ); + +INSTANTIATE_TEST_CASE_P( + DebugInfoDebugTypeComposite, ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_IEILLIIF( + TypeComposite, Class, 12, 13, + "FlagIsPrivate|FlagFwdDecl|FlagIndirectVariable|FlagIsOptimized", + uint32_t(DebugInfoFlagIsPrivate) | uint32_t(DebugInfoFlagFwdDecl) | + uint32_t(DebugInfoFlagIndirectVariable) | + uint32_t(DebugInfoFlagIsOptimized)), + // Cover all tag values: Class, Structure, Union + CASE_IEILLIIF(TypeComposite, Class, 12, 13, "FlagIsPrivate", + uint32_t(DebugInfoFlagIsPrivate)), + CASE_IEILLIIF(TypeComposite, Structure, 12, 13, "FlagIsPrivate", + uint32_t(DebugInfoFlagIsPrivate)), + CASE_IEILLIIF(TypeComposite, Union, 12, 13, "FlagIsPrivate", + uint32_t(DebugInfoFlagIsPrivate)), + // Now add members + CASE_IEILLIIFI(TypeComposite, Class, 9, 10, "FlagIsPrivate", + uint32_t(DebugInfoFlagIsPrivate)), + CASE_IEILLIIFII(TypeComposite, Class, 9, 10, "FlagIsPrivate", + uint32_t(DebugInfoFlagIsPrivate)), + CASE_IEILLIIFIII(TypeComposite, Class, 9, 10, "FlagIsPrivate", + uint32_t(DebugInfoFlagIsPrivate)), + CASE_IEILLIIFIIII(TypeComposite, Class, 9, 10, "FlagIsPrivate", + uint32_t(DebugInfoFlagIsPrivate)), + })), ); + +INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeMember, ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_IIILLIIIF(TypeMember, 12, 13, "FlagIsPrivate", + uint32_t(DebugInfoFlagIsPrivate)), + CASE_IIILLIIIF(TypeMember, 99, 100, + "FlagIsPrivate|FlagFwdDecl", + uint32_t(DebugInfoFlagIsPrivate) | + uint32_t(DebugInfoFlagFwdDecl)), + // Add the optional Id argument. + CASE_IIILLIIIFI(TypeMember, 12, 13, "FlagIsPrivate", + uint32_t(DebugInfoFlagIsPrivate)), + })), ); + +INSTANTIATE_TEST_CASE_P( + DebugInfoDebugTypeInheritance, ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_IIIIF(TypeInheritance, "FlagIsPrivate", + uint32_t(DebugInfoFlagIsPrivate)), + CASE_IIIIF(TypeInheritance, "FlagIsPrivate|FlagFwdDecl", + uint32_t(DebugInfoFlagIsPrivate) | + uint32_t(DebugInfoFlagFwdDecl)), + })), ); + +INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypePtrToMember, + ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_II(TypePtrToMember), + })), ); + +// DebugInfo 4.4 Templates + +INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeTemplate, + ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_II(TypeTemplate), + CASE_III(TypeTemplate), + CASE_IIII(TypeTemplate), + CASE_IIIII(TypeTemplate), + })), ); + +INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeTemplateParameter, + ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_IIIILL(TypeTemplateParameter, 1, 2), + CASE_IIIILL(TypeTemplateParameter, 99, 102), + CASE_IIIILL(TypeTemplateParameter, 10, 7), + })), ); + +INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeTemplateTemplateParameter, + ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_IIILL(TypeTemplateTemplateParameter, 1, 2), + CASE_IIILL(TypeTemplateTemplateParameter, 99, 102), + CASE_IIILL(TypeTemplateTemplateParameter, 10, 7), + })), ); + +INSTANTIATE_TEST_CASE_P(DebugInfoDebugTypeTemplateParameterPack, + ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_IILLI(TypeTemplateParameterPack, 1, 2), + CASE_IILLII(TypeTemplateParameterPack, 99, 102), + CASE_IILLIII(TypeTemplateParameterPack, 10, 7), + CASE_IILLIIII(TypeTemplateParameterPack, 10, 7), + })), ); + +// DebugInfo 4.5 Global Variables + +INSTANTIATE_TEST_CASE_P( + DebugInfoDebugGlobalVariable, ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_IIILLIIIF(GlobalVariable, 1, 2, "FlagIsOptimized", + uint32_t(DebugInfoFlagIsOptimized)), + CASE_IIILLIIIF(GlobalVariable, 42, 43, "FlagIsOptimized", + uint32_t(DebugInfoFlagIsOptimized)), + CASE_IIILLIIIFI(GlobalVariable, 1, 2, "FlagIsOptimized", + uint32_t(DebugInfoFlagIsOptimized)), + CASE_IIILLIIIFI(GlobalVariable, 42, 43, "FlagIsOptimized", + uint32_t(DebugInfoFlagIsOptimized)), + })), ); + +// DebugInfo 4.6 Functions + +INSTANTIATE_TEST_CASE_P( + DebugInfoDebugFunctionDeclaration, ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_IIILLIIF(FunctionDeclaration, 1, 2, "FlagIsOptimized", + uint32_t(DebugInfoFlagIsOptimized)), + CASE_IIILLIIF(FunctionDeclaration, 42, 43, "FlagFwdDecl", + uint32_t(DebugInfoFlagFwdDecl)), + })), ); + +INSTANTIATE_TEST_CASE_P( + DebugInfoDebugFunction, ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_IIILLIIFLI(Function, 1, 2, "FlagIsOptimized", + uint32_t(DebugInfoFlagIsOptimized), 3), + CASE_IIILLIIFLI(Function, 42, 43, "FlagFwdDecl", + uint32_t(DebugInfoFlagFwdDecl), 44), + // Add the optional declaration Id. + CASE_IIILLIIFLII(Function, 1, 2, "FlagIsOptimized", + uint32_t(DebugInfoFlagIsOptimized), 3), + CASE_IIILLIIFLII(Function, 42, 43, "FlagFwdDecl", + uint32_t(DebugInfoFlagFwdDecl), 44), + })), ); + +// DebugInfo 4.7 Local Information + +INSTANTIATE_TEST_CASE_P(DebugInfoDebugLexicalBlock, + ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_ILLII(LexicalBlock, 1, 2), + CASE_ILLII(LexicalBlock, 42, 43), + })), ); + +INSTANTIATE_TEST_CASE_P(DebugInfoDebugLexicalBlockDiscriminator, + ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_ILI(LexicalBlockDiscriminator, 1), + CASE_ILI(LexicalBlockDiscriminator, 42), + })), ); + +INSTANTIATE_TEST_CASE_P(DebugInfoDebugScope, ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_I(Scope), + CASE_II(Scope), + })), ); + +INSTANTIATE_TEST_CASE_P(DebugInfoDebugNoScope, ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_0(NoScope), + })), ); + +INSTANTIATE_TEST_CASE_P(DebugInfoDebugInlinedAt, ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_LII(InlinedAt, 1), + CASE_LII(InlinedAt, 42), + })), ); + +// DebugInfo 4.8 Local Variables + +INSTANTIATE_TEST_CASE_P(DebugInfoDebugLocalVariable, + ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_IIILLI(LocalVariable, 1, 2), + CASE_IIILLI(LocalVariable, 42, 43), + CASE_IIILLIL(LocalVariable, 1, 2, 3), + CASE_IIILLIL(LocalVariable, 42, 43, 44), + })), ); + +INSTANTIATE_TEST_CASE_P(DebugInfoDebugInlinedVariable, + ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_II(InlinedVariable), + })), ); + +INSTANTIATE_TEST_CASE_P(DebugInfoDebugDebugDeclare, + ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_III(Declare), + })), ); + +INSTANTIATE_TEST_CASE_P( + DebugInfoDebugDebugValue, ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_III(Value), + CASE_IIII(Value), + CASE_IIIII(Value), + CASE_IIIIII(Value), + // Test up to 4 id parameters. We can always try more. + CASE_IIIIIII(Value), + })), ); + +INSTANTIATE_TEST_CASE_P(DebugInfoDebugDebugOperation, + ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_E(Operation, Deref), + CASE_E(Operation, Plus), + CASE_E(Operation, Minus), + CASE_EL(Operation, PlusUconst, 1), + CASE_EL(Operation, PlusUconst, 42), + CASE_ELL(Operation, BitPiece, 1, 2), + CASE_ELL(Operation, BitPiece, 4, 5), + CASE_E(Operation, Swap), + CASE_E(Operation, Xderef), + CASE_E(Operation, StackValue), + CASE_EL(Operation, Constu, 1), + CASE_EL(Operation, Constu, 42), + })), ); + +INSTANTIATE_TEST_CASE_P(DebugInfoDebugDebugExpression, + ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_0(Expression), + CASE_I(Expression), + CASE_II(Expression), + CASE_III(Expression), + CASE_IIII(Expression), + CASE_IIIII(Expression), + CASE_IIIIII(Expression), + CASE_IIIIIII(Expression), + })), ); + +// DebugInfo 4.9 Macros + +INSTANTIATE_TEST_CASE_P(DebugInfoDebugMacroDef, ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_ILI(MacroDef, 1), + CASE_ILI(MacroDef, 42), + CASE_ILII(MacroDef, 1), + CASE_ILII(MacroDef, 42), + })), ); + +INSTANTIATE_TEST_CASE_P(DebugInfoDebugMacroUndef, ExtInstDebugInfoRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE_ILI(MacroUndef, 1), + CASE_ILI(MacroUndef, 42), + })), ); + +#undef CASE_0 +#undef CASE_ILL +#undef CASE_IL +#undef CASE_I +#undef CASE_II +#undef CASE_III +#undef CASE_IIII +#undef CASE_IIIII +#undef CASE_IIIIII +#undef CASE_IIIIIII +#undef CASE_IIILLI +#undef CASE_IIILLIL +#undef CASE_IE +#undef CASE_IIE +#undef CASE_ISF +#undef CASE_LII +#undef CASE_ILI +#undef CASE_ILII +#undef CASE_ILLII +#undef CASE_IIILLIIF +#undef CASE_IIILLIIFII +#undef CASE_IIILLIIFIIII +#undef CASE_IIILLIIFIIIIII +#undef CASE_IEILLIIF +#undef CASE_IEILLIIFI +#undef CASE_IEILLIIFII +#undef CASE_IEILLIIFIII +#undef CASE_IEILLIIFIIII +#undef CASE_IIILLIIIF +#undef CASE_IIILLIIIFI +#undef CASE_IIIIF +#undef CASE_IIILL +#undef CASE_IIIILL +#undef CASE_IILLI +#undef CASE_IILLII +#undef CASE_IILLIII +#undef CASE_IILLIIII +#undef CASE_IIILLIIFLI +#undef CASE_IIILLIIFLII +#undef CASE_E +#undef CASE_EL +#undef CASE_ELL + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/ext_inst.glsl_test.cpp b/3rdparty/spirv-tools/test/ext_inst.glsl_test.cpp new file mode 100644 index 000000000..5e569ead2 --- /dev/null +++ b/3rdparty/spirv-tools/test/ext_inst.glsl_test.cpp @@ -0,0 +1,200 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "latest_version_glsl_std_450_header.h" +#include "unit_spirv.h" + +namespace { + +/// Context for an extended instruction. +/// +/// Information about a GLSL extended instruction (including its opname, return +/// type, etc.) and related instructions used to generate the return type and +/// constant as the operands. Used in generating extended instruction tests. +struct ExtInstContext { + const char* extInstOpName; + const char* extInstOperandVars; + /// The following fields are used to check the SPIR-V binary representation + /// of this instruction. + uint32_t extInstOpcode; ///< Opcode value for this extended instruction. + uint32_t extInstLength; ///< Wordcount of this extended instruction. + std::vector extInstOperandIds; ///< Ids for operands. +}; + +using ExtInstGLSLstd450RoundTripTest = ::testing::TestWithParam; + +TEST_P(ExtInstGLSLstd450RoundTripTest, ParameterizedExtInst) { + spv_context context = spvContextCreate(SPV_ENV_UNIVERSAL_1_0); + const std::string spirv = R"( +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical Simple +OpEntryPoint Vertex %2 "main" +%3 = OpTypeVoid +%4 = OpTypeFunction %3 +%2 = OpFunction %3 None %5 +%6 = OpLabel +%8 = OpExtInst %7 %1 )" + std::string(GetParam().extInstOpName) + + " " + GetParam().extInstOperandVars + R"( +OpReturn +OpFunctionEnd +)"; + const std::string spirv_header = + R"(; SPIR-V +; Version: 1.0 +; Generator: Khronos SPIR-V Tools Assembler; 0 +; Bound: 9 +; Schema: 0)"; + spv_binary binary; + spv_diagnostic diagnostic; + spv_result_t error = spvTextToBinary(context, spirv.c_str(), spirv.size(), + &binary, &diagnostic); + if (error) { + spvDiagnosticPrint(diagnostic); + spvDiagnosticDestroy(diagnostic); + ASSERT_EQ(SPV_SUCCESS, error) + << "Source was: " << std::endl + << spirv << std::endl + << "Test case for : " << GetParam().extInstOpName << std::endl; + } + + // Check we do have the extended instruction's corresponding binary code in + // the generated SPIR-V binary. + std::vector expected_contains( + {12 /*OpExtInst*/ | GetParam().extInstLength << 16, 7 /*return type*/, + 8 /*result id*/, 1 /*glsl450 import*/, GetParam().extInstOpcode}); + for (uint32_t operand : GetParam().extInstOperandIds) { + expected_contains.push_back(operand); + } + EXPECT_NE(binary->code + binary->wordCount, + std::search(binary->code, binary->code + binary->wordCount, + expected_contains.begin(), expected_contains.end())) + << "Cannot find\n" + << spvtest::WordVector(expected_contains).str() << "in\n" + << spvtest::WordVector(*binary).str(); + + // Check round trip gives the same text. + spv_text output_text = nullptr; + error = spvBinaryToText(context, binary->code, binary->wordCount, + SPV_BINARY_TO_TEXT_OPTION_NONE, &output_text, + &diagnostic); + + if (error) { + spvDiagnosticPrint(diagnostic); + spvDiagnosticDestroy(diagnostic); + ASSERT_EQ(SPV_SUCCESS, error); + } + EXPECT_EQ(spirv_header + spirv, output_text->str); + spvTextDestroy(output_text); + spvContextDestroy(context); +} + +INSTANTIATE_TEST_CASE_P( + ExtInstParameters, ExtInstGLSLstd450RoundTripTest, + ::testing::ValuesIn(std::vector({ + // We are only testing the correctness of encoding and decoding here. + // Semantic correctness should be the responsibility of validator. So + // some of the instructions below have incorrect operand and/or return + // types, e.g, Modf, ModfStruct, etc. + {"Round", "%5", 1, 6, {5}}, + {"RoundEven", "%5", 2, 6, {5}}, + {"Trunc", "%5", 3, 6, {5}}, + {"FAbs", "%5", 4, 6, {5}}, + {"SAbs", "%5", 5, 6, {5}}, + {"FSign", "%5", 6, 6, {5}}, + {"SSign", "%5", 7, 6, {5}}, + {"Floor", "%5", 8, 6, {5}}, + {"Ceil", "%5", 9, 6, {5}}, + {"Fract", "%5", 10, 6, {5}}, + {"Radians", "%5", 11, 6, {5}}, + {"Degrees", "%5", 12, 6, {5}}, + {"Sin", "%5", 13, 6, {5}}, + {"Cos", "%5", 14, 6, {5}}, + {"Tan", "%5", 15, 6, {5}}, + {"Asin", "%5", 16, 6, {5}}, + {"Acos", "%5", 17, 6, {5}}, + {"Atan", "%5", 18, 6, {5}}, + {"Sinh", "%5", 19, 6, {5}}, + {"Cosh", "%5", 20, 6, {5}}, + {"Tanh", "%5", 21, 6, {5}}, + {"Asinh", "%5", 22, 6, {5}}, + {"Acosh", "%5", 23, 6, {5}}, + {"Atanh", "%5", 24, 6, {5}}, + {"Atan2", "%5 %5", 25, 7, {5, 5}}, + {"Pow", "%5 %5", 26, 7, {5, 5}}, + {"Exp", "%5", 27, 6, {5}}, + {"Log", "%5", 28, 6, {5}}, + {"Exp2", "%5", 29, 6, {5}}, + {"Log2", "%5", 30, 6, {5}}, + {"Sqrt", "%5", 31, 6, {5}}, + {"InverseSqrt", "%5", 32, 6, {5}}, + {"Determinant", "%5", 33, 6, {5}}, + {"MatrixInverse", "%5", 34, 6, {5}}, + {"Modf", "%5 %5", 35, 7, {5, 5}}, + {"ModfStruct", "%5", 36, 6, {5}}, + {"FMin", "%5 %5", 37, 7, {5, 5}}, + {"UMin", "%5 %5", 38, 7, {5, 5}}, + {"SMin", "%5 %5", 39, 7, {5, 5}}, + {"FMax", "%5 %5", 40, 7, {5, 5}}, + {"UMax", "%5 %5", 41, 7, {5, 5}}, + {"SMax", "%5 %5", 42, 7, {5, 5}}, + {"FClamp", "%5 %5 %5", 43, 8, {5, 5, 5}}, + {"UClamp", "%5 %5 %5", 44, 8, {5, 5, 5}}, + {"SClamp", "%5 %5 %5", 45, 8, {5, 5, 5}}, + {"FMix", "%5 %5 %5", 46, 8, {5, 5, 5}}, + {"IMix", "%5 %5 %5", 47, 8, {5, 5, 5}}, // Bug 15452. Reserved. + {"Step", "%5 %5", 48, 7, {5, 5}}, + {"SmoothStep", "%5 %5 %5", 49, 8, {5, 5, 5}}, + {"Fma", "%5 %5 %5", 50, 8, {5, 5, 5}}, + {"Frexp", "%5 %5", 51, 7, {5, 5}}, + {"FrexpStruct", "%5", 52, 6, {5}}, + {"Ldexp", "%5 %5", 53, 7, {5, 5}}, + {"PackSnorm4x8", "%5", 54, 6, {5}}, + {"PackUnorm4x8", "%5", 55, 6, {5}}, + {"PackSnorm2x16", "%5", 56, 6, {5}}, + {"PackUnorm2x16", "%5", 57, 6, {5}}, + {"PackHalf2x16", "%5", 58, 6, {5}}, + {"PackDouble2x32", "%5", 59, 6, {5}}, + {"UnpackSnorm2x16", "%5", 60, 6, {5}}, + {"UnpackUnorm2x16", "%5", 61, 6, {5}}, + {"UnpackHalf2x16", "%5", 62, 6, {5}}, + {"UnpackSnorm4x8", "%5", 63, 6, {5}}, + {"UnpackUnorm4x8", "%5", 64, 6, {5}}, + {"UnpackDouble2x32", "%5", 65, 6, {5}}, + {"Length", "%5", 66, 6, {5}}, + {"Distance", "%5 %5", 67, 7, {5, 5}}, + {"Cross", "%5 %5", 68, 7, {5, 5}}, + {"Normalize", "%5", 69, 6, {5}}, + // clang-format off + {"FaceForward", "%5 %5 %5", 70, 8, {5, 5, 5}}, + // clang-format on + {"Reflect", "%5 %5", 71, 7, {5, 5}}, + {"Refract", "%5 %5 %5", 72, 8, {5, 5, 5}}, + {"FindILsb", "%5", 73, 6, {5}}, + {"FindSMsb", "%5", 74, 6, {5}}, + {"FindUMsb", "%5", 75, 6, {5}}, + {"InterpolateAtCentroid", "%5", 76, 6, {5}}, + // clang-format off + {"InterpolateAtSample", "%5 %5", 77, 7, {5, 5}}, + {"InterpolateAtOffset", "%5 %5", 78, 7, {5, 5}}, + // clang-format on + {"NMin", "%5 %5", 79, 7, {5, 5}}, + {"NMax", "%5 %5", 80, 7, {5, 5}}, + {"NClamp", "%5 %5 %5", 81, 8, {5, 5, 5}}, + })), ); + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/ext_inst.opencl_test.cpp b/3rdparty/spirv-tools/test/ext_inst.opencl_test.cpp new file mode 100644 index 000000000..6829e3cab --- /dev/null +++ b/3rdparty/spirv-tools/test/ext_inst.opencl_test.cpp @@ -0,0 +1,369 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "unit_spirv.h" + +#include +#include "latest_version_opencl_std_header.h" +#include "test_fixture.h" + +namespace { + +using spvtest::Concatenate; +using spvtest::MakeInstruction; +using spvtest::MakeVector; +using spvtest::TextToBinaryTest; +using testing::Eq; + +struct InstructionCase { + uint32_t opcode; + std::string name; + std::string operands; + std::vector expected_operands; +}; + +using ExtInstOpenCLStdRoundTripTest = + spvtest::TextToBinaryTestBase<::testing::TestWithParam>; + +TEST_P(ExtInstOpenCLStdRoundTripTest, ParameterizedExtInst) { + // This example should not validate. + const std::string input = + "%1 = OpExtInstImport \"OpenCL.std\"\n" + "%3 = OpExtInst %2 %1 " + + GetParam().name + " " + GetParam().operands + "\n"; + // First make sure it assembles correctly. + EXPECT_THAT( + CompiledInstructions(input), + Eq(Concatenate( + {MakeInstruction(SpvOpExtInstImport, {1}, MakeVector("OpenCL.std")), + MakeInstruction(SpvOpExtInst, {2, 3, 1, GetParam().opcode}, + GetParam().expected_operands)}))) + << input; + // Now check the round trip through the disassembler. + EXPECT_THAT(EncodeAndDecodeSuccessfully(input), input) << input; +} + +#define CASE1(Enum, Name) \ + { \ + uint32_t(OpenCLLIB::Entrypoints::Enum), #Name, "%4", { 4 } \ + } +#define CASE2(Enum, Name) \ + { \ + uint32_t(OpenCLLIB::Entrypoints::Enum), #Name, "%4 %5", { 4, 5 } \ + } +#define CASE3(Enum, Name) \ + { \ + uint32_t(OpenCLLIB::Entrypoints::Enum), #Name, "%4 %5 %6", { 4, 5, 6 } \ + } +#define CASE4(Enum, Name) \ + { \ + uint32_t(OpenCLLIB::Entrypoints::Enum), #Name, "%4 %5 %6 %7", { \ + 4, 5, 6, 7 \ + } \ + } +#define CASE2Lit(Enum, Name, LiteralNumber) \ + { \ + uint32_t(OpenCLLIB::Entrypoints::Enum), #Name, "%4 %5 " #LiteralNumber, { \ + 4, 5, LiteralNumber \ + } \ + } +#define CASE3Round(Enum, Name, Mode) \ + { \ + uint32_t(OpenCLLIB::Entrypoints::Enum), #Name, "%4 %5 %6 " #Mode, { \ + 4, 5, 6, uint32_t(SpvFPRoundingMode##Mode) \ + } \ + } + +// clang-format off +// OpenCL.std: 2.1 Math extended instructions +INSTANTIATE_TEST_CASE_P( + OpenCLMath, ExtInstOpenCLStdRoundTripTest, + ::testing::ValuesIn(std::vector({ + // We are only testing the correctness of encoding and decoding here. + // Semantic correctness should be the responsibility of validator. + CASE1(Acos, acos), // enum value 0 + CASE1(Acosh, acosh), + CASE1(Acospi, acospi), + CASE1(Asin, asin), + CASE1(Asinh, asinh), + CASE1(Asinh, asinh), + CASE1(Asinpi, asinpi), + CASE1(Atan, atan), + CASE2(Atan2, atan2), + CASE1(Atanh, atanh), + CASE1(Atanpi, atanpi), + CASE2(Atan2pi, atan2pi), + CASE1(Cbrt, cbrt), + CASE1(Ceil, ceil), + CASE1(Ceil, ceil), + CASE2(Copysign, copysign), + CASE1(Cos, cos), + CASE1(Cosh, cosh), + CASE1(Cospi, cospi), + CASE1(Erfc, erfc), + CASE1(Erf, erf), + CASE1(Exp, exp), + CASE1(Exp2, exp2), + CASE1(Exp10, exp10), + CASE1(Expm1, expm1), + CASE1(Fabs, fabs), + CASE2(Fdim, fdim), + CASE1(Floor, floor), + CASE3(Fma, fma), + CASE2(Fmax, fmax), + CASE2(Fmin, fmin), + CASE2(Fmod, fmod), + CASE2(Fract, fract), + CASE2(Frexp, frexp), + CASE2(Hypot, hypot), + CASE1(Ilogb, ilogb), + CASE2(Ldexp, ldexp), + CASE1(Lgamma, lgamma), + CASE2(Lgamma_r, lgamma_r), + CASE1(Log, log), + CASE1(Log2, log2), + CASE1(Log10, log10), + CASE1(Log1p, log1p), + CASE3(Mad, mad), + CASE2(Maxmag, maxmag), + CASE2(Minmag, minmag), + CASE2(Modf, modf), + CASE1(Nan, nan), + CASE2(Nextafter, nextafter), + CASE2(Pow, pow), + CASE2(Pown, pown), + CASE2(Powr, powr), + CASE2(Remainder, remainder), + CASE3(Remquo, remquo), + CASE1(Rint, rint), + CASE2(Rootn, rootn), + CASE1(Round, round), + CASE1(Rsqrt, rsqrt), + CASE1(Sin, sin), + CASE2(Sincos, sincos), + CASE1(Sinh, sinh), + CASE1(Sinpi, sinpi), + CASE1(Sqrt, sqrt), + CASE1(Tan, tan), + CASE1(Tanh, tanh), + CASE1(Tanpi, tanpi), + CASE1(Tgamma, tgamma), + CASE1(Trunc, trunc), + CASE1(Half_cos, half_cos), + CASE2(Half_divide, half_divide), + CASE1(Half_exp, half_exp), + CASE1(Half_exp2, half_exp2), + CASE1(Half_exp10, half_exp10), + CASE1(Half_log, half_log), + CASE1(Half_log2, half_log2), + CASE1(Half_log10, half_log10), + CASE2(Half_powr, half_powr), + CASE1(Half_recip, half_recip), + CASE1(Half_rsqrt, half_rsqrt), + CASE1(Half_sin, half_sin), + CASE1(Half_sqrt, half_sqrt), + CASE1(Half_tan, half_tan), + CASE1(Native_cos, native_cos), + CASE2(Native_divide, native_divide), + CASE1(Native_exp, native_exp), + CASE1(Native_exp2, native_exp2), + CASE1(Native_exp10, native_exp10), + CASE1(Native_log, native_log), + CASE1(Native_log10, native_log10), + CASE2(Native_powr, native_powr), + CASE1(Native_recip, native_recip), + CASE1(Native_rsqrt, native_rsqrt), + CASE1(Native_sin, native_sin), + CASE1(Native_sqrt, native_sqrt), + CASE1(Native_tan, native_tan), // enum value 94 + })),); + +// OpenCL.std: 2.1 Integer instructions +INSTANTIATE_TEST_CASE_P( + OpenCLInteger, ExtInstOpenCLStdRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE1(SAbs, s_abs), // enum value 141 + CASE2(SAbs_diff, s_abs_diff), + CASE2(SAdd_sat, s_add_sat), + CASE2(UAdd_sat, u_add_sat), + CASE2(SHadd, s_hadd), + CASE2(UHadd, u_hadd), + CASE2(SRhadd, s_rhadd), + CASE2(SRhadd, s_rhadd), + CASE3(SClamp, s_clamp), + CASE3(UClamp, u_clamp), + CASE1(Clz, clz), + CASE1(Ctz, ctz), + CASE3(SMad_hi, s_mad_hi), + CASE3(UMad_sat, u_mad_sat), + CASE3(SMad_sat, s_mad_sat), + CASE2(SMax, s_max), + CASE2(UMax, u_max), + CASE2(SMin, s_min), + CASE2(UMin, u_min), + CASE2(SMul_hi, s_mul_hi), + CASE2(Rotate, rotate), + CASE2(SSub_sat, s_sub_sat), + CASE2(USub_sat, u_sub_sat), + CASE2(U_Upsample, u_upsample), + CASE2(S_Upsample, s_upsample), + CASE1(Popcount, popcount), + CASE3(SMad24, s_mad24), + CASE3(UMad24, u_mad24), + CASE2(SMul24, s_mul24), + CASE2(UMul24, u_mul24), // enum value 170 + CASE1(UAbs, u_abs), // enum value 201 + CASE2(UAbs_diff, u_abs_diff), + CASE2(UMul_hi, u_mul_hi), + CASE3(UMad_hi, u_mad_hi), // enum value 204 + })),); + +// OpenCL.std: 2.3 Common instrucitons +INSTANTIATE_TEST_CASE_P( + OpenCLCommon, ExtInstOpenCLStdRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE3(FClamp, fclamp), // enum value 95 + CASE1(Degrees, degrees), + CASE2(FMax_common, fmax_common), + CASE2(FMin_common, fmin_common), + CASE3(Mix, mix), + CASE1(Radians, radians), + CASE2(Step, step), + CASE3(Smoothstep, smoothstep), + CASE1(Sign, sign), // enum value 103 + })),); + +// OpenCL.std: 2.4 Geometric instructions +INSTANTIATE_TEST_CASE_P( + OpenCLGeometric, ExtInstOpenCLStdRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE2(Cross, cross), // enum value 104 + CASE2(Distance, distance), + CASE1(Length, length), + CASE1(Normalize, normalize), + CASE2(Fast_distance, fast_distance), + CASE1(Fast_length, fast_length), + CASE1(Fast_normalize, fast_normalize), // enum value 110 + })),); + +// OpenCL.std: 2.5 Relational instructions +INSTANTIATE_TEST_CASE_P( + OpenCLRelational, ExtInstOpenCLStdRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE3(Bitselect, bitselect), // enum value 186 + CASE3(Select, select), // enum value 187 + })),); + +// OpenCL.std: 2.6 Vector data load and store instructions +INSTANTIATE_TEST_CASE_P( + OpenCLVectorLoadStore, ExtInstOpenCLStdRoundTripTest, + ::testing::ValuesIn(std::vector({ + // The last argument to Vloadn must be one of 2, 3, 4, 8, 16. + CASE2Lit(Vloadn, vloadn, 2), + CASE2Lit(Vloadn, vloadn, 3), + CASE2Lit(Vloadn, vloadn, 4), + CASE2Lit(Vloadn, vloadn, 8), + CASE2Lit(Vloadn, vloadn, 16), + CASE3(Vstoren, vstoren), + CASE2(Vload_half, vload_half), + CASE2Lit(Vload_halfn, vload_halfn, 2), + CASE2Lit(Vload_halfn, vload_halfn, 3), + CASE2Lit(Vload_halfn, vload_halfn, 4), + CASE2Lit(Vload_halfn, vload_halfn, 8), + CASE2Lit(Vload_halfn, vload_halfn, 16), + CASE3(Vstore_half, vstore_half), + // Try all the rounding modes. + CASE3Round(Vstore_half_r, vstore_half_r, RTE), + CASE3Round(Vstore_half_r, vstore_half_r, RTZ), + CASE3Round(Vstore_half_r, vstore_half_r, RTP), + CASE3Round(Vstore_half_r, vstore_half_r, RTN), + CASE3(Vstore_halfn, vstore_halfn), + CASE3Round(Vstore_halfn_r, vstore_halfn_r, RTE), + CASE3Round(Vstore_halfn_r, vstore_halfn_r, RTZ), + CASE3Round(Vstore_halfn_r, vstore_halfn_r, RTP), + CASE3Round(Vstore_halfn_r, vstore_halfn_r, RTN), + CASE2Lit(Vloada_halfn, vloada_halfn, 2), + CASE2Lit(Vloada_halfn, vloada_halfn, 3), + CASE2Lit(Vloada_halfn, vloada_halfn, 4), + CASE2Lit(Vloada_halfn, vloada_halfn, 8), + CASE2Lit(Vloada_halfn, vloada_halfn, 16), + CASE3(Vstorea_halfn, vstorea_halfn), + CASE3Round(Vstorea_halfn_r, vstorea_halfn_r, RTE), + CASE3Round(Vstorea_halfn_r, vstorea_halfn_r, RTZ), + CASE3Round(Vstorea_halfn_r, vstorea_halfn_r, RTP), + CASE3Round(Vstorea_halfn_r, vstorea_halfn_r, RTN), + })),); + +// OpenCL.std: 2.7 Miscellaneous vector instructions +INSTANTIATE_TEST_CASE_P( + OpenCLMiscellaneousVector, ExtInstOpenCLStdRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE2(Shuffle, shuffle), + CASE3(Shuffle2, shuffle2), + })),); + +// OpenCL.std: 2.8 Miscellaneous instructions + +#define PREFIX uint32_t(OpenCLLIB::Entrypoints::Printf), "printf" +INSTANTIATE_TEST_CASE_P( + OpenCLMiscPrintf, ExtInstOpenCLStdRoundTripTest, + ::testing::ValuesIn(std::vector({ + // Printf is interesting because it takes a variable number of arguments. + // Start with zero optional arguments. + {PREFIX, "%4", {4}}, + {PREFIX, "%4 %5", {4, 5}}, + {PREFIX, "%4 %5 %6", {4, 5, 6}}, + {PREFIX, "%4 %5 %6 %7", {4, 5, 6, 7}}, + {PREFIX, "%4 %5 %6 %7 %8", {4, 5, 6, 7, 8}}, + {PREFIX, "%4 %5 %6 %7 %8 %9", {4, 5, 6, 7, 8, 9}}, + {PREFIX, "%4 %5 %6 %7 %8 %9 %10", {4, 5, 6, 7, 8, 9, 10}}, + {PREFIX, "%4 %5 %6 %7 %8 %9 %10 %11", {4, 5, 6, 7, 8, 9, 10, 11}}, + {PREFIX, "%4 %5 %6 %7 %8 %9 %10 %11 %12", + {4, 5, 6, 7, 8, 9, 10, 11, 12}}, + {PREFIX, "%4 %5 %6 %7 %8 %9 %10 %11 %12 %13", + {4, 5, 6, 7, 8, 9, 10, 11, 12, 13}}, + {PREFIX, "%4 %5 %6 %7 %8 %9 %10 %11 %12 %13 %14", + {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}}, + })),); +#undef PREFIX + +INSTANTIATE_TEST_CASE_P( + OpenCLMiscPrefetch, ExtInstOpenCLStdRoundTripTest, + ::testing::ValuesIn(std::vector({ + CASE2(Prefetch, prefetch), + })),); + +// OpenCL.std: 2.9.1 Image encoding +// No new instructions defined in this section. + +// OpenCL.std: 2.9.2 Sampler encoding +// No new instructions defined in this section. + +// OpenCL.std: 2.9.3 Image read +// No new instructions defined in this section. +// Use core instruction OpImageSampleExplicitLod instead. + +// OpenCL.std: 2.9.4 Image write +// No new instructions defined in this section. + +// clang-format on + +#undef CASE1 +#undef CASE2 +#undef CASE3 +#undef CASE4 +#undef CASE2Lit +#undef CASE3Round + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/fix_word_test.cpp b/3rdparty/spirv-tools/test/fix_word_test.cpp new file mode 100644 index 000000000..45ba6e637 --- /dev/null +++ b/3rdparty/spirv-tools/test/fix_word_test.cpp @@ -0,0 +1,62 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "unit_spirv.h" + +namespace { + +TEST(FixWord, Default) { + spv_endianness_t endian; + if (I32_ENDIAN_HOST == I32_ENDIAN_LITTLE) { + endian = SPV_ENDIANNESS_LITTLE; + } else { + endian = SPV_ENDIANNESS_BIG; + } + uint32_t word = 0x53780921; + ASSERT_EQ(word, spvFixWord(word, endian)); +} + +TEST(FixWord, Reorder) { + spv_endianness_t endian; + if (I32_ENDIAN_HOST == I32_ENDIAN_LITTLE) { + endian = SPV_ENDIANNESS_BIG; + } else { + endian = SPV_ENDIANNESS_LITTLE; + } + uint32_t word = 0x53780921; + uint32_t result = 0x21097853; + ASSERT_EQ(result, spvFixWord(word, endian)); +} + +TEST(FixDoubleWord, Default) { + spv_endianness_t endian = + (I32_ENDIAN_HOST == I32_ENDIAN_LITTLE ? SPV_ENDIANNESS_LITTLE + : SPV_ENDIANNESS_BIG); + uint32_t low = 0x53780921; + uint32_t high = 0xdeadbeef; + uint64_t result = 0xdeadbeef53780921; + ASSERT_EQ(result, spvFixDoubleWord(low, high, endian)); +} + +TEST(FixDoubleWord, Reorder) { + spv_endianness_t endian = + (I32_ENDIAN_HOST == I32_ENDIAN_LITTLE ? SPV_ENDIANNESS_BIG + : SPV_ENDIANNESS_LITTLE); + uint32_t low = 0x53780921; + uint32_t high = 0xdeadbeef; + uint64_t result = 0xefbeadde21097853; + ASSERT_EQ(result, spvFixDoubleWord(low, high, endian)); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/generator_magic_number_test.cpp b/3rdparty/spirv-tools/test/generator_magic_number_test.cpp new file mode 100644 index 000000000..c88022899 --- /dev/null +++ b/3rdparty/spirv-tools/test/generator_magic_number_test.cpp @@ -0,0 +1,57 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "unit_spirv.h" + +#include + +#include "source/opcode.h" + +using ::spvtest::EnumCase; +using ::testing::Eq; + +namespace { + +using GeneratorMagicNumberTest = + ::testing::TestWithParam>; + +TEST_P(GeneratorMagicNumberTest, Single) { + EXPECT_THAT(std::string(spvGeneratorStr(GetParam().value())), + GetParam().name()); +} + +INSTANTIATE_TEST_CASE_P( + Registered, GeneratorMagicNumberTest, + ::testing::ValuesIn(std::vector>{ + {SPV_GENERATOR_KHRONOS, "Khronos"}, + {SPV_GENERATOR_LUNARG, "LunarG"}, + {SPV_GENERATOR_VALVE, "Valve"}, + {SPV_GENERATOR_CODEPLAY, "Codeplay"}, + {SPV_GENERATOR_NVIDIA, "NVIDIA"}, + {SPV_GENERATOR_ARM, "ARM"}, + {SPV_GENERATOR_KHRONOS_LLVM_TRANSLATOR, + "Khronos LLVM/SPIR-V Translator"}, + {SPV_GENERATOR_KHRONOS_ASSEMBLER, "Khronos SPIR-V Tools Assembler"}, + {SPV_GENERATOR_KHRONOS_GLSLANG, "Khronos Glslang Reference Front End"}, + }), ); + +INSTANTIATE_TEST_CASE_P( + Unregistered, GeneratorMagicNumberTest, + ::testing::ValuesIn(std::vector>{ + // We read registered entries from the SPIR-V XML Registry file + // which can change over time. + {spv_generator_t(1000), "Unknown"}, + {spv_generator_t(9999), "Unknown"}, + }), ); +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/hex_float_test.cpp b/3rdparty/spirv-tools/test/hex_float_test.cpp new file mode 100644 index 000000000..a2bff53ed --- /dev/null +++ b/3rdparty/spirv-tools/test/hex_float_test.cpp @@ -0,0 +1,1329 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include + +#include + +#include "source/util/hex_float.h" +#include "unit_spirv.h" + +namespace { +using spvutils::BitwiseCast; +using spvutils::Float16; +using spvutils::FloatProxy; +using spvutils::HexFloat; +using spvutils::ParseNormalFloat; +using ::testing::Eq; + +// In this file "encode" means converting a number into a string, +// and "decode" means converting a string into a number. + +using HexFloatTest = + ::testing::TestWithParam, std::string>>; +using DecodeHexFloatTest = + ::testing::TestWithParam>>; +using HexDoubleTest = + ::testing::TestWithParam, std::string>>; +using DecodeHexDoubleTest = + ::testing::TestWithParam>>; +using RoundTripFloatTest = ::testing::TestWithParam; +using RoundTripDoubleTest = ::testing::TestWithParam; + +// Hex-encodes a float value. +template +std::string EncodeViaHexFloat(const T& value) { + std::stringstream ss; + ss << spvutils::HexFloat(value); + return ss.str(); +} + +// The following two tests can't be DRY because they take different parameter +// types. + +TEST_P(HexFloatTest, EncodeCorrectly) { + EXPECT_THAT(EncodeViaHexFloat(GetParam().first), Eq(GetParam().second)); +} + +TEST_P(HexDoubleTest, EncodeCorrectly) { + EXPECT_THAT(EncodeViaHexFloat(GetParam().first), Eq(GetParam().second)); +} + +// Decodes a hex-float string. +template +FloatProxy Decode(const std::string& str) { + spvutils::HexFloat> decoded(0.f); + EXPECT_TRUE((std::stringstream(str) >> decoded).eof()); + return decoded.value(); +} + +TEST_P(HexFloatTest, DecodeCorrectly) { + EXPECT_THAT(Decode(GetParam().second), Eq(GetParam().first)); +} + +TEST_P(HexDoubleTest, DecodeCorrectly) { + EXPECT_THAT(Decode(GetParam().second), Eq(GetParam().first)); +} + +INSTANTIATE_TEST_CASE_P( + Float32Tests, HexFloatTest, + ::testing::ValuesIn(std::vector, std::string>>({ + {0.f, "0x0p+0"}, + {1.f, "0x1p+0"}, + {2.f, "0x1p+1"}, + {3.f, "0x1.8p+1"}, + {0.5f, "0x1p-1"}, + {0.25f, "0x1p-2"}, + {0.75f, "0x1.8p-1"}, + {-0.f, "-0x0p+0"}, + {-1.f, "-0x1p+0"}, + {-0.5f, "-0x1p-1"}, + {-0.25f, "-0x1p-2"}, + {-0.75f, "-0x1.8p-1"}, + + // Larger numbers + {512.f, "0x1p+9"}, + {-512.f, "-0x1p+9"}, + {1024.f, "0x1p+10"}, + {-1024.f, "-0x1p+10"}, + {1024.f + 8.f, "0x1.02p+10"}, + {-1024.f - 8.f, "-0x1.02p+10"}, + + // Small numbers + {1.0f / 512.f, "0x1p-9"}, + {1.0f / -512.f, "-0x1p-9"}, + {1.0f / 1024.f, "0x1p-10"}, + {1.0f / -1024.f, "-0x1p-10"}, + {1.0f / 1024.f + 1.0f / 8.f, "0x1.02p-3"}, + {1.0f / -1024.f - 1.0f / 8.f, "-0x1.02p-3"}, + + // lowest non-denorm + {float(ldexp(1.0f, -126)), "0x1p-126"}, + {float(ldexp(-1.0f, -126)), "-0x1p-126"}, + + // Denormalized values + {float(ldexp(1.0f, -127)), "0x1p-127"}, + {float(ldexp(1.0f, -127) / 2.0f), "0x1p-128"}, + {float(ldexp(1.0f, -127) / 4.0f), "0x1p-129"}, + {float(ldexp(1.0f, -127) / 8.0f), "0x1p-130"}, + {float(ldexp(-1.0f, -127)), "-0x1p-127"}, + {float(ldexp(-1.0f, -127) / 2.0f), "-0x1p-128"}, + {float(ldexp(-1.0f, -127) / 4.0f), "-0x1p-129"}, + {float(ldexp(-1.0f, -127) / 8.0f), "-0x1p-130"}, + + {float(ldexp(1.0, -127) + (ldexp(1.0, -127) / 2.0f)), "0x1.8p-127"}, + {float(ldexp(1.0, -127) / 2.0 + (ldexp(1.0, -127) / 4.0f)), + "0x1.8p-128"}, + + })), ); + +INSTANTIATE_TEST_CASE_P( + Float32NanTests, HexFloatTest, + ::testing::ValuesIn(std::vector, std::string>>({ + // Various NAN and INF cases + {uint32_t(0xFF800000), "-0x1p+128"}, // -inf + {uint32_t(0x7F800000), "0x1p+128"}, // inf + {uint32_t(0xFFC00000), "-0x1.8p+128"}, // -nan + {uint32_t(0xFF800100), "-0x1.0002p+128"}, // -nan + {uint32_t(0xFF800c00), "-0x1.0018p+128"}, // -nan + {uint32_t(0xFF80F000), "-0x1.01ep+128"}, // -nan + {uint32_t(0xFFFFFFFF), "-0x1.fffffep+128"}, // -nan + {uint32_t(0x7FC00000), "0x1.8p+128"}, // +nan + {uint32_t(0x7F800100), "0x1.0002p+128"}, // +nan + {uint32_t(0x7f800c00), "0x1.0018p+128"}, // +nan + {uint32_t(0x7F80F000), "0x1.01ep+128"}, // +nan + {uint32_t(0x7FFFFFFF), "0x1.fffffep+128"}, // +nan + })), ); + +INSTANTIATE_TEST_CASE_P( + Float64Tests, HexDoubleTest, + ::testing::ValuesIn( + std::vector, std::string>>({ + {0., "0x0p+0"}, + {1., "0x1p+0"}, + {2., "0x1p+1"}, + {3., "0x1.8p+1"}, + {0.5, "0x1p-1"}, + {0.25, "0x1p-2"}, + {0.75, "0x1.8p-1"}, + {-0., "-0x0p+0"}, + {-1., "-0x1p+0"}, + {-0.5, "-0x1p-1"}, + {-0.25, "-0x1p-2"}, + {-0.75, "-0x1.8p-1"}, + + // Larger numbers + {512., "0x1p+9"}, + {-512., "-0x1p+9"}, + {1024., "0x1p+10"}, + {-1024., "-0x1p+10"}, + {1024. + 8., "0x1.02p+10"}, + {-1024. - 8., "-0x1.02p+10"}, + + // Large outside the range of normal floats + {ldexp(1.0, 128), "0x1p+128"}, + {ldexp(1.0, 129), "0x1p+129"}, + {ldexp(-1.0, 128), "-0x1p+128"}, + {ldexp(-1.0, 129), "-0x1p+129"}, + {ldexp(1.0, 128) + ldexp(1.0, 90), "0x1.0000000004p+128"}, + {ldexp(1.0, 129) + ldexp(1.0, 120), "0x1.008p+129"}, + {ldexp(-1.0, 128) + ldexp(1.0, 90), "-0x1.fffffffff8p+127"}, + {ldexp(-1.0, 129) + ldexp(1.0, 120), "-0x1.ffp+128"}, + + // Small numbers + {1.0 / 512., "0x1p-9"}, + {1.0 / -512., "-0x1p-9"}, + {1.0 / 1024., "0x1p-10"}, + {1.0 / -1024., "-0x1p-10"}, + {1.0 / 1024. + 1.0 / 8., "0x1.02p-3"}, + {1.0 / -1024. - 1.0 / 8., "-0x1.02p-3"}, + + // Small outside the range of normal floats + {ldexp(1.0, -128), "0x1p-128"}, + {ldexp(1.0, -129), "0x1p-129"}, + {ldexp(-1.0, -128), "-0x1p-128"}, + {ldexp(-1.0, -129), "-0x1p-129"}, + {ldexp(1.0, -128) + ldexp(1.0, -90), "0x1.0000000004p-90"}, + {ldexp(1.0, -129) + ldexp(1.0, -120), "0x1.008p-120"}, + {ldexp(-1.0, -128) + ldexp(1.0, -90), "0x1.fffffffff8p-91"}, + {ldexp(-1.0, -129) + ldexp(1.0, -120), "0x1.ffp-121"}, + + // lowest non-denorm + {ldexp(1.0, -1022), "0x1p-1022"}, + {ldexp(-1.0, -1022), "-0x1p-1022"}, + + // Denormalized values + {ldexp(1.0, -1023), "0x1p-1023"}, + {ldexp(1.0, -1023) / 2.0, "0x1p-1024"}, + {ldexp(1.0, -1023) / 4.0, "0x1p-1025"}, + {ldexp(1.0, -1023) / 8.0, "0x1p-1026"}, + {ldexp(-1.0, -1024), "-0x1p-1024"}, + {ldexp(-1.0, -1024) / 2.0, "-0x1p-1025"}, + {ldexp(-1.0, -1024) / 4.0, "-0x1p-1026"}, + {ldexp(-1.0, -1024) / 8.0, "-0x1p-1027"}, + + {ldexp(1.0, -1023) + (ldexp(1.0, -1023) / 2.0), "0x1.8p-1023"}, + {ldexp(1.0, -1023) / 2.0 + (ldexp(1.0, -1023) / 4.0), + "0x1.8p-1024"}, + + })), ); + +INSTANTIATE_TEST_CASE_P( + Float64NanTests, HexDoubleTest, + ::testing::ValuesIn(std::vector< + std::pair, std::string>>({ + // Various NAN and INF cases + {uint64_t(0xFFF0000000000000LL), "-0x1p+1024"}, //-inf + {uint64_t(0x7FF0000000000000LL), "0x1p+1024"}, //+inf + {uint64_t(0xFFF8000000000000LL), "-0x1.8p+1024"}, // -nan + {uint64_t(0xFFF0F00000000000LL), "-0x1.0fp+1024"}, // -nan + {uint64_t(0xFFF0000000000001LL), "-0x1.0000000000001p+1024"}, // -nan + {uint64_t(0xFFF0000300000000LL), "-0x1.00003p+1024"}, // -nan + {uint64_t(0xFFFFFFFFFFFFFFFFLL), "-0x1.fffffffffffffp+1024"}, // -nan + {uint64_t(0x7FF8000000000000LL), "0x1.8p+1024"}, // +nan + {uint64_t(0x7FF0F00000000000LL), "0x1.0fp+1024"}, // +nan + {uint64_t(0x7FF0000000000001LL), "0x1.0000000000001p+1024"}, // -nan + {uint64_t(0x7FF0000300000000LL), "0x1.00003p+1024"}, // -nan + {uint64_t(0x7FFFFFFFFFFFFFFFLL), "0x1.fffffffffffffp+1024"}, // -nan + })), ); + +// Tests that encoding a value and decoding it again restores +// the same value. +TEST_P(RoundTripFloatTest, CanStoreAccurately) { + std::stringstream ss; + ss << FloatProxy(GetParam()); + ss.seekg(0); + FloatProxy res; + ss >> res; + EXPECT_THAT(GetParam(), Eq(res.getAsFloat())); +} + +TEST_P(RoundTripDoubleTest, CanStoreAccurately) { + std::stringstream ss; + ss << FloatProxy(GetParam()); + ss.seekg(0); + FloatProxy res; + ss >> res; + EXPECT_THAT(GetParam(), Eq(res.getAsFloat())); +} + +INSTANTIATE_TEST_CASE_P( + Float32StoreTests, RoundTripFloatTest, + ::testing::ValuesIn(std::vector( + {// Value requiring more than 6 digits of precision to be + // represented accurately. + 3.0000002f}))); + +INSTANTIATE_TEST_CASE_P( + Float64StoreTests, RoundTripDoubleTest, + ::testing::ValuesIn(std::vector( + {// Value requiring more than 15 digits of precision to be + // represented accurately. + 1.5000000000000002}))); + +TEST(HexFloatStreamTest, OperatorLeftShiftPreservesFloatAndFill) { + std::stringstream s; + s << std::setw(4) << std::oct << std::setfill('x') << 8 << " " + << FloatProxy(uint32_t(0xFF800100)) << " " << std::setw(4) << 9; + EXPECT_THAT(s.str(), Eq(std::string("xx10 -0x1.0002p+128 xx11"))); +} + +TEST(HexDoubleStreamTest, OperatorLeftShiftPreservesFloatAndFill) { + std::stringstream s; + s << std::setw(4) << std::oct << std::setfill('x') << 8 << " " + << FloatProxy(uint64_t(0x7FF0F00000000000LL)) << " " << std::setw(4) + << 9; + EXPECT_THAT(s.str(), Eq(std::string("xx10 0x1.0fp+1024 xx11"))); +} + +TEST_P(DecodeHexFloatTest, DecodeCorrectly) { + EXPECT_THAT(Decode(GetParam().first), Eq(GetParam().second)); +} + +TEST_P(DecodeHexDoubleTest, DecodeCorrectly) { + EXPECT_THAT(Decode(GetParam().first), Eq(GetParam().second)); +} + +INSTANTIATE_TEST_CASE_P( + Float32DecodeTests, DecodeHexFloatTest, + ::testing::ValuesIn(std::vector>>({ + {"0x0p+000", 0.f}, + {"0x0p0", 0.f}, + {"0x0p-0", 0.f}, + + // flush to zero cases + {"0x1p-500", 0.f}, // Exponent underflows. + {"-0x1p-500", -0.f}, + {"0x0.00000000001p-126", 0.f}, // Fraction causes underflow. + {"-0x0.0000000001p-127", -0.f}, + {"-0x0.01p-142", -0.f}, // Fraction causes additional underflow. + {"0x0.01p-142", 0.f}, + + // Some floats that do not encode the same way as they decode. + {"0x2p+0", 2.f}, + {"0xFFp+0", 255.f}, + {"0x0.8p+0", 0.5f}, + {"0x0.4p+0", 0.25f}, + })), ); + +INSTANTIATE_TEST_CASE_P( + Float32DecodeInfTests, DecodeHexFloatTest, + ::testing::ValuesIn(std::vector>>({ + // inf cases + {"-0x1p+128", uint32_t(0xFF800000)}, // -inf + {"0x32p+127", uint32_t(0x7F800000)}, // inf + {"0x32p+500", uint32_t(0x7F800000)}, // inf + {"-0x32p+127", uint32_t(0xFF800000)}, // -inf + })), ); + +INSTANTIATE_TEST_CASE_P( + Float64DecodeTests, DecodeHexDoubleTest, + ::testing::ValuesIn( + std::vector>>({ + {"0x0p+000", 0.}, + {"0x0p0", 0.}, + {"0x0p-0", 0.}, + + // flush to zero cases + {"0x1p-5000", 0.}, // Exponent underflows. + {"-0x1p-5000", -0.}, + {"0x0.0000000000000001p-1023", 0.}, // Fraction causes underflow. + {"-0x0.000000000000001p-1024", -0.}, + {"-0x0.01p-1090", -0.f}, // Fraction causes additional underflow. + {"0x0.01p-1090", 0.}, + + // Some floats that do not encode the same way as they decode. + {"0x2p+0", 2.}, + {"0xFFp+0", 255.}, + {"0x0.8p+0", 0.5}, + {"0x0.4p+0", 0.25}, + })), ); + +INSTANTIATE_TEST_CASE_P( + Float64DecodeInfTests, DecodeHexDoubleTest, + ::testing::ValuesIn( + std::vector>>({ + // inf cases + {"-0x1p+1024", uint64_t(0xFFF0000000000000)}, // -inf + {"0x32p+1023", uint64_t(0x7FF0000000000000)}, // inf + {"0x32p+5000", uint64_t(0x7FF0000000000000)}, // inf + {"-0x32p+1023", uint64_t(0xFFF0000000000000)}, // -inf + })), ); + +TEST(FloatProxy, ValidConversion) { + EXPECT_THAT(FloatProxy(1.f).getAsFloat(), Eq(1.0f)); + EXPECT_THAT(FloatProxy(32.f).getAsFloat(), Eq(32.0f)); + EXPECT_THAT(FloatProxy(-1.f).getAsFloat(), Eq(-1.0f)); + EXPECT_THAT(FloatProxy(0.f).getAsFloat(), Eq(0.0f)); + EXPECT_THAT(FloatProxy(-0.f).getAsFloat(), Eq(-0.0f)); + EXPECT_THAT(FloatProxy(1.2e32f).getAsFloat(), Eq(1.2e32f)); + + EXPECT_TRUE(std::isinf(FloatProxy(uint32_t(0xFF800000)).getAsFloat())); + EXPECT_TRUE(std::isinf(FloatProxy(uint32_t(0x7F800000)).getAsFloat())); + EXPECT_TRUE(std::isnan(FloatProxy(uint32_t(0xFFC00000)).getAsFloat())); + EXPECT_TRUE(std::isnan(FloatProxy(uint32_t(0xFF800100)).getAsFloat())); + EXPECT_TRUE(std::isnan(FloatProxy(uint32_t(0xFF800c00)).getAsFloat())); + EXPECT_TRUE(std::isnan(FloatProxy(uint32_t(0xFF80F000)).getAsFloat())); + EXPECT_TRUE(std::isnan(FloatProxy(uint32_t(0xFFFFFFFF)).getAsFloat())); + EXPECT_TRUE(std::isnan(FloatProxy(uint32_t(0x7FC00000)).getAsFloat())); + EXPECT_TRUE(std::isnan(FloatProxy(uint32_t(0x7F800100)).getAsFloat())); + EXPECT_TRUE(std::isnan(FloatProxy(uint32_t(0x7f800c00)).getAsFloat())); + EXPECT_TRUE(std::isnan(FloatProxy(uint32_t(0x7F80F000)).getAsFloat())); + EXPECT_TRUE(std::isnan(FloatProxy(uint32_t(0x7FFFFFFF)).getAsFloat())); + + EXPECT_THAT(FloatProxy(uint32_t(0xFF800000)).data(), Eq(0xFF800000u)); + EXPECT_THAT(FloatProxy(uint32_t(0x7F800000)).data(), Eq(0x7F800000u)); + EXPECT_THAT(FloatProxy(uint32_t(0xFFC00000)).data(), Eq(0xFFC00000u)); + EXPECT_THAT(FloatProxy(uint32_t(0xFF800100)).data(), Eq(0xFF800100u)); + EXPECT_THAT(FloatProxy(uint32_t(0xFF800c00)).data(), Eq(0xFF800c00u)); + EXPECT_THAT(FloatProxy(uint32_t(0xFF80F000)).data(), Eq(0xFF80F000u)); + EXPECT_THAT(FloatProxy(uint32_t(0xFFFFFFFF)).data(), Eq(0xFFFFFFFFu)); + EXPECT_THAT(FloatProxy(uint32_t(0x7FC00000)).data(), Eq(0x7FC00000u)); + EXPECT_THAT(FloatProxy(uint32_t(0x7F800100)).data(), Eq(0x7F800100u)); + EXPECT_THAT(FloatProxy(uint32_t(0x7f800c00)).data(), Eq(0x7f800c00u)); + EXPECT_THAT(FloatProxy(uint32_t(0x7F80F000)).data(), Eq(0x7F80F000u)); + EXPECT_THAT(FloatProxy(uint32_t(0x7FFFFFFF)).data(), Eq(0x7FFFFFFFu)); +} + +TEST(FloatProxy, Nan) { + EXPECT_TRUE(FloatProxy(uint32_t(0xFFC00000)).isNan()); + EXPECT_TRUE(FloatProxy(uint32_t(0xFF800100)).isNan()); + EXPECT_TRUE(FloatProxy(uint32_t(0xFF800c00)).isNan()); + EXPECT_TRUE(FloatProxy(uint32_t(0xFF80F000)).isNan()); + EXPECT_TRUE(FloatProxy(uint32_t(0xFFFFFFFF)).isNan()); + EXPECT_TRUE(FloatProxy(uint32_t(0x7FC00000)).isNan()); + EXPECT_TRUE(FloatProxy(uint32_t(0x7F800100)).isNan()); + EXPECT_TRUE(FloatProxy(uint32_t(0x7f800c00)).isNan()); + EXPECT_TRUE(FloatProxy(uint32_t(0x7F80F000)).isNan()); + EXPECT_TRUE(FloatProxy(uint32_t(0x7FFFFFFF)).isNan()); +} + +TEST(FloatProxy, Negation) { + EXPECT_THAT((-FloatProxy(1.f)).getAsFloat(), Eq(-1.0f)); + EXPECT_THAT((-FloatProxy(0.f)).getAsFloat(), Eq(-0.0f)); + + EXPECT_THAT((-FloatProxy(-1.f)).getAsFloat(), Eq(1.0f)); + EXPECT_THAT((-FloatProxy(-0.f)).getAsFloat(), Eq(0.0f)); + + EXPECT_THAT((-FloatProxy(32.f)).getAsFloat(), Eq(-32.0f)); + EXPECT_THAT((-FloatProxy(-32.f)).getAsFloat(), Eq(32.0f)); + + EXPECT_THAT((-FloatProxy(1.2e32f)).getAsFloat(), Eq(-1.2e32f)); + EXPECT_THAT((-FloatProxy(-1.2e32f)).getAsFloat(), Eq(1.2e32f)); + + EXPECT_THAT( + (-FloatProxy(std::numeric_limits::infinity())).getAsFloat(), + Eq(-std::numeric_limits::infinity())); + EXPECT_THAT((-FloatProxy(-std::numeric_limits::infinity())) + .getAsFloat(), + Eq(std::numeric_limits::infinity())); +} + +// Test conversion of FloatProxy values to strings. +// +// In previous cases, we always wrapped the FloatProxy value in a HexFloat +// before conversion to a string. In the following cases, the FloatProxy +// decides for itself whether to print as a regular number or as a hex float. + +using FloatProxyFloatTest = + ::testing::TestWithParam, std::string>>; +using FloatProxyDoubleTest = + ::testing::TestWithParam, std::string>>; + +// Converts a float value to a string via a FloatProxy. +template +std::string EncodeViaFloatProxy(const T& value) { + std::stringstream ss; + ss << value; + return ss.str(); +} + +// Converts a floating point string so that the exponent prefix +// is 'e', and the exponent value does not have leading zeros. +// The Microsoft runtime library likes to write things like "2.5E+010". +// Convert that to "2.5e+10". +// We don't care what happens to strings that are not floating point +// strings. +std::string NormalizeExponentInFloatString(std::string in) { + std::string result; + // Reserve one spot for the terminating null, even when the sscanf fails. + std::vector prefix(in.size() + 1); + char e; + char plus_or_minus; + int exponent; // in base 10 + if ((4 == std::sscanf(in.c_str(), "%[-+.0123456789]%c%c%d", prefix.data(), &e, + &plus_or_minus, &exponent)) && + (e == 'e' || e == 'E') && + (plus_or_minus == '-' || plus_or_minus == '+')) { + // It looks like a floating point value with exponent. + std::stringstream out; + out << prefix.data() << 'e' << plus_or_minus << exponent; + result = out.str(); + } else { + result = in; + } + return result; +} + +TEST(NormalizeFloat, Sample) { + EXPECT_THAT(NormalizeExponentInFloatString(""), Eq("")); + EXPECT_THAT(NormalizeExponentInFloatString("1e-12"), Eq("1e-12")); + EXPECT_THAT(NormalizeExponentInFloatString("1E+14"), Eq("1e+14")); + EXPECT_THAT(NormalizeExponentInFloatString("1e-0012"), Eq("1e-12")); + EXPECT_THAT(NormalizeExponentInFloatString("1.263E+014"), Eq("1.263e+14")); +} + +// The following two tests can't be DRY because they take different parameter +// types. +TEST_P(FloatProxyFloatTest, EncodeCorrectly) { + EXPECT_THAT( + NormalizeExponentInFloatString(EncodeViaFloatProxy(GetParam().first)), + Eq(GetParam().second)); +} + +TEST_P(FloatProxyDoubleTest, EncodeCorrectly) { + EXPECT_THAT( + NormalizeExponentInFloatString(EncodeViaFloatProxy(GetParam().first)), + Eq(GetParam().second)); +} + +INSTANTIATE_TEST_CASE_P( + Float32Tests, FloatProxyFloatTest, + ::testing::ValuesIn(std::vector, std::string>>({ + // Zero + {0.f, "0"}, + // Normal numbers + {1.f, "1"}, + {-0.25f, "-0.25"}, + {1000.0f, "1000"}, + + // Still normal numbers, but with large magnitude exponents. + {float(ldexp(1.f, 126)), "8.50705917e+37"}, + {float(ldexp(-1.f, -126)), "-1.17549435e-38"}, + + // denormalized values are printed as hex floats. + {float(ldexp(1.0f, -127)), "0x1p-127"}, + {float(ldexp(1.5f, -128)), "0x1.8p-128"}, + {float(ldexp(1.25, -129)), "0x1.4p-129"}, + {float(ldexp(1.125, -130)), "0x1.2p-130"}, + {float(ldexp(-1.0f, -127)), "-0x1p-127"}, + {float(ldexp(-1.0f, -128)), "-0x1p-128"}, + {float(ldexp(-1.0f, -129)), "-0x1p-129"}, + {float(ldexp(-1.5f, -130)), "-0x1.8p-130"}, + + // NaNs + {FloatProxy(uint32_t(0xFFC00000)), "-0x1.8p+128"}, + {FloatProxy(uint32_t(0xFF800100)), "-0x1.0002p+128"}, + + {std::numeric_limits::infinity(), "0x1p+128"}, + {-std::numeric_limits::infinity(), "-0x1p+128"}, + })), ); + +INSTANTIATE_TEST_CASE_P( + Float64Tests, FloatProxyDoubleTest, + ::testing::ValuesIn( + std::vector, std::string>>({ + {0., "0"}, + {1., "1"}, + {-0.25, "-0.25"}, + {1000.0, "1000"}, + + // Large outside the range of normal floats + {ldexp(1.0, 128), "3.4028236692093846e+38"}, + {ldexp(1.5, 129), "1.0208471007628154e+39"}, + {ldexp(-1.0, 128), "-3.4028236692093846e+38"}, + {ldexp(-1.5, 129), "-1.0208471007628154e+39"}, + + // Small outside the range of normal floats + {ldexp(1.5, -129), "2.2040519077917891e-39"}, + {ldexp(-1.5, -129), "-2.2040519077917891e-39"}, + + // lowest non-denorm + {ldexp(1.0, -1022), "2.2250738585072014e-308"}, + {ldexp(-1.0, -1022), "-2.2250738585072014e-308"}, + + // Denormalized values + {ldexp(1.125, -1023), "0x1.2p-1023"}, + {ldexp(-1.375, -1024), "-0x1.6p-1024"}, + + // NaNs + {uint64_t(0x7FF8000000000000LL), "0x1.8p+1024"}, + {uint64_t(0xFFF0F00000000000LL), "-0x1.0fp+1024"}, + + // Infinity + {std::numeric_limits::infinity(), "0x1p+1024"}, + {-std::numeric_limits::infinity(), "-0x1p+1024"}, + + })), ); + +// double is used so that unbiased_exponent can be used with the output +// of ldexp directly. +int32_t unbiased_exponent(double f) { + return spvutils::HexFloat>(static_cast(f)) + .getUnbiasedNormalizedExponent(); +} + +int16_t unbiased_half_exponent(uint16_t f) { + return spvutils::HexFloat>(f) + .getUnbiasedNormalizedExponent(); +} + +TEST(HexFloatOperationTest, UnbiasedExponent) { + // Float cases + EXPECT_EQ(0, unbiased_exponent(ldexp(1.0f, 0))); + EXPECT_EQ(-32, unbiased_exponent(ldexp(1.0f, -32))); + EXPECT_EQ(42, unbiased_exponent(ldexp(1.0f, 42))); + EXPECT_EQ(125, unbiased_exponent(ldexp(1.0f, 125))); + + EXPECT_EQ(128, spvutils::HexFloat>( + std::numeric_limits::infinity()) + .getUnbiasedNormalizedExponent()); + + EXPECT_EQ(-100, unbiased_exponent(ldexp(1.0f, -100))); + EXPECT_EQ(-127, unbiased_exponent(ldexp(1.0f, -127))); // First denorm + EXPECT_EQ(-128, unbiased_exponent(ldexp(1.0f, -128))); + EXPECT_EQ(-129, unbiased_exponent(ldexp(1.0f, -129))); + EXPECT_EQ(-140, unbiased_exponent(ldexp(1.0f, -140))); + // Smallest representable number + EXPECT_EQ(-126 - 23, unbiased_exponent(ldexp(1.0f, -126 - 23))); + // Should get rounded to 0 first. + EXPECT_EQ(0, unbiased_exponent(ldexp(1.0f, -127 - 23))); + + // Float16 cases + // The exponent is represented in the bits 0x7C00 + // The offset is -15 + EXPECT_EQ(0, unbiased_half_exponent(0x3C00)); + EXPECT_EQ(3, unbiased_half_exponent(0x4800)); + EXPECT_EQ(-1, unbiased_half_exponent(0x3800)); + EXPECT_EQ(-14, unbiased_half_exponent(0x0400)); + EXPECT_EQ(16, unbiased_half_exponent(0x7C00)); + EXPECT_EQ(10, unbiased_half_exponent(0x6400)); + + // Smallest representable number + EXPECT_EQ(-24, unbiased_half_exponent(0x0001)); +} + +// Creates a float that is the sum of 1/(2 ^ fractions[i]) for i in factions +float float_fractions(const std::vector& fractions) { + float f = 0; + for (int32_t i : fractions) { + f += std::ldexp(1.0f, -i); + } + return f; +} + +// Returns the normalized significand of a HexFloat> +// that was created by calling float_fractions with the input fractions, +// raised to the power of exp. +uint32_t normalized_significand(const std::vector& fractions, + uint32_t exp) { + return spvutils::HexFloat>( + static_cast(ldexp(float_fractions(fractions), exp))) + .getNormalizedSignificand(); +} + +// Sets the bits from MSB to LSB of the significand part of a float. +// For example 0 would set the bit 23 (counting from LSB to MSB), +// and 1 would set the 22nd bit. +uint32_t bits_set(const std::vector& bits) { + const uint32_t top_bit = 1u << 22u; + uint32_t val = 0; + for (uint32_t i : bits) { + val |= top_bit >> i; + } + return val; +} + +// The same as bits_set but for a Float16 value instead of 32-bit floating +// point. +uint16_t half_bits_set(const std::vector& bits) { + const uint32_t top_bit = 1u << 9u; + uint32_t val = 0; + for (uint32_t i : bits) { + val |= top_bit >> i; + } + return static_cast(val); +} + +TEST(HexFloatOperationTest, NormalizedSignificand) { + // For normalized numbers (the following) it should be a simple matter + // of getting rid of the top implicit bit + EXPECT_EQ(bits_set({}), normalized_significand({0}, 0)); + EXPECT_EQ(bits_set({0}), normalized_significand({0, 1}, 0)); + EXPECT_EQ(bits_set({0, 1}), normalized_significand({0, 1, 2}, 0)); + EXPECT_EQ(bits_set({1}), normalized_significand({0, 2}, 0)); + EXPECT_EQ(bits_set({1}), normalized_significand({0, 2}, 32)); + EXPECT_EQ(bits_set({1}), normalized_significand({0, 2}, 126)); + + // For denormalized numbers we expect the normalized significand to + // shift as if it were normalized. This means, in practice that the + // top_most set bit will be cut off. Looks very similar to above (on purpose) + EXPECT_EQ(bits_set({}), normalized_significand({0}, -127)); + EXPECT_EQ(bits_set({3}), normalized_significand({0, 4}, -128)); + EXPECT_EQ(bits_set({3}), normalized_significand({0, 4}, -127)); + EXPECT_EQ(bits_set({}), normalized_significand({22}, -127)); + EXPECT_EQ(bits_set({0}), normalized_significand({21, 22}, -127)); +} + +// Returns the 32-bit floating point value created by +// calling setFromSignUnbiasedExponentAndNormalizedSignificand +// on a HexFloat> +float set_from_sign(bool negative, int32_t unbiased_exponent, + uint32_t significand, bool round_denorm_up) { + spvutils::HexFloat> f(0.f); + f.setFromSignUnbiasedExponentAndNormalizedSignificand( + negative, unbiased_exponent, significand, round_denorm_up); + return f.value().getAsFloat(); +} + +TEST(HexFloatOperationTests, + SetFromSignUnbiasedExponentAndNormalizedSignificand) { + EXPECT_EQ(1.f, set_from_sign(false, 0, 0, false)); + + // Tests insertion of various denormalized numbers with and without round up. + EXPECT_EQ(static_cast(ldexp(1.f, -149)), + set_from_sign(false, -149, 0, false)); + EXPECT_EQ(static_cast(ldexp(1.f, -149)), + set_from_sign(false, -149, 0, true)); + EXPECT_EQ(0.f, set_from_sign(false, -150, 1, false)); + EXPECT_EQ(static_cast(ldexp(1.f, -149)), + set_from_sign(false, -150, 1, true)); + + EXPECT_EQ(ldexp(1.0f, -127), set_from_sign(false, -127, 0, false)); + EXPECT_EQ(ldexp(1.0f, -128), set_from_sign(false, -128, 0, false)); + EXPECT_EQ(float_fractions({0, 1, 2, 5}), + set_from_sign(false, 0, bits_set({0, 1, 4}), false)); + EXPECT_EQ(ldexp(float_fractions({0, 1, 2, 5}), -32), + set_from_sign(false, -32, bits_set({0, 1, 4}), false)); + EXPECT_EQ(ldexp(float_fractions({0, 1, 2, 5}), -128), + set_from_sign(false, -128, bits_set({0, 1, 4}), false)); + + // The negative cases from above. + EXPECT_EQ(-1.f, set_from_sign(true, 0, 0, false)); + EXPECT_EQ(-ldexp(1.0, -127), set_from_sign(true, -127, 0, false)); + EXPECT_EQ(-ldexp(1.0, -128), set_from_sign(true, -128, 0, false)); + EXPECT_EQ(-float_fractions({0, 1, 2, 5}), + set_from_sign(true, 0, bits_set({0, 1, 4}), false)); + EXPECT_EQ(-ldexp(float_fractions({0, 1, 2, 5}), -32), + set_from_sign(true, -32, bits_set({0, 1, 4}), false)); + EXPECT_EQ(-ldexp(float_fractions({0, 1, 2, 5}), -128), + set_from_sign(true, -128, bits_set({0, 1, 4}), false)); +} + +TEST(HexFloatOperationTests, NonRounding) { + // Rounding from 32-bit hex-float to 32-bit hex-float should be trivial, + // except in the denorm case which is a bit more complex. + using HF = spvutils::HexFloat>; + bool carry_bit = false; + + spvutils::round_direction rounding[] = { + spvutils::round_direction::kToZero, + spvutils::round_direction::kToNearestEven, + spvutils::round_direction::kToPositiveInfinity, + spvutils::round_direction::kToNegativeInfinity}; + + // Everything fits, so this should be straight-forward + for (spvutils::round_direction round : rounding) { + EXPECT_EQ(bits_set({}), + HF(0.f).getRoundedNormalizedSignificand(round, &carry_bit)); + EXPECT_FALSE(carry_bit); + + EXPECT_EQ(bits_set({0}), + HF(float_fractions({0, 1})) + .getRoundedNormalizedSignificand(round, &carry_bit)); + EXPECT_FALSE(carry_bit); + + EXPECT_EQ(bits_set({1, 3}), + HF(float_fractions({0, 2, 4})) + .getRoundedNormalizedSignificand(round, &carry_bit)); + EXPECT_FALSE(carry_bit); + + EXPECT_EQ( + bits_set({0, 1, 4}), + HF(static_cast(-ldexp(float_fractions({0, 1, 2, 5}), -128))) + .getRoundedNormalizedSignificand(round, &carry_bit)); + EXPECT_FALSE(carry_bit); + + EXPECT_EQ(bits_set({0, 1, 4, 22}), + HF(static_cast(float_fractions({0, 1, 2, 5, 23}))) + .getRoundedNormalizedSignificand(round, &carry_bit)); + EXPECT_FALSE(carry_bit); + } +} + +using RD = spvutils::round_direction; +struct RoundSignificandCase { + float source_float; + std::pair expected_results; + spvutils::round_direction round; +}; + +using HexFloatRoundTest = ::testing::TestWithParam; + +TEST_P(HexFloatRoundTest, RoundDownToFP16) { + using HF = spvutils::HexFloat>; + using HF16 = spvutils::HexFloat>; + + HF input_value(GetParam().source_float); + bool carry_bit = false; + EXPECT_EQ(GetParam().expected_results.first, + input_value.getRoundedNormalizedSignificand(GetParam().round, + &carry_bit)); + EXPECT_EQ(carry_bit, GetParam().expected_results.second); +} + +// clang-format off +INSTANTIATE_TEST_CASE_P(F32ToF16, HexFloatRoundTest, + ::testing::ValuesIn(std::vector( + { + {float_fractions({0}), std::make_pair(half_bits_set({}), false), RD::kToZero}, + {float_fractions({0}), std::make_pair(half_bits_set({}), false), RD::kToNearestEven}, + {float_fractions({0}), std::make_pair(half_bits_set({}), false), RD::kToPositiveInfinity}, + {float_fractions({0}), std::make_pair(half_bits_set({}), false), RD::kToNegativeInfinity}, + {float_fractions({0, 1}), std::make_pair(half_bits_set({0}), false), RD::kToZero}, + + {float_fractions({0, 1, 11}), std::make_pair(half_bits_set({0}), false), RD::kToZero}, + {float_fractions({0, 1, 11}), std::make_pair(half_bits_set({0, 9}), false), RD::kToPositiveInfinity}, + {float_fractions({0, 1, 11}), std::make_pair(half_bits_set({0}), false), RD::kToNegativeInfinity}, + {float_fractions({0, 1, 11}), std::make_pair(half_bits_set({0}), false), RD::kToNearestEven}, + + {float_fractions({0, 1, 10, 11}), std::make_pair(half_bits_set({0, 9}), false), RD::kToZero}, + {float_fractions({0, 1, 10, 11}), std::make_pair(half_bits_set({0, 8}), false), RD::kToPositiveInfinity}, + {float_fractions({0, 1, 10, 11}), std::make_pair(half_bits_set({0, 9}), false), RD::kToNegativeInfinity}, + {float_fractions({0, 1, 10, 11}), std::make_pair(half_bits_set({0, 8}), false), RD::kToNearestEven}, + + {float_fractions({0, 1, 11, 12}), std::make_pair(half_bits_set({0}), false), RD::kToZero}, + {float_fractions({0, 1, 11, 12}), std::make_pair(half_bits_set({0, 9}), false), RD::kToPositiveInfinity}, + {float_fractions({0, 1, 11, 12}), std::make_pair(half_bits_set({0}), false), RD::kToNegativeInfinity}, + {float_fractions({0, 1, 11, 12}), std::make_pair(half_bits_set({0, 9}), false), RD::kToNearestEven}, + + {-float_fractions({0, 1, 11, 12}), std::make_pair(half_bits_set({0}), false), RD::kToZero}, + {-float_fractions({0, 1, 11, 12}), std::make_pair(half_bits_set({0}), false), RD::kToPositiveInfinity}, + {-float_fractions({0, 1, 11, 12}), std::make_pair(half_bits_set({0, 9}), false), RD::kToNegativeInfinity}, + {-float_fractions({0, 1, 11, 12}), std::make_pair(half_bits_set({0, 9}), false), RD::kToNearestEven}, + + {float_fractions({0, 1, 11, 22}), std::make_pair(half_bits_set({0}), false), RD::kToZero}, + {float_fractions({0, 1, 11, 22}), std::make_pair(half_bits_set({0, 9}), false), RD::kToPositiveInfinity}, + {float_fractions({0, 1, 11, 22}), std::make_pair(half_bits_set({0}), false), RD::kToNegativeInfinity}, + {float_fractions({0, 1, 11, 22}), std::make_pair(half_bits_set({0, 9}), false), RD::kToNearestEven}, + + // Carries + {float_fractions({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}), std::make_pair(half_bits_set({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}), false), RD::kToZero}, + {float_fractions({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}), std::make_pair(half_bits_set({}), true), RD::kToPositiveInfinity}, + {float_fractions({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}), std::make_pair(half_bits_set({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}), false), RD::kToNegativeInfinity}, + {float_fractions({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}), std::make_pair(half_bits_set({}), true), RD::kToNearestEven}, + + // Cases where original number was denorm. Note: this should have no effect + // the number is pre-normalized. + {static_cast(ldexp(float_fractions({0, 1, 11, 13}), -128)), std::make_pair(half_bits_set({0}), false), RD::kToZero}, + {static_cast(ldexp(float_fractions({0, 1, 11, 13}), -129)), std::make_pair(half_bits_set({0, 9}), false), RD::kToPositiveInfinity}, + {static_cast(ldexp(float_fractions({0, 1, 11, 13}), -131)), std::make_pair(half_bits_set({0}), false), RD::kToNegativeInfinity}, + {static_cast(ldexp(float_fractions({0, 1, 11, 13}), -130)), std::make_pair(half_bits_set({0, 9}), false), RD::kToNearestEven}, + })),); +// clang-format on + +struct UpCastSignificandCase { + uint16_t source_half; + uint32_t expected_result; +}; + +using HexFloatRoundUpSignificandTest = + ::testing::TestWithParam; +TEST_P(HexFloatRoundUpSignificandTest, Widening) { + using HF = spvutils::HexFloat>; + using HF16 = spvutils::HexFloat>; + bool carry_bit = false; + + spvutils::round_direction rounding[] = { + spvutils::round_direction::kToZero, + spvutils::round_direction::kToNearestEven, + spvutils::round_direction::kToPositiveInfinity, + spvutils::round_direction::kToNegativeInfinity}; + + // Everything fits, so everything should just be bit-shifts. + for (spvutils::round_direction round : rounding) { + carry_bit = false; + HF16 input_value(GetParam().source_half); + EXPECT_EQ( + GetParam().expected_result, + input_value.getRoundedNormalizedSignificand(round, &carry_bit)) + << std::hex << "0x" + << input_value.getRoundedNormalizedSignificand(round, &carry_bit) + << " 0x" << GetParam().expected_result; + EXPECT_FALSE(carry_bit); + } +} + +INSTANTIATE_TEST_CASE_P( + F16toF32, HexFloatRoundUpSignificandTest, + // 0xFC00 of the source 16-bit hex value cover the sign and the exponent. + // They are ignored for this test. + ::testing::ValuesIn(std::vector({ + {0x3F00, 0x600000}, + {0x0F00, 0x600000}, + {0x0F01, 0x602000}, + {0x0FFF, 0x7FE000}, + })), ); + +struct DownCastTest { + float source_float; + uint16_t expected_half; + std::vector directions; +}; + +std::string get_round_text(spvutils::round_direction direction) { +#define CASE(round_direction) \ + case round_direction: \ + return #round_direction + + switch (direction) { + CASE(spvutils::round_direction::kToZero); + CASE(spvutils::round_direction::kToPositiveInfinity); + CASE(spvutils::round_direction::kToNegativeInfinity); + CASE(spvutils::round_direction::kToNearestEven); + } +#undef CASE + return ""; +} + +using HexFloatFP32To16Tests = ::testing::TestWithParam; + +TEST_P(HexFloatFP32To16Tests, NarrowingCasts) { + using HF = spvutils::HexFloat>; + using HF16 = spvutils::HexFloat>; + HF f(GetParam().source_float); + for (auto round : GetParam().directions) { + HF16 half(0); + f.castTo(half, round); + EXPECT_EQ(GetParam().expected_half, half.value().getAsFloat().get_value()) + << get_round_text(round) << " " << std::hex + << spvutils::BitwiseCast(GetParam().source_float) + << " cast to: " << half.value().getAsFloat().get_value(); + } +} + +const uint16_t positive_infinity = 0x7C00; +const uint16_t negative_infinity = 0xFC00; + +INSTANTIATE_TEST_CASE_P( + F32ToF16, HexFloatFP32To16Tests, + ::testing::ValuesIn(std::vector({ + // Exactly representable as half. + {0.f, + 0x0, + {RD::kToZero, RD::kToPositiveInfinity, RD::kToNegativeInfinity, + RD::kToNearestEven}}, + {-0.f, + 0x8000, + {RD::kToZero, RD::kToPositiveInfinity, RD::kToNegativeInfinity, + RD::kToNearestEven}}, + {1.0f, + 0x3C00, + {RD::kToZero, RD::kToPositiveInfinity, RD::kToNegativeInfinity, + RD::kToNearestEven}}, + {-1.0f, + 0xBC00, + {RD::kToZero, RD::kToPositiveInfinity, RD::kToNegativeInfinity, + RD::kToNearestEven}}, + + {float_fractions({0, 1, 10}), + 0x3E01, + {RD::kToZero, RD::kToPositiveInfinity, RD::kToNegativeInfinity, + RD::kToNearestEven}}, + {-float_fractions({0, 1, 10}), + 0xBE01, + {RD::kToZero, RD::kToPositiveInfinity, RD::kToNegativeInfinity, + RD::kToNearestEven}}, + {static_cast(ldexp(float_fractions({0, 1, 10}), 3)), + 0x4A01, + {RD::kToZero, RD::kToPositiveInfinity, RD::kToNegativeInfinity, + RD::kToNearestEven}}, + {static_cast(-ldexp(float_fractions({0, 1, 10}), 3)), + 0xCA01, + {RD::kToZero, RD::kToPositiveInfinity, RD::kToNegativeInfinity, + RD::kToNearestEven}}, + + // Underflow + {static_cast(ldexp(1.0f, -25)), + 0x0, + {RD::kToZero, RD::kToNegativeInfinity, RD::kToNearestEven}}, + {static_cast(ldexp(1.0f, -25)), 0x1, {RD::kToPositiveInfinity}}, + {static_cast(-ldexp(1.0f, -25)), + 0x8000, + {RD::kToZero, RD::kToPositiveInfinity, RD::kToNearestEven}}, + {static_cast(-ldexp(1.0f, -25)), + 0x8001, + {RD::kToNegativeInfinity}}, + {static_cast(ldexp(1.0f, -24)), + 0x1, + {RD::kToZero, RD::kToPositiveInfinity, RD::kToNegativeInfinity, + RD::kToNearestEven}}, + + // Overflow + {static_cast(ldexp(1.0f, 16)), + positive_infinity, + {RD::kToZero, RD::kToPositiveInfinity, RD::kToNegativeInfinity, + RD::kToNearestEven}}, + {static_cast(ldexp(1.0f, 18)), + positive_infinity, + {RD::kToZero, RD::kToPositiveInfinity, RD::kToNegativeInfinity, + RD::kToNearestEven}}, + {static_cast(ldexp(1.3f, 16)), + positive_infinity, + {RD::kToZero, RD::kToPositiveInfinity, RD::kToNegativeInfinity, + RD::kToNearestEven}}, + {static_cast(-ldexp(1.0f, 16)), + negative_infinity, + {RD::kToZero, RD::kToPositiveInfinity, RD::kToNegativeInfinity, + RD::kToNearestEven}}, + {static_cast(-ldexp(1.0f, 18)), + negative_infinity, + {RD::kToZero, RD::kToPositiveInfinity, RD::kToNegativeInfinity, + RD::kToNearestEven}}, + {static_cast(-ldexp(1.3f, 16)), + negative_infinity, + {RD::kToZero, RD::kToPositiveInfinity, RD::kToNegativeInfinity, + RD::kToNearestEven}}, + + // Transfer of Infinities + {std::numeric_limits::infinity(), + positive_infinity, + {RD::kToZero, RD::kToPositiveInfinity, RD::kToNegativeInfinity, + RD::kToNearestEven}}, + {-std::numeric_limits::infinity(), + negative_infinity, + {RD::kToZero, RD::kToPositiveInfinity, RD::kToNegativeInfinity, + RD::kToNearestEven}}, + + // Nans are below because we cannot test for equality. + })), ); + +struct UpCastCase { + uint16_t source_half; + float expected_float; +}; + +using HexFloatFP16To32Tests = ::testing::TestWithParam; +TEST_P(HexFloatFP16To32Tests, WideningCasts) { + using HF = spvutils::HexFloat>; + using HF16 = spvutils::HexFloat>; + HF16 f(GetParam().source_half); + + spvutils::round_direction rounding[] = { + spvutils::round_direction::kToZero, + spvutils::round_direction::kToNearestEven, + spvutils::round_direction::kToPositiveInfinity, + spvutils::round_direction::kToNegativeInfinity}; + + // Everything fits, so everything should just be bit-shifts. + for (spvutils::round_direction round : rounding) { + HF flt(0.f); + f.castTo(flt, round); + EXPECT_EQ(GetParam().expected_float, flt.value().getAsFloat()) + << get_round_text(round) << " " << std::hex + << spvutils::BitwiseCast(GetParam().source_half) + << " cast to: " << flt.value().getAsFloat(); + } +} + +INSTANTIATE_TEST_CASE_P( + F16ToF32, HexFloatFP16To32Tests, + ::testing::ValuesIn(std::vector({ + {0x0000, 0.f}, + {0x8000, -0.f}, + {0x3C00, 1.0f}, + {0xBC00, -1.0f}, + {0x3F00, float_fractions({0, 1, 2})}, + {0xBF00, -float_fractions({0, 1, 2})}, + {0x3F01, float_fractions({0, 1, 2, 10})}, + {0xBF01, -float_fractions({0, 1, 2, 10})}, + + // denorm + {0x0001, static_cast(ldexp(1.0, -24))}, + {0x0002, static_cast(ldexp(1.0, -23))}, + {0x8001, static_cast(-ldexp(1.0, -24))}, + {0x8011, static_cast(-ldexp(1.0, -20) + -ldexp(1.0, -24))}, + + // inf + {0x7C00, std::numeric_limits::infinity()}, + {0xFC00, -std::numeric_limits::infinity()}, + })), ); + +TEST(HexFloatOperationTests, NanTests) { + using HF = spvutils::HexFloat>; + using HF16 = spvutils::HexFloat>; + spvutils::round_direction rounding[] = { + spvutils::round_direction::kToZero, + spvutils::round_direction::kToNearestEven, + spvutils::round_direction::kToPositiveInfinity, + spvutils::round_direction::kToNegativeInfinity}; + + // Everything fits, so everything should just be bit-shifts. + for (spvutils::round_direction round : rounding) { + HF16 f16(0); + HF f(0.f); + HF(std::numeric_limits::quiet_NaN()).castTo(f16, round); + EXPECT_TRUE(f16.value().isNan()); + HF(std::numeric_limits::signaling_NaN()).castTo(f16, round); + EXPECT_TRUE(f16.value().isNan()); + + HF16(0x7C01).castTo(f, round); + EXPECT_TRUE(f.value().isNan()); + HF16(0x7C11).castTo(f, round); + EXPECT_TRUE(f.value().isNan()); + HF16(0xFC01).castTo(f, round); + EXPECT_TRUE(f.value().isNan()); + HF16(0x7C10).castTo(f, round); + EXPECT_TRUE(f.value().isNan()); + HF16(0xFF00).castTo(f, round); + EXPECT_TRUE(f.value().isNan()); + } +} + +// A test case for parsing good and bad HexFloat> literals. +template +struct FloatParseCase { + std::string literal; + bool negate_value; + bool expect_success; + HexFloat> expected_value; +}; + +using ParseNormalFloatTest = ::testing::TestWithParam>; + +TEST_P(ParseNormalFloatTest, Samples) { + std::stringstream input(GetParam().literal); + HexFloat> parsed_value(0.0f); + ParseNormalFloat(input, GetParam().negate_value, parsed_value); + EXPECT_NE(GetParam().expect_success, input.fail()) + << " literal: " << GetParam().literal + << " negate: " << GetParam().negate_value; + if (GetParam().expect_success) { + EXPECT_THAT(parsed_value.value(), Eq(GetParam().expected_value.value())) + << " literal: " << GetParam().literal + << " negate: " << GetParam().negate_value; + } +} + +// Returns a FloatParseCase with expected failure. +template +FloatParseCase BadFloatParseCase(std::string literal, bool negate_value, + T expected_value) { + HexFloat> proxy_expected_value(expected_value); + return FloatParseCase{literal, negate_value, false, proxy_expected_value}; +} + +// Returns a FloatParseCase that should successfully parse to a given value. +template +FloatParseCase GoodFloatParseCase(std::string literal, bool negate_value, + T expected_value) { + HexFloat> proxy_expected_value(expected_value); + return FloatParseCase{literal, negate_value, true, proxy_expected_value}; +} + +INSTANTIATE_TEST_CASE_P( + FloatParse, ParseNormalFloatTest, + ::testing::ValuesIn(std::vector>{ + // Failing cases due to trivially incorrect syntax. + BadFloatParseCase("abc", false, 0.0f), + BadFloatParseCase("abc", true, 0.0f), + + // Valid cases. + GoodFloatParseCase("0", false, 0.0f), + GoodFloatParseCase("0.0", false, 0.0f), + GoodFloatParseCase("-0.0", false, -0.0f), + GoodFloatParseCase("2.0", false, 2.0f), + GoodFloatParseCase("-2.0", false, -2.0f), + GoodFloatParseCase("+2.0", false, 2.0f), + // Cases with negate_value being true. + GoodFloatParseCase("0.0", true, -0.0f), + GoodFloatParseCase("2.0", true, -2.0f), + + // When negate_value is true, we should not accept a + // leading minus or plus. + BadFloatParseCase("-0.0", true, 0.0f), + BadFloatParseCase("-2.0", true, 0.0f), + BadFloatParseCase("+0.0", true, 0.0f), + BadFloatParseCase("+2.0", true, 0.0f), + + // Overflow is an error for 32-bit float parsing. + BadFloatParseCase("1e40", false, FLT_MAX), + BadFloatParseCase("1e40", true, -FLT_MAX), + BadFloatParseCase("-1e40", false, -FLT_MAX), + // We can't have -1e40 and negate_value == true since + // that represents an original case of "--1e40" which + // is invalid. + }), ); + +using ParseNormalFloat16Test = + ::testing::TestWithParam>; + +TEST_P(ParseNormalFloat16Test, Samples) { + std::stringstream input(GetParam().literal); + HexFloat> parsed_value(0); + ParseNormalFloat(input, GetParam().negate_value, parsed_value); + EXPECT_NE(GetParam().expect_success, input.fail()) + << " literal: " << GetParam().literal + << " negate: " << GetParam().negate_value; + if (GetParam().expect_success) { + EXPECT_THAT(parsed_value.value(), Eq(GetParam().expected_value.value())) + << " literal: " << GetParam().literal + << " negate: " << GetParam().negate_value; + } +} + +INSTANTIATE_TEST_CASE_P( + Float16Parse, ParseNormalFloat16Test, + ::testing::ValuesIn(std::vector>{ + // Failing cases due to trivially incorrect syntax. + BadFloatParseCase("abc", false, uint16_t{0}), + BadFloatParseCase("abc", true, uint16_t{0}), + + // Valid cases. + GoodFloatParseCase("0", false, uint16_t{0}), + GoodFloatParseCase("0.0", false, uint16_t{0}), + GoodFloatParseCase("-0.0", false, uint16_t{0x8000}), + GoodFloatParseCase("2.0", false, uint16_t{0x4000}), + GoodFloatParseCase("-2.0", false, uint16_t{0xc000}), + GoodFloatParseCase("+2.0", false, uint16_t{0x4000}), + // Cases with negate_value being true. + GoodFloatParseCase("0.0", true, uint16_t{0x8000}), + GoodFloatParseCase("2.0", true, uint16_t{0xc000}), + + // When negate_value is true, we should not accept a leading minus or + // plus. + BadFloatParseCase("-0.0", true, uint16_t{0}), + BadFloatParseCase("-2.0", true, uint16_t{0}), + BadFloatParseCase("+0.0", true, uint16_t{0}), + BadFloatParseCase("+2.0", true, uint16_t{0}), + }), ); + +// A test case for detecting infinities. +template +struct OverflowParseCase { + std::string input; + bool expect_success; + T expected_value; +}; + +using FloatProxyParseOverflowFloatTest = + ::testing::TestWithParam>; + +TEST_P(FloatProxyParseOverflowFloatTest, Sample) { + std::istringstream input(GetParam().input); + HexFloat> value(0.0f); + input >> value; + EXPECT_NE(GetParam().expect_success, input.fail()); + if (GetParam().expect_success) { + EXPECT_THAT(value.value().getAsFloat(), GetParam().expected_value); + } +} + +INSTANTIATE_TEST_CASE_P( + FloatOverflow, FloatProxyParseOverflowFloatTest, + ::testing::ValuesIn(std::vector>({ + {"0", true, 0.0f}, + {"0.0", true, 0.0f}, + {"1.0", true, 1.0f}, + {"1e38", true, 1e38f}, + {"-1e38", true, -1e38f}, + {"1e40", false, FLT_MAX}, + {"-1e40", false, -FLT_MAX}, + {"1e400", false, FLT_MAX}, + {"-1e400", false, -FLT_MAX}, + })), ); + +using FloatProxyParseOverflowDoubleTest = + ::testing::TestWithParam>; + +TEST_P(FloatProxyParseOverflowDoubleTest, Sample) { + std::istringstream input(GetParam().input); + HexFloat> value(0.0); + input >> value; + EXPECT_NE(GetParam().expect_success, input.fail()); + if (GetParam().expect_success) { + EXPECT_THAT(value.value().getAsFloat(), Eq(GetParam().expected_value)); + } +} + +INSTANTIATE_TEST_CASE_P( + DoubleOverflow, FloatProxyParseOverflowDoubleTest, + ::testing::ValuesIn(std::vector>({ + {"0", true, 0.0}, + {"0.0", true, 0.0}, + {"1.0", true, 1.0}, + {"1e38", true, 1e38}, + {"-1e38", true, -1e38}, + {"1e40", true, 1e40}, + {"-1e40", true, -1e40}, + {"1e400", false, DBL_MAX}, + {"-1e400", false, -DBL_MAX}, + })), ); + +using FloatProxyParseOverflowFloat16Test = + ::testing::TestWithParam>; + +TEST_P(FloatProxyParseOverflowFloat16Test, Sample) { + std::istringstream input(GetParam().input); + HexFloat> value(0); + input >> value; + EXPECT_NE(GetParam().expect_success, input.fail()) + << " literal: " << GetParam().input; + if (GetParam().expect_success) { + EXPECT_THAT(value.value().data(), Eq(GetParam().expected_value)) + << " literal: " << GetParam().input; + } +} + +INSTANTIATE_TEST_CASE_P( + Float16Overflow, FloatProxyParseOverflowFloat16Test, + ::testing::ValuesIn(std::vector>({ + {"0", true, uint16_t{0}}, + {"0.0", true, uint16_t{0}}, + {"1.0", true, uint16_t{0x3c00}}, + // Overflow for 16-bit float is an error, and returns max or + // lowest value. + {"1e38", false, uint16_t{0x7bff}}, + {"1e40", false, uint16_t{0x7bff}}, + {"1e400", false, uint16_t{0x7bff}}, + {"-1e38", false, uint16_t{0xfbff}}, + {"-1e40", false, uint16_t{0xfbff}}, + {"-1e400", false, uint16_t{0xfbff}}, + })), ); + +TEST(FloatProxy, Max) { + EXPECT_THAT(FloatProxy::max().getAsFloat().get_value(), + Eq(uint16_t{0x7bff})); + EXPECT_THAT(FloatProxy::max().getAsFloat(), + Eq(std::numeric_limits::max())); + EXPECT_THAT(FloatProxy::max().getAsFloat(), + Eq(std::numeric_limits::max())); +} + +TEST(FloatProxy, Lowest) { + EXPECT_THAT(FloatProxy::lowest().getAsFloat().get_value(), + Eq(uint16_t{0xfbff})); + EXPECT_THAT(FloatProxy::lowest().getAsFloat(), + Eq(std::numeric_limits::lowest())); + EXPECT_THAT(FloatProxy::lowest().getAsFloat(), + Eq(std::numeric_limits::lowest())); +} + +// TODO(awoloszyn): Add fp16 tests and HexFloatTraits. +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/huffman_codec.cpp b/3rdparty/spirv-tools/test/huffman_codec.cpp new file mode 100644 index 000000000..3d5d293eb --- /dev/null +++ b/3rdparty/spirv-tools/test/huffman_codec.cpp @@ -0,0 +1,315 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Contains utils for reading, writing and debug printing bit streams. + +#include +#include +#include +#include + +#include "gmock/gmock.h" +#include "util/bit_stream.h" +#include "util/huffman_codec.h" + +namespace { + +using spvutils::BitsToStream; +using spvutils::HuffmanCodec; + +const std::map& GetTestSet() { + static const std::map hist = { + {"a", 4}, {"e", 7}, {"f", 3}, {"h", 2}, {"i", 3}, + {"m", 2}, {"n", 2}, {"s", 2}, {"t", 2}, {"l", 1}, + {"o", 2}, {"p", 1}, {"r", 1}, {"u", 1}, {"x", 1}, + }; + + return hist; +} + +class TestBitReader { + public: + TestBitReader(const std::string& bits) : bits_(bits) {} + + bool ReadBit(bool* bit) { + if (pos_ < bits_.length()) { + *bit = bits_[pos_++] == '0' ? false : true; + return true; + } + return false; + } + + private: + std::string bits_; + size_t pos_ = 0; +}; + +TEST(Huffman, PrintTree) { + HuffmanCodec huffman(GetTestSet()); + std::stringstream ss; + huffman.PrintTree(ss); + + // clang-format off + const std::string expected = std::string(R"( +15-----7------e + 8------4------a + 4------2------m + 2------n +19-----8------4------2------o + 2------s + 4------2------t + 2------1------l + 1------p + 11-----5------2------1------r + 1------u + 3------f + 6------3------i + 3------1------x + 2------h +)").substr(1); + // clang-format on + + EXPECT_EQ(expected, ss.str()); +} + +TEST(Huffman, PrintTable) { + HuffmanCodec huffman(GetTestSet()); + std::stringstream ss; + huffman.PrintTable(ss); + + const std::string expected = std::string(R"( +e 7 11 +a 4 101 +i 3 0001 +f 3 0010 +t 2 0101 +s 2 0110 +o 2 0111 +n 2 1000 +m 2 1001 +h 2 00000 +x 1 00001 +u 1 00110 +r 1 00111 +p 1 01000 +l 1 01001 +)") + .substr(1); + + EXPECT_EQ(expected, ss.str()); +} + +TEST(Huffman, TestValidity) { + HuffmanCodec huffman(GetTestSet()); + const auto& encoding_table = huffman.GetEncodingTable(); + std::vector codes; + for (const auto& entry : encoding_table) { + codes.push_back(BitsToStream(entry.second.first, entry.second.second)); + } + + std::sort(codes.begin(), codes.end()); + + ASSERT_LT(codes.size(), 20u) << "Inefficient test ahead"; + + for (size_t i = 0; i < codes.size(); ++i) { + for (size_t j = i + 1; j < codes.size(); ++j) { + ASSERT_FALSE(codes[i] == codes[j].substr(0, codes[i].length())) + << codes[i] << " is prefix of " << codes[j]; + } + } +} + +TEST(Huffman, TestEncode) { + HuffmanCodec huffman(GetTestSet()); + + uint64_t bits = 0; + size_t num_bits = 0; + + EXPECT_TRUE(huffman.Encode("e", &bits, &num_bits)); + EXPECT_EQ(2u, num_bits); + EXPECT_EQ("11", BitsToStream(bits, num_bits)); + + EXPECT_TRUE(huffman.Encode("a", &bits, &num_bits)); + EXPECT_EQ(3u, num_bits); + EXPECT_EQ("101", BitsToStream(bits, num_bits)); + + EXPECT_TRUE(huffman.Encode("x", &bits, &num_bits)); + EXPECT_EQ(5u, num_bits); + EXPECT_EQ("00001", BitsToStream(bits, num_bits)); + + EXPECT_FALSE(huffman.Encode("y", &bits, &num_bits)); +} + +TEST(Huffman, TestDecode) { + HuffmanCodec huffman(GetTestSet()); + TestBitReader bit_reader( + "01001" + "0001" + "1000" + "00110" + "00001" + "00"); + auto read_bit = [&bit_reader](bool* bit) { return bit_reader.ReadBit(bit); }; + + std::string decoded; + + ASSERT_TRUE(huffman.DecodeFromStream(read_bit, &decoded)); + EXPECT_EQ("l", decoded); + + ASSERT_TRUE(huffman.DecodeFromStream(read_bit, &decoded)); + EXPECT_EQ("i", decoded); + + ASSERT_TRUE(huffman.DecodeFromStream(read_bit, &decoded)); + EXPECT_EQ("n", decoded); + + ASSERT_TRUE(huffman.DecodeFromStream(read_bit, &decoded)); + EXPECT_EQ("u", decoded); + + ASSERT_TRUE(huffman.DecodeFromStream(read_bit, &decoded)); + EXPECT_EQ("x", decoded); + + ASSERT_FALSE(huffman.DecodeFromStream(read_bit, &decoded)); +} + +TEST(Huffman, TestDecodeNumbers) { + const std::map hist = {{1, 10}, {2, 5}, {3, 15}}; + HuffmanCodec huffman(hist); + + TestBitReader bit_reader( + "1" + "1" + "01" + "00" + "01" + "1"); + auto read_bit = [&bit_reader](bool* bit) { return bit_reader.ReadBit(bit); }; + + uint32_t decoded; + + ASSERT_TRUE(huffman.DecodeFromStream(read_bit, &decoded)); + EXPECT_EQ(3u, decoded); + + ASSERT_TRUE(huffman.DecodeFromStream(read_bit, &decoded)); + EXPECT_EQ(3u, decoded); + + ASSERT_TRUE(huffman.DecodeFromStream(read_bit, &decoded)); + EXPECT_EQ(2u, decoded); + + ASSERT_TRUE(huffman.DecodeFromStream(read_bit, &decoded)); + EXPECT_EQ(1u, decoded); + + ASSERT_TRUE(huffman.DecodeFromStream(read_bit, &decoded)); + EXPECT_EQ(2u, decoded); + + ASSERT_TRUE(huffman.DecodeFromStream(read_bit, &decoded)); + EXPECT_EQ(3u, decoded); +} + +TEST(Huffman, SerializeToTextU64) { + const std::map hist = {{1001, 10}, {1002, 5}, {1003, 15}}; + HuffmanCodec huffman(hist); + + const std::string code = huffman.SerializeToText(2); + + const std::string expected = R"((5, { + {0, 0, 0}, + {1001, 0, 0}, + {1002, 0, 0}, + {1003, 0, 0}, + {0, 1, 2}, + {0, 4, 3}, + }))"; + + ASSERT_EQ(expected, code); +} + +TEST(Huffman, SerializeToTextString) { + const std::map hist = { + {"aaa", 10}, {"bbb", 20}, {"ccc", 15}}; + HuffmanCodec huffman(hist); + + const std::string code = huffman.SerializeToText(4); + + const std::string expected = R"((5, { + {"", 0, 0}, + {"aaa", 0, 0}, + {"bbb", 0, 0}, + {"ccc", 0, 0}, + {"", 3, 1}, + {"", 4, 2}, + }))"; + + ASSERT_EQ(expected, code); +} + +TEST(Huffman, CreateFromTextString) { + std::vector::Node> nodes = { + {}, + {"root", 2, 3}, + {"left", 0, 0}, + {"right", 0, 0}, + }; + + HuffmanCodec huffman(1, std::move(nodes)); + + std::stringstream ss; + huffman.PrintTree(ss); + + const std::string expected = std::string(R"( +0------right +0------left +)") + .substr(1); + + EXPECT_EQ(expected, ss.str()); +} + +TEST(Huffman, CreateFromTextU64) { + HuffmanCodec huffman(5, { + {0, 0, 0}, + {1001, 0, 0}, + {1002, 0, 0}, + {1003, 0, 0}, + {0, 1, 2}, + {0, 4, 3}, + }); + + std::stringstream ss; + huffman.PrintTree(ss); + + const std::string expected = std::string(R"( +0------1003 +0------0------1002 + 0------1001 +)") + .substr(1); + + EXPECT_EQ(expected, ss.str()); + + TestBitReader bit_reader("01"); + auto read_bit = [&bit_reader](bool* bit) { return bit_reader.ReadBit(bit); }; + + uint64_t decoded = 0; + ASSERT_TRUE(huffman.DecodeFromStream(read_bit, &decoded)); + EXPECT_EQ(1002u, decoded); + + uint64_t bits = 0; + size_t num_bits = 0; + + EXPECT_TRUE(huffman.Encode(1001, &bits, &num_bits)); + EXPECT_EQ(2u, num_bits); + EXPECT_EQ("00", BitsToStream(bits, num_bits)); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/immediate_int_test.cpp b/3rdparty/spirv-tools/test/immediate_int_test.cpp new file mode 100644 index 000000000..3e95d0c6c --- /dev/null +++ b/3rdparty/spirv-tools/test/immediate_int_test.cpp @@ -0,0 +1,289 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include + +#include "source/util/bitutils.h" +#include "test_fixture.h" + +namespace { + +using spvtest::Concatenate; +using spvtest::MakeInstruction; +using spvtest::ScopedContext; +using spvtest::TextToBinaryTest; +using spvutils::BitwiseCast; +using ::testing::ElementsAre; +using ::testing::Eq; +using ::testing::HasSubstr; +using ::testing::StrEq; + +TEST_F(TextToBinaryTest, ImmediateIntOpCode) { + SetText("!0x00FF00FF"); + ASSERT_EQ(SPV_SUCCESS, spvTextToBinary(ScopedContext().context, text.str, + text.length, &binary, &diagnostic)); + EXPECT_EQ(0x00FF00FFu, binary->code[5]); + if (diagnostic) { + spvDiagnosticPrint(diagnostic); + } +} + +TEST_F(TextToBinaryTest, ImmediateIntOperand) { + SetText("OpCapability !0x00FF00FF"); + EXPECT_EQ(SPV_SUCCESS, spvTextToBinary(ScopedContext().context, text.str, + text.length, &binary, &diagnostic)); + EXPECT_EQ(0x00FF00FFu, binary->code[6]); + if (diagnostic) { + spvDiagnosticPrint(diagnostic); + } +} + +using ImmediateIntTest = TextToBinaryTest; + +TEST_F(ImmediateIntTest, AnyWordInSimpleStatement) { + EXPECT_THAT(CompiledInstructions("!0x00040018 %a %b %123"), + Eq(MakeInstruction(SpvOpTypeMatrix, {1, 2, 3}))); + EXPECT_THAT(CompiledInstructions("!0x00040018 !1 %b %123"), + Eq(MakeInstruction(SpvOpTypeMatrix, {1, 1, 2}))); + EXPECT_THAT(CompiledInstructions("%a = OpTypeMatrix !2 %123"), + Eq(MakeInstruction(SpvOpTypeMatrix, {1, 2, 2}))); + EXPECT_THAT(CompiledInstructions("%a = OpTypeMatrix %b !123"), + Eq(MakeInstruction(SpvOpTypeMatrix, {1, 2, 123}))); + EXPECT_THAT(CompiledInstructions("!0x00040018 %a !2 %123"), + Eq(MakeInstruction(SpvOpTypeMatrix, {1, 2, 2}))); + EXPECT_THAT(CompiledInstructions("!0x00040018 !1 %b !123"), + Eq(MakeInstruction(SpvOpTypeMatrix, {1, 1, 123}))); + EXPECT_THAT(CompiledInstructions("!0x00040018 !1 !2 !123"), + Eq(MakeInstruction(SpvOpTypeMatrix, {1, 2, 123}))); +} + +TEST_F(ImmediateIntTest, AnyWordAfterEqualsAndOpCode) { + EXPECT_THAT(CompiledInstructions("%a = OpArrayLength !2 %c 123"), + Eq(MakeInstruction(SpvOpArrayLength, {2, 1, 2, 123}))); + EXPECT_THAT(CompiledInstructions("%a = OpArrayLength %b !3 123"), + Eq(MakeInstruction(SpvOpArrayLength, {1, 2, 3, 123}))); + EXPECT_THAT(CompiledInstructions("%a = OpArrayLength %b %c !123"), + Eq(MakeInstruction(SpvOpArrayLength, {1, 2, 3, 123}))); + EXPECT_THAT(CompiledInstructions("%a = OpArrayLength %b !3 !123"), + Eq(MakeInstruction(SpvOpArrayLength, {1, 2, 3, 123}))); + EXPECT_THAT(CompiledInstructions("%a = OpArrayLength !2 !3 123"), + Eq(MakeInstruction(SpvOpArrayLength, {2, 1, 3, 123}))); + EXPECT_THAT(CompiledInstructions("%a = OpArrayLength !2 !3 !123"), + Eq(MakeInstruction(SpvOpArrayLength, {2, 1, 3, 123}))); +} + +TEST_F(ImmediateIntTest, ResultIdInAssignment) { + EXPECT_EQ("!2 not allowed before =.", + CompileFailure("!2 = OpArrayLength %12 %1 123")); + EXPECT_EQ("!2 not allowed before =.", + CompileFailure("!2 = !0x00040044 %12 %1 123")); +} + +TEST_F(ImmediateIntTest, OpCodeInAssignment) { + EXPECT_EQ("Invalid Opcode prefix '!0x00040044'.", + CompileFailure("%2 = !0x00040044 %12 %1 123")); +} + +// Literal integers after ! are handled correctly. +TEST_F(ImmediateIntTest, IntegerFollowingImmediate) { + const SpirvVector original = CompiledInstructions("%1 = OpTypeInt 8 1"); + EXPECT_EQ(original, CompiledInstructions("!0x00040015 1 8 1")); + EXPECT_EQ(original, CompiledInstructions("!0x00040015 !1 8 1")); + + // With !, we can (and can only) accept 32-bit number literals, + // even when we declare the return type is 64-bit. + EXPECT_EQ(Concatenate({ + MakeInstruction(SpvOpTypeInt, {1, 64, 0}), + MakeInstruction(SpvOpConstant, {1, 2, 4294967295}), + }), + CompiledInstructions("%i64 = OpTypeInt 64 0\n" + "!0x0004002b %i64 !2 4294967295")); + // 64-bit integer literal. + EXPECT_EQ("Invalid word following !: 5000000000", + CompileFailure("%2 = OpConstant !1 5000000000")); + EXPECT_EQ("Invalid word following !: 5000000000", + CompileFailure("%i64 = OpTypeInt 64 0\n" + "!0x0005002b %i64 !2 5000000000")); + + // Negative integer. + EXPECT_EQ(CompiledInstructions("%i64 = OpTypeInt 32 1\n" + "%2 = OpConstant %i64 -123"), + CompiledInstructions("%i64 = OpTypeInt 32 1\n" + "!0x0004002b %i64 !2 -123")); + + // TODO(deki): uncomment assertions below and make them pass. + // Hex value(s). + // EXPECT_EQ(CompileSuccessfully("%1 = OpConstant %10 0x12345678"), + // CompileSuccessfully("OpConstant %10 !1 0x12345678", kCAF)); + // EXPECT_EQ( + // CompileSuccessfully("%1 = OpConstant %10 0x12345678 0x87654321"), + // CompileSuccessfully("OpConstant %10 !1 0x12345678 0x87654321", kCAF)); +} + +// Literal floats after ! are handled correctly. +TEST_F(ImmediateIntTest, FloatFollowingImmediate) { + EXPECT_EQ( + CompiledInstructions("%1 = OpTypeFloat 32\n%2 = OpConstant %1 0.123"), + CompiledInstructions("%1 = OpTypeFloat 32\n!0x0004002b %1 !2 0.123")); + EXPECT_EQ( + CompiledInstructions("%1 = OpTypeFloat 32\n%2 = OpConstant %1 -0.5"), + CompiledInstructions("%1 = OpTypeFloat 32\n!0x0004002b %1 !2 -0.5")); + EXPECT_EQ( + CompiledInstructions("%1 = OpTypeFloat 32\n%2 = OpConstant %1 0.123"), + CompiledInstructions("%1 = OpTypeFloat 32\n!0x0004002b %1 %2 0.123")); + EXPECT_EQ( + CompiledInstructions("%1 = OpTypeFloat 32\n%2 = OpConstant %1 -0.5"), + CompiledInstructions("%1 = OpTypeFloat 32\n!0x0004002b %1 %2 -0.5")); + + EXPECT_EQ(Concatenate({ + MakeInstruction(SpvOpTypeInt, {1, 64, 0}), + MakeInstruction(SpvOpConstant, {1, 2, 0xb, 0xa}), + MakeInstruction(SpvOpSwitch, + {2, 1234, BitwiseCast(2.5f), 3}), + }), + CompiledInstructions("%i64 = OpTypeInt 64 0\n" + "%big = OpConstant %i64 0xa0000000b\n" + "OpSwitch %big !1234 2.5 %target\n")); +} + +// Literal strings after ! are handled correctly. +TEST_F(ImmediateIntTest, StringFollowingImmediate) { + // Try a variety of strings, including empty and single-character. + for (std::string name : {"", "s", "longish", "really looooooooooooooooong"}) { + const SpirvVector original = + CompiledInstructions("OpMemberName %10 4 \"" + name + "\""); + EXPECT_EQ(original, + CompiledInstructions("OpMemberName %10 !4 \"" + name + "\"")) + << name; + EXPECT_EQ(original, + CompiledInstructions("OpMemberName !1 !4 \"" + name + "\"")) + << name; + const uint16_t wordCount = static_cast(4 + name.size() / 4); + const uint32_t firstWord = spvOpcodeMake(wordCount, SpvOpMemberName); + EXPECT_EQ(original, CompiledInstructions("!" + std::to_string(firstWord) + + " %10 !4 \"" + name + "\"")) + << name; + } +} + +// IDs after ! are handled correctly. +TEST_F(ImmediateIntTest, IdFollowingImmediate) { + EXPECT_EQ(CompileSuccessfully("%123 = OpDecorationGroup"), + CompileSuccessfully("!0x00020049 %123")); + EXPECT_EQ(CompileSuccessfully("%group = OpDecorationGroup"), + CompileSuccessfully("!0x00020049 %group")); +} + +// ! after ! is handled correctly. +TEST_F(ImmediateIntTest, ImmediateFollowingImmediate) { + const SpirvVector original = CompiledInstructions("%a = OpTypeMatrix %b 7"); + EXPECT_EQ(original, CompiledInstructions("%a = OpTypeMatrix !2 !7")); + EXPECT_EQ(original, CompiledInstructions("!0x00040018 %a !2 !7")); +} + +TEST_F(ImmediateIntTest, InvalidStatement) { + EXPECT_THAT(Subvector(CompileSuccessfully("!4 !3 !2 !1"), kFirstInstruction), + ElementsAre(4, 3, 2, 1)); +} + +TEST_F(ImmediateIntTest, InvalidStatementBetweenValidOnes) { + EXPECT_THAT(Subvector(CompileSuccessfully( + "%10 = OpTypeFloat 32 !5 !6 !7 OpEmitVertex"), + kFirstInstruction), + ElementsAre(spvOpcodeMake(3, SpvOpTypeFloat), 1, 32, 5, 6, 7, + spvOpcodeMake(1, SpvOpEmitVertex))); +} + +TEST_F(ImmediateIntTest, NextOpcodeRecognized) { + const SpirvVector original = CompileSuccessfully(R"( +%1 = OpLoad %10 %2 Volatile +%4 = OpCompositeInsert %11 %1 %3 0 1 2 +)"); + const SpirvVector alternate = CompileSuccessfully(R"( +%1 = OpLoad %10 %2 !1 +%4 = OpCompositeInsert %11 %1 %3 0 1 2 +)"); + EXPECT_EQ(original, alternate); +} + +TEST_F(ImmediateIntTest, WrongLengthButNextOpcodeStillRecognized) { + const SpirvVector original = CompileSuccessfully(R"( +%1 = OpLoad %10 %2 Volatile +OpCopyMemorySized %3 %4 %1 +)"); + const SpirvVector alternate = CompileSuccessfully(R"( +!0x0002003D %10 %1 %2 !1 +OpCopyMemorySized %3 %4 %1 +)"); + EXPECT_EQ(0x0002003Du, alternate[kFirstInstruction]); + EXPECT_EQ(Subvector(original, kFirstInstruction + 1), + Subvector(alternate, kFirstInstruction + 1)); +} + +// Like NextOpcodeRecognized, but next statement is in assignment form. +TEST_F(ImmediateIntTest, NextAssignmentRecognized) { + const SpirvVector original = CompileSuccessfully(R"( +%1 = OpLoad %10 %2 None +%4 = OpFunctionCall %10 %3 %123 +)"); + const SpirvVector alternate = CompileSuccessfully(R"( +%1 = OpLoad %10 %2 !0 +%4 = OpFunctionCall %10 %3 %123 +)"); + EXPECT_EQ(original, alternate); +} + +// Two instructions in a row each have ! opcode. +TEST_F(ImmediateIntTest, ConsecutiveImmediateOpcodes) { + const SpirvVector original = CompileSuccessfully(R"( +%1 = OpConstantSampler %10 Clamp 78 Linear +%4 = OpFRem %11 %3 %2 +%5 = OpIsValidEvent %12 %2 +)"); + const SpirvVector alternate = CompileSuccessfully(R"( +!0x0006002D %10 %1 !2 78 !1 +!0x0005008C %11 %4 %3 %2 +%5 = OpIsValidEvent %12 %2 +)"); + EXPECT_EQ(original, alternate); +} + +// ! followed by, eg, an enum or '=' or a random bareword. +TEST_F(ImmediateIntTest, ForbiddenOperands) { + EXPECT_THAT(CompileFailure("OpMemoryModel !0 OpenCL"), HasSubstr("OpenCL")); + EXPECT_THAT(CompileFailure("!1 %0 = !2"), HasSubstr("=")); + EXPECT_THAT(CompileFailure("OpMemoryModel !0 random_bareword"), + HasSubstr("random_bareword")); + // Immediate integers longer than one 32-bit word. + EXPECT_THAT(CompileFailure("!5000000000"), HasSubstr("5000000000")); + EXPECT_THAT(CompileFailure("!999999999999999999"), + HasSubstr("999999999999999999")); + EXPECT_THAT(CompileFailure("!0x00020049 !5000000000"), + HasSubstr("5000000000")); + // Negative numbers. + EXPECT_THAT(CompileFailure("!0x00020049 !-123"), HasSubstr("-123")); +} + +TEST_F(ImmediateIntTest, NotInteger) { + EXPECT_THAT(CompileFailure("!abc"), StrEq("Invalid immediate integer: !abc")); + EXPECT_THAT(CompileFailure("!12.3"), + StrEq("Invalid immediate integer: !12.3")); + EXPECT_THAT(CompileFailure("!12K"), StrEq("Invalid immediate integer: !12K")); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/libspirv_macros_test.cpp b/3rdparty/spirv-tools/test/libspirv_macros_test.cpp new file mode 100644 index 000000000..5b9b54131 --- /dev/null +++ b/3rdparty/spirv-tools/test/libspirv_macros_test.cpp @@ -0,0 +1,23 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "unit_spirv.h" + +namespace { + +TEST(Macros, BitShiftInnerParens) { ASSERT_EQ(65536, SPV_BIT(2 << 3)); } + +TEST(Macros, BitShiftOuterParens) { ASSERT_EQ(15, SPV_BIT(4) - 1); } + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/link/CMakeLists.txt b/3rdparty/spirv-tools/test/link/CMakeLists.txt new file mode 100644 index 000000000..33810aae8 --- /dev/null +++ b/3rdparty/spirv-tools/test/link/CMakeLists.txt @@ -0,0 +1,53 @@ +# Copyright (c) 2017 Pierre Moreau +# +# 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. + +add_spvtools_unittest(TARGET link_binary_version + SRCS binary_version_test.cpp + LIBS SPIRV-Tools-opt SPIRV-Tools-link +) + +add_spvtools_unittest(TARGET link_memory_model + SRCS memory_model_test.cpp + LIBS SPIRV-Tools-opt SPIRV-Tools-link +) + +add_spvtools_unittest(TARGET link_entry_points + SRCS entry_points_test.cpp + LIBS SPIRV-Tools-opt SPIRV-Tools-link +) + +add_spvtools_unittest(TARGET link_global_values_amount + SRCS global_values_amount_test.cpp + LIBS SPIRV-Tools-opt SPIRV-Tools-link +) + +add_spvtools_unittest(TARGET link_ids_limit + SRCS ids_limit_test.cpp + LIBS SPIRV-Tools-opt SPIRV-Tools-link +) + +add_spvtools_unittest(TARGET link_matching_imports_to_exports + SRCS matching_imports_to_exports_test.cpp + LIBS SPIRV-Tools-opt SPIRV-Tools-link +) + +add_spvtools_unittest(TARGET link_unique_ids + SRCS unique_ids_test.cpp + LIBS SPIRV-Tools-opt SPIRV-Tools-link +) + +add_spvtools_unittest(TARGET link_partial_linkage + SRCS partial_linkage_test.cpp + LIBS SPIRV-Tools-opt SPIRV-Tools-link +) diff --git a/3rdparty/spirv-tools/test/link/binary_version_test.cpp b/3rdparty/spirv-tools/test/link/binary_version_test.cpp new file mode 100644 index 000000000..14eb91350 --- /dev/null +++ b/3rdparty/spirv-tools/test/link/binary_version_test.cpp @@ -0,0 +1,56 @@ +// Copyright (c) 2017 Pierre Moreau +// +// 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 "gmock/gmock.h" +#include "linker_fixture.h" + +namespace { + +using BinaryVersion = spvtest::LinkerTest; + +TEST_F(BinaryVersion, LinkerChoosesMaxSpirvVersion) { + // clang-format off + spvtest::Binaries binaries = { + { + SpvMagicNumber, + 0x00000300u, + SPV_GENERATOR_CODEPLAY, + 1u, // NOTE: Bound + 0u // NOTE: Schema; reserved + }, + { + SpvMagicNumber, + 0x00000600u, + SPV_GENERATOR_CODEPLAY, + 1u, // NOTE: Bound + 0u // NOTE: Schema; reserved + }, + { + SpvMagicNumber, + 0x00000100u, + SPV_GENERATOR_CODEPLAY, + 1u, // NOTE: Bound + 0u // NOTE: Schema; reserved + } + }; + // clang-format on + spvtest::Binary linked_binary; + + ASSERT_EQ(SPV_SUCCESS, Link(binaries, &linked_binary)); + EXPECT_THAT(GetErrorMessage(), std::string()); + + EXPECT_EQ(0x00000600u, linked_binary[1]); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/link/entry_points_test.cpp b/3rdparty/spirv-tools/test/link/entry_points_test.cpp new file mode 100644 index 000000000..1df6ec76c --- /dev/null +++ b/3rdparty/spirv-tools/test/link/entry_points_test.cpp @@ -0,0 +1,90 @@ +// Copyright (c) 2017 Pierre Moreau +// +// 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 "gmock/gmock.h" +#include "linker_fixture.h" + +namespace { + +using ::testing::HasSubstr; + +class EntryPoints : public spvtest::LinkerTest {}; + +TEST_F(EntryPoints, SameModelDifferentName) { + const std::string body1 = R"( +OpEntryPoint GLCompute %3 "foo" +%1 = OpTypeVoid +%2 = OpTypeFunction %1 +%3 = OpFunction %1 None %2 +OpFunctionEnd +)"; + const std::string body2 = R"( +OpEntryPoint GLCompute %3 "bar" +%1 = OpTypeVoid +%2 = OpTypeFunction %1 +%3 = OpFunction %1 None %2 +OpFunctionEnd +)"; + + spvtest::Binary linked_binary; + EXPECT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary)); + EXPECT_THAT(GetErrorMessage(), std::string()); +} + +TEST_F(EntryPoints, DifferentModelSameName) { + const std::string body1 = R"( +OpEntryPoint GLCompute %3 "foo" +%1 = OpTypeVoid +%2 = OpTypeFunction %1 +%3 = OpFunction %1 None %2 +OpFunctionEnd +)"; + const std::string body2 = R"( +OpEntryPoint Vertex %3 "foo" +%1 = OpTypeVoid +%2 = OpTypeFunction %1 +%3 = OpFunction %1 None %2 +OpFunctionEnd +)"; + + spvtest::Binary linked_binary; + EXPECT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary)); + EXPECT_THAT(GetErrorMessage(), std::string()); +} + +TEST_F(EntryPoints, SameModelAndName) { + const std::string body1 = R"( +OpEntryPoint GLCompute %3 "foo" +%1 = OpTypeVoid +%2 = OpTypeFunction %1 +%3 = OpFunction %1 None %2 +OpFunctionEnd +)"; + const std::string body2 = R"( +OpEntryPoint GLCompute %3 "foo" +%1 = OpTypeVoid +%2 = OpTypeFunction %1 +%3 = OpFunction %1 None %2 +OpFunctionEnd +)"; + + spvtest::Binary linked_binary; + EXPECT_EQ(SPV_ERROR_INTERNAL, + AssembleAndLink({body1, body2}, &linked_binary)); + EXPECT_THAT(GetErrorMessage(), + HasSubstr("The entry point \"foo\", with execution model " + "GLCompute, was already defined.")); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/link/global_values_amount_test.cpp b/3rdparty/spirv-tools/test/link/global_values_amount_test.cpp new file mode 100644 index 000000000..79fb23c82 --- /dev/null +++ b/3rdparty/spirv-tools/test/link/global_values_amount_test.cpp @@ -0,0 +1,149 @@ +// Copyright (c) 2017 Pierre Moreau +// +// 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 "gmock/gmock.h" +#include "linker_fixture.h" + +namespace { + +using ::testing::HasSubstr; + +class EntryPoints : public spvtest::LinkerTest { + public: + EntryPoints() { binaries.reserve(0xFFFF); } + + virtual void SetUp() override { + binaries.push_back({SpvMagicNumber, + SpvVersion, + SPV_GENERATOR_CODEPLAY, + 10u, // NOTE: Bound + 0u, // NOTE: Schema; reserved + + 3u << SpvWordCountShift | SpvOpTypeFloat, + 1u, // NOTE: Result ID + 32u, // NOTE: Width + + 4u << SpvWordCountShift | SpvOpTypePointer, + 2u, // NOTE: Result ID + SpvStorageClassInput, + 1u, // NOTE: Type ID + + 2u << SpvWordCountShift | SpvOpTypeVoid, + 3u, // NOTE: Result ID + + 3u << SpvWordCountShift | SpvOpTypeFunction, + 4u, // NOTE: Result ID + 3u, // NOTE: Return type + + 5u << SpvWordCountShift | SpvOpFunction, + 3u, // NOTE: Result type + 5u, // NOTE: Result ID + SpvFunctionControlMaskNone, + 4u, // NOTE: Function type + + 2u << SpvWordCountShift | SpvOpLabel, + 6u, // NOTE: Result ID + + 4u << SpvWordCountShift | SpvOpVariable, + 2u, // NOTE: Type ID + 7u, // NOTE: Result ID + SpvStorageClassFunction, + + 4u << SpvWordCountShift | SpvOpVariable, + 2u, // NOTE: Type ID + 8u, // NOTE: Result ID + SpvStorageClassFunction, + + 4u << SpvWordCountShift | SpvOpVariable, + 2u, // NOTE: Type ID + 9u, // NOTE: Result ID + SpvStorageClassFunction, + + 1u << SpvWordCountShift | SpvOpReturn, + + 1u << SpvWordCountShift | SpvOpFunctionEnd}); + for (size_t i = 0u; i < 2u; ++i) { + spvtest::Binary binary = { + SpvMagicNumber, + SpvVersion, + SPV_GENERATOR_CODEPLAY, + 103u, // NOTE: Bound + 0u, // NOTE: Schema; reserved + + 3u << SpvWordCountShift | SpvOpTypeFloat, + 1u, // NOTE: Result ID + 32u, // NOTE: Width + + 4u << SpvWordCountShift | SpvOpTypePointer, + 2u, // NOTE: Result ID + SpvStorageClassInput, + 1u // NOTE: Type ID + }; + + for (uint32_t j = 0u; j < 0xFFFFu / 2u; ++j) { + binary.push_back(4u << SpvWordCountShift | SpvOpVariable); + binary.push_back(2u); // NOTE: Type ID + binary.push_back(j + 3u); // NOTE: Result ID + binary.push_back(SpvStorageClassInput); + } + binaries.push_back(binary); + } + } + virtual void TearDown() override { binaries.clear(); } + + spvtest::Binaries binaries; +}; + +TEST_F(EntryPoints, UnderLimit) { + spvtest::Binary linked_binary; + + EXPECT_EQ(SPV_SUCCESS, Link(binaries, &linked_binary)); + EXPECT_THAT(GetErrorMessage(), std::string()); +} + +TEST_F(EntryPoints, OverLimit) { + binaries.push_back({SpvMagicNumber, + SpvVersion, + SPV_GENERATOR_CODEPLAY, + 5u, // NOTE: Bound + 0u, // NOTE: Schema; reserved + + 3u << SpvWordCountShift | SpvOpTypeFloat, + 1u, // NOTE: Result ID + 32u, // NOTE: Width + + 4u << SpvWordCountShift | SpvOpTypePointer, + 2u, // NOTE: Result ID + SpvStorageClassInput, + 1u, // NOTE: Type ID + + 4u << SpvWordCountShift | SpvOpVariable, + 2u, // NOTE: Type ID + 3u, // NOTE: Result ID + SpvStorageClassInput, + + 4u << SpvWordCountShift | SpvOpVariable, + 2u, // NOTE: Type ID + 4u, // NOTE: Result ID + SpvStorageClassInput}); + + spvtest::Binary linked_binary; + + EXPECT_EQ(SPV_ERROR_INTERNAL, Link(binaries, &linked_binary)); + EXPECT_THAT(GetErrorMessage(), + HasSubstr("The limit of global values, 65535, was exceeded; " + "65536 global values were found.")); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/link/ids_limit_test.cpp b/3rdparty/spirv-tools/test/link/ids_limit_test.cpp new file mode 100644 index 000000000..73649cfef --- /dev/null +++ b/3rdparty/spirv-tools/test/link/ids_limit_test.cpp @@ -0,0 +1,69 @@ +// Copyright (c) 2017 Pierre Moreau +// +// 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 "gmock/gmock.h" +#include "linker_fixture.h" + +namespace { + +using ::testing::HasSubstr; + +using IdsLimit = spvtest::LinkerTest; + +TEST_F(IdsLimit, UnderLimit) { + spvtest::Binaries binaries = { + { + SpvMagicNumber, SpvVersion, SPV_GENERATOR_CODEPLAY, + 0x2FFFFFu, // NOTE: Bound + 0u, // NOTE: Schema; reserved + }, + { + SpvMagicNumber, SpvVersion, SPV_GENERATOR_CODEPLAY, + 0x100000u, // NOTE: Bound + 0u, // NOTE: Schema; reserved + }}; + spvtest::Binary linked_binary; + + ASSERT_EQ(SPV_SUCCESS, Link(binaries, &linked_binary)); + EXPECT_THAT(GetErrorMessage(), std::string()); + EXPECT_EQ(0x3FFFFEu, linked_binary[3]); +} + +TEST_F(IdsLimit, OverLimit) { + spvtest::Binaries binaries = { + { + SpvMagicNumber, SpvVersion, SPV_GENERATOR_CODEPLAY, + 0x2FFFFFu, // NOTE: Bound + 0u, // NOTE: Schema; reserved + }, + { + SpvMagicNumber, SpvVersion, SPV_GENERATOR_CODEPLAY, + 0x100000u, // NOTE: Bound + 0u, // NOTE: Schema; reserved + }, + { + SpvMagicNumber, SpvVersion, SPV_GENERATOR_CODEPLAY, + 3u, // NOTE: Bound + 0u, // NOTE: Schema; reserved + }}; + + spvtest::Binary linked_binary; + + EXPECT_EQ(SPV_ERROR_INVALID_ID, Link(binaries, &linked_binary)); + EXPECT_THAT(GetErrorMessage(), + HasSubstr("The limit of IDs, 4194303, was exceeded: 4194304 is " + "the current ID bound.")); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/link/linker_fixture.h b/3rdparty/spirv-tools/test/link/linker_fixture.h new file mode 100644 index 000000000..a1e3ec866 --- /dev/null +++ b/3rdparty/spirv-tools/test/link/linker_fixture.h @@ -0,0 +1,124 @@ +// Copyright (c) 2017 Pierre Moreau +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_TEST_LINK_LINK_TEST +#define LIBSPIRV_TEST_LINK_LINK_TEST + +#include + +#include "source/spirv_constant.h" +#include "unit_spirv.h" + +#include "spirv-tools/linker.hpp" + +namespace spvtest { + +using Binary = std::vector; +using Binaries = std::vector; + +class LinkerTest : public ::testing::Test { + public: + LinkerTest() + : context_(SPV_ENV_UNIVERSAL_1_2), + tools_(SPV_ENV_UNIVERSAL_1_2), + assemble_options_(spvtools::SpirvTools::kDefaultAssembleOption), + disassemble_options_(spvtools::SpirvTools::kDefaultDisassembleOption) { + const auto consumer = [this](spv_message_level_t level, const char*, + const spv_position_t& position, + const char* message) { + if (!error_message_.empty()) error_message_ += "\n"; + switch (level) { + case SPV_MSG_FATAL: + case SPV_MSG_INTERNAL_ERROR: + case SPV_MSG_ERROR: + error_message_ += "ERROR"; + break; + case SPV_MSG_WARNING: + error_message_ += "WARNING"; + break; + case SPV_MSG_INFO: + error_message_ += "INFO"; + break; + case SPV_MSG_DEBUG: + error_message_ += "DEBUG"; + break; + } + error_message_ += ": " + std::to_string(position.index) + ": " + message; + }; + context_.SetMessageConsumer(consumer); + tools_.SetMessageConsumer(consumer); + } + + virtual void TearDown() override { error_message_.clear(); } + + // Assembles each of the given strings into SPIR-V binaries before linking + // them together. SPV_ERROR_INVALID_TEXT is returned if the assembling failed + // for any of the input strings, and SPV_ERROR_INVALID_POINTER if + // |linked_binary| is a null pointer. + spv_result_t AssembleAndLink( + const std::vector& bodies, spvtest::Binary* linked_binary, + spvtools::LinkerOptions options = spvtools::LinkerOptions()) { + if (!linked_binary) return SPV_ERROR_INVALID_POINTER; + + spvtest::Binaries binaries(bodies.size()); + for (size_t i = 0u; i < bodies.size(); ++i) + if (!tools_.Assemble(bodies[i], binaries.data() + i, assemble_options_)) + return SPV_ERROR_INVALID_TEXT; + + return spvtools::Link(context_, binaries, linked_binary, options); + } + + // Links the given SPIR-V binaries together; SPV_ERROR_INVALID_POINTER is + // returned if |linked_binary| is a null pointer. + spv_result_t Link( + const spvtest::Binaries& binaries, spvtest::Binary* linked_binary, + spvtools::LinkerOptions options = spvtools::LinkerOptions()) { + if (!linked_binary) return SPV_ERROR_INVALID_POINTER; + return spvtools::Link(context_, binaries, linked_binary, options); + } + + // Disassembles |binary| and outputs the result in |text|. If |text| is a + // null pointer, SPV_ERROR_INVALID_POINTER is returned. + spv_result_t Disassemble(const spvtest::Binary& binary, std::string* text) { + if (!text) return SPV_ERROR_INVALID_POINTER; + return tools_.Disassemble(binary, text, disassemble_options_) + ? SPV_SUCCESS + : SPV_ERROR_INVALID_BINARY; + } + + // Sets the options for the assembler. + void SetAssembleOptions(uint32_t assemble_options) { + assemble_options_ = assemble_options; + } + + // Sets the options used by the disassembler. + void SetDisassembleOptions(uint32_t disassemble_options) { + disassemble_options_ = disassemble_options; + } + + // Returns the accumulated error messages for the test. + std::string GetErrorMessage() const { return error_message_; } + + private: + spvtools::Context context_; + spvtools::SpirvTools + tools_; // An instance for calling SPIRV-Tools functionalities. + uint32_t assemble_options_; + uint32_t disassemble_options_; + std::string error_message_; +}; + +} // namespace spvtest + +#endif // LIBSPIRV_TEST_LINK_LINK_TEST diff --git a/3rdparty/spirv-tools/test/link/matching_imports_to_exports_test.cpp b/3rdparty/spirv-tools/test/link/matching_imports_to_exports_test.cpp new file mode 100644 index 000000000..2126d928b --- /dev/null +++ b/3rdparty/spirv-tools/test/link/matching_imports_to_exports_test.cpp @@ -0,0 +1,399 @@ +// Copyright (c) 2017 Pierre Moreau +// +// 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 "gmock/gmock.h" +#include "linker_fixture.h" + +namespace { + +using ::testing::HasSubstr; +using MatchingImportsToExports = spvtest::LinkerTest; + +TEST_F(MatchingImportsToExports, Default) { + const std::string body1 = R"( +OpCapability Linkage +OpDecorate %1 LinkageAttributes "foo" Import +%2 = OpTypeFloat 32 +%1 = OpVariable %2 Uniform +%3 = OpVariable %2 Input +)"; + const std::string body2 = R"( +OpCapability Linkage +OpDecorate %1 LinkageAttributes "foo" Export +%2 = OpTypeFloat 32 +%3 = OpConstant %2 42 +%1 = OpVariable %2 Uniform %3 +)"; + + spvtest::Binary linked_binary; + EXPECT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary)) + << GetErrorMessage(); + + const std::string expected_res = + R"(OpModuleProcessed "Linked by SPIR-V Tools Linker" +%1 = OpTypeFloat 32 +%2 = OpVariable %1 Input +%3 = OpConstant %1 42 +%4 = OpVariable %1 Uniform %3 +)"; + std::string res_body; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + EXPECT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body)) + << GetErrorMessage(); + EXPECT_EQ(expected_res, res_body); +} + +TEST_F(MatchingImportsToExports, NotALibraryExtraExports) { + const std::string body = R"( +OpCapability Linkage +OpDecorate %1 LinkageAttributes "foo" Export +%2 = OpTypeFloat 32 +%1 = OpVariable %2 Uniform +)"; + + spvtest::Binary linked_binary; + EXPECT_EQ(SPV_SUCCESS, AssembleAndLink({body}, &linked_binary)) + << GetErrorMessage(); + + const std::string expected_res = + R"(OpModuleProcessed "Linked by SPIR-V Tools Linker" +%1 = OpTypeFloat 32 +%2 = OpVariable %1 Uniform +)"; + std::string res_body; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + EXPECT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body)) + << GetErrorMessage(); + EXPECT_EQ(expected_res, res_body); +} + +TEST_F(MatchingImportsToExports, LibraryExtraExports) { + const std::string body = R"( +OpCapability Linkage +OpDecorate %1 LinkageAttributes "foo" Export +%2 = OpTypeFloat 32 +%1 = OpVariable %2 Uniform +)"; + + spvtest::Binary linked_binary; + spvtools::LinkerOptions options; + options.SetCreateLibrary(true); + EXPECT_EQ(SPV_SUCCESS, AssembleAndLink({body}, &linked_binary, options)) + << GetErrorMessage(); + + const std::string expected_res = R"(OpCapability Linkage +OpModuleProcessed "Linked by SPIR-V Tools Linker" +OpDecorate %1 LinkageAttributes "foo" Export +%2 = OpTypeFloat 32 +%1 = OpVariable %2 Uniform +)"; + std::string res_body; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + EXPECT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body)) + << GetErrorMessage(); + EXPECT_EQ(expected_res, res_body); +} + +TEST_F(MatchingImportsToExports, UnresolvedImports) { + const std::string body1 = R"( +OpCapability Linkage +OpDecorate %1 LinkageAttributes "foo" Import +%2 = OpTypeFloat 32 +%1 = OpVariable %2 Uniform +)"; + const std::string body2 = R"()"; + + spvtest::Binary linked_binary; + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, + AssembleAndLink({body1, body2}, &linked_binary)); + EXPECT_THAT(GetErrorMessage(), + HasSubstr("Unresolved external reference to \"foo\".")); +} + +TEST_F(MatchingImportsToExports, TypeMismatch) { + const std::string body1 = R"( +OpCapability Linkage +OpDecorate %1 LinkageAttributes "foo" Import +%2 = OpTypeFloat 32 +%1 = OpVariable %2 Uniform +%3 = OpVariable %2 Input +)"; + const std::string body2 = R"( +OpCapability Linkage +OpDecorate %1 LinkageAttributes "foo" Export +%2 = OpTypeInt 32 0 +%3 = OpConstant %2 42 +%1 = OpVariable %2 Uniform %3 +)"; + + spvtest::Binary linked_binary; + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, + AssembleAndLink({body1, body2}, &linked_binary)) + << GetErrorMessage(); + EXPECT_THAT( + GetErrorMessage(), + HasSubstr("Type mismatch on symbol \"foo\" between imported " + "variable/function %1 and exported variable/function %4")); +} + +TEST_F(MatchingImportsToExports, MultipleDefinitions) { + const std::string body1 = R"( +OpCapability Linkage +OpDecorate %1 LinkageAttributes "foo" Import +%2 = OpTypeFloat 32 +%1 = OpVariable %2 Uniform +%3 = OpVariable %2 Input +)"; + const std::string body2 = R"( +OpCapability Linkage +OpDecorate %1 LinkageAttributes "foo" Export +%2 = OpTypeFloat 32 +%3 = OpConstant %2 42 +%1 = OpVariable %2 Uniform %3 +)"; + const std::string body3 = R"( +OpCapability Linkage +OpDecorate %1 LinkageAttributes "foo" Export +%2 = OpTypeFloat 32 +%3 = OpConstant %2 -1 +%1 = OpVariable %2 Uniform %3 +)"; + + spvtest::Binary linked_binary; + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, + AssembleAndLink({body1, body2, body3}, &linked_binary)) + << GetErrorMessage(); + EXPECT_THAT(GetErrorMessage(), + HasSubstr("Too many external references, 2, were found " + "for \"foo\".")); +} + +TEST_F(MatchingImportsToExports, SameNameDifferentTypes) { + const std::string body1 = R"( +OpCapability Linkage +OpDecorate %1 LinkageAttributes "foo" Import +%2 = OpTypeFloat 32 +%1 = OpVariable %2 Uniform +%3 = OpVariable %2 Input +)"; + const std::string body2 = R"( +OpCapability Linkage +OpDecorate %1 LinkageAttributes "foo" Export +%2 = OpTypeInt 32 0 +%3 = OpConstant %2 42 +%1 = OpVariable %2 Uniform %3 +)"; + const std::string body3 = R"( +OpCapability Linkage +OpDecorate %1 LinkageAttributes "foo" Export +%2 = OpTypeFloat 32 +%3 = OpConstant %2 12 +%1 = OpVariable %2 Uniform %3 +)"; + + spvtest::Binary linked_binary; + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, + AssembleAndLink({body1, body2, body3}, &linked_binary)) + << GetErrorMessage(); + EXPECT_THAT(GetErrorMessage(), + HasSubstr("Too many external references, 2, were found " + "for \"foo\".")); +} + +TEST_F(MatchingImportsToExports, DecorationMismatch) { + const std::string body1 = R"( +OpCapability Linkage +OpDecorate %1 LinkageAttributes "foo" Import +OpDecorate %2 Constant +%2 = OpTypeFloat 32 +%1 = OpVariable %2 Uniform +%3 = OpVariable %2 Input +)"; + const std::string body2 = R"( +OpCapability Linkage +OpDecorate %1 LinkageAttributes "foo" Export +%2 = OpTypeFloat 32 +%3 = OpConstant %2 42 +%1 = OpVariable %2 Uniform %3 +)"; + + spvtest::Binary linked_binary; + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, + AssembleAndLink({body1, body2}, &linked_binary)) + << GetErrorMessage(); + EXPECT_THAT( + GetErrorMessage(), + HasSubstr("Type mismatch on symbol \"foo\" between imported " + "variable/function %1 and exported variable/function %4")); +} + +TEST_F(MatchingImportsToExports, + FuncParamAttrDifferButStillMatchExportToImport) { + const std::string body1 = R"( +OpCapability Kernel +OpCapability Linkage +OpDecorate %1 LinkageAttributes "foo" Import +OpDecorate %2 FuncParamAttr Zext +%3 = OpTypeVoid +%4 = OpTypeInt 32 0 +%5 = OpTypeFunction %3 %4 +%1 = OpFunction %3 None %5 +%2 = OpFunctionParameter %4 +OpFunctionEnd +)"; + const std::string body2 = R"( +OpCapability Kernel +OpCapability Linkage +OpDecorate %1 LinkageAttributes "foo" Export +OpDecorate %2 FuncParamAttr Sext +%3 = OpTypeVoid +%4 = OpTypeInt 32 0 +%5 = OpTypeFunction %3 %4 +%1 = OpFunction %3 None %5 +%2 = OpFunctionParameter %4 +%6 = OpLabel +OpReturn +OpFunctionEnd +)"; + + spvtest::Binary linked_binary; + EXPECT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary)) + << GetErrorMessage(); + + const std::string expected_res = R"(OpCapability Kernel +OpModuleProcessed "Linked by SPIR-V Tools Linker" +OpDecorate %1 FuncParamAttr Sext +%2 = OpTypeVoid +%3 = OpTypeInt 32 0 +%4 = OpTypeFunction %2 %3 +%5 = OpFunction %2 None %4 +%1 = OpFunctionParameter %3 +%6 = OpLabel +OpReturn +OpFunctionEnd +)"; + std::string res_body; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + EXPECT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body)) + << GetErrorMessage(); + EXPECT_EQ(expected_res, res_body); +} + +TEST_F(MatchingImportsToExports, FunctionCtrl) { + const std::string body1 = R"( +OpCapability Linkage +OpDecorate %1 LinkageAttributes "foo" Import +%2 = OpTypeVoid +%3 = OpTypeFunction %2 +%4 = OpTypeFloat 32 +%5 = OpVariable %4 Uniform +%1 = OpFunction %2 None %3 +OpFunctionEnd +)"; + const std::string body2 = R"( +OpCapability Linkage +OpDecorate %1 LinkageAttributes "foo" Export +%2 = OpTypeVoid +%3 = OpTypeFunction %2 +%1 = OpFunction %2 Inline %3 +%4 = OpLabel +OpReturn +OpFunctionEnd +)"; + + spvtest::Binary linked_binary; + EXPECT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary)) + << GetErrorMessage(); + + const std::string expected_res = + R"(OpModuleProcessed "Linked by SPIR-V Tools Linker" +%1 = OpTypeVoid +%2 = OpTypeFunction %1 +%3 = OpTypeFloat 32 +%4 = OpVariable %3 Uniform +%5 = OpFunction %1 Inline %2 +%6 = OpLabel +OpReturn +OpFunctionEnd +)"; + std::string res_body; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + EXPECT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body)) + << GetErrorMessage(); + EXPECT_EQ(expected_res, res_body); +} + +TEST_F(MatchingImportsToExports, UseExportedFuncParamAttr) { + const std::string body1 = R"( +OpCapability Kernel +OpCapability Linkage +OpDecorate %1 LinkageAttributes "foo" Import +OpDecorate %2 FuncParamAttr Zext +%2 = OpDecorationGroup +OpGroupDecorate %2 %3 %4 +%5 = OpTypeVoid +%6 = OpTypeInt 32 0 +%7 = OpTypeFunction %5 %6 +%1 = OpFunction %5 None %7 +%3 = OpFunctionParameter %6 +OpFunctionEnd +%8 = OpFunction %5 None %7 +%4 = OpFunctionParameter %6 +OpFunctionEnd +)"; + const std::string body2 = R"( +OpCapability Kernel +OpCapability Linkage +OpDecorate %1 LinkageAttributes "foo" Export +OpDecorate %2 FuncParamAttr Sext +%3 = OpTypeVoid +%4 = OpTypeInt 32 0 +%5 = OpTypeFunction %3 %4 +%1 = OpFunction %3 None %5 +%2 = OpFunctionParameter %4 +%6 = OpLabel +OpReturn +OpFunctionEnd +)"; + + spvtest::Binary linked_binary; + EXPECT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary)) + << GetErrorMessage(); + + const std::string expected_res = R"(OpCapability Kernel +OpModuleProcessed "Linked by SPIR-V Tools Linker" +OpDecorate %1 FuncParamAttr Zext +%1 = OpDecorationGroup +OpGroupDecorate %1 %2 +OpDecorate %3 FuncParamAttr Sext +%4 = OpTypeVoid +%5 = OpTypeInt 32 0 +%6 = OpTypeFunction %4 %5 +%7 = OpFunction %4 None %6 +%2 = OpFunctionParameter %5 +OpFunctionEnd +%8 = OpFunction %4 None %6 +%3 = OpFunctionParameter %5 +%9 = OpLabel +OpReturn +OpFunctionEnd +)"; + std::string res_body; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + EXPECT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body)) + << GetErrorMessage(); + EXPECT_EQ(expected_res, res_body); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/link/memory_model_test.cpp b/3rdparty/spirv-tools/test/link/memory_model_test.cpp new file mode 100644 index 000000000..9ab1c2273 --- /dev/null +++ b/3rdparty/spirv-tools/test/link/memory_model_test.cpp @@ -0,0 +1,71 @@ +// Copyright (c) 2017 Pierre Moreau +// +// 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 "gmock/gmock.h" +#include "linker_fixture.h" + +namespace { + +using ::testing::HasSubstr; + +using MemoryModel = spvtest::LinkerTest; + +TEST_F(MemoryModel, Default) { + const std::string body1 = R"( +OpMemoryModel Logical Simple +)"; + const std::string body2 = R"( +OpMemoryModel Logical Simple +)"; + + spvtest::Binary linked_binary; + ASSERT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary)); + EXPECT_THAT(GetErrorMessage(), std::string()); + + EXPECT_EQ(SpvAddressingModelLogical, linked_binary[6]); + EXPECT_EQ(SpvMemoryModelSimple, linked_binary[7]); +} + +TEST_F(MemoryModel, AddressingMismatch) { + const std::string body1 = R"( +OpMemoryModel Logical Simple +)"; + const std::string body2 = R"( +OpMemoryModel Physical32 Simple +)"; + + spvtest::Binary linked_binary; + EXPECT_EQ(SPV_ERROR_INTERNAL, + AssembleAndLink({body1, body2}, &linked_binary)); + EXPECT_THAT( + GetErrorMessage(), + HasSubstr("Conflicting addressing models: Logical vs Physical32.")); +} + +TEST_F(MemoryModel, MemoryMismatch) { + const std::string body1 = R"( +OpMemoryModel Logical Simple +)"; + const std::string body2 = R"( +OpMemoryModel Logical GLSL450 +)"; + + spvtest::Binary linked_binary; + EXPECT_EQ(SPV_ERROR_INTERNAL, + AssembleAndLink({body1, body2}, &linked_binary)); + EXPECT_THAT(GetErrorMessage(), + HasSubstr("Conflicting memory models: Simple vs GLSL450.")); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/link/partial_linkage_test.cpp b/3rdparty/spirv-tools/test/link/partial_linkage_test.cpp new file mode 100644 index 000000000..89142c0c8 --- /dev/null +++ b/3rdparty/spirv-tools/test/link/partial_linkage_test.cpp @@ -0,0 +1,85 @@ +// Copyright (c) 2018 Pierre Moreau +// +// 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 "gmock/gmock.h" +#include "linker_fixture.h" + +namespace { + +using ::testing::HasSubstr; +using PartialLinkage = spvtest::LinkerTest; + +TEST_F(PartialLinkage, Allowed) { + const std::string body1 = R"( +OpCapability Linkage +OpDecorate %1 LinkageAttributes "foo" Import +OpDecorate %2 LinkageAttributes "bar" Import +%3 = OpTypeFloat 32 +%1 = OpVariable %3 Uniform +%2 = OpVariable %3 Uniform +)"; + const std::string body2 = R"( +OpCapability Linkage +OpDecorate %1 LinkageAttributes "bar" Export +%2 = OpTypeFloat 32 +%3 = OpConstant %2 3.1415 +%1 = OpVariable %2 Uniform %3 +)"; + + spvtest::Binary linked_binary; + spvtools::LinkerOptions linker_options; + linker_options.SetAllowPartialLinkage(true); + ASSERT_EQ(SPV_SUCCESS, + AssembleAndLink({body1, body2}, &linked_binary, linker_options)); + + const std::string expected_res = R"(OpCapability Linkage +OpModuleProcessed "Linked by SPIR-V Tools Linker" +OpDecorate %1 LinkageAttributes "foo" Import +%2 = OpTypeFloat 32 +%1 = OpVariable %2 Uniform +%3 = OpConstant %2 3.1415 +%4 = OpVariable %2 Uniform %3 +)"; + std::string res_body; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + ASSERT_EQ(SPV_SUCCESS, Disassemble(linked_binary, &res_body)) + << GetErrorMessage(); + EXPECT_EQ(expected_res, res_body); +} + +TEST_F(PartialLinkage, Disallowed) { + const std::string body1 = R"( +OpCapability Linkage +OpDecorate %1 LinkageAttributes "foo" Import +OpDecorate %2 LinkageAttributes "bar" Import +%3 = OpTypeFloat 32 +%1 = OpVariable %3 Uniform +%2 = OpVariable %3 Uniform +)"; + const std::string body2 = R"( +OpCapability Linkage +OpDecorate %1 LinkageAttributes "bar" Export +%2 = OpTypeFloat 32 +%3 = OpConstant %2 3.1415 +%1 = OpVariable %2 Uniform %3 +)"; + + spvtest::Binary linked_binary; + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, + AssembleAndLink({body1, body2}, &linked_binary)); + EXPECT_THAT(GetErrorMessage(), + HasSubstr("Unresolved external reference to \"foo\".")); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/link/unique_ids_test.cpp b/3rdparty/spirv-tools/test/link/unique_ids_test.cpp new file mode 100644 index 000000000..c926f0e67 --- /dev/null +++ b/3rdparty/spirv-tools/test/link/unique_ids_test.cpp @@ -0,0 +1,137 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "gmock/gmock.h" +#include "linker_fixture.h" + +namespace { + +using UniqueIds = spvtest::LinkerTest; + +TEST_F(UniqueIds, UniquelyMerged) { + std::vector bodies(2); + bodies[0] = + // clang-format off + "OpCapability Shader\n" + "%1 = OpExtInstImport \"GLSL.std.450\"\n" + "OpMemoryModel Logical GLSL450\n" + "OpEntryPoint Vertex %main \"main\"\n" + "OpSource ESSL 310\n" + "OpName %main \"main\"\n" + "OpName %f_ \"f(\"\n" + "OpName %gv1 \"gv1\"\n" + "OpName %gv2 \"gv2\"\n" + "OpName %lv1 \"lv1\"\n" + "OpName %lv2 \"lv2\"\n" + "OpName %lv1_0 \"lv1\"\n" + "%void = OpTypeVoid\n" + "%10 = OpTypeFunction %void\n" + "%float = OpTypeFloat 32\n" + "%12 = OpTypeFunction %float\n" + "%_ptr_Private_float = OpTypePointer Private %float\n" + "%gv1 = OpVariable %_ptr_Private_float Private\n" + "%float_10 = OpConstant %float 10\n" + "%gv2 = OpVariable %_ptr_Private_float Private\n" + "%float_100 = OpConstant %float 100\n" + "%_ptr_Function_float = OpTypePointer Function %float\n" + "%main = OpFunction %void None %10\n" + "%17 = OpLabel\n" + "%lv1_0 = OpVariable %_ptr_Function_float Function\n" + "OpStore %gv1 %float_10\n" + "OpStore %gv2 %float_100\n" + "%18 = OpLoad %float %gv1\n" + "%19 = OpLoad %float %gv2\n" + "%20 = OpFSub %float %18 %19\n" + "OpStore %lv1_0 %20\n" + "OpReturn\n" + "OpFunctionEnd\n" + "%f_ = OpFunction %float None %12\n" + "%21 = OpLabel\n" + "%lv1 = OpVariable %_ptr_Function_float Function\n" + "%lv2 = OpVariable %_ptr_Function_float Function\n" + "%22 = OpLoad %float %gv1\n" + "%23 = OpLoad %float %gv2\n" + "%24 = OpFAdd %float %22 %23\n" + "OpStore %lv1 %24\n" + "%25 = OpLoad %float %gv1\n" + "%26 = OpLoad %float %gv2\n" + "%27 = OpFMul %float %25 %26\n" + "OpStore %lv2 %27\n" + "%28 = OpLoad %float %lv1\n" + "%29 = OpLoad %float %lv2\n" + "%30 = OpFDiv %float %28 %29\n" + "OpReturnValue %30\n" + "OpFunctionEnd\n"; + // clang-format on + bodies[1] = + // clang-format off + "OpCapability Shader\n" + "%1 = OpExtInstImport \"GLSL.std.450\"\n" + "OpMemoryModel Logical GLSL450\n" + "OpSource ESSL 310\n" + "OpName %main \"main2\"\n" + "OpName %f_ \"f(\"\n" + "OpName %gv1 \"gv12\"\n" + "OpName %gv2 \"gv22\"\n" + "OpName %lv1 \"lv12\"\n" + "OpName %lv2 \"lv22\"\n" + "OpName %lv1_0 \"lv12\"\n" + "%void = OpTypeVoid\n" + "%10 = OpTypeFunction %void\n" + "%float = OpTypeFloat 32\n" + "%12 = OpTypeFunction %float\n" + "%_ptr_Private_float = OpTypePointer Private %float\n" + "%gv1 = OpVariable %_ptr_Private_float Private\n" + "%float_10 = OpConstant %float 10\n" + "%gv2 = OpVariable %_ptr_Private_float Private\n" + "%float_100 = OpConstant %float 100\n" + "%_ptr_Function_float = OpTypePointer Function %float\n" + "%main = OpFunction %void None %10\n" + "%17 = OpLabel\n" + "%lv1_0 = OpVariable %_ptr_Function_float Function\n" + "OpStore %gv1 %float_10\n" + "OpStore %gv2 %float_100\n" + "%18 = OpLoad %float %gv1\n" + "%19 = OpLoad %float %gv2\n" + "%20 = OpFSub %float %18 %19\n" + "OpStore %lv1_0 %20\n" + "OpReturn\n" + "OpFunctionEnd\n" + "%f_ = OpFunction %float None %12\n" + "%21 = OpLabel\n" + "%lv1 = OpVariable %_ptr_Function_float Function\n" + "%lv2 = OpVariable %_ptr_Function_float Function\n" + "%22 = OpLoad %float %gv1\n" + "%23 = OpLoad %float %gv2\n" + "%24 = OpFAdd %float %22 %23\n" + "OpStore %lv1 %24\n" + "%25 = OpLoad %float %gv1\n" + "%26 = OpLoad %float %gv2\n" + "%27 = OpFMul %float %25 %26\n" + "OpStore %lv2 %27\n" + "%28 = OpLoad %float %lv1\n" + "%29 = OpLoad %float %lv2\n" + "%30 = OpFDiv %float %28 %29\n" + "OpReturnValue %30\n" + "OpFunctionEnd\n"; + // clang-format on + + spvtest::Binary linked_binary; + spvtools::LinkerOptions options; + options.SetVerifyIds(true); + spv_result_t res = AssembleAndLink(bodies, &linked_binary, options); + EXPECT_EQ(SPV_SUCCESS, res); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/log_test.cpp b/3rdparty/spirv-tools/test/log_test.cpp new file mode 100644 index 000000000..f14f77d82 --- /dev/null +++ b/3rdparty/spirv-tools/test/log_test.cpp @@ -0,0 +1,54 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "message.h" +#include "opt/log.h" + +namespace { + +using namespace spvtools; +using ::testing::MatchesRegex; + +TEST(Log, Unimplemented) { + int invocation = 0; + auto consumer = [&invocation](spv_message_level_t level, const char* source, + const spv_position_t&, const char* message) { + ++invocation; + EXPECT_EQ(SPV_MSG_INTERNAL_ERROR, level); + EXPECT_THAT(source, MatchesRegex(".*log_test.cpp$")); + EXPECT_STREQ("unimplemented: the-ultimite-feature", message); + }; + + SPIRV_UNIMPLEMENTED(consumer, "the-ultimite-feature"); + EXPECT_EQ(1, invocation); +} + +TEST(Log, Unreachable) { + int invocation = 0; + auto consumer = [&invocation](spv_message_level_t level, const char* source, + const spv_position_t&, const char* message) { + ++invocation; + EXPECT_EQ(SPV_MSG_INTERNAL_ERROR, level); + EXPECT_THAT(source, MatchesRegex(".*log_test.cpp$")); + EXPECT_STREQ("unreachable", message); + }; + + SPIRV_UNREACHABLE(consumer); + EXPECT_EQ(1, invocation); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/move_to_front_test.cpp b/3rdparty/spirv-tools/test/move_to_front_test.cpp new file mode 100644 index 000000000..08c29212a --- /dev/null +++ b/3rdparty/spirv-tools/test/move_to_front_test.cpp @@ -0,0 +1,941 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "gmock/gmock.h" +#include "util/move_to_front.h" + +namespace { + +using spvutils::MoveToFront; +using spvutils::MultiMoveToFront; + +// Class used to test the inner workings of MoveToFront. +class MoveToFrontTester : public MoveToFront { + public: + // Inserts the value in the internal tree data structure. For testing only. + void TestInsert(uint32_t val) { InsertNode(CreateNode(val, val)); } + + // Removes the value from the internal tree data structure. For testing only. + void TestRemove(uint32_t val) { + const auto it = value_to_node_.find(val); + assert(it != value_to_node_.end()); + RemoveNode(it->second); + } + + // Prints the internal tree data structure to |out|. For testing only. + void PrintTree(std::ostream& out, bool print_timestamp = false) const { + if (root_) PrintTreeInternal(out, root_, 1, print_timestamp); + } + + // Returns node handle corresponding to the value. The value may not be in the + // tree. + uint32_t GetNodeHandle(uint32_t value) const { + const auto it = value_to_node_.find(value); + if (it == value_to_node_.end()) return 0; + + return it->second; + } + + // Returns total node count (both those in the tree and removed, + // but not the NIL singleton). + size_t GetTotalNodeCount() const { + assert(nodes_.size()); + return nodes_.size() - 1; + } + + uint32_t GetLastAccessedValue() const { return last_accessed_value_; } + + private: + // Prints the internal tree data structure for debug purposes in the following + // format: + // 10H3S4----5H1S1-----D2 + // 15H2S2----12H1S1----D3 + // Right links are horizontal, left links step down one line. + // 5H1S1 is read as value 5, height 1, size 1. Optionally node label can also + // contain timestamp (5H1S1T15). D3 stands for depth 3. + void PrintTreeInternal(std::ostream& out, uint32_t node, size_t depth, + bool print_timestamp) const; +}; + +void MoveToFrontTester::PrintTreeInternal(std::ostream& out, uint32_t node, + size_t depth, + bool print_timestamp) const { + if (!node) { + out << "D" << depth - 1 << std::endl; + return; + } + + const size_t kTextFieldWvaluethWithoutTimestamp = 10; + const size_t kTextFieldWvaluethWithTimestamp = 14; + const size_t text_field_wvalueth = print_timestamp + ? kTextFieldWvaluethWithTimestamp + : kTextFieldWvaluethWithoutTimestamp; + + std::stringstream label; + label << ValueOf(node) << "H" << HeightOf(node) << "S" << SizeOf(node); + if (print_timestamp) label << "T" << TimestampOf(node); + const size_t label_length = label.str().length(); + if (label_length < text_field_wvalueth) + label << std::string(text_field_wvalueth - label_length, '-'); + + out << label.str(); + + PrintTreeInternal(out, RightOf(node), depth + 1, print_timestamp); + + if (LeftOf(node)) { + out << std::string(depth * text_field_wvalueth, ' '); + PrintTreeInternal(out, LeftOf(node), depth + 1, print_timestamp); + } +} + +void CheckTree(const MoveToFrontTester& mtf, const std::string& expected, + bool print_timestamp = false) { + std::stringstream ss; + mtf.PrintTree(ss, print_timestamp); + EXPECT_EQ(expected, ss.str()); +} + +TEST(MoveToFront, EmptyTree) { + MoveToFrontTester mtf; + CheckTree(mtf, std::string()); +} + +TEST(MoveToFront, InsertLeftRotation) { + MoveToFrontTester mtf; + + mtf.TestInsert(30); + mtf.TestInsert(20); + + CheckTree(mtf, std::string(R"( +30H2S2----20H1S1----D2 +)") + .substr(1)); + + mtf.TestInsert(10); + CheckTree(mtf, std::string(R"( +20H2S3----10H1S1----D2 + 30H1S1----D2 +)") + .substr(1)); +} + +TEST(MoveToFront, InsertRightRotation) { + MoveToFrontTester mtf; + + mtf.TestInsert(10); + mtf.TestInsert(20); + + CheckTree(mtf, std::string(R"( +10H2S2----D1 + 20H1S1----D2 +)") + .substr(1)); + + mtf.TestInsert(30); + CheckTree(mtf, std::string(R"( +20H2S3----10H1S1----D2 + 30H1S1----D2 +)") + .substr(1)); +} + +TEST(MoveToFront, InsertRightLeftRotation) { + MoveToFrontTester mtf; + + mtf.TestInsert(30); + mtf.TestInsert(20); + + CheckTree(mtf, std::string(R"( +30H2S2----20H1S1----D2 +)") + .substr(1)); + + mtf.TestInsert(25); + CheckTree(mtf, std::string(R"( +25H2S3----20H1S1----D2 + 30H1S1----D2 +)") + .substr(1)); +} + +TEST(MoveToFront, InsertLeftRightRotation) { + MoveToFrontTester mtf; + + mtf.TestInsert(10); + mtf.TestInsert(20); + + CheckTree(mtf, std::string(R"( +10H2S2----D1 + 20H1S1----D2 +)") + .substr(1)); + + mtf.TestInsert(15); + CheckTree(mtf, std::string(R"( +15H2S3----10H1S1----D2 + 20H1S1----D2 +)") + .substr(1)); +} + +TEST(MoveToFront, RemoveSingleton) { + MoveToFrontTester mtf; + + mtf.TestInsert(10); + CheckTree(mtf, std::string(R"( +10H1S1----D1 +)") + .substr(1)); + + mtf.TestRemove(10); + CheckTree(mtf, ""); +} + +TEST(MoveToFront, RemoveRootWithScapegoat) { + MoveToFrontTester mtf; + + mtf.TestInsert(10); + mtf.TestInsert(5); + mtf.TestInsert(15); + CheckTree(mtf, std::string(R"( +10H2S3----5H1S1-----D2 + 15H1S1----D2 +)") + .substr(1)); + + mtf.TestRemove(10); + CheckTree(mtf, std::string(R"( +15H2S2----5H1S1-----D2 +)") + .substr(1)); +} + +TEST(MoveToFront, RemoveRightRotation) { + MoveToFrontTester mtf; + + mtf.TestInsert(10); + mtf.TestInsert(5); + mtf.TestInsert(15); + mtf.TestInsert(20); + CheckTree(mtf, std::string(R"( +10H3S4----5H1S1-----D2 + 15H2S2----D2 + 20H1S1----D3 +)") + .substr(1)); + + mtf.TestRemove(5); + + CheckTree(mtf, std::string(R"( +15H2S3----10H1S1----D2 + 20H1S1----D2 +)") + .substr(1)); +} + +TEST(MoveToFront, RemoveLeftRotation) { + MoveToFrontTester mtf; + + mtf.TestInsert(10); + mtf.TestInsert(15); + mtf.TestInsert(5); + mtf.TestInsert(1); + CheckTree(mtf, std::string(R"( +10H3S4----5H2S2-----1H1S1-----D3 + 15H1S1----D2 +)") + .substr(1)); + + mtf.TestRemove(15); + + CheckTree(mtf, std::string(R"( +5H2S3-----1H1S1-----D2 + 10H1S1----D2 +)") + .substr(1)); +} + +TEST(MoveToFront, RemoveLeftRightRotation) { + MoveToFrontTester mtf; + + mtf.TestInsert(10); + mtf.TestInsert(15); + mtf.TestInsert(5); + mtf.TestInsert(12); + CheckTree(mtf, std::string(R"( +10H3S4----5H1S1-----D2 + 15H2S2----12H1S1----D3 +)") + .substr(1)); + + mtf.TestRemove(5); + + CheckTree(mtf, std::string(R"( +12H2S3----10H1S1----D2 + 15H1S1----D2 +)") + .substr(1)); +} + +TEST(MoveToFront, RemoveRightLeftRotation) { + MoveToFrontTester mtf; + + mtf.TestInsert(10); + mtf.TestInsert(15); + mtf.TestInsert(5); + mtf.TestInsert(8); + CheckTree(mtf, std::string(R"( +10H3S4----5H2S2-----D2 + 8H1S1-----D3 + 15H1S1----D2 +)") + .substr(1)); + + mtf.TestRemove(15); + + CheckTree(mtf, std::string(R"( +8H2S3-----5H1S1-----D2 + 10H1S1----D2 +)") + .substr(1)); +} + +TEST(MoveToFront, MultipleOperations) { + MoveToFrontTester mtf; + std::vector vals = {5, 11, 12, 16, 15, 6, 14, 2, + 7, 10, 4, 8, 9, 3, 1, 13}; + + for (uint32_t i : vals) { + mtf.TestInsert(i); + } + + CheckTree(mtf, std::string(R"( +11H5S16---5H4S10----3H3S4-----2H2S2-----1H1S1-----D5 + 4H1S1-----D4 + 7H3S5-----6H1S1-----D4 + 9H2S3-----8H1S1-----D5 + 10H1S1----D5 + 15H3S5----13H2S3----12H1S1----D4 + 14H1S1----D4 + 16H1S1----D3 +)") + .substr(1)); + + mtf.TestRemove(11); + + CheckTree(mtf, std::string(R"( +10H5S15---5H4S9-----3H3S4-----2H2S2-----1H1S1-----D5 + 4H1S1-----D4 + 7H3S4-----6H1S1-----D4 + 9H2S2-----8H1S1-----D5 + 15H3S5----13H2S3----12H1S1----D4 + 14H1S1----D4 + 16H1S1----D3 +)") + .substr(1)); + + mtf.TestInsert(11); + + CheckTree(mtf, std::string(R"( +10H5S16---5H4S9-----3H3S4-----2H2S2-----1H1S1-----D5 + 4H1S1-----D4 + 7H3S4-----6H1S1-----D4 + 9H2S2-----8H1S1-----D5 + 13H3S6----12H2S2----11H1S1----D4 + 15H2S3----14H1S1----D4 + 16H1S1----D4 +)") + .substr(1)); + + mtf.TestRemove(5); + + CheckTree(mtf, std::string(R"( +10H5S15---6H4S8-----3H3S4-----2H2S2-----1H1S1-----D5 + 4H1S1-----D4 + 8H2S3-----7H1S1-----D4 + 9H1S1-----D4 + 13H3S6----12H2S2----11H1S1----D4 + 15H2S3----14H1S1----D4 + 16H1S1----D4 +)") + .substr(1)); + + mtf.TestInsert(5); + + CheckTree(mtf, std::string(R"( +10H5S16---6H4S9-----3H3S5-----2H2S2-----1H1S1-----D5 + 4H2S2-----D4 + 5H1S1-----D5 + 8H2S3-----7H1S1-----D4 + 9H1S1-----D4 + 13H3S6----12H2S2----11H1S1----D4 + 15H2S3----14H1S1----D4 + 16H1S1----D4 +)") + .substr(1)); + + mtf.TestRemove(2); + mtf.TestRemove(1); + mtf.TestRemove(4); + mtf.TestRemove(3); + mtf.TestRemove(6); + mtf.TestRemove(5); + mtf.TestRemove(7); + mtf.TestRemove(9); + + CheckTree(mtf, std::string(R"( +13H4S8----10H3S4----8H1S1-----D3 + 12H2S2----11H1S1----D4 + 15H2S3----14H1S1----D3 + 16H1S1----D3 +)") + .substr(1)); +} + +TEST(MoveToFront, BiggerScaleTreeTest) { + MoveToFrontTester mtf; + std::set all_vals; + + const uint32_t kMagic1 = 2654435761; + const uint32_t kMagic2 = 10000; + + for (uint32_t i = 1; i < 1000; ++i) { + const uint32_t val = (i * kMagic1) % kMagic2; + if (!all_vals.count(val)) { + mtf.TestInsert(val); + all_vals.insert(val); + } + } + + for (uint32_t i = 1; i < 1000; ++i) { + const uint32_t val = (i * kMagic1) % kMagic2; + if (val % 2 == 0) { + mtf.TestRemove(val); + all_vals.erase(val); + } + } + + for (uint32_t i = 1000; i < 2000; ++i) { + const uint32_t val = (i * kMagic1) % kMagic2; + if (!all_vals.count(val)) { + mtf.TestInsert(val); + all_vals.insert(val); + } + } + + for (uint32_t i = 1; i < 2000; ++i) { + const uint32_t val = (i * kMagic1) % kMagic2; + if (val > 50) { + mtf.TestRemove(val); + all_vals.erase(val); + } + } + + EXPECT_EQ(all_vals, std::set({2, 4, 11, 13, 24, 33, 35, 37, 46})); + + CheckTree(mtf, std::string(R"( +33H4S9----11H3S5----2H2S2-----D3 + 4H1S1-----D4 + 13H2S2----D3 + 24H1S1----D4 + 37H2S3----35H1S1----D3 + 46H1S1----D3 +)") + .substr(1)); +} + +TEST(MoveToFront, RankFromValue) { + MoveToFrontTester mtf; + + uint32_t rank = 0; + EXPECT_FALSE(mtf.RankFromValue(1, &rank)); + + EXPECT_TRUE(mtf.Insert(1)); + EXPECT_TRUE(mtf.Insert(2)); + EXPECT_TRUE(mtf.Insert(3)); + EXPECT_FALSE(mtf.Insert(2)); + CheckTree(mtf, + std::string(R"( +2H2S3T2-------1H1S1T1-------D2 + 3H1S1T3-------D2 +)") + .substr(1), + /* print_timestamp = */ true); + + EXPECT_FALSE(mtf.RankFromValue(4, &rank)); + + EXPECT_TRUE(mtf.RankFromValue(1, &rank)); + EXPECT_EQ(3u, rank); + + CheckTree(mtf, + std::string(R"( +3H2S3T3-------2H1S1T2-------D2 + 1H1S1T4-------D2 +)") + .substr(1), + /* print_timestamp = */ true); + + EXPECT_TRUE(mtf.RankFromValue(1, &rank)); + EXPECT_EQ(1u, rank); + + EXPECT_TRUE(mtf.RankFromValue(3, &rank)); + EXPECT_EQ(2u, rank); + + EXPECT_TRUE(mtf.RankFromValue(2, &rank)); + EXPECT_EQ(3u, rank); + + EXPECT_TRUE(mtf.Insert(40)); + + EXPECT_TRUE(mtf.RankFromValue(1, &rank)); + EXPECT_EQ(4u, rank); + + EXPECT_TRUE(mtf.Insert(50)); + + EXPECT_TRUE(mtf.RankFromValue(1, &rank)); + EXPECT_EQ(2u, rank); + + CheckTree(mtf, + std::string(R"( +2H3S5T6-------3H1S1T5-------D2 + 50H2S3T9------40H1S1T7------D3 + 1H1S1T10------D3 +)") + .substr(1), + /* print_timestamp = */ true); + + EXPECT_TRUE(mtf.RankFromValue(50, &rank)); + EXPECT_EQ(2u, rank); + + EXPECT_EQ(5u, mtf.GetSize()); + CheckTree(mtf, + std::string(R"( +2H3S5T6-------3H1S1T5-------D2 + 1H2S3T10------40H1S1T7------D3 + 50H1S1T11-----D3 +)") + .substr(1), + /* print_timestamp = */ true); + + EXPECT_FALSE(mtf.RankFromValue(0, &rank)); + EXPECT_FALSE(mtf.RankFromValue(20, &rank)); +} + +TEST(MoveToFront, ValueFromRank) { + MoveToFrontTester mtf; + + uint32_t value = 0; + EXPECT_FALSE(mtf.ValueFromRank(0, &value)); + EXPECT_FALSE(mtf.ValueFromRank(1, &value)); + + EXPECT_TRUE(mtf.Insert(1)); + EXPECT_EQ(1u, mtf.GetLastAccessedValue()); + EXPECT_TRUE(mtf.Insert(2)); + EXPECT_EQ(2u, mtf.GetLastAccessedValue()); + EXPECT_TRUE(mtf.Insert(3)); + EXPECT_EQ(3u, mtf.GetLastAccessedValue()); + + EXPECT_TRUE(mtf.ValueFromRank(3, &value)); + EXPECT_EQ(1u, value); + EXPECT_EQ(1u, mtf.GetLastAccessedValue()); + + EXPECT_TRUE(mtf.ValueFromRank(1, &value)); + EXPECT_EQ(1u, value); + EXPECT_EQ(1u, mtf.GetLastAccessedValue()); + + CheckTree(mtf, + std::string(R"( +3H2S3T3-------2H1S1T2-------D2 + 1H1S1T4-------D2 +)") + .substr(1), + /* print_timestamp = */ true); + + EXPECT_TRUE(mtf.ValueFromRank(2, &value)); + EXPECT_EQ(3u, value); + + EXPECT_EQ(3u, mtf.GetSize()); + + CheckTree(mtf, + std::string(R"( +1H2S3T4-------2H1S1T2-------D2 + 3H1S1T5-------D2 +)") + .substr(1), + /* print_timestamp = */ true); + + EXPECT_TRUE(mtf.ValueFromRank(3, &value)); + EXPECT_EQ(2u, value); + + CheckTree(mtf, + std::string(R"( +3H2S3T5-------1H1S1T4-------D2 + 2H1S1T6-------D2 +)") + .substr(1), + /* print_timestamp = */ true); + + EXPECT_TRUE(mtf.Insert(10)); + CheckTree(mtf, + std::string(R"( +3H3S4T5-------1H1S1T4-------D2 + 2H2S2T6-------D2 + 10H1S1T7------D3 +)") + .substr(1), + /* print_timestamp = */ true); + + EXPECT_TRUE(mtf.ValueFromRank(1, &value)); + EXPECT_EQ(10u, value); +} + +TEST(MoveToFront, Remove) { + MoveToFrontTester mtf; + + EXPECT_FALSE(mtf.Remove(1)); + EXPECT_EQ(0u, mtf.GetTotalNodeCount()); + + EXPECT_TRUE(mtf.Insert(1)); + EXPECT_TRUE(mtf.Insert(2)); + EXPECT_TRUE(mtf.Insert(3)); + + CheckTree(mtf, + std::string(R"( +2H2S3T2-------1H1S1T1-------D2 + 3H1S1T3-------D2 +)") + .substr(1), + /* print_timestamp = */ true); + + EXPECT_EQ(1u, mtf.GetNodeHandle(1)); + EXPECT_EQ(3u, mtf.GetTotalNodeCount()); + EXPECT_TRUE(mtf.Remove(1)); + EXPECT_EQ(3u, mtf.GetTotalNodeCount()); + + CheckTree(mtf, + std::string(R"( +2H2S2T2-------D1 + 3H1S1T3-------D2 +)") + .substr(1), + /* print_timestamp = */ true); + + uint32_t value = 0; + EXPECT_TRUE(mtf.ValueFromRank(2, &value)); + EXPECT_EQ(2u, value); + + CheckTree(mtf, + std::string(R"( +3H2S2T3-------D1 + 2H1S1T4-------D2 +)") + .substr(1), + /* print_timestamp = */ true); + + EXPECT_TRUE(mtf.Insert(1)); + EXPECT_EQ(1u, mtf.GetNodeHandle(1)); + EXPECT_EQ(3u, mtf.GetTotalNodeCount()); +} + +TEST(MoveToFront, LargerScale) { + MoveToFrontTester mtf; + uint32_t value = 0; + uint32_t rank = 0; + + for (uint32_t i = 1; i < 1000; ++i) { + ASSERT_TRUE(mtf.Insert(i)); + ASSERT_EQ(i, mtf.GetSize()); + + ASSERT_TRUE(mtf.RankFromValue(i, &rank)); + ASSERT_EQ(1u, rank); + + ASSERT_TRUE(mtf.ValueFromRank(1, &value)); + ASSERT_EQ(i, value); + } + + ASSERT_TRUE(mtf.ValueFromRank(999, &value)); + ASSERT_EQ(1u, value); + + ASSERT_TRUE(mtf.ValueFromRank(999, &value)); + ASSERT_EQ(2u, value); + + ASSERT_TRUE(mtf.ValueFromRank(999, &value)); + ASSERT_EQ(3u, value); + + ASSERT_TRUE(mtf.ValueFromRank(999, &value)); + ASSERT_EQ(4u, value); + + ASSERT_TRUE(mtf.ValueFromRank(999, &value)); + ASSERT_EQ(5u, value); + + ASSERT_TRUE(mtf.ValueFromRank(999, &value)); + ASSERT_EQ(6u, value); + + ASSERT_TRUE(mtf.ValueFromRank(101, &value)); + ASSERT_EQ(905u, value); + + ASSERT_TRUE(mtf.ValueFromRank(101, &value)); + ASSERT_EQ(906u, value); + + ASSERT_TRUE(mtf.ValueFromRank(101, &value)); + ASSERT_EQ(907u, value); + + ASSERT_TRUE(mtf.ValueFromRank(201, &value)); + ASSERT_EQ(805u, value); + + ASSERT_TRUE(mtf.ValueFromRank(201, &value)); + ASSERT_EQ(806u, value); + + ASSERT_TRUE(mtf.ValueFromRank(201, &value)); + ASSERT_EQ(807u, value); + + ASSERT_TRUE(mtf.ValueFromRank(301, &value)); + ASSERT_EQ(705u, value); + + ASSERT_TRUE(mtf.ValueFromRank(301, &value)); + ASSERT_EQ(706u, value); + + ASSERT_TRUE(mtf.ValueFromRank(301, &value)); + ASSERT_EQ(707u, value); + + ASSERT_TRUE(mtf.RankFromValue(605, &rank)); + ASSERT_EQ(401u, rank); + + ASSERT_TRUE(mtf.RankFromValue(606, &rank)); + ASSERT_EQ(401u, rank); + + ASSERT_TRUE(mtf.RankFromValue(607, &rank)); + ASSERT_EQ(401u, rank); + + ASSERT_TRUE(mtf.ValueFromRank(1, &value)); + ASSERT_EQ(607u, value); + + ASSERT_TRUE(mtf.ValueFromRank(2, &value)); + ASSERT_EQ(606u, value); + + ASSERT_TRUE(mtf.ValueFromRank(3, &value)); + ASSERT_EQ(605u, value); + + ASSERT_TRUE(mtf.ValueFromRank(4, &value)); + ASSERT_EQ(707u, value); + + ASSERT_TRUE(mtf.ValueFromRank(5, &value)); + ASSERT_EQ(706u, value); + + ASSERT_TRUE(mtf.ValueFromRank(6, &value)); + ASSERT_EQ(705u, value); + + ASSERT_TRUE(mtf.ValueFromRank(7, &value)); + ASSERT_EQ(807u, value); + + ASSERT_TRUE(mtf.ValueFromRank(8, &value)); + ASSERT_EQ(806u, value); + + ASSERT_TRUE(mtf.ValueFromRank(9, &value)); + ASSERT_EQ(805u, value); + + ASSERT_TRUE(mtf.ValueFromRank(10, &value)); + ASSERT_EQ(907u, value); + + ASSERT_TRUE(mtf.ValueFromRank(11, &value)); + ASSERT_EQ(906u, value); + + ASSERT_TRUE(mtf.ValueFromRank(12, &value)); + ASSERT_EQ(905u, value); + + ASSERT_TRUE(mtf.ValueFromRank(13, &value)); + ASSERT_EQ(6u, value); + + ASSERT_TRUE(mtf.ValueFromRank(14, &value)); + ASSERT_EQ(5u, value); + + ASSERT_TRUE(mtf.ValueFromRank(15, &value)); + ASSERT_EQ(4u, value); + + ASSERT_TRUE(mtf.ValueFromRank(16, &value)); + ASSERT_EQ(3u, value); + + ASSERT_TRUE(mtf.ValueFromRank(17, &value)); + ASSERT_EQ(2u, value); + + ASSERT_TRUE(mtf.ValueFromRank(18, &value)); + ASSERT_EQ(1u, value); + + ASSERT_TRUE(mtf.ValueFromRank(19, &value)); + ASSERT_EQ(999u, value); + + ASSERT_TRUE(mtf.ValueFromRank(20, &value)); + ASSERT_EQ(998u, value); + + ASSERT_TRUE(mtf.ValueFromRank(21, &value)); + ASSERT_EQ(997u, value); + + ASSERT_TRUE(mtf.RankFromValue(997, &rank)); + ASSERT_EQ(1u, rank); + + ASSERT_TRUE(mtf.RankFromValue(998, &rank)); + ASSERT_EQ(2u, rank); + + ASSERT_TRUE(mtf.RankFromValue(996, &rank)); + ASSERT_EQ(22u, rank); + + ASSERT_TRUE(mtf.Remove(995)); + + ASSERT_TRUE(mtf.RankFromValue(994, &rank)); + ASSERT_EQ(23u, rank); + + for (uint32_t i = 10; i < 1000; ++i) { + if (i != 995) { + ASSERT_TRUE(mtf.Remove(i)); + } else { + ASSERT_FALSE(mtf.Remove(i)); + } + } + + CheckTree(mtf, + std::string(R"( +6H4S9T1029----8H2S3T8-------7H1S1T7-------D3 + 9H1S1T9-------D3 + 2H3S5T1033----4H2S3T1031----5H1S1T1030----D4 + 3H1S1T1032----D4 + 1H1S1T1034----D3 +)") + .substr(1), + /* print_timestamp = */ true); + + ASSERT_TRUE(mtf.Insert(1000)); + ASSERT_TRUE(mtf.ValueFromRank(1, &value)); + ASSERT_EQ(1000u, value); +} + +TEST(MoveToFront, String) { + MoveToFront mtf; + + EXPECT_TRUE(mtf.Insert("AAA")); + EXPECT_TRUE(mtf.Insert("BBB")); + EXPECT_TRUE(mtf.Insert("CCC")); + EXPECT_FALSE(mtf.Insert("AAA")); + + EXPECT_TRUE(mtf.HasValue("AAA")); + EXPECT_FALSE(mtf.HasValue("DDD")); + + std::string value; + EXPECT_TRUE(mtf.ValueFromRank(2, &value)); + EXPECT_EQ("BBB", value); + + EXPECT_TRUE(mtf.ValueFromRank(2, &value)); + EXPECT_EQ("CCC", value); + + uint32_t rank = 0; + EXPECT_TRUE(mtf.RankFromValue("AAA", &rank)); + EXPECT_EQ(3u, rank); + + EXPECT_FALSE(mtf.ValueFromRank(0, &value)); + EXPECT_FALSE(mtf.RankFromValue("ABC", &rank)); + EXPECT_FALSE(mtf.Remove("ABC")); + + EXPECT_TRUE(mtf.Remove("AAA")); + EXPECT_FALSE(mtf.Remove("AAA")); + EXPECT_FALSE(mtf.RankFromValue("AAA", &rank)); + + EXPECT_TRUE(mtf.Insert("AAA")); + EXPECT_TRUE(mtf.RankFromValue("AAA", &rank)); + EXPECT_EQ(1u, rank); + + EXPECT_TRUE(mtf.Promote("BBB")); + EXPECT_TRUE(mtf.RankFromValue("BBB", &rank)); + EXPECT_EQ(1u, rank); +} + +TEST(MultiMoveToFront, Empty) { + MultiMoveToFront multi_mtf; + + uint32_t rank = 0; + std::string value; + + EXPECT_EQ(0u, multi_mtf.GetSize(1001)); + EXPECT_FALSE(multi_mtf.RankFromValue(1001, "AAA", &rank)); + EXPECT_FALSE(multi_mtf.ValueFromRank(1001, 1, &value)); + EXPECT_FALSE(multi_mtf.HasValue(1001, "AAA")); + EXPECT_FALSE(multi_mtf.Remove(1001, "AAA")); +} + +TEST(MultiMoveToFront, TwoSequences) { + MultiMoveToFront multi_mtf; + + uint32_t rank = 0; + std::string value; + + EXPECT_TRUE(multi_mtf.Insert(1001, "AAA")); + + EXPECT_EQ(1u, multi_mtf.GetSize(1001)); + EXPECT_EQ(0u, multi_mtf.GetSize(1002)); + EXPECT_TRUE(multi_mtf.HasValue(1001, "AAA")); + EXPECT_FALSE(multi_mtf.HasValue(1002, "AAA")); + + EXPECT_TRUE(multi_mtf.RankFromValue(1001, "AAA", &rank)); + EXPECT_EQ(1u, rank); + EXPECT_FALSE(multi_mtf.RankFromValue(1002, "AAA", &rank)); + + EXPECT_TRUE(multi_mtf.ValueFromRank(1001, rank, &value)); + EXPECT_EQ("AAA", value); + EXPECT_FALSE(multi_mtf.ValueFromRank(1002, rank, &value)); + + EXPECT_TRUE(multi_mtf.Insert(1001, "BBB")); + + EXPECT_EQ(2u, multi_mtf.GetSize(1001)); + EXPECT_EQ(0u, multi_mtf.GetSize(1002)); + EXPECT_TRUE(multi_mtf.HasValue(1001, "BBB")); + EXPECT_FALSE(multi_mtf.HasValue(1002, "BBB")); + + EXPECT_TRUE(multi_mtf.RankFromValue(1001, "BBB", &rank)); + EXPECT_EQ(1u, rank); + EXPECT_FALSE(multi_mtf.RankFromValue(1002, "BBB", &rank)); + + EXPECT_TRUE(multi_mtf.ValueFromRank(1001, rank, &value)); + EXPECT_EQ("BBB", value); + EXPECT_FALSE(multi_mtf.ValueFromRank(1002, rank, &value)); + + EXPECT_TRUE(multi_mtf.Insert(1002, "AAA")); + + EXPECT_EQ(2u, multi_mtf.GetSize(1001)); + EXPECT_EQ(1u, multi_mtf.GetSize(1002)); + EXPECT_TRUE(multi_mtf.HasValue(1002, "AAA")); + + EXPECT_TRUE(multi_mtf.RankFromValue(1002, "AAA", &rank)); + EXPECT_EQ(1u, rank); + + EXPECT_TRUE(multi_mtf.RankFromValue(1001, "AAA", &rank)); + EXPECT_EQ(2u, rank); + + multi_mtf.Promote("BBB"); + + EXPECT_TRUE(multi_mtf.RankFromValue(1001, "BBB", &rank)); + EXPECT_EQ(1u, rank); + + EXPECT_TRUE(multi_mtf.Insert(1002, "CCC")); + EXPECT_TRUE(multi_mtf.RankFromValue(1002, "CCC", &rank)); + EXPECT_EQ(1u, rank); + + multi_mtf.Promote("AAA"); + EXPECT_TRUE(multi_mtf.RankFromValue(1001, "AAA", &rank)); + EXPECT_EQ(1u, rank); + EXPECT_TRUE(multi_mtf.RankFromValue(1002, "AAA", &rank)); + EXPECT_EQ(1u, rank); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/name_mapper_test.cpp b/3rdparty/spirv-tools/test/name_mapper_test.cpp new file mode 100644 index 000000000..d2a5f4e4d --- /dev/null +++ b/3rdparty/spirv-tools/test/name_mapper_test.cpp @@ -0,0 +1,346 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "gmock/gmock.h" + +#include "test_fixture.h" +#include "unit_spirv.h" + +#include "source/name_mapper.h" + +using libspirv::FriendlyNameMapper; +using libspirv::NameMapper; +using spvtest::ScopedContext; +using ::testing::Eq; + +namespace { + +TEST(TrivialNameTest, Samples) { + auto mapper = libspirv::GetTrivialNameMapper(); + EXPECT_EQ(mapper(1), "1"); + EXPECT_EQ(mapper(1999), "1999"); + EXPECT_EQ(mapper(1024), "1024"); +} + +// A test case for the name mappers that actually look at an assembled module. +struct NameIdCase { + std::string assembly; // Input assembly text + uint32_t id; + std::string expected_name; +}; + +using FriendlyNameTest = + spvtest::TextToBinaryTestBase<::testing::TestWithParam>; + +TEST_P(FriendlyNameTest, SingleMapping) { + ScopedContext context(SPV_ENV_UNIVERSAL_1_1); + auto words = CompileSuccessfully(GetParam().assembly, SPV_ENV_UNIVERSAL_1_1); + auto friendly_mapper = + FriendlyNameMapper(context.context, words.data(), words.size()); + NameMapper mapper = friendly_mapper.GetNameMapper(); + EXPECT_THAT(mapper(GetParam().id), Eq(GetParam().expected_name)) + << GetParam().assembly << std::endl + << " for id " << GetParam().id; +} + +INSTANTIATE_TEST_CASE_P(ScalarType, FriendlyNameTest, + ::testing::ValuesIn(std::vector{ + {"%1 = OpTypeVoid", 1, "void"}, + {"%1 = OpTypeBool", 1, "bool"}, + {"%1 = OpTypeInt 8 0", 1, "uchar"}, + {"%1 = OpTypeInt 8 1", 1, "char"}, + {"%1 = OpTypeInt 16 0", 1, "ushort"}, + {"%1 = OpTypeInt 16 1", 1, "short"}, + {"%1 = OpTypeInt 32 0", 1, "uint"}, + {"%1 = OpTypeInt 32 1", 1, "int"}, + {"%1 = OpTypeInt 64 0", 1, "ulong"}, + {"%1 = OpTypeInt 64 1", 1, "long"}, + {"%1 = OpTypeInt 1 0", 1, "u1"}, + {"%1 = OpTypeInt 1 1", 1, "i1"}, + {"%1 = OpTypeInt 33 0", 1, "u33"}, + {"%1 = OpTypeInt 33 1", 1, "i33"}, + + {"%1 = OpTypeFloat 16", 1, "half"}, + {"%1 = OpTypeFloat 32", 1, "float"}, + {"%1 = OpTypeFloat 64", 1, "double"}, + {"%1 = OpTypeFloat 10", 1, "fp10"}, + {"%1 = OpTypeFloat 55", 1, "fp55"}, + }), ); + +INSTANTIATE_TEST_CASE_P( + VectorType, FriendlyNameTest, + ::testing::ValuesIn(std::vector{ + {"%1 = OpTypeBool %2 = OpTypeVector %1 1", 2, "v1bool"}, + {"%1 = OpTypeBool %2 = OpTypeVector %1 2", 2, "v2bool"}, + {"%1 = OpTypeBool %2 = OpTypeVector %1 3", 2, "v3bool"}, + {"%1 = OpTypeBool %2 = OpTypeVector %1 4", 2, "v4bool"}, + + {"%1 = OpTypeInt 8 0 %2 = OpTypeVector %1 2", 2, "v2uchar"}, + {"%1 = OpTypeInt 16 1 %2 = OpTypeVector %1 3", 2, "v3short"}, + {"%1 = OpTypeInt 32 0 %2 = OpTypeVector %1 4", 2, "v4uint"}, + {"%1 = OpTypeInt 64 1 %2 = OpTypeVector %1 3", 2, "v3long"}, + {"%1 = OpTypeInt 20 0 %2 = OpTypeVector %1 4", 2, "v4u20"}, + {"%1 = OpTypeInt 21 1 %2 = OpTypeVector %1 3", 2, "v3i21"}, + + {"%1 = OpTypeFloat 32 %2 = OpTypeVector %1 2", 2, "v2float"}, + // OpName overrides the element name. + {"OpName %1 \"time\" %1 = OpTypeFloat 32 %2 = OpTypeVector %1 2", 2, + "v2time"}, + }), ); + +INSTANTIATE_TEST_CASE_P( + MatrixType, FriendlyNameTest, + ::testing::ValuesIn(std::vector{ + {"%1 = OpTypeBool %2 = OpTypeVector %1 2 %3 = OpTypeMatrix %2 2", 3, + "mat2v2bool"}, + {"%1 = OpTypeFloat 32 %2 = OpTypeVector %1 2 %3 = OpTypeMatrix %2 3", 3, + "mat3v2float"}, + {"%1 = OpTypeFloat 32 %2 = OpTypeVector %1 2 %3 = OpTypeMatrix %2 4", 3, + "mat4v2float"}, + {"OpName %1 \"time\" %1 = OpTypeFloat 32 %2 = OpTypeVector %1 2 %3 = " + "OpTypeMatrix %2 4", + 3, "mat4v2time"}, + {"OpName %2 \"lat_long\" %1 = OpTypeFloat 32 %2 = OpTypeVector %1 2 %3 " + "= OpTypeMatrix %2 4", + 3, "mat4lat_long"}, + }), ); + +INSTANTIATE_TEST_CASE_P( + OpName, FriendlyNameTest, + ::testing::ValuesIn(std::vector{ + {"OpName %1 \"abcdefg\"", 1, "abcdefg"}, + {"OpName %1 \"Hello world!\"", 1, "Hello_world_"}, + {"OpName %1 \"0123456789\"", 1, "0123456789"}, + {"OpName %1 \"_\"", 1, "_"}, + // An empty string is not valid for SPIR-V assembly IDs. + {"OpName %1 \"\"", 1, "_"}, + // Test uniqueness when presented with things mapping to "_" + {"OpName %1 \"\" OpName %2 \"\"", 1, "_"}, + {"OpName %1 \"\" OpName %2 \"\"", 2, "__0"}, + {"OpName %1 \"\" OpName %2 \"\" OpName %3 \"_\"", 3, "__1"}, + // Test uniqueness of names that are forced to be + // numbers. + {"OpName %1 \"2\" OpName %2 \"2\"", 1, "2"}, + {"OpName %1 \"2\" OpName %2 \"2\"", 2, "2_0"}, + // Test uniqueness in the face of forward references + // for Ids that don't already have friendly names. + // In particular, the first OpDecorate assigns the name, and + // the second one can't override it. + {"OpDecorate %1 Volatile OpDecorate %1 Restrict", 1, "1"}, + // But a forced name can override the name that + // would have been assigned via the OpDecorate + // forward reference. + {"OpName %1 \"mememe\" OpDecorate %1 Volatile OpDecorate %1 Restrict", + 1, "mememe"}, + // OpName can override other inferences. We assume valid instruction + // ordering, where OpName precedes type definitions. + {"OpName %1 \"myfloat\" %1 = OpTypeFloat 32", 1, "myfloat"}, + }), ); + +INSTANTIATE_TEST_CASE_P( + UniquenessHeuristic, FriendlyNameTest, + ::testing::ValuesIn(std::vector{ + {"%1 = OpTypeVoid %2 = OpTypeVoid %3 = OpTypeVoid", 1, "void"}, + {"%1 = OpTypeVoid %2 = OpTypeVoid %3 = OpTypeVoid", 2, "void_0"}, + {"%1 = OpTypeVoid %2 = OpTypeVoid %3 = OpTypeVoid", 3, "void_1"}, + }), ); + +INSTANTIATE_TEST_CASE_P(Arrays, FriendlyNameTest, + ::testing::ValuesIn(std::vector{ + {"OpName %2 \"FortyTwo\" %1 = OpTypeFloat 32 " + "%2 = OpConstant %1 42 %3 = OpTypeArray %1 %2", + 3, "_arr_float_FortyTwo"}, + {"%1 = OpTypeInt 32 0 " + "%2 = OpTypeRuntimeArray %1", + 2, "_runtimearr_uint"}, + }), ); + +INSTANTIATE_TEST_CASE_P(Structs, FriendlyNameTest, + ::testing::ValuesIn(std::vector{ + {"%1 = OpTypeBool " + "%2 = OpTypeStruct %1 %1 %1", + 2, "_struct_2"}, + {"%1 = OpTypeBool " + "%2 = OpTypeStruct %1 %1 %1 " + "%3 = OpTypeStruct %2 %2", + 3, "_struct_3"}, + }), ); + +INSTANTIATE_TEST_CASE_P( + Pointer, FriendlyNameTest, + ::testing::ValuesIn(std::vector{ + {"%1 = OpTypeFloat 32 %2 = OpTypePointer Workgroup %1", 2, + "_ptr_Workgroup_float"}, + {"%1 = OpTypeBool %2 = OpTypePointer Private %1", 2, + "_ptr_Private_bool"}, + // OpTypeForwardPointer doesn't force generation of the name for its + // target type. + {"%1 = OpTypeBool OpTypeForwardPointer %2 Private %2 = OpTypePointer " + "Private %1", + 2, "_ptr_Private_bool"}, + }), ); + +INSTANTIATE_TEST_CASE_P(ExoticTypes, FriendlyNameTest, + ::testing::ValuesIn(std::vector{ + {"%1 = OpTypeEvent", 1, "Event"}, + {"%1 = OpTypeDeviceEvent", 1, "DeviceEvent"}, + {"%1 = OpTypeReserveId", 1, "ReserveId"}, + {"%1 = OpTypeQueue", 1, "Queue"}, + {"%1 = OpTypeOpaque \"hello world!\"", 1, + "Opaque_hello_world_"}, + {"%1 = OpTypePipe ReadOnly", 1, "PipeReadOnly"}, + {"%1 = OpTypePipe WriteOnly", 1, "PipeWriteOnly"}, + {"%1 = OpTypePipe ReadWrite", 1, "PipeReadWrite"}, + {"%1 = OpTypePipeStorage", 1, "PipeStorage"}, + {"%1 = OpTypeNamedBarrier", 1, "NamedBarrier"}, + }), ); + +// Makes a test case for a BuiltIn variable declaration. +NameIdCase BuiltInCase(std::string assembly_name, std::string expected) { + return NameIdCase{std::string("OpDecorate %1 BuiltIn ") + assembly_name + + " %1 = OpVariable %2 Input", + 1, expected}; +} + +// Makes a test case for a BuiltIn variable declaration. In this overload, +// the expected result is the same as the assembly name. +NameIdCase BuiltInCase(std::string assembly_name) { + return BuiltInCase(assembly_name, assembly_name); +} + +// Makes a test case for a BuiltIn variable declaration. In this overload, +// the expected result is the same as the assembly name, but with a "gl_" +// prefix. +NameIdCase BuiltInGLCase(std::string assembly_name) { + return BuiltInCase(assembly_name, std::string("gl_") + assembly_name); +} + +INSTANTIATE_TEST_CASE_P( + BuiltIns, FriendlyNameTest, + ::testing::ValuesIn(std::vector{ + BuiltInGLCase("Position"), + BuiltInGLCase("PointSize"), + BuiltInGLCase("ClipDistance"), + BuiltInGLCase("CullDistance"), + BuiltInCase("VertexId", "gl_VertexID"), + BuiltInCase("InstanceId", "gl_InstanceID"), + BuiltInCase("PrimitiveId", "gl_PrimitiveID"), + BuiltInCase("InvocationId", "gl_InvocationID"), + BuiltInGLCase("Layer"), + BuiltInGLCase("ViewportIndex"), + BuiltInGLCase("TessLevelOuter"), + BuiltInGLCase("TessLevelInner"), + BuiltInGLCase("TessCoord"), + BuiltInGLCase("PatchVertices"), + BuiltInGLCase("FragCoord"), + BuiltInGLCase("PointCoord"), + BuiltInGLCase("FrontFacing"), + BuiltInCase("SampleId", "gl_SampleID"), + BuiltInGLCase("SamplePosition"), + BuiltInGLCase("SampleMask"), + BuiltInGLCase("FragDepth"), + BuiltInGLCase("HelperInvocation"), + BuiltInCase("NumWorkgroups", "gl_NumWorkGroups"), + BuiltInCase("WorkgroupSize", "gl_WorkGroupSize"), + BuiltInCase("WorkgroupId", "gl_WorkGroupID"), + BuiltInCase("LocalInvocationId", "gl_LocalInvocationID"), + BuiltInCase("GlobalInvocationId", "gl_GlobalInvocationID"), + BuiltInGLCase("LocalInvocationIndex"), + BuiltInCase("WorkDim"), + BuiltInCase("GlobalSize"), + BuiltInCase("EnqueuedWorkgroupSize"), + BuiltInCase("GlobalOffset"), + BuiltInCase("GlobalLinearId"), + BuiltInCase("SubgroupSize"), + BuiltInCase("SubgroupMaxSize"), + BuiltInCase("NumSubgroups"), + BuiltInCase("NumEnqueuedSubgroups"), + BuiltInCase("SubgroupId"), + BuiltInCase("SubgroupLocalInvocationId"), + BuiltInGLCase("VertexIndex"), + BuiltInGLCase("InstanceIndex"), + BuiltInCase("SubgroupEqMaskKHR"), + BuiltInCase("SubgroupGeMaskKHR"), + BuiltInCase("SubgroupGtMaskKHR"), + BuiltInCase("SubgroupLeMaskKHR"), + BuiltInCase("SubgroupLtMaskKHR"), + }), ); + +INSTANTIATE_TEST_CASE_P(DebugNameOverridesBuiltin, FriendlyNameTest, + ::testing::ValuesIn(std::vector{ + {"OpName %1 \"foo\" OpDecorate %1 BuiltIn WorkDim " + "%1 = OpVariable %2 Input", + 1, "foo"}}), ); + +INSTANTIATE_TEST_CASE_P( + SimpleIntegralConstants, FriendlyNameTest, + ::testing::ValuesIn(std::vector{ + {"%1 = OpTypeInt 32 0 %2 = OpConstant %1 0", 2, "uint_0"}, + {"%1 = OpTypeInt 32 0 %2 = OpConstant %1 1", 2, "uint_1"}, + {"%1 = OpTypeInt 32 0 %2 = OpConstant %1 2", 2, "uint_2"}, + {"%1 = OpTypeInt 32 0 %2 = OpConstant %1 9", 2, "uint_9"}, + {"%1 = OpTypeInt 32 0 %2 = OpConstant %1 42", 2, "uint_42"}, + {"%1 = OpTypeInt 32 1 %2 = OpConstant %1 0", 2, "int_0"}, + {"%1 = OpTypeInt 32 1 %2 = OpConstant %1 1", 2, "int_1"}, + {"%1 = OpTypeInt 32 1 %2 = OpConstant %1 2", 2, "int_2"}, + {"%1 = OpTypeInt 32 1 %2 = OpConstant %1 9", 2, "int_9"}, + {"%1 = OpTypeInt 32 1 %2 = OpConstant %1 42", 2, "int_42"}, + {"%1 = OpTypeInt 32 1 %2 = OpConstant %1 -42", 2, "int_n42"}, + // Exotic bit widths + {"%1 = OpTypeInt 33 0 %2 = OpConstant %1 0", 2, "u33_0"}, + {"%1 = OpTypeInt 33 1 %2 = OpConstant %1 10", 2, "i33_10"}, + {"%1 = OpTypeInt 33 1 %2 = OpConstant %1 -19", 2, "i33_n19"}, + }), ); + +INSTANTIATE_TEST_CASE_P( + SimpleFloatConstants, FriendlyNameTest, + ::testing::ValuesIn(std::vector{ + {"%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1.ff4p+16", 2, + "half_0x1_ff4p_16"}, + {"%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1.d2cp-10", 2, + "half_n0x1_d2cpn10"}, + // 32-bit floats + {"%1 = OpTypeFloat 32\n%2 = OpConstant %1 -3.125", 2, "float_n3_125"}, + {"%1 = OpTypeFloat 32\n%2 = OpConstant %1 0x1.8p+128", 2, + "float_0x1_8p_128"}, // NaN + {"%1 = OpTypeFloat 32\n%2 = OpConstant %1 -0x1.0002p+128", 2, + "float_n0x1_0002p_128"}, // NaN + {"%1 = OpTypeFloat 32\n%2 = OpConstant %1 0x1p+128", 2, + "float_0x1p_128"}, // Inf + {"%1 = OpTypeFloat 32\n%2 = OpConstant %1 -0x1p+128", 2, + "float_n0x1p_128"}, // -Inf + // 64-bit floats + {"%1 = OpTypeFloat 64\n%2 = OpConstant %1 -3.125", 2, "double_n3_125"}, + {"%1 = OpTypeFloat 64\n%2 = OpConstant %1 0x1.ffffffffffffap-1023", 2, + "double_0x1_ffffffffffffapn1023"}, // small normal + {"%1 = OpTypeFloat 64\n%2 = OpConstant %1 -0x1.ffffffffffffap-1023", 2, + "double_n0x1_ffffffffffffapn1023"}, + {"%1 = OpTypeFloat 64\n%2 = OpConstant %1 0x1.8p+1024", 2, + "double_0x1_8p_1024"}, // NaN + {"%1 = OpTypeFloat 64\n%2 = OpConstant %1 -0x1.0002p+1024", 2, + "double_n0x1_0002p_1024"}, // NaN + {"%1 = OpTypeFloat 64\n%2 = OpConstant %1 0x1p+1024", 2, + "double_0x1p_1024"}, // Inf + {"%1 = OpTypeFloat 64\n%2 = OpConstant %1 -0x1p+1024", 2, + "double_n0x1p_1024"}, // -Inf + }), ); + +INSTANTIATE_TEST_CASE_P( + BooleanConstants, FriendlyNameTest, + ::testing::ValuesIn(std::vector{ + {"%1 = OpTypeBool\n%2 = OpConstantTrue %1", 2, "true"}, + {"%1 = OpTypeBool\n%2 = OpConstantFalse %1", 2, "false"}, + }), ); + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/named_id_test.cpp b/3rdparty/spirv-tools/test/named_id_test.cpp new file mode 100644 index 000000000..f83f5e864 --- /dev/null +++ b/3rdparty/spirv-tools/test/named_id_test.cpp @@ -0,0 +1,84 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "test_fixture.h" +#include "unit_spirv.h" + +namespace { + +using NamedIdTest = spvtest::TextToBinaryTest; + +TEST_F(NamedIdTest, Default) { + const std::string input = R"( + OpCapability Shader + OpMemoryModel Logical Simple + OpEntryPoint Vertex %main "foo" + %void = OpTypeVoid +%fnMain = OpTypeFunction %void + %main = OpFunction %void None %fnMain +%lbMain = OpLabel + OpReturn + OpFunctionEnd)"; + const std::string output = + "OpCapability Shader\n" + "OpMemoryModel Logical Simple\n" + "OpEntryPoint Vertex %1 \"foo\"\n" + "%2 = OpTypeVoid\n" + "%3 = OpTypeFunction %2\n" + "%1 = OpFunction %2 None %3\n" + "%4 = OpLabel\n" + "OpReturn\n" + "OpFunctionEnd\n"; + EXPECT_EQ(output, EncodeAndDecodeSuccessfully(input)); +} + +struct IdCheckCase { + std::string id; + bool valid; +}; + +using IdValidityTest = + spvtest::TextToBinaryTestBase<::testing::TestWithParam>; + +TEST_P(IdValidityTest, IdTypes) { + const std::string input = GetParam().id + " = OpTypeVoid"; + SetText(input); + if (GetParam().valid) { + CompileSuccessfully(input); + } else { + CompileFailure(input); + } +} + +INSTANTIATE_TEST_CASE_P( + ValidAndInvalidIds, IdValidityTest, + ::testing::ValuesIn(std::vector( + {{"%1", true}, {"%2abc", true}, {"%3Def", true}, + {"%4GHI", true}, {"%5_j_k", true}, {"%6J_M", true}, + {"%n", true}, {"%O", true}, {"%p7", true}, + {"%Q8", true}, {"%R_S", true}, {"%T_10_U", true}, + {"%V_11", true}, {"%W_X_13", true}, {"%_A", true}, + {"%_", true}, {"%__", true}, {"%A_", true}, + {"%_A_", true}, + + {"%@", false}, {"%!", false}, {"%ABC!", false}, + {"%__A__@", false}, {"%%", false}, {"%-", false}, + {"%foo_@_bar", false}, {"%", false}, + + {"5", false}, {"32", false}, {"foo", false}, + {"a%bar", false}})), ); + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opcode_make_test.cpp b/3rdparty/spirv-tools/test/opcode_make_test.cpp new file mode 100644 index 000000000..5353b7190 --- /dev/null +++ b/3rdparty/spirv-tools/test/opcode_make_test.cpp @@ -0,0 +1,42 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "unit_spirv.h" + +namespace { + +// A sampling of word counts. Covers extreme points well, and all bit +// positions, and some combinations of bit positions. +const uint16_t kSampleWordCounts[] = { + 0, 1, 2, 3, 4, 8, 16, 32, 64, 127, 128, + 256, 511, 512, 1024, 2048, 4096, 8192, 16384, 32768, 0xfffe, 0xffff}; + +// A sampling of opcode values. Covers the lower values well, a few samples +// around the number of core instructions (as of this writing), and some +// higher values. +const uint16_t kSampleOpcodes[] = {0, 1, 2, 3, 4, 100, + 300, 305, 1023, 0xfffe, 0xffff}; + +TEST(OpcodeMake, Samples) { + for (auto wordCount : kSampleWordCounts) { + for (auto opcode : kSampleOpcodes) { + uint32_t word = 0; + word |= uint32_t(opcode); + word |= uint32_t(wordCount) << 16; + EXPECT_EQ(word, spvOpcodeMake(wordCount, SpvOp(opcode))); + } + } +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opcode_require_capabilities_test.cpp b/3rdparty/spirv-tools/test/opcode_require_capabilities_test.cpp new file mode 100644 index 000000000..2aa1b86e9 --- /dev/null +++ b/3rdparty/spirv-tools/test/opcode_require_capabilities_test.cpp @@ -0,0 +1,77 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "unit_spirv.h" + +#include "enum_set.h" + +namespace { + +using libspirv::CapabilitySet; +using spvtest::ElementsIn; + +// Capabilities required by an Opcode. +struct ExpectedOpCodeCapabilities { + SpvOp opcode; + CapabilitySet capabilities; +}; + +using OpcodeTableCapabilitiesTest = + ::testing::TestWithParam; + +TEST_P(OpcodeTableCapabilitiesTest, TableEntryMatchesExpectedCapabilities) { + auto env = SPV_ENV_UNIVERSAL_1_1; + spv_opcode_table opcodeTable; + ASSERT_EQ(SPV_SUCCESS, spvOpcodeTableGet(&opcodeTable, env)); + spv_opcode_desc entry; + ASSERT_EQ(SPV_SUCCESS, spvOpcodeTableValueLookup(env, opcodeTable, + GetParam().opcode, &entry)); + EXPECT_EQ( + ElementsIn(GetParam().capabilities), + ElementsIn(CapabilitySet(entry->numCapabilities, entry->capabilities))); +} + +INSTANTIATE_TEST_CASE_P( + TableRowTest, OpcodeTableCapabilitiesTest, + // Spot-check a few opcodes. + ::testing::Values( + ExpectedOpCodeCapabilities{ + SpvOpImageQuerySize, + CapabilitySet{SpvCapabilityKernel, SpvCapabilityImageQuery}}, + ExpectedOpCodeCapabilities{ + SpvOpImageQuerySizeLod, + CapabilitySet{SpvCapabilityKernel, SpvCapabilityImageQuery}}, + ExpectedOpCodeCapabilities{ + SpvOpImageQueryLevels, + CapabilitySet{SpvCapabilityKernel, SpvCapabilityImageQuery}}, + ExpectedOpCodeCapabilities{ + SpvOpImageQuerySamples, + CapabilitySet{SpvCapabilityKernel, SpvCapabilityImageQuery}}, + ExpectedOpCodeCapabilities{SpvOpImageSparseSampleImplicitLod, + CapabilitySet{SpvCapabilitySparseResidency}}, + ExpectedOpCodeCapabilities{SpvOpCopyMemorySized, + CapabilitySet{SpvCapabilityAddresses}}, + ExpectedOpCodeCapabilities{SpvOpArrayLength, + CapabilitySet{SpvCapabilityShader}}, + ExpectedOpCodeCapabilities{SpvOpFunction, CapabilitySet()}, + ExpectedOpCodeCapabilities{SpvOpConvertFToS, CapabilitySet()}, + ExpectedOpCodeCapabilities{SpvOpEmitStreamVertex, + CapabilitySet{SpvCapabilityGeometryStreams}}, + ExpectedOpCodeCapabilities{SpvOpTypeNamedBarrier, + CapabilitySet{SpvCapabilityNamedBarrier}}, + ExpectedOpCodeCapabilities{ + SpvOpGetKernelMaxNumSubgroups, + CapabilitySet{SpvCapabilitySubgroupDispatch}}), ); + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opcode_split_test.cpp b/3rdparty/spirv-tools/test/opcode_split_test.cpp new file mode 100644 index 000000000..f42d903ae --- /dev/null +++ b/3rdparty/spirv-tools/test/opcode_split_test.cpp @@ -0,0 +1,28 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "unit_spirv.h" + +namespace { + +TEST(OpcodeSplit, Default) { + uint32_t word = spvOpcodeMake(42, (SpvOp)23); + uint16_t wordCount = 0; + uint16_t opcode; + spvOpcodeSplit(word, &wordCount, &opcode); + ASSERT_EQ(42, wordCount); + ASSERT_EQ(23, opcode); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opcode_table_get_test.cpp b/3rdparty/spirv-tools/test/opcode_table_get_test.cpp new file mode 100644 index 000000000..f2272f076 --- /dev/null +++ b/3rdparty/spirv-tools/test/opcode_table_get_test.cpp @@ -0,0 +1,38 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "unit_spirv.h" + +namespace { + +using GetTargetOpcodeTableGetTest = ::testing::TestWithParam; +using ::testing::ValuesIn; + +TEST_P(GetTargetOpcodeTableGetTest, SanityCheck) { + spv_opcode_table table; + ASSERT_EQ(SPV_SUCCESS, spvOpcodeTableGet(&table, GetParam())); + ASSERT_NE(0u, table->count); + ASSERT_NE(nullptr, table->entries); +} + +TEST_P(GetTargetOpcodeTableGetTest, InvalidPointerTable) { + ASSERT_EQ(SPV_ERROR_INVALID_POINTER, spvOpcodeTableGet(nullptr, GetParam())); +} + +INSTANTIATE_TEST_CASE_P(OpcodeTableGet, GetTargetOpcodeTableGetTest, + ValuesIn(spvtest::AllTargetEnvironments())); + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/operand-class-test-coverage.csv b/3rdparty/spirv-tools/test/operand-class-test-coverage.csv new file mode 100644 index 000000000..116fdfeee --- /dev/null +++ b/3rdparty/spirv-tools/test/operand-class-test-coverage.csv @@ -0,0 +1,43 @@ +Operand class,Example instruction,Notes,example unit test,negative-enum coverage location +" OperandNone,",UNUSED,not in grammar,,not enum +" OperandId,",many,ID,too many to count,not enum +" OperandOptionalId,","Source, Variable",OPTIONAL_ID,OpSourceAcceptsOptionalFileId,not enum +" OperandOptionalImage,",ImageFetch,,ImageOperandsTest,"TEST_F(ImageOperandsTest, WrongOperand)" +" OperandVariableIds,",ExtInst,,,not enum +" OperandOptionalLiteral,",ExecutionMode,,AnyExecutionMode,not enum +" OperandOptionalLiteralString,",Source,,OpSourceAcceptsOptionalSourceText,not enum +" OperandVariableLiterals,",Decorate,,OpDecorateSimpleTest,not enum +" OperandVariableIdLiteral,",GroupMemberDecorate,,GroupMemberDecorate*,not enum +" OperandVariableLiteralId,",Switch,,Switch*,not enum +" OperandLiteralNumber,","Source, Switch, ...",,Switch*,not enum +" OperandLiteralString,",SourceContinued,,OpSourceContinued,not enum +" OperandSource,",Source,,OpSource,not enum +" OperandExecutionModel,",EntryPoint,,OpEntryPointTest,"TEST_F(OpEntryPointTest, WrongModel)" +" OperandAddressing,",OpMemoryModel,,OpMemoryModelTest,"TEST_F(OpMemoryModelTest, WrongModel)" +" OperandMemory,",OpMemoryModel,,OpMemoryModelTest,"TEST_F(OpMemoryModelTest, WrongModel)" +" OperandExecutionMode,",OpExecutionMode,,OpExecutionModeTest,"TEST_F(OpExecutionModeTest, WrongMode)" +" OperandStorage,","TypePointer, TypeForwardPointer, Variable",,StorageClassTest,"TEST_F(OpTypeForwardPointerTest, WrongClass)" +" OperandDimensionality,",TypeImage,,DimTest/AnyDim,"TEST_F(DimTest, WrongDim)" +" OperandSamplerAddressingMode,",ConstantSampler,,SamplerAddressingModeTest,"TEST_F(SamplerAddressingModeTest, WrongMode)" +" OperandSamplerFilterMode,",ConstantSampler,,AnySamplerFilterMode,"TEST_F(SamplerFilterModeTest, WrongMode)" +" OperandSamplerImageFormat,",TypeImage,SAMPLER_IMAGE_FORMAT,ImageFormatTest,"TEST_F(ImageFormatTest, WrongFormat)" +" OperandImageChannelOrder,",UNUSED,returned as result value only,, +" OperandImageChannelDataType,",UNUSED,returned as result value only,, +" OperandImageOperands,",UNUSED,used to make a spec section,,see OperandOptionalImage +" OperandFPFastMath,",OpDecorate,,CombinedFPFastMathMask,"TEST_F(OpDecorateEnumTest, WrongFPFastMathMode)" +" OperandFPRoundingMode,",OpDecorate,,,"TEST_F(OpDecorateEnumTest, WrongFPRoundingMode)" +" OperandLinkageType,",OpDecorate,,OpDecorateLinkageTest,"TEST_F(OpDecorateLinkageTest, WrongType)" +" OperandAccessQualifier,",OpTypePipe,,AnyAccessQualifier,"TEST_F(OpTypePipeTest, WrongAccessQualifier)" +" OperandFuncParamAttr,",OpDecorate,,TextToBinaryDecorateFuncParamAttr,"TEST_F(OpDecorateEnumTest, WrongFuncParamAttr)" +" OperandDecoration,",OpDecorate,,AnyAccessQualifier,"TEST_F(OpTypePipeTest, WrongAccessQualifier)" +" OperandBuiltIn,",OpDecorate,,TextToBinaryDecorateBultIn,"TEST_F(OpDecorateEnumTest, WrongBuiltIn)" +" OperandSelect,",SelectionMerge,,TextToBinarySelectionMerge,"TEST_F(OpSelectionMergeTest, WrongSelectionControl)" +" OperandLoop,",LoopMerge,,CombinedLoopControlMask,"TEST_F(OpLoopMergeTest, WrongLoopControl)" +" OperandFunction,",Function,,AnySingleFunctionControlMask,"TEST_F(OpFunctionControlTest, WrongFunctionControl)" +" OperandMemorySemantics,",OpMemoryBarrier,"it's an ID, not in grammar",OpMemoryBarrier*,not enum +" OperandMemoryAccess,",UNUSED,"should be on opstore, but hacked in opcode.cpp",,not enum +" OperandScope,",MemoryBarrier,"it's an ID, not in grammar",OpMemoryBarrier*,not enum +" OperandGroupOperation,",GroupIAdd,,GroupOperationTest,"TEST_F(GroupOperationTest, WrongGroupOperation)" +" OperandKernelEnqueueFlags,",OpEnqueueKernel,"it's an ID, not in grammar",should not have one,not enum +" OperandKernelProfilingInfo,",OpCaptureEventProfilingInfo,"it's an ID, not in grammar",should not have one,not enum +" OperandCapability,",Capability,,OpCapabilityTest,"TEST_F(TextToBinaryCapability, BadInvalidCapability)" diff --git a/3rdparty/spirv-tools/test/operand_capabilities_test.cpp b/3rdparty/spirv-tools/test/operand_capabilities_test.cpp new file mode 100644 index 000000000..ddc60e267 --- /dev/null +++ b/3rdparty/spirv-tools/test/operand_capabilities_test.cpp @@ -0,0 +1,723 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Test capability dependencies for enums. + +#include + +#include "gmock/gmock.h" + +#include "enum_set.h" +#include "unit_spirv.h" + +namespace { + +using libspirv::CapabilitySet; +using spvtest::ElementsIn; +using std::get; +using std::tuple; +using ::testing::Combine; +using ::testing::Eq; +using ::testing::TestWithParam; +using ::testing::Values; +using ::testing::ValuesIn; + +// A test case for mapping an enum to a capability mask. +struct EnumCapabilityCase { + spv_operand_type_t type; + uint32_t value; + CapabilitySet expected_capabilities; +}; + +// Test fixture for testing EnumCapabilityCases. +using EnumCapabilityTest = + TestWithParam>; + +TEST_P(EnumCapabilityTest, Sample) { + const auto env = get<0>(GetParam()); + const auto context = spvContextCreate(env); + const libspirv::AssemblyGrammar grammar(context); + spv_operand_desc entry; + + ASSERT_EQ(SPV_SUCCESS, + grammar.lookupOperand(get<1>(GetParam()).type, + get<1>(GetParam()).value, &entry)); + const auto cap_set = grammar.filterCapsAgainstTargetEnv( + entry->capabilities, entry->numCapabilities); + + EXPECT_THAT(ElementsIn(cap_set), + Eq(ElementsIn(get<1>(GetParam()).expected_capabilities))) + << " capability value " << get<1>(GetParam()).value; +} + +#define CASE0(TYPE, VALUE) \ + { \ + SPV_OPERAND_TYPE_##TYPE, uint32_t(Spv##VALUE), {} \ + } +#define CASE1(TYPE, VALUE, CAP) \ + { \ + SPV_OPERAND_TYPE_##TYPE, uint32_t(Spv##VALUE), CapabilitySet { \ + SpvCapability##CAP \ + } \ + } +#define CASE2(TYPE, VALUE, CAP1, CAP2) \ + { \ + SPV_OPERAND_TYPE_##TYPE, uint32_t(Spv##VALUE), CapabilitySet { \ + SpvCapability##CAP1, SpvCapability##CAP2 \ + } \ + } +#define CASE5(TYPE, VALUE, CAP1, CAP2, CAP3, CAP4, CAP5) \ + { \ + SPV_OPERAND_TYPE_##TYPE, uint32_t(Spv##VALUE), CapabilitySet { \ + SpvCapability##CAP1, SpvCapability##CAP2, SpvCapability##CAP3, \ + SpvCapability##CAP4, SpvCapability##CAP5 \ + } \ + } + +// See SPIR-V Section 3.3 Execution Model +INSTANTIATE_TEST_CASE_P( + ExecutionModel, EnumCapabilityTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + CASE1(EXECUTION_MODEL, ExecutionModelVertex, Shader), + CASE1(EXECUTION_MODEL, ExecutionModelTessellationControl, + Tessellation), + CASE1(EXECUTION_MODEL, ExecutionModelTessellationEvaluation, + Tessellation), + CASE1(EXECUTION_MODEL, ExecutionModelGeometry, Geometry), + CASE1(EXECUTION_MODEL, ExecutionModelFragment, Shader), + CASE1(EXECUTION_MODEL, ExecutionModelGLCompute, Shader), + CASE1(EXECUTION_MODEL, ExecutionModelKernel, Kernel), + })), ); + +// See SPIR-V Section 3.4 Addressing Model +INSTANTIATE_TEST_CASE_P( + AddressingModel, EnumCapabilityTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + CASE0(ADDRESSING_MODEL, AddressingModelLogical), + CASE1(ADDRESSING_MODEL, AddressingModelPhysical32, Addresses), + CASE1(ADDRESSING_MODEL, AddressingModelPhysical64, Addresses), + })), ); + +// See SPIR-V Section 3.5 Memory Model +INSTANTIATE_TEST_CASE_P( + MemoryModel, EnumCapabilityTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + CASE1(MEMORY_MODEL, MemoryModelSimple, Shader), + CASE1(MEMORY_MODEL, MemoryModelGLSL450, Shader), + CASE1(MEMORY_MODEL, MemoryModelOpenCL, Kernel), + })), ); + +// See SPIR-V Section 3.6 Execution Mode +INSTANTIATE_TEST_CASE_P( + ExecutionMode, EnumCapabilityTest, + Combine( + Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + CASE1(EXECUTION_MODE, ExecutionModeInvocations, Geometry), + CASE1(EXECUTION_MODE, ExecutionModeSpacingEqual, Tessellation), + CASE1(EXECUTION_MODE, ExecutionModeSpacingFractionalEven, + Tessellation), + CASE1(EXECUTION_MODE, ExecutionModeSpacingFractionalOdd, + Tessellation), + CASE1(EXECUTION_MODE, ExecutionModeVertexOrderCw, Tessellation), + CASE1(EXECUTION_MODE, ExecutionModeVertexOrderCcw, Tessellation), + CASE1(EXECUTION_MODE, ExecutionModePixelCenterInteger, Shader), + CASE1(EXECUTION_MODE, ExecutionModeOriginUpperLeft, Shader), + CASE1(EXECUTION_MODE, ExecutionModeOriginLowerLeft, Shader), + CASE1(EXECUTION_MODE, ExecutionModeEarlyFragmentTests, Shader), + CASE1(EXECUTION_MODE, ExecutionModePointMode, Tessellation), + CASE1(EXECUTION_MODE, ExecutionModeXfb, TransformFeedback), + CASE1(EXECUTION_MODE, ExecutionModeDepthReplacing, Shader), + CASE1(EXECUTION_MODE, ExecutionModeDepthGreater, Shader), + CASE1(EXECUTION_MODE, ExecutionModeDepthLess, Shader), + CASE1(EXECUTION_MODE, ExecutionModeDepthUnchanged, Shader), + CASE0(EXECUTION_MODE, ExecutionModeLocalSize), + CASE1(EXECUTION_MODE, ExecutionModeLocalSizeHint, Kernel), + CASE1(EXECUTION_MODE, ExecutionModeInputPoints, Geometry), + CASE1(EXECUTION_MODE, ExecutionModeInputLines, Geometry), + CASE1(EXECUTION_MODE, ExecutionModeInputLinesAdjacency, Geometry), + CASE2(EXECUTION_MODE, ExecutionModeTriangles, Geometry, + Tessellation), + CASE1(EXECUTION_MODE, ExecutionModeInputTrianglesAdjacency, + Geometry), + CASE1(EXECUTION_MODE, ExecutionModeQuads, Tessellation), + CASE1(EXECUTION_MODE, ExecutionModeIsolines, Tessellation), + CASE2(EXECUTION_MODE, ExecutionModeOutputVertices, Geometry, + Tessellation), + CASE1(EXECUTION_MODE, ExecutionModeOutputPoints, Geometry), + CASE1(EXECUTION_MODE, ExecutionModeOutputLineStrip, Geometry), + CASE1(EXECUTION_MODE, ExecutionModeOutputTriangleStrip, Geometry), + CASE1(EXECUTION_MODE, ExecutionModeVecTypeHint, Kernel), + CASE1(EXECUTION_MODE, ExecutionModeContractionOff, Kernel), + })), ); + +INSTANTIATE_TEST_CASE_P( + ExecutionModeV11, EnumCapabilityTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + CASE1(EXECUTION_MODE, ExecutionModeInitializer, Kernel), + CASE1(EXECUTION_MODE, ExecutionModeFinalizer, Kernel), + CASE1(EXECUTION_MODE, ExecutionModeSubgroupSize, + SubgroupDispatch), + CASE1(EXECUTION_MODE, ExecutionModeSubgroupsPerWorkgroup, + SubgroupDispatch)})), ); + +// See SPIR-V Section 3.7 Storage Class +INSTANTIATE_TEST_CASE_P( + StorageClass, EnumCapabilityTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + CASE0(STORAGE_CLASS, StorageClassUniformConstant), + CASE1(STORAGE_CLASS, StorageClassUniform, Shader), + CASE1(STORAGE_CLASS, StorageClassOutput, Shader), + CASE0(STORAGE_CLASS, StorageClassWorkgroup), + CASE0(STORAGE_CLASS, StorageClassCrossWorkgroup), + CASE1(STORAGE_CLASS, StorageClassPrivate, Shader), + CASE0(STORAGE_CLASS, StorageClassFunction), + CASE1(STORAGE_CLASS, StorageClassGeneric, + GenericPointer), // Bug 14287 + CASE1(STORAGE_CLASS, StorageClassPushConstant, Shader), + CASE1(STORAGE_CLASS, StorageClassAtomicCounter, AtomicStorage), + CASE0(STORAGE_CLASS, StorageClassImage), + })), ); + +// See SPIR-V Section 3.8 Dim +INSTANTIATE_TEST_CASE_P( + Dim, EnumCapabilityTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + CASE1(DIMENSIONALITY, Dim1D, Sampled1D), + CASE0(DIMENSIONALITY, Dim2D), + CASE0(DIMENSIONALITY, Dim3D), + CASE1(DIMENSIONALITY, DimCube, Shader), + CASE1(DIMENSIONALITY, DimRect, SampledRect), + CASE1(DIMENSIONALITY, DimBuffer, SampledBuffer), + CASE1(DIMENSIONALITY, DimSubpassData, InputAttachment), + })), ); + +// See SPIR-V Section 3.9 Sampler Addressing Mode +INSTANTIATE_TEST_CASE_P( + SamplerAddressingMode, EnumCapabilityTest, + Combine( + Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + CASE1(SAMPLER_ADDRESSING_MODE, SamplerAddressingModeNone, Kernel), + CASE1(SAMPLER_ADDRESSING_MODE, SamplerAddressingModeClampToEdge, + Kernel), + CASE1(SAMPLER_ADDRESSING_MODE, SamplerAddressingModeClamp, Kernel), + CASE1(SAMPLER_ADDRESSING_MODE, SamplerAddressingModeRepeat, Kernel), + CASE1(SAMPLER_ADDRESSING_MODE, SamplerAddressingModeRepeatMirrored, + Kernel), + })), ); + +// See SPIR-V Section 3.10 Sampler Filter Mode +INSTANTIATE_TEST_CASE_P( + SamplerFilterMode, EnumCapabilityTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + CASE1(SAMPLER_FILTER_MODE, SamplerFilterModeNearest, Kernel), + CASE1(SAMPLER_FILTER_MODE, SamplerFilterModeLinear, Kernel), + })), ); + +// See SPIR-V Section 3.11 Image Format +INSTANTIATE_TEST_CASE_P( + ImageFormat, EnumCapabilityTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + // clang-format off + CASE0(SAMPLER_IMAGE_FORMAT, ImageFormatUnknown), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatRgba32f, Shader), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatRgba16f, Shader), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatR32f, Shader), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatRgba8, Shader), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatRgba8Snorm, Shader), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatRg32f, StorageImageExtendedFormats), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatRg16f, StorageImageExtendedFormats), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatR11fG11fB10f, StorageImageExtendedFormats), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatR16f, StorageImageExtendedFormats), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatRgba16, StorageImageExtendedFormats), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatRgb10A2, StorageImageExtendedFormats), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatRg16, StorageImageExtendedFormats), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatRg8, StorageImageExtendedFormats), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatR16, StorageImageExtendedFormats), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatR8, StorageImageExtendedFormats), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatRgba16Snorm, StorageImageExtendedFormats), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatRg16Snorm, StorageImageExtendedFormats), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatRg8Snorm, StorageImageExtendedFormats), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatR16Snorm, StorageImageExtendedFormats), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatR8Snorm, StorageImageExtendedFormats), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatRgba32i, Shader), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatRgba16i, Shader), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatRgba8i, Shader), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatR32i, Shader), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatRg32i, StorageImageExtendedFormats), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatRg16i, StorageImageExtendedFormats), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatRg8i, StorageImageExtendedFormats), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatR16i, StorageImageExtendedFormats), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatR8i, StorageImageExtendedFormats), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatRgba32ui, Shader), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatRgba16ui, Shader), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatRgba8ui, Shader), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatRgba8ui, Shader), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatRgb10a2ui, StorageImageExtendedFormats), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatRg32ui, StorageImageExtendedFormats), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatRg16ui, StorageImageExtendedFormats), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatRg8ui, StorageImageExtendedFormats), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatR16ui, StorageImageExtendedFormats), + CASE1(SAMPLER_IMAGE_FORMAT, ImageFormatR8ui, StorageImageExtendedFormats), + // clang-format on + })), ); + +// See SPIR-V Section 3.12 Image Channel Order +INSTANTIATE_TEST_CASE_P( + ImageChannelOrder, EnumCapabilityTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + CASE1(IMAGE_CHANNEL_ORDER, ImageChannelOrderR, Kernel), + CASE1(IMAGE_CHANNEL_ORDER, ImageChannelOrderA, Kernel), + CASE1(IMAGE_CHANNEL_ORDER, ImageChannelOrderRG, Kernel), + CASE1(IMAGE_CHANNEL_ORDER, ImageChannelOrderRA, Kernel), + CASE1(IMAGE_CHANNEL_ORDER, ImageChannelOrderRGB, Kernel), + CASE1(IMAGE_CHANNEL_ORDER, ImageChannelOrderRGBA, Kernel), + CASE1(IMAGE_CHANNEL_ORDER, ImageChannelOrderBGRA, Kernel), + CASE1(IMAGE_CHANNEL_ORDER, ImageChannelOrderARGB, Kernel), + CASE1(IMAGE_CHANNEL_ORDER, ImageChannelOrderIntensity, Kernel), + CASE1(IMAGE_CHANNEL_ORDER, ImageChannelOrderLuminance, Kernel), + CASE1(IMAGE_CHANNEL_ORDER, ImageChannelOrderRx, Kernel), + CASE1(IMAGE_CHANNEL_ORDER, ImageChannelOrderRGx, Kernel), + CASE1(IMAGE_CHANNEL_ORDER, ImageChannelOrderRGBx, Kernel), + CASE1(IMAGE_CHANNEL_ORDER, ImageChannelOrderDepth, Kernel), + CASE1(IMAGE_CHANNEL_ORDER, ImageChannelOrderDepthStencil, + Kernel), + CASE1(IMAGE_CHANNEL_ORDER, ImageChannelOrdersRGB, Kernel), + CASE1(IMAGE_CHANNEL_ORDER, ImageChannelOrdersRGBx, Kernel), + CASE1(IMAGE_CHANNEL_ORDER, ImageChannelOrdersRGBA, Kernel), + CASE1(IMAGE_CHANNEL_ORDER, ImageChannelOrdersBGRA, Kernel), + CASE1(IMAGE_CHANNEL_ORDER, ImageChannelOrderABGR, Kernel), + })), ); + +// See SPIR-V Section 3.13 Image Channel Data Type +INSTANTIATE_TEST_CASE_P( + ImageChannelDataType, EnumCapabilityTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + // clang-format off + CASE1(IMAGE_CHANNEL_DATA_TYPE, ImageChannelDataTypeSnormInt8, Kernel), + CASE1(IMAGE_CHANNEL_DATA_TYPE, ImageChannelDataTypeSnormInt16, Kernel), + CASE1(IMAGE_CHANNEL_DATA_TYPE, ImageChannelDataTypeUnormInt8, Kernel), + CASE1(IMAGE_CHANNEL_DATA_TYPE, ImageChannelDataTypeUnormInt16, Kernel), + CASE1(IMAGE_CHANNEL_DATA_TYPE, ImageChannelDataTypeUnormShort565, Kernel), + CASE1(IMAGE_CHANNEL_DATA_TYPE, ImageChannelDataTypeUnormShort555, Kernel), + CASE1(IMAGE_CHANNEL_DATA_TYPE, ImageChannelDataTypeUnormInt101010, Kernel), + CASE1(IMAGE_CHANNEL_DATA_TYPE, ImageChannelDataTypeSignedInt8, Kernel), + CASE1(IMAGE_CHANNEL_DATA_TYPE, ImageChannelDataTypeSignedInt16, Kernel), + CASE1(IMAGE_CHANNEL_DATA_TYPE, ImageChannelDataTypeSignedInt32, Kernel), + CASE1(IMAGE_CHANNEL_DATA_TYPE, ImageChannelDataTypeUnsignedInt8, Kernel), + CASE1(IMAGE_CHANNEL_DATA_TYPE, ImageChannelDataTypeUnsignedInt16, Kernel), + CASE1(IMAGE_CHANNEL_DATA_TYPE, ImageChannelDataTypeUnsignedInt32, Kernel), + CASE1(IMAGE_CHANNEL_DATA_TYPE, ImageChannelDataTypeHalfFloat, Kernel), + CASE1(IMAGE_CHANNEL_DATA_TYPE, ImageChannelDataTypeFloat, Kernel), + CASE1(IMAGE_CHANNEL_DATA_TYPE, ImageChannelDataTypeUnormInt24, Kernel), + CASE1(IMAGE_CHANNEL_DATA_TYPE, ImageChannelDataTypeUnormInt101010_2, Kernel), + // clang-format on + })), ); + +// See SPIR-V Section 3.14 Image Operands +INSTANTIATE_TEST_CASE_P( + ImageOperands, EnumCapabilityTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + // clang-format off + CASE0(OPTIONAL_IMAGE, ImageOperandsMaskNone), + CASE1(OPTIONAL_IMAGE, ImageOperandsBiasMask, Shader), + CASE0(OPTIONAL_IMAGE, ImageOperandsLodMask), + CASE0(OPTIONAL_IMAGE, ImageOperandsGradMask), + CASE0(OPTIONAL_IMAGE, ImageOperandsConstOffsetMask), + CASE1(OPTIONAL_IMAGE, ImageOperandsOffsetMask, ImageGatherExtended), + CASE0(OPTIONAL_IMAGE, ImageOperandsConstOffsetsMask), + CASE0(OPTIONAL_IMAGE, ImageOperandsSampleMask), + CASE1(OPTIONAL_IMAGE, ImageOperandsMinLodMask, MinLod), + // clang-format on + })), ); + +// See SPIR-V Section 3.15 FP Fast Math Mode +INSTANTIATE_TEST_CASE_P( + FPFastMathMode, EnumCapabilityTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + CASE0(FP_FAST_MATH_MODE, FPFastMathModeMaskNone), + CASE1(FP_FAST_MATH_MODE, FPFastMathModeNotNaNMask, Kernel), + CASE1(FP_FAST_MATH_MODE, FPFastMathModeNotInfMask, Kernel), + CASE1(FP_FAST_MATH_MODE, FPFastMathModeNSZMask, Kernel), + CASE1(FP_FAST_MATH_MODE, FPFastMathModeAllowRecipMask, Kernel), + CASE1(FP_FAST_MATH_MODE, FPFastMathModeFastMask, Kernel), + })), ); + +// See SPIR-V Section 3.17 Linkage Type +INSTANTIATE_TEST_CASE_P( + LinkageType, EnumCapabilityTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + CASE1(LINKAGE_TYPE, LinkageTypeExport, Linkage), + CASE1(LINKAGE_TYPE, LinkageTypeImport, Linkage), + })), ); + +// See SPIR-V Section 3.18 Access Qualifier +INSTANTIATE_TEST_CASE_P( + AccessQualifier, EnumCapabilityTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + CASE1(ACCESS_QUALIFIER, AccessQualifierReadOnly, Kernel), + CASE1(ACCESS_QUALIFIER, AccessQualifierWriteOnly, Kernel), + CASE1(ACCESS_QUALIFIER, AccessQualifierReadWrite, Kernel), + })), ); + +// See SPIR-V Section 3.19 Function Parameter Attribute +INSTANTIATE_TEST_CASE_P( + FunctionParameterAttribute, EnumCapabilityTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + // clang-format off + CASE1(FUNCTION_PARAMETER_ATTRIBUTE, FunctionParameterAttributeZext, Kernel), + CASE1(FUNCTION_PARAMETER_ATTRIBUTE, FunctionParameterAttributeSext, Kernel), + CASE1(FUNCTION_PARAMETER_ATTRIBUTE, FunctionParameterAttributeByVal, Kernel), + CASE1(FUNCTION_PARAMETER_ATTRIBUTE, FunctionParameterAttributeSret, Kernel), + CASE1(FUNCTION_PARAMETER_ATTRIBUTE, FunctionParameterAttributeNoAlias, Kernel), + CASE1(FUNCTION_PARAMETER_ATTRIBUTE, FunctionParameterAttributeNoCapture, Kernel), + CASE1(FUNCTION_PARAMETER_ATTRIBUTE, FunctionParameterAttributeNoWrite, Kernel), + CASE1(FUNCTION_PARAMETER_ATTRIBUTE, FunctionParameterAttributeNoReadWrite, Kernel), + // clang-format on + })), ); + +// See SPIR-V Section 3.20 Decoration +INSTANTIATE_TEST_CASE_P( + Decoration, EnumCapabilityTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + CASE1(DECORATION, DecorationRelaxedPrecision, Shader), + // DecorationSpecId handled below. + CASE1(DECORATION, DecorationBlock, Shader), + CASE1(DECORATION, DecorationBufferBlock, Shader), + CASE1(DECORATION, DecorationRowMajor, Matrix), + CASE1(DECORATION, DecorationColMajor, Matrix), + CASE1(DECORATION, DecorationArrayStride, Shader), + CASE1(DECORATION, DecorationMatrixStride, Matrix), // Bug 15234 + CASE1(DECORATION, DecorationGLSLShared, Shader), + CASE1(DECORATION, DecorationGLSLPacked, Shader), + CASE1(DECORATION, DecorationCPacked, Kernel), + CASE0(DECORATION, DecorationBuiltIn), // Bug 15248 + // Value 12 placeholder + CASE1(DECORATION, DecorationNoPerspective, Shader), + CASE1(DECORATION, DecorationFlat, Shader), + CASE1(DECORATION, DecorationPatch, Tessellation), + CASE1(DECORATION, DecorationCentroid, Shader), + CASE1(DECORATION, DecorationSample, + SampleRateShading), // Bug 15234 + CASE1(DECORATION, DecorationInvariant, Shader), + CASE0(DECORATION, DecorationRestrict), + CASE0(DECORATION, DecorationAliased), + CASE0(DECORATION, DecorationVolatile), + CASE1(DECORATION, DecorationConstant, Kernel), + CASE0(DECORATION, DecorationCoherent), + CASE0(DECORATION, DecorationNonWritable), + CASE0(DECORATION, DecorationNonReadable), + CASE1(DECORATION, DecorationUniform, Shader), + // Value 27 is an intentional gap in the spec numbering. + CASE1(DECORATION, DecorationSaturatedConversion, Kernel), + CASE1(DECORATION, DecorationStream, GeometryStreams), + CASE1(DECORATION, DecorationLocation, Shader), + CASE1(DECORATION, DecorationComponent, Shader), + CASE1(DECORATION, DecorationIndex, Shader), + CASE1(DECORATION, DecorationBinding, Shader), + CASE1(DECORATION, DecorationDescriptorSet, Shader), + CASE1(DECORATION, DecorationOffset, Shader), // Bug 15268 + CASE1(DECORATION, DecorationXfbBuffer, TransformFeedback), + CASE1(DECORATION, DecorationXfbStride, TransformFeedback), + CASE1(DECORATION, DecorationFuncParamAttr, Kernel), + CASE1(DECORATION, DecorationFPFastMathMode, Kernel), + CASE1(DECORATION, DecorationLinkageAttributes, Linkage), + CASE1(DECORATION, DecorationNoContraction, Shader), + CASE1(DECORATION, DecorationInputAttachmentIndex, + InputAttachment), + CASE1(DECORATION, DecorationAlignment, Kernel), + })), ); + +#if 0 +// SpecId has different requirements in v1.0 and v1.1: +INSTANTIATE_TEST_CASE_P(DecorationSpecIdV10, EnumCapabilityTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_0), + ValuesIn(std::vector{CASE1( + DECORATION, DecorationSpecId, Shader)})), ); +#endif + +INSTANTIATE_TEST_CASE_P( + DecorationV11, EnumCapabilityTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + CASE2(DECORATION, DecorationSpecId, Shader, Kernel), + CASE1(DECORATION, DecorationMaxByteOffset, Addresses)})), ); + +// See SPIR-V Section 3.21 BuiltIn +INSTANTIATE_TEST_CASE_P( + BuiltIn, EnumCapabilityTest, + Combine( + Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + // clang-format off + CASE1(BUILT_IN, BuiltInPosition, Shader), + CASE1(BUILT_IN, BuiltInPointSize, Shader), + // 2 is an intentional gap in the spec numbering. + CASE1(BUILT_IN, BuiltInClipDistance, ClipDistance), // Bug 1407, 15234 + CASE1(BUILT_IN, BuiltInCullDistance, CullDistance), // Bug 1407, 15234 + CASE1(BUILT_IN, BuiltInVertexId, Shader), + CASE1(BUILT_IN, BuiltInInstanceId, Shader), + CASE2(BUILT_IN, BuiltInPrimitiveId, Geometry, Tessellation), + CASE2(BUILT_IN, BuiltInInvocationId, Geometry, Tessellation), + CASE1(BUILT_IN, BuiltInLayer, Geometry), + CASE1(BUILT_IN, BuiltInViewportIndex, MultiViewport), // Bug 15234 + CASE1(BUILT_IN, BuiltInTessLevelOuter, Tessellation), + CASE1(BUILT_IN, BuiltInTessLevelInner, Tessellation), + CASE1(BUILT_IN, BuiltInTessCoord, Tessellation), + CASE1(BUILT_IN, BuiltInPatchVertices, Tessellation), + CASE1(BUILT_IN, BuiltInFragCoord, Shader), + CASE1(BUILT_IN, BuiltInPointCoord, Shader), + CASE1(BUILT_IN, BuiltInFrontFacing, Shader), + CASE1(BUILT_IN, BuiltInSampleId, SampleRateShading), // Bug 15234 + CASE1(BUILT_IN, BuiltInSamplePosition, SampleRateShading), // Bug 15234 + CASE1(BUILT_IN, BuiltInSampleMask, Shader), // Bug 15234, Issue 182 + // Value 21 intentionally missing + CASE1(BUILT_IN, BuiltInFragDepth, Shader), + CASE1(BUILT_IN, BuiltInHelperInvocation, Shader), + CASE0(BUILT_IN, BuiltInNumWorkgroups), + CASE0(BUILT_IN, BuiltInWorkgroupSize), + CASE0(BUILT_IN, BuiltInWorkgroupId), + CASE0(BUILT_IN, BuiltInLocalInvocationId), + CASE0(BUILT_IN, BuiltInGlobalInvocationId), + CASE0(BUILT_IN, BuiltInLocalInvocationIndex), + CASE1(BUILT_IN, BuiltInWorkDim, Kernel), + CASE1(BUILT_IN, BuiltInGlobalSize, Kernel), + CASE1(BUILT_IN, BuiltInEnqueuedWorkgroupSize, Kernel), + CASE1(BUILT_IN, BuiltInGlobalOffset, Kernel), + CASE1(BUILT_IN, BuiltInGlobalLinearId, Kernel), + // Value 35 intentionally missing + CASE2(BUILT_IN, BuiltInSubgroupSize, Kernel, SubgroupBallotKHR), + CASE1(BUILT_IN, BuiltInSubgroupMaxSize, Kernel), + CASE1(BUILT_IN, BuiltInNumSubgroups, Kernel), + CASE1(BUILT_IN, BuiltInNumEnqueuedSubgroups, Kernel), + CASE1(BUILT_IN, BuiltInSubgroupId, Kernel), + CASE2(BUILT_IN, BuiltInSubgroupLocalInvocationId, Kernel, SubgroupBallotKHR), + CASE1(BUILT_IN, BuiltInVertexIndex, Shader), + CASE1(BUILT_IN, BuiltInInstanceIndex, Shader), + // clang-format on + })), ); + +// See SPIR-V Section 3.22 Selection Control +INSTANTIATE_TEST_CASE_P( + SelectionControl, EnumCapabilityTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + CASE0(SELECTION_CONTROL, SelectionControlMaskNone), + CASE0(SELECTION_CONTROL, SelectionControlFlattenMask), + CASE0(SELECTION_CONTROL, SelectionControlDontFlattenMask), + })), ); + +// See SPIR-V Section 3.23 Loop Control +INSTANTIATE_TEST_CASE_P( + LoopControl, EnumCapabilityTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + CASE0(LOOP_CONTROL, LoopControlMaskNone), + CASE0(LOOP_CONTROL, LoopControlUnrollMask), + CASE0(LOOP_CONTROL, LoopControlDontUnrollMask), + })), ); + +INSTANTIATE_TEST_CASE_P( + LoopControlV11, EnumCapabilityTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + CASE0(LOOP_CONTROL, LoopControlDependencyInfiniteMask), + CASE0(LOOP_CONTROL, LoopControlDependencyLengthMask), + })), ); + +// See SPIR-V Section 3.24 Function Control +INSTANTIATE_TEST_CASE_P( + FunctionControl, EnumCapabilityTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + CASE0(FUNCTION_CONTROL, FunctionControlMaskNone), + CASE0(FUNCTION_CONTROL, FunctionControlInlineMask), + CASE0(FUNCTION_CONTROL, FunctionControlDontInlineMask), + CASE0(FUNCTION_CONTROL, FunctionControlPureMask), + CASE0(FUNCTION_CONTROL, FunctionControlConstMask), + })), ); + +// See SPIR-V Section 3.25 Memory Semantics +INSTANTIATE_TEST_CASE_P( + MemorySemantics, EnumCapabilityTest, + Combine( + Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + CASE0(MEMORY_SEMANTICS_ID, MemorySemanticsMaskNone), + CASE0(MEMORY_SEMANTICS_ID, MemorySemanticsAcquireMask), + CASE0(MEMORY_SEMANTICS_ID, MemorySemanticsReleaseMask), + CASE0(MEMORY_SEMANTICS_ID, MemorySemanticsAcquireReleaseMask), + CASE0(MEMORY_SEMANTICS_ID, + MemorySemanticsSequentiallyConsistentMask), + CASE1(MEMORY_SEMANTICS_ID, MemorySemanticsUniformMemoryMask, + Shader), + CASE0(MEMORY_SEMANTICS_ID, MemorySemanticsSubgroupMemoryMask), + CASE0(MEMORY_SEMANTICS_ID, MemorySemanticsWorkgroupMemoryMask), + CASE0(MEMORY_SEMANTICS_ID, MemorySemanticsCrossWorkgroupMemoryMask), + CASE1(MEMORY_SEMANTICS_ID, MemorySemanticsAtomicCounterMemoryMask, + AtomicStorage), // Bug 15234 + CASE0(MEMORY_SEMANTICS_ID, MemorySemanticsImageMemoryMask), + })), ); + +// See SPIR-V Section 3.26 Memory Access +INSTANTIATE_TEST_CASE_P( + MemoryAccess, EnumCapabilityTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + CASE0(OPTIONAL_MEMORY_ACCESS, MemoryAccessMaskNone), + CASE0(OPTIONAL_MEMORY_ACCESS, MemoryAccessVolatileMask), + CASE0(OPTIONAL_MEMORY_ACCESS, MemoryAccessAlignedMask), + CASE0(OPTIONAL_MEMORY_ACCESS, MemoryAccessNontemporalMask), + })), ); + +// See SPIR-V Section 3.27 Scope +INSTANTIATE_TEST_CASE_P(Scope, EnumCapabilityTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_0, + SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + CASE0(SCOPE_ID, ScopeCrossDevice), + CASE0(SCOPE_ID, ScopeDevice), + CASE0(SCOPE_ID, ScopeWorkgroup), + CASE0(SCOPE_ID, ScopeSubgroup), + CASE0(SCOPE_ID, ScopeInvocation), + })), ); + +// See SPIR-V Section 3.28 Group Operation +INSTANTIATE_TEST_CASE_P( + GroupOperation, EnumCapabilityTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + CASE1(GROUP_OPERATION, GroupOperationReduce, Kernel), + CASE1(GROUP_OPERATION, GroupOperationInclusiveScan, Kernel), + CASE1(GROUP_OPERATION, GroupOperationExclusiveScan, Kernel), + })), ); + +// See SPIR-V Section 3.29 Kernel Enqueue Flags +INSTANTIATE_TEST_CASE_P( + KernelEnqueueFlags, EnumCapabilityTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + CASE1(KERNEL_ENQ_FLAGS, KernelEnqueueFlagsNoWait, Kernel), + CASE1(KERNEL_ENQ_FLAGS, KernelEnqueueFlagsWaitKernel, Kernel), + CASE1(KERNEL_ENQ_FLAGS, KernelEnqueueFlagsWaitWorkGroup, + Kernel), + })), ); + +// See SPIR-V Section 3.30 Kernel Profiling Info +INSTANTIATE_TEST_CASE_P( + KernelProfilingInfo, EnumCapabilityTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + CASE0(KERNEL_PROFILING_INFO, KernelProfilingInfoMaskNone), + CASE1(KERNEL_PROFILING_INFO, KernelProfilingInfoCmdExecTimeMask, + Kernel), + })), ); + +// See SPIR-V Section 3.31 Capability +INSTANTIATE_TEST_CASE_P( + CapabilityDependsOn, EnumCapabilityTest, + Combine( + Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + // clang-format off + CASE0(CAPABILITY, CapabilityMatrix), + CASE1(CAPABILITY, CapabilityShader, Matrix), + CASE1(CAPABILITY, CapabilityGeometry, Shader), + CASE1(CAPABILITY, CapabilityTessellation, Shader), + CASE0(CAPABILITY, CapabilityAddresses), + CASE0(CAPABILITY, CapabilityLinkage), + CASE0(CAPABILITY, CapabilityKernel), + CASE1(CAPABILITY, CapabilityVector16, Kernel), + CASE1(CAPABILITY, CapabilityFloat16Buffer, Kernel), + CASE0(CAPABILITY, CapabilityFloat16), // Bug 15234 + CASE0(CAPABILITY, CapabilityFloat64), + CASE0(CAPABILITY, CapabilityInt64), + CASE1(CAPABILITY, CapabilityInt64Atomics, Int64), + CASE1(CAPABILITY, CapabilityImageBasic, Kernel), + CASE1(CAPABILITY, CapabilityImageReadWrite, ImageBasic), + CASE1(CAPABILITY, CapabilityImageMipmap, ImageBasic), + // Value 16 intentionally missing. + CASE1(CAPABILITY, CapabilityPipes, Kernel), + CASE0(CAPABILITY, CapabilityGroups), + CASE1(CAPABILITY, CapabilityDeviceEnqueue, Kernel), + CASE1(CAPABILITY, CapabilityLiteralSampler, Kernel), + CASE1(CAPABILITY, CapabilityAtomicStorage, Shader), + CASE0(CAPABILITY, CapabilityInt16), + CASE1(CAPABILITY, CapabilityTessellationPointSize, Tessellation), + CASE1(CAPABILITY, CapabilityGeometryPointSize, Geometry), + CASE1(CAPABILITY, CapabilityImageGatherExtended, Shader), + // Value 26 intentionally missing. + CASE1(CAPABILITY, CapabilityStorageImageMultisample, Shader), + CASE1(CAPABILITY, CapabilityUniformBufferArrayDynamicIndexing, Shader), + CASE1(CAPABILITY, CapabilitySampledImageArrayDynamicIndexing, Shader), + CASE1(CAPABILITY, CapabilityStorageBufferArrayDynamicIndexing, Shader), + CASE1(CAPABILITY, CapabilityStorageImageArrayDynamicIndexing, Shader), + CASE1(CAPABILITY, CapabilityClipDistance, Shader), + CASE1(CAPABILITY, CapabilityCullDistance, Shader), + CASE1(CAPABILITY, CapabilityImageCubeArray, SampledCubeArray), + CASE1(CAPABILITY, CapabilitySampleRateShading, Shader), + CASE1(CAPABILITY, CapabilityImageRect, SampledRect), + CASE1(CAPABILITY, CapabilitySampledRect, Shader), + CASE1(CAPABILITY, CapabilityGenericPointer, Addresses), + CASE1(CAPABILITY, CapabilityInt8, Kernel), + CASE1(CAPABILITY, CapabilityInputAttachment, Shader), + CASE1(CAPABILITY, CapabilitySparseResidency, Shader), + CASE1(CAPABILITY, CapabilityMinLod, Shader), + CASE1(CAPABILITY, CapabilityImage1D, Sampled1D), + CASE1(CAPABILITY, CapabilitySampledCubeArray, Shader), + CASE1(CAPABILITY, CapabilityImageBuffer, SampledBuffer), + CASE1(CAPABILITY, CapabilityImageMSArray, Shader), + CASE1(CAPABILITY, CapabilityStorageImageExtendedFormats, Shader), + CASE1(CAPABILITY, CapabilityImageQuery, Shader), + CASE1(CAPABILITY, CapabilityDerivativeControl, Shader), + CASE1(CAPABILITY, CapabilityInterpolationFunction, Shader), + CASE1(CAPABILITY, CapabilityTransformFeedback, Shader), + CASE1(CAPABILITY, CapabilityGeometryStreams, Geometry), + CASE1(CAPABILITY, CapabilityStorageImageReadWithoutFormat, Shader), + CASE1(CAPABILITY, CapabilityStorageImageWriteWithoutFormat, Shader), + CASE1(CAPABILITY, CapabilityMultiViewport, Geometry), + // clang-format on + })), ); + +INSTANTIATE_TEST_CASE_P( + CapabilityDependsOnV11, EnumCapabilityTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector{ + CASE1(CAPABILITY, CapabilitySubgroupDispatch, DeviceEnqueue), + CASE1(CAPABILITY, CapabilityNamedBarrier, Kernel), + CASE1(CAPABILITY, CapabilityPipeStorage, Pipes), + })), ); + +#undef CASE0 +#undef CASE1 +#undef CASE2 + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/operand_pattern_test.cpp b/3rdparty/spirv-tools/test/operand_pattern_test.cpp new file mode 100644 index 000000000..8f35fcf1c --- /dev/null +++ b/3rdparty/spirv-tools/test/operand_pattern_test.cpp @@ -0,0 +1,265 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "unit_spirv.h" + +#include "gmock/gmock.h" +#include "source/operand.h" + +using ::testing::Eq; + +namespace { + +TEST(OperandPattern, InitiallyEmpty) { + spv_operand_pattern_t empty; + EXPECT_THAT(empty, Eq(spv_operand_pattern_t{})); + EXPECT_EQ(0u, empty.size()); + EXPECT_TRUE(empty.empty()); +} + +TEST(OperandPattern, PushBacksAreOnTheRight) { + spv_operand_pattern_t pattern; + + pattern.push_back(SPV_OPERAND_TYPE_ID); + EXPECT_THAT(pattern, Eq(spv_operand_pattern_t{SPV_OPERAND_TYPE_ID})); + EXPECT_EQ(1u, pattern.size()); + EXPECT_TRUE(!pattern.empty()); + EXPECT_EQ(SPV_OPERAND_TYPE_ID, pattern.back()); + + pattern.push_back(SPV_OPERAND_TYPE_NONE); + EXPECT_THAT(pattern, Eq(spv_operand_pattern_t{SPV_OPERAND_TYPE_ID, + SPV_OPERAND_TYPE_NONE})); + EXPECT_EQ(2u, pattern.size()); + EXPECT_TRUE(!pattern.empty()); + EXPECT_EQ(SPV_OPERAND_TYPE_NONE, pattern.back()); +} + +TEST(OperandPattern, PopBacksAreOnTheRight) { + spv_operand_pattern_t pattern{SPV_OPERAND_TYPE_ID, + SPV_OPERAND_TYPE_LITERAL_INTEGER}; + + pattern.pop_back(); + EXPECT_THAT(pattern, Eq(spv_operand_pattern_t{SPV_OPERAND_TYPE_ID})); + + pattern.pop_back(); + EXPECT_THAT(pattern, Eq(spv_operand_pattern_t{})); +} + +// A test case for typed mask expansion +struct MaskExpansionCase { + spv_operand_type_t type; + uint32_t mask; + spv_operand_pattern_t initial; + spv_operand_pattern_t expected; +}; + +using MaskExpansionTest = ::testing::TestWithParam; + +TEST_P(MaskExpansionTest, Sample) { + spv_operand_table operandTable = nullptr; + auto env = SPV_ENV_UNIVERSAL_1_0; + ASSERT_EQ(SPV_SUCCESS, spvOperandTableGet(&operandTable, env)); + + spv_operand_pattern_t pattern(GetParam().initial); + spvPushOperandTypesForMask(env, operandTable, GetParam().type, + GetParam().mask, &pattern); + EXPECT_THAT(pattern, Eq(GetParam().expected)); +} + +// These macros let us write non-trivial examples without too much text. +#define PREFIX0 SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_NONE +#define PREFIX1 \ + SPV_OPERAND_TYPE_STORAGE_CLASS, SPV_OPERAND_TYPE_SAMPLER_FILTER_MODE, \ + SPV_OPERAND_TYPE_ID +INSTANTIATE_TEST_CASE_P( + OperandPattern, MaskExpansionTest, + ::testing::ValuesIn(std::vector{ + // No bits means no change. + {SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS, 0, {PREFIX0}, {PREFIX0}}, + // Unknown bits means no change. + {SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS, + 0xfffffffc, + {PREFIX1}, + {PREFIX1}}, + // Volatile has no operands. + {SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS, + SpvMemoryAccessVolatileMask, + {PREFIX0}, + {PREFIX0}}, + // Aligned has one literal number operand. + {SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS, + SpvMemoryAccessAlignedMask, + {PREFIX1}, + {PREFIX1, SPV_OPERAND_TYPE_LITERAL_INTEGER}}, + // Volatile with Aligned still has just one literal number operand. + {SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS, + SpvMemoryAccessVolatileMask | SpvMemoryAccessAlignedMask, + {PREFIX1}, + {PREFIX1, SPV_OPERAND_TYPE_LITERAL_INTEGER}}, + }), ); +#undef PREFIX0 +#undef PREFIX1 + +// Returns a vector of all operand types that can be used in a pattern. +std::vector allOperandTypes() { + std::vector result; + for (int i = 0; i < SPV_OPERAND_TYPE_NUM_OPERAND_TYPES; i++) { + result.push_back(spv_operand_type_t(i)); + } + return result; +} + +using MatchableOperandExpansionTest = + ::testing::TestWithParam; + +TEST_P(MatchableOperandExpansionTest, MatchableOperandsDontExpand) { + const spv_operand_type_t type = GetParam(); + if (!spvOperandIsVariable(type)) { + spv_operand_pattern_t pattern; + const bool did_expand = spvExpandOperandSequenceOnce(type, &pattern); + EXPECT_FALSE(did_expand); + EXPECT_THAT(pattern, Eq(spv_operand_pattern_t{})); + } +} + +INSTANTIATE_TEST_CASE_P(MatchableOperandExpansion, + MatchableOperandExpansionTest, + ::testing::ValuesIn(allOperandTypes()), ); + +using VariableOperandExpansionTest = + ::testing::TestWithParam; + +TEST_P(VariableOperandExpansionTest, NonMatchableOperandsExpand) { + const spv_operand_type_t type = GetParam(); + if (spvOperandIsVariable(type)) { + spv_operand_pattern_t pattern; + const bool did_expand = spvExpandOperandSequenceOnce(type, &pattern); + EXPECT_TRUE(did_expand); + EXPECT_FALSE(pattern.empty()); + // For the existing rules, the first expansion of a zero-or-more operand + // type yields a matchable operand type. This isn't strictly necessary. + EXPECT_FALSE(spvOperandIsVariable(pattern.back())); + } +} + +INSTANTIATE_TEST_CASE_P(NonMatchableOperandExpansion, + VariableOperandExpansionTest, + ::testing::ValuesIn(allOperandTypes()), ); + +TEST(AlternatePatternFollowingImmediate, Empty) { + EXPECT_THAT(spvAlternatePatternFollowingImmediate({}), + Eq(spv_operand_pattern_t{SPV_OPERAND_TYPE_OPTIONAL_CIV})); +} + +TEST(AlternatePatternFollowingImmediate, SingleElement) { + // Spot-check a random selection of types. + EXPECT_THAT(spvAlternatePatternFollowingImmediate( + {SPV_OPERAND_TYPE_VARIABLE_ID_LITERAL_INTEGER}), + Eq(spv_operand_pattern_t{SPV_OPERAND_TYPE_OPTIONAL_CIV})); + EXPECT_THAT( + spvAlternatePatternFollowingImmediate({SPV_OPERAND_TYPE_CAPABILITY}), + Eq(spv_operand_pattern_t{SPV_OPERAND_TYPE_OPTIONAL_CIV})); + EXPECT_THAT( + spvAlternatePatternFollowingImmediate({SPV_OPERAND_TYPE_LOOP_CONTROL}), + Eq(spv_operand_pattern_t{SPV_OPERAND_TYPE_OPTIONAL_CIV})); + EXPECT_THAT(spvAlternatePatternFollowingImmediate( + {SPV_OPERAND_TYPE_OPTIONAL_LITERAL_INTEGER}), + Eq(spv_operand_pattern_t{SPV_OPERAND_TYPE_OPTIONAL_CIV})); + EXPECT_THAT(spvAlternatePatternFollowingImmediate({SPV_OPERAND_TYPE_ID}), + Eq(spv_operand_pattern_t{SPV_OPERAND_TYPE_OPTIONAL_CIV})); +} + +TEST(AlternatePatternFollowingImmediate, SingleResultId) { + EXPECT_THAT( + spvAlternatePatternFollowingImmediate({SPV_OPERAND_TYPE_RESULT_ID}), + Eq(spv_operand_pattern_t{SPV_OPERAND_TYPE_OPTIONAL_CIV, + SPV_OPERAND_TYPE_RESULT_ID})); +} + +TEST(AlternatePatternFollowingImmediate, MultipleNonResultIds) { + EXPECT_THAT( + spvAlternatePatternFollowingImmediate( + {SPV_OPERAND_TYPE_VARIABLE_ID_LITERAL_INTEGER, + SPV_OPERAND_TYPE_CAPABILITY, SPV_OPERAND_TYPE_LOOP_CONTROL, + SPV_OPERAND_TYPE_OPTIONAL_LITERAL_INTEGER, SPV_OPERAND_TYPE_ID}), + Eq(spv_operand_pattern_t{SPV_OPERAND_TYPE_OPTIONAL_CIV})); +} + +TEST(AlternatePatternFollowingImmediate, ResultIdFront) { + EXPECT_THAT(spvAlternatePatternFollowingImmediate( + {SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}), + Eq(spv_operand_pattern_t{SPV_OPERAND_TYPE_OPTIONAL_CIV, + SPV_OPERAND_TYPE_RESULT_ID, + SPV_OPERAND_TYPE_OPTIONAL_CIV})); + EXPECT_THAT( + spvAlternatePatternFollowingImmediate({SPV_OPERAND_TYPE_RESULT_ID, + SPV_OPERAND_TYPE_FP_ROUNDING_MODE, + SPV_OPERAND_TYPE_ID}), + Eq(spv_operand_pattern_t{ + SPV_OPERAND_TYPE_OPTIONAL_CIV, SPV_OPERAND_TYPE_RESULT_ID, + SPV_OPERAND_TYPE_OPTIONAL_CIV, SPV_OPERAND_TYPE_OPTIONAL_CIV})); + EXPECT_THAT( + spvAlternatePatternFollowingImmediate( + {SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_DIMENSIONALITY, + SPV_OPERAND_TYPE_LINKAGE_TYPE, + SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE, + SPV_OPERAND_TYPE_FP_ROUNDING_MODE, SPV_OPERAND_TYPE_ID, + SPV_OPERAND_TYPE_VARIABLE_ID}), + Eq(spv_operand_pattern_t{ + SPV_OPERAND_TYPE_OPTIONAL_CIV, SPV_OPERAND_TYPE_RESULT_ID, + SPV_OPERAND_TYPE_OPTIONAL_CIV, SPV_OPERAND_TYPE_OPTIONAL_CIV, + SPV_OPERAND_TYPE_OPTIONAL_CIV, SPV_OPERAND_TYPE_OPTIONAL_CIV, + SPV_OPERAND_TYPE_OPTIONAL_CIV, SPV_OPERAND_TYPE_OPTIONAL_CIV})); +} + +TEST(AlternatePatternFollowingImmediate, ResultIdMiddle) { + EXPECT_THAT(spvAlternatePatternFollowingImmediate( + {SPV_OPERAND_TYPE_FP_ROUNDING_MODE, + SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_ID}), + Eq(spv_operand_pattern_t{SPV_OPERAND_TYPE_OPTIONAL_CIV, + SPV_OPERAND_TYPE_RESULT_ID, + SPV_OPERAND_TYPE_OPTIONAL_CIV})); + EXPECT_THAT( + spvAlternatePatternFollowingImmediate( + {SPV_OPERAND_TYPE_DIMENSIONALITY, SPV_OPERAND_TYPE_LINKAGE_TYPE, + SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE, + SPV_OPERAND_TYPE_RESULT_ID, SPV_OPERAND_TYPE_FP_ROUNDING_MODE, + SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_VARIABLE_ID}), + Eq(spv_operand_pattern_t{ + SPV_OPERAND_TYPE_OPTIONAL_CIV, SPV_OPERAND_TYPE_RESULT_ID, + SPV_OPERAND_TYPE_OPTIONAL_CIV, SPV_OPERAND_TYPE_OPTIONAL_CIV, + SPV_OPERAND_TYPE_OPTIONAL_CIV})); +} + +TEST(AlternatePatternFollowingImmediate, ResultIdBack) { + EXPECT_THAT(spvAlternatePatternFollowingImmediate( + {SPV_OPERAND_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID}), + Eq(spv_operand_pattern_t{SPV_OPERAND_TYPE_OPTIONAL_CIV, + SPV_OPERAND_TYPE_RESULT_ID})); + EXPECT_THAT(spvAlternatePatternFollowingImmediate( + {SPV_OPERAND_TYPE_FP_ROUNDING_MODE, SPV_OPERAND_TYPE_ID, + SPV_OPERAND_TYPE_RESULT_ID}), + Eq(spv_operand_pattern_t{SPV_OPERAND_TYPE_OPTIONAL_CIV, + SPV_OPERAND_TYPE_RESULT_ID})); + EXPECT_THAT( + spvAlternatePatternFollowingImmediate( + {SPV_OPERAND_TYPE_DIMENSIONALITY, SPV_OPERAND_TYPE_LINKAGE_TYPE, + SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE, + SPV_OPERAND_TYPE_FP_ROUNDING_MODE, SPV_OPERAND_TYPE_ID, + SPV_OPERAND_TYPE_VARIABLE_ID, SPV_OPERAND_TYPE_RESULT_ID}), + Eq(spv_operand_pattern_t{SPV_OPERAND_TYPE_OPTIONAL_CIV, + SPV_OPERAND_TYPE_RESULT_ID})); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/operand_test.cpp b/3rdparty/spirv-tools/test/operand_test.cpp new file mode 100644 index 000000000..464d38fcc --- /dev/null +++ b/3rdparty/spirv-tools/test/operand_test.cpp @@ -0,0 +1,72 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "unit_spirv.h" + +namespace { + +using GetTargetTest = ::testing::TestWithParam; +using std::vector; +using ::testing::ValuesIn; + +TEST_P(GetTargetTest, Default) { + spv_operand_table table; + ASSERT_EQ(SPV_SUCCESS, spvOperandTableGet(&table, GetParam())); + ASSERT_NE(0u, table->count); + ASSERT_NE(nullptr, table->types); +} + +TEST_P(GetTargetTest, InvalidPointerTable) { + ASSERT_EQ(SPV_ERROR_INVALID_POINTER, spvOperandTableGet(nullptr, GetParam())); +} + +INSTANTIATE_TEST_CASE_P(OperandTableGet, GetTargetTest, + ValuesIn(vector{SPV_ENV_UNIVERSAL_1_0, + SPV_ENV_UNIVERSAL_1_1, + SPV_ENV_VULKAN_1_0}), ); + +TEST(OperandString, AllAreDefinedExceptVariable) { + // None has no string, so don't test it. + EXPECT_EQ(0u, SPV_OPERAND_TYPE_NONE); + // Start testing at enum with value 1, skipping None. + for (int i = 1; i < int(SPV_OPERAND_TYPE_FIRST_VARIABLE_TYPE); i++) { + EXPECT_NE(nullptr, spvOperandTypeStr(static_cast(i))) + << " Operand type " << i; + } +} + +TEST(OperandIsConcreteMask, Sample) { + // Check a few operand types preceding the concrete mask types. + EXPECT_FALSE(spvOperandIsConcreteMask(SPV_OPERAND_TYPE_NONE)); + EXPECT_FALSE(spvOperandIsConcreteMask(SPV_OPERAND_TYPE_ID)); + EXPECT_FALSE(spvOperandIsConcreteMask(SPV_OPERAND_TYPE_LITERAL_INTEGER)); + EXPECT_FALSE(spvOperandIsConcreteMask(SPV_OPERAND_TYPE_CAPABILITY)); + + // Check all the concrete mask operand types. + EXPECT_TRUE(spvOperandIsConcreteMask(SPV_OPERAND_TYPE_IMAGE)); + EXPECT_TRUE(spvOperandIsConcreteMask(SPV_OPERAND_TYPE_FP_FAST_MATH_MODE)); + EXPECT_TRUE(spvOperandIsConcreteMask(SPV_OPERAND_TYPE_SELECTION_CONTROL)); + EXPECT_TRUE(spvOperandIsConcreteMask(SPV_OPERAND_TYPE_LOOP_CONTROL)); + EXPECT_TRUE(spvOperandIsConcreteMask(SPV_OPERAND_TYPE_FUNCTION_CONTROL)); + EXPECT_TRUE(spvOperandIsConcreteMask(SPV_OPERAND_TYPE_MEMORY_ACCESS)); + + // Check a few operand types after the concrete mask types, including the + // optional forms for Image and MemoryAccess. + EXPECT_FALSE(spvOperandIsConcreteMask(SPV_OPERAND_TYPE_OPTIONAL_ID)); + EXPECT_FALSE(spvOperandIsConcreteMask(SPV_OPERAND_TYPE_OPTIONAL_IMAGE)); + EXPECT_FALSE( + spvOperandIsConcreteMask(SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS)); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/CMakeLists.txt b/3rdparty/spirv-tools/test/opt/CMakeLists.txt new file mode 100644 index 000000000..de1cd16d3 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/CMakeLists.txt @@ -0,0 +1,313 @@ +# Copyright (c) 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +add_subdirectory(dominator_tree) +add_subdirectory(loop_optimizations) + +add_spvtools_unittest(TARGET instruction + SRCS instruction_test.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET instruction_list + SRCS instruction_list_test.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET ir_loader + SRCS ir_loader_test.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_manager + SRCS module_utils.h + pass_manager_test.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET optimizer + SRCS optimizer_test.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_strip_debug_info + SRCS strip_debug_info_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_strip_reflect_info + SRCS strip_reflect_info_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_compact_ids + SRCS compact_ids_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_flatten_decoration + SRCS flatten_decoration_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_freeze_spec_const + SRCS freeze_spec_const_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_block_merge + SRCS block_merge_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_inline + SRCS inline_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_inline_opaque + SRCS inline_opaque_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_insert_extract_elim + SRCS insert_extract_elim_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_dead_insert_elim + SRCS dead_insert_elim_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_local_ssa_elim + SRCS local_ssa_elim_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_local_single_block_elim + SRCS local_single_block_elim.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_local_access_chain_convert + SRCS local_access_chain_convert_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_local_single_store_elim + SRCS local_single_store_elim_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_dead_branch_elim + SRCS dead_branch_elim_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_dead_variable_elim + SRCS dead_variable_elim_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_aggressive_dce + SRCS aggressive_dead_code_elim_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_common_uniform_elim + SRCS common_uniform_elim_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_eliminate_dead_const + SRCS eliminate_dead_const_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_eliminate_dead_functions + SRCS eliminate_dead_functions_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_pass + SRCS pass_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_utils + SRCS utils_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET def_use + SRCS def_use_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET assembly_builder + SRCS assembly_builder_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET types + SRCS types_test.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET type_manager + SRCS type_manager_test.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET iterator + SRCS iterator_test.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET module + SRCS module_utils.h + module_test.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_fold_spec_const_op_composite + SRCS fold_spec_const_op_composite_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_unify_const + SRCS unify_const_test.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_set_spec_const_default_value + SRCS set_spec_const_default_value_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET line_debug_info + SRCS line_debug_info_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_strength_reduction + SRCS strength_reduction_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_scalar_replacement + SRCS scalar_replacement_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET cfg_cleanup + SRCS cfg_cleanup_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET ir_context + SRCS ir_context_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET feature_manager + SRCS feature_manager_test.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_merge_return + SRCS pass_merge_return_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET value_table + SRCS value_table_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET local_redundancy_elimination + SRCS local_redundancy_elimination_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET propagator + SRCS propagator_test.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET redundancy_elimination + SRCS redundancy_elimination_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET private_to_local + SRCS private_to_local_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET decoration_manager + SRCS decoration_manager_test.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_remove_duplicates + SRCS pass_remove_duplicates_test.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET ccp + SRCS ccp_test.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_workaround1209 + SRCS workaround1209_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET pass_if_conversion + SRCS if_conversion_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET ir_builder + SRCS ir_builder.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET instruction_folding + SRCS fold_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET replace_invalid_opc + SRCS replace_invalid_opc_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET simplification + SRCS simplification_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) +add_spvtools_unittest(TARGET copy_prop_array + SRCS copy_prop_array_test.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET scalar_analysis + SRCS scalar_analysis.cpp pass_utils.cpp + LIBS SPIRV-Tools-opt +) + 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 new file mode 100644 index 000000000..b3346dbff --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/aggressive_dead_code_elim_test.cpp @@ -0,0 +1,5419 @@ +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "assembly_builder.h" +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; + +using AggressiveDCETest = PassTest<::testing::Test>; + +TEST_F(AggressiveDCETest, EliminateExtendedInst) { + // #version 140 + // + // in vec4 BaseColor; + // in vec4 Dead; + // + // void main() + // { + // vec4 v = BaseColor; + // vec4 dv = sqrt(Dead); + // gl_FragColor = v; + // } + + const std::string predefs1 = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %Dead %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +)"; + + const std::string names_before = + R"(OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %dv "dv" +OpName %Dead "Dead" +OpName %gl_FragColor "gl_FragColor" +)"; + + const std::string names_after = + R"(OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %Dead "Dead" +OpName %gl_FragColor "gl_FragColor" +)"; + + const std::string predefs2 = + R"(%void = OpTypeVoid +%9 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%Dead = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string func_before = + R"(%main = OpFunction %void None %9 +%15 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%dv = OpVariable %_ptr_Function_v4float Function +%16 = OpLoad %v4float %BaseColor +OpStore %v %16 +%17 = OpLoad %v4float %Dead +%18 = OpExtInst %v4float %1 Sqrt %17 +OpStore %dv %18 +%19 = OpLoad %v4float %v +OpStore %gl_FragColor %19 +OpReturn +OpFunctionEnd +)"; + + const std::string func_after = + R"(%main = OpFunction %void None %9 +%15 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%16 = OpLoad %v4float %BaseColor +OpStore %v %16 +%19 = OpLoad %v4float %v +OpStore %gl_FragColor %19 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs1 + names_before + predefs2 + func_before, + predefs1 + names_after + predefs2 + func_after, true, true); +} + +TEST_F(AggressiveDCETest, NoEliminateFrexp) { + // Note: SPIR-V hand-edited to utilize Frexp + // + // #version 450 + // + // in vec4 BaseColor; + // in vec4 Dead; + // out vec4 Color; + // out ivec4 iv2; + // + // void main() + // { + // vec4 v = BaseColor; + // vec4 dv = frexp(Dead, iv2); + // Color = v; + // } + + const std::string predefs1 = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %Dead %iv2 %Color +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +)"; + + const std::string names_before = + R"(OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %dv "dv" +OpName %Dead "Dead" +OpName %iv2 "iv2" +OpName %ResType "ResType" +OpName %Color "Color" +)"; + + const std::string names_after = + R"(OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %Dead "Dead" +OpName %iv2 "iv2" +OpName %Color "Color" +)"; + + const std::string predefs2_before = + R"(%void = OpTypeVoid +%11 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%Dead = OpVariable %_ptr_Input_v4float Input +%int = OpTypeInt 32 1 +%v4int = OpTypeVector %int 4 +%_ptr_Output_v4int = OpTypePointer Output %v4int +%iv2 = OpVariable %_ptr_Output_v4int Output +%ResType = OpTypeStruct %v4float %v4int +%_ptr_Output_v4float = OpTypePointer Output %v4float +%Color = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string predefs2_after = + R"(%void = OpTypeVoid +%11 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%Dead = OpVariable %_ptr_Input_v4float Input +%int = OpTypeInt 32 1 +%v4int = OpTypeVector %int 4 +%_ptr_Output_v4int = OpTypePointer Output %v4int +%iv2 = OpVariable %_ptr_Output_v4int Output +%_ptr_Output_v4float = OpTypePointer Output %v4float +%Color = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string func_before = + R"(%main = OpFunction %void None %11 +%20 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%dv = OpVariable %_ptr_Function_v4float Function +%21 = OpLoad %v4float %BaseColor +OpStore %v %21 +%22 = OpLoad %v4float %Dead +%23 = OpExtInst %v4float %1 Frexp %22 %iv2 +OpStore %dv %23 +%24 = OpLoad %v4float %v +OpStore %Color %24 +OpReturn +OpFunctionEnd +)"; + + const std::string func_after = + R"(%main = OpFunction %void None %11 +%20 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%21 = OpLoad %v4float %BaseColor +OpStore %v %21 +%22 = OpLoad %v4float %Dead +%23 = OpExtInst %v4float %1 Frexp %22 %iv2 +%24 = OpLoad %v4float %v +OpStore %Color %24 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs1 + names_before + predefs2_before + func_before, + predefs1 + names_after + predefs2_after + func_after, true, true); +} + +TEST_F(AggressiveDCETest, EliminateDecorate) { + // Note: The SPIR-V was hand-edited to add the OpDecorate + // + // #version 140 + // + // in vec4 BaseColor; + // in vec4 Dead; + // + // void main() + // { + // vec4 v = BaseColor; + // vec4 dv = Dead * 0.5; + // gl_FragColor = v; + // } + + const std::string predefs1 = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %Dead %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +)"; + + const std::string names_before = + R"(OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %dv "dv" +OpName %Dead "Dead" +OpName %gl_FragColor "gl_FragColor" +OpDecorate %8 RelaxedPrecision +)"; + + const std::string names_after = + R"(OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %Dead "Dead" +OpName %gl_FragColor "gl_FragColor" +)"; + + const std::string predefs2_before = + R"(%void = OpTypeVoid +%10 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%Dead = OpVariable %_ptr_Input_v4float Input +%float_0_5 = OpConstant %float 0.5 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string predefs2_after = + R"(%void = OpTypeVoid +%10 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%Dead = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string func_before = + R"(%main = OpFunction %void None %10 +%17 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%dv = OpVariable %_ptr_Function_v4float Function +%18 = OpLoad %v4float %BaseColor +OpStore %v %18 +%19 = OpLoad %v4float %Dead +%8 = OpVectorTimesScalar %v4float %19 %float_0_5 +OpStore %dv %8 +%20 = OpLoad %v4float %v +OpStore %gl_FragColor %20 +OpReturn +OpFunctionEnd +)"; + + const std::string func_after = + R"(%main = OpFunction %void None %10 +%17 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%18 = OpLoad %v4float %BaseColor +OpStore %v %18 +%20 = OpLoad %v4float %v +OpStore %gl_FragColor %20 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs1 + names_before + predefs2_before + func_before, + predefs1 + names_after + predefs2_after + func_after, true, true); +} + +TEST_F(AggressiveDCETest, Simple) { + // #version 140 + // + // in vec4 BaseColor; + // in vec4 Dead; + // + // void main() + // { + // vec4 v = BaseColor; + // vec4 dv = Dead; + // gl_FragColor = v; + // } + + const std::string predefs1 = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %Dead %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +)"; + + const std::string names_before = + R"(OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %dv "dv" +OpName %Dead "Dead" +OpName %gl_FragColor "gl_FragColor" +)"; + + const std::string names_after = + R"(OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %Dead "Dead" +OpName %gl_FragColor "gl_FragColor" +)"; + + const std::string predefs2 = + R"(%void = OpTypeVoid +%9 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%Dead = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string func_before = + R"(%main = OpFunction %void None %9 +%15 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%dv = OpVariable %_ptr_Function_v4float Function +%16 = OpLoad %v4float %BaseColor +OpStore %v %16 +%17 = OpLoad %v4float %Dead +OpStore %dv %17 +%18 = OpLoad %v4float %v +OpStore %gl_FragColor %18 +OpReturn +OpFunctionEnd +)"; + + const std::string func_after = + R"(%main = OpFunction %void None %9 +%15 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%16 = OpLoad %v4float %BaseColor +OpStore %v %16 +%18 = OpLoad %v4float %v +OpStore %gl_FragColor %18 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs1 + names_before + predefs2 + func_before, + predefs1 + names_after + predefs2 + func_after, true, true); +} + +TEST_F(AggressiveDCETest, OptWhitelistExtension) { + // #version 140 + // + // in vec4 BaseColor; + // in vec4 Dead; + // + // void main() + // { + // vec4 v = BaseColor; + // vec4 dv = Dead; + // gl_FragColor = v; + // } + + const std::string predefs1 = + R"(OpCapability Shader +OpExtension "SPV_AMD_gpu_shader_int16" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %Dead %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +)"; + + const std::string names_before = + R"(OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %dv "dv" +OpName %Dead "Dead" +OpName %gl_FragColor "gl_FragColor" +)"; + + const std::string names_after = + R"(OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %Dead "Dead" +OpName %gl_FragColor "gl_FragColor" +)"; + + const std::string predefs2 = + R"(%void = OpTypeVoid +%9 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%Dead = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string func_before = + R"(%main = OpFunction %void None %9 +%15 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%dv = OpVariable %_ptr_Function_v4float Function +%16 = OpLoad %v4float %BaseColor +OpStore %v %16 +%17 = OpLoad %v4float %Dead +OpStore %dv %17 +%18 = OpLoad %v4float %v +OpStore %gl_FragColor %18 +OpReturn +OpFunctionEnd +)"; + + const std::string func_after = + R"(%main = OpFunction %void None %9 +%15 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%16 = OpLoad %v4float %BaseColor +OpStore %v %16 +%18 = OpLoad %v4float %v +OpStore %gl_FragColor %18 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs1 + names_before + predefs2 + func_before, + predefs1 + names_after + predefs2 + func_after, true, true); +} + +TEST_F(AggressiveDCETest, NoOptBlacklistExtension) { + // #version 140 + // + // in vec4 BaseColor; + // in vec4 Dead; + // + // void main() + // { + // vec4 v = BaseColor; + // vec4 dv = Dead; + // gl_FragColor = v; + // } + + const std::string assembly = + R"(OpCapability Shader +OpExtension "SPV_KHR_variable_pointers" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %Dead %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %dv "dv" +OpName %Dead "Dead" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%9 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%Dead = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %9 +%15 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%dv = OpVariable %_ptr_Function_v4float Function +%16 = OpLoad %v4float %BaseColor +OpStore %v %16 +%17 = OpLoad %v4float %Dead +OpStore %dv %17 +%18 = OpLoad %v4float %v +OpStore %gl_FragColor %18 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(assembly, assembly, true, true); +} + +TEST_F(AggressiveDCETest, ElimWithCall) { + // This demonstrates that "dead" function calls are not eliminated. + // Also demonstrates that DCE will happen in presence of function call. + // #version 140 + // in vec4 i1; + // in vec4 i2; + // + // void nothing(vec4 v) + // { + // } + // + // void main() + // { + // vec4 v1 = i1; + // vec4 v2 = i2; + // nothing(v1); + // gl_FragColor = vec4(0.0); + // } + + const std::string defs_before = + R"( OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %i1 %i2 %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %nothing_vf4_ "nothing(vf4;" +OpName %v "v" +OpName %v1 "v1" +OpName %i1 "i1" +OpName %v2 "v2" +OpName %i2 "i2" +OpName %param "param" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%12 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%16 = OpTypeFunction %void %_ptr_Function_v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%i1 = OpVariable %_ptr_Input_v4float Input +%i2 = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +%float_0 = OpConstant %float 0 +%20 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 +)"; + + const std::string defs_after = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %i1 %i2 %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %nothing_vf4_ "nothing(vf4;" +OpName %v "v" +OpName %v1 "v1" +OpName %i1 "i1" +OpName %i2 "i2" +OpName %param "param" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%12 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%16 = OpTypeFunction %void %_ptr_Function_v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%i1 = OpVariable %_ptr_Input_v4float Input +%i2 = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +%float_0 = OpConstant %float 0 +%20 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 +)"; + + const std::string func_before = + R"(%main = OpFunction %void None %12 +%21 = OpLabel +%v1 = OpVariable %_ptr_Function_v4float Function +%v2 = OpVariable %_ptr_Function_v4float Function +%param = OpVariable %_ptr_Function_v4float Function +%22 = OpLoad %v4float %i1 +OpStore %v1 %22 +%23 = OpLoad %v4float %i2 +OpStore %v2 %23 +%24 = OpLoad %v4float %v1 +OpStore %param %24 +%25 = OpFunctionCall %void %nothing_vf4_ %param +OpStore %gl_FragColor %20 +OpReturn +OpFunctionEnd +%nothing_vf4_ = OpFunction %void None %16 +%v = OpFunctionParameter %_ptr_Function_v4float +%26 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string func_after = + R"(%main = OpFunction %void None %12 +%21 = OpLabel +%v1 = OpVariable %_ptr_Function_v4float Function +%param = OpVariable %_ptr_Function_v4float Function +%22 = OpLoad %v4float %i1 +OpStore %v1 %22 +%24 = OpLoad %v4float %v1 +OpStore %param %24 +%25 = OpFunctionCall %void %nothing_vf4_ %param +OpStore %gl_FragColor %20 +OpReturn +OpFunctionEnd +%nothing_vf4_ = OpFunction %void None %16 +%v = OpFunctionParameter %_ptr_Function_v4float +%26 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + defs_before + func_before, defs_after + func_after, true, true); +} + +TEST_F(AggressiveDCETest, NoParamElim) { + // This demonstrates that unused parameters are not eliminated, but + // dead uses of them are. + // #version 140 + // + // in vec4 BaseColor; + // + // vec4 foo(vec4 v1, vec4 v2) + // { + // vec4 t = -v1; + // return v2; + // } + // + // void main() + // { + // vec4 dead; + // gl_FragColor = foo(dead, BaseColor); + // } + + const std::string defs_before = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %gl_FragColor %BaseColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %foo_vf4_vf4_ "foo(vf4;vf4;" +OpName %v1 "v1" +OpName %v2 "v2" +OpName %t "t" +OpName %gl_FragColor "gl_FragColor" +OpName %dead "dead" +OpName %BaseColor "BaseColor" +OpName %param "param" +OpName %param_0 "param" +%void = OpTypeVoid +%13 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%17 = OpTypeFunction %v4float %_ptr_Function_v4float %_ptr_Function_v4float +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%main = OpFunction %void None %13 +%20 = OpLabel +%dead = OpVariable %_ptr_Function_v4float Function +%param = OpVariable %_ptr_Function_v4float Function +%param_0 = OpVariable %_ptr_Function_v4float Function +%21 = OpLoad %v4float %dead +OpStore %param %21 +%22 = OpLoad %v4float %BaseColor +OpStore %param_0 %22 +%23 = OpFunctionCall %v4float %foo_vf4_vf4_ %param %param_0 +OpStore %gl_FragColor %23 +OpReturn +OpFunctionEnd +)"; + + const std::string defs_after = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %gl_FragColor %BaseColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %foo_vf4_vf4_ "foo(vf4;vf4;" +OpName %v1 "v1" +OpName %v2 "v2" +OpName %gl_FragColor "gl_FragColor" +OpName %dead "dead" +OpName %BaseColor "BaseColor" +OpName %param "param" +OpName %param_0 "param" +%void = OpTypeVoid +%13 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%17 = OpTypeFunction %v4float %_ptr_Function_v4float %_ptr_Function_v4float +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%main = OpFunction %void None %13 +%20 = OpLabel +%dead = OpVariable %_ptr_Function_v4float Function +%param = OpVariable %_ptr_Function_v4float Function +%param_0 = OpVariable %_ptr_Function_v4float Function +%21 = OpLoad %v4float %dead +OpStore %param %21 +%22 = OpLoad %v4float %BaseColor +OpStore %param_0 %22 +%23 = OpFunctionCall %v4float %foo_vf4_vf4_ %param %param_0 +OpStore %gl_FragColor %23 +OpReturn +OpFunctionEnd +)"; + + const std::string func_before = + R"(%foo_vf4_vf4_ = OpFunction %v4float None %17 +%v1 = OpFunctionParameter %_ptr_Function_v4float +%v2 = OpFunctionParameter %_ptr_Function_v4float +%24 = OpLabel +%t = OpVariable %_ptr_Function_v4float Function +%25 = OpLoad %v4float %v1 +%26 = OpFNegate %v4float %25 +OpStore %t %26 +%27 = OpLoad %v4float %v2 +OpReturnValue %27 +OpFunctionEnd +)"; + + const std::string func_after = + R"(%foo_vf4_vf4_ = OpFunction %v4float None %17 +%v1 = OpFunctionParameter %_ptr_Function_v4float +%v2 = OpFunctionParameter %_ptr_Function_v4float +%24 = OpLabel +%27 = OpLoad %v4float %v2 +OpReturnValue %27 +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + defs_before + func_before, defs_after + func_after, true, true); +} + +TEST_F(AggressiveDCETest, ElimOpaque) { + // SPIR-V not representable from GLSL; not generatable from HLSL + // for the moment. + + const std::string defs_before = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %outColor %texCoords +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %S_t "S_t" +OpMemberName %S_t 0 "v0" +OpMemberName %S_t 1 "v1" +OpMemberName %S_t 2 "smp" +OpName %outColor "outColor" +OpName %sampler15 "sampler15" +OpName %s0 "s0" +OpName %texCoords "texCoords" +OpDecorate %sampler15 DescriptorSet 0 +%void = OpTypeVoid +%9 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v2float = OpTypeVector %float 2 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%outColor = OpVariable %_ptr_Output_v4float Output +%14 = OpTypeImage %float 2D 0 0 0 1 Unknown +%15 = OpTypeSampledImage %14 +%S_t = OpTypeStruct %v2float %v2float %15 +%_ptr_Function_S_t = OpTypePointer Function %S_t +%17 = OpTypeFunction %void %_ptr_Function_S_t +%_ptr_UniformConstant_15 = OpTypePointer UniformConstant %15 +%_ptr_Function_15 = OpTypePointer Function %15 +%sampler15 = OpVariable %_ptr_UniformConstant_15 UniformConstant +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%int_2 = OpConstant %int 2 +%_ptr_Function_v2float = OpTypePointer Function %v2float +%_ptr_Input_v2float = OpTypePointer Input %v2float +%texCoords = OpVariable %_ptr_Input_v2float Input +)"; + + const std::string defs_after = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %outColor %texCoords +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %outColor "outColor" +OpName %sampler15 "sampler15" +OpName %texCoords "texCoords" +OpDecorate %sampler15 DescriptorSet 0 +%void = OpTypeVoid +%9 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v2float = OpTypeVector %float 2 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%outColor = OpVariable %_ptr_Output_v4float Output +%14 = OpTypeImage %float 2D 0 0 0 1 Unknown +%15 = OpTypeSampledImage %14 +%_ptr_UniformConstant_15 = OpTypePointer UniformConstant %15 +%sampler15 = OpVariable %_ptr_UniformConstant_15 UniformConstant +%_ptr_Input_v2float = OpTypePointer Input %v2float +%texCoords = OpVariable %_ptr_Input_v2float Input +)"; + + const std::string func_before = + R"(%main = OpFunction %void None %9 +%25 = OpLabel +%s0 = OpVariable %_ptr_Function_S_t Function +%26 = OpLoad %v2float %texCoords +%27 = OpLoad %S_t %s0 +%28 = OpCompositeInsert %S_t %26 %27 0 +%29 = OpLoad %15 %sampler15 +%30 = OpCompositeInsert %S_t %29 %28 2 +OpStore %s0 %30 +%31 = OpImageSampleImplicitLod %v4float %29 %26 +OpStore %outColor %31 +OpReturn +OpFunctionEnd +)"; + + const std::string func_after = + R"(%main = OpFunction %void None %9 +%25 = OpLabel +%26 = OpLoad %v2float %texCoords +%29 = OpLoad %15 %sampler15 +%31 = OpImageSampleImplicitLod %v4float %29 %26 +OpStore %outColor %31 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + defs_before + func_before, defs_after + func_after, true, true); +} + +TEST_F(AggressiveDCETest, NoParamStoreElim) { + // Should not eliminate stores to params + // + // #version 450 + // + // layout(location = 0) in vec4 BaseColor; + // layout(location = 0) out vec4 OutColor; + // + // void foo(in vec4 v1, out vec4 v2) + // { + // v2 = -v1; + // } + // + // void main() + // { + // foo(BaseColor, OutColor); + // } + + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %foo_vf4_vf4_ "foo(vf4;vf4;" +OpName %v1 "v1" +OpName %v2 "v2" +OpName %BaseColor "BaseColor" +OpName %OutColor "OutColor" +OpName %param "param" +OpName %param_0 "param" +OpDecorate %BaseColor Location 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%11 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%15 = OpTypeFunction %void %_ptr_Function_v4float %_ptr_Function_v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %11 +%18 = OpLabel +%param = OpVariable %_ptr_Function_v4float Function +%param_0 = OpVariable %_ptr_Function_v4float Function +%19 = OpLoad %v4float %BaseColor +OpStore %param %19 +%20 = OpFunctionCall %void %foo_vf4_vf4_ %param %param_0 +%21 = OpLoad %v4float %param_0 +OpStore %OutColor %21 +OpReturn +OpFunctionEnd +%foo_vf4_vf4_ = OpFunction %void None %15 +%v1 = OpFunctionParameter %_ptr_Function_v4float +%v2 = OpFunctionParameter %_ptr_Function_v4float +%22 = OpLabel +%23 = OpLoad %v4float %v1 +%24 = OpFNegate %v4float %23 +OpStore %v2 %24 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(assembly, assembly, true, true); +} + +TEST_F(AggressiveDCETest, PrivateStoreElimInEntryNoCalls) { + // Eliminate stores to private in entry point with no calls + // Note: Not legal GLSL + // + // layout(location = 0) in vec4 BaseColor; + // layout(location = 1) in vec4 Dead; + // layout(location = 0) out vec4 OutColor; + // + // private vec4 dv; + // + // void main() + // { + // vec4 v = BaseColor; + // dv = Dead; + // OutColor = v; + // } + + const std::string predefs_before = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %Dead %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %dv "dv" +OpName %Dead "Dead" +OpName %OutColor "OutColor" +OpDecorate %BaseColor Location 0 +OpDecorate %Dead Location 1 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%9 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Private_v4float = OpTypePointer Private %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%Dead = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%dv = OpVariable %_ptr_Private_v4float Private +%OutColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string predefs_after = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %Dead %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %Dead "Dead" +OpName %OutColor "OutColor" +OpDecorate %BaseColor Location 0 +OpDecorate %Dead Location 1 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%9 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%Dead = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string main_before = + R"(%main = OpFunction %void None %9 +%16 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%17 = OpLoad %v4float %BaseColor +OpStore %v %17 +%18 = OpLoad %v4float %Dead +OpStore %dv %18 +%19 = OpLoad %v4float %v +%20 = OpFNegate %v4float %19 +OpStore %OutColor %20 +OpReturn +OpFunctionEnd +)"; + + const std::string main_after = + R"(%main = OpFunction %void None %9 +%16 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%17 = OpLoad %v4float %BaseColor +OpStore %v %17 +%19 = OpLoad %v4float %v +%20 = OpFNegate %v4float %19 +OpStore %OutColor %20 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs_before + main_before, predefs_after + main_after, true, true); +} + +TEST_F(AggressiveDCETest, NoPrivateStoreElimIfLoad) { + // Should not eliminate stores to private when there is a load + // Note: Not legal GLSL + // + // #version 450 + // + // layout(location = 0) in vec4 BaseColor; + // layout(location = 0) out vec4 OutColor; + // + // private vec4 pv; + // + // void main() + // { + // pv = BaseColor; + // OutColor = pv; + // } + + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %pv "pv" +OpName %BaseColor "BaseColor" +OpName %OutColor "OutColor" +OpDecorate %BaseColor Location 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Private_v4float = OpTypePointer Private %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%pv = OpVariable %_ptr_Private_v4float Private +%main = OpFunction %void None %7 +%13 = OpLabel +%14 = OpLoad %v4float %BaseColor +OpStore %pv %14 +%15 = OpLoad %v4float %pv +%16 = OpFNegate %v4float %15 +OpStore %OutColor %16 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(assembly, assembly, true, true); +} + +TEST_F(AggressiveDCETest, NoPrivateStoreElimWithCall) { + // Should not eliminate stores to private when function contains call + // Note: Not legal GLSL + // + // #version 450 + // + // layout(location = 0) in vec4 BaseColor; + // layout(location = 0) out vec4 OutColor; + // + // private vec4 v1; + // + // void foo() + // { + // OutColor = -v1; + // } + // + // void main() + // { + // v1 = BaseColor; + // foo(); + // } + + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %OutColor %BaseColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %foo_ "foo(" +OpName %OutColor "OutColor" +OpName %v1 "v1" +OpName %BaseColor "BaseColor" +OpDecorate %OutColor Location 0 +OpDecorate %BaseColor Location 0 +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%_ptr_Private_v4float = OpTypePointer Private %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%v1 = OpVariable %_ptr_Private_v4float Private +%BaseColor = OpVariable %_ptr_Input_v4float Input +%main = OpFunction %void None %8 +%14 = OpLabel +%15 = OpLoad %v4float %BaseColor +OpStore %v1 %15 +%16 = OpFunctionCall %void %foo_ +OpReturn +OpFunctionEnd +%foo_ = OpFunction %void None %8 +%17 = OpLabel +%18 = OpLoad %v4float %v1 +%19 = OpFNegate %v4float %18 +OpStore %OutColor %19 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(assembly, assembly, true, true); +} + +TEST_F(AggressiveDCETest, NoPrivateStoreElimInNonEntry) { + // Should not eliminate stores to private when function is not entry point + // Note: Not legal GLSL + // + // #version 450 + // + // layout(location = 0) in vec4 BaseColor; + // layout(location = 0) out vec4 OutColor; + // + // private vec4 v1; + // + // void foo() + // { + // v1 = BaseColor; + // } + // + // void main() + // { + // foo(); + // OutColor = -v1; + // } + + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %foo_ "foo(" +OpName %v1 "v1" +OpName %BaseColor "BaseColor" +OpName %OutColor "OutColor" +OpDecorate %BaseColor Location 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Private_v4float = OpTypePointer Private %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%v1 = OpVariable %_ptr_Private_v4float Private +%OutColor = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %8 +%14 = OpLabel +%15 = OpFunctionCall %void %foo_ +%16 = OpLoad %v4float %v1 +%17 = OpFNegate %v4float %16 +OpStore %OutColor %17 +OpReturn +OpFunctionEnd +%foo_ = OpFunction %void None %8 +%18 = OpLabel +%19 = OpLoad %v4float %BaseColor +OpStore %v1 %19 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(assembly, assembly, true, true); +} + +TEST_F(AggressiveDCETest, EliminateDeadIfThenElse) { + // #version 450 + // + // layout(location = 0) in vec4 BaseColor; + // layout(location = 0) out vec4 OutColor; + // + // void main() + // { + // float d; + // if (BaseColor.x == 0) + // d = BaseColor.y; + // else + // d = BaseColor.z; + // OutColor = vec4(1.0,1.0,1.0,1.0); + // } + + const std::string predefs_before = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %BaseColor "BaseColor" +OpName %d "d" +OpName %OutColor "OutColor" +OpDecorate %BaseColor Location 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%_ptr_Input_float = OpTypePointer Input %float +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%_ptr_Function_float = OpTypePointer Function %float +%uint_1 = OpConstant %uint 1 +%uint_2 = OpConstant %uint 2 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%float_1 = OpConstant %float 1 +%21 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +)"; + + const std::string predefs_after = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %BaseColor "BaseColor" +OpName %OutColor "OutColor" +OpDecorate %BaseColor Location 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%float_1 = OpConstant %float 1 +%21 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +)"; + + const std::string func_before = + R"(%main = OpFunction %void None %7 +%22 = OpLabel +%d = OpVariable %_ptr_Function_float Function +%23 = OpAccessChain %_ptr_Input_float %BaseColor %uint_0 +%24 = OpLoad %float %23 +%25 = OpFOrdEqual %bool %24 %float_0 +OpSelectionMerge %26 None +OpBranchConditional %25 %27 %28 +%27 = OpLabel +%29 = OpAccessChain %_ptr_Input_float %BaseColor %uint_1 +%30 = OpLoad %float %29 +OpStore %d %30 +OpBranch %26 +%28 = OpLabel +%31 = OpAccessChain %_ptr_Input_float %BaseColor %uint_2 +%32 = OpLoad %float %31 +OpStore %d %32 +OpBranch %26 +%26 = OpLabel +OpStore %OutColor %21 +OpReturn +OpFunctionEnd +)"; + + const std::string func_after = + R"(%main = OpFunction %void None %7 +%22 = OpLabel +OpBranch %26 +%26 = OpLabel +OpStore %OutColor %21 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs_before + func_before, predefs_after + func_after, true, true); +} + +TEST_F(AggressiveDCETest, EliminateDeadIfThen) { + // #version 450 + // + // layout(location = 0) in vec4 BaseColor; + // layout(location = 0) out vec4 OutColor; + // + // void main() + // { + // float d; + // if (BaseColor.x == 0) + // d = BaseColor.y; + // OutColor = vec4(1.0,1.0,1.0,1.0); + // } + + const std::string predefs_before = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %BaseColor "BaseColor" +OpName %d "d" +OpName %OutColor "OutColor" +OpDecorate %BaseColor Location 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%_ptr_Input_float = OpTypePointer Input %float +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%_ptr_Function_float = OpTypePointer Function %float +%uint_1 = OpConstant %uint 1 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%float_1 = OpConstant %float 1 +%20 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +)"; + + const std::string predefs_after = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %BaseColor "BaseColor" +OpName %OutColor "OutColor" +OpDecorate %BaseColor Location 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%float_1 = OpConstant %float 1 +%20 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +)"; + + const std::string func_before = + R"(%main = OpFunction %void None %7 +%21 = OpLabel +%d = OpVariable %_ptr_Function_float Function +%22 = OpAccessChain %_ptr_Input_float %BaseColor %uint_0 +%23 = OpLoad %float %22 +%24 = OpFOrdEqual %bool %23 %float_0 +OpSelectionMerge %25 None +OpBranchConditional %24 %26 %25 +%26 = OpLabel +%27 = OpAccessChain %_ptr_Input_float %BaseColor %uint_1 +%28 = OpLoad %float %27 +OpStore %d %28 +OpBranch %25 +%25 = OpLabel +OpStore %OutColor %20 +OpReturn +OpFunctionEnd +)"; + + const std::string func_after = + R"(%main = OpFunction %void None %7 +%21 = OpLabel +OpBranch %25 +%25 = OpLabel +OpStore %OutColor %20 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs_before + func_before, predefs_after + func_after, true, true); +} + +TEST_F(AggressiveDCETest, EliminateDeadSwitch) { + // #version 450 + // + // layout(location = 0) in vec4 BaseColor; + // layout(location = 1) in flat int x; + // layout(location = 0) out vec4 OutColor; + // + // void main() + // { + // float d; + // switch (x) { + // case 0: + // d = BaseColor.y; + // } + // OutColor = vec4(1.0,1.0,1.0,1.0); + // } + const std::string before = + R"(OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %x %BaseColor %OutColor + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 450 + OpName %main "main" + OpName %x "x" + OpName %d "d" + OpName %BaseColor "BaseColor" + OpName %OutColor "OutColor" + OpDecorate %x Flat + OpDecorate %x Location 1 + OpDecorate %BaseColor Location 0 + OpDecorate %OutColor Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Input_int = OpTypePointer Input %int + %x = OpVariable %_ptr_Input_int Input + %float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float + %v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float + %BaseColor = OpVariable %_ptr_Input_v4float Input + %uint = OpTypeInt 32 0 + %uint_1 = OpConstant %uint 1 +%_ptr_Input_float = OpTypePointer Input %float +%_ptr_Output_v4float = OpTypePointer Output %v4float + %OutColor = OpVariable %_ptr_Output_v4float Output + %float_1 = OpConstant %float 1 + %27 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 + %main = OpFunction %void None %3 + %5 = OpLabel + %d = OpVariable %_ptr_Function_float Function + %9 = OpLoad %int %x + OpSelectionMerge %11 None + OpSwitch %9 %11 0 %10 + %10 = OpLabel + %21 = OpAccessChain %_ptr_Input_float %BaseColor %uint_1 + %22 = OpLoad %float %21 + OpStore %d %22 + OpBranch %11 + %11 = OpLabel + OpStore %OutColor %27 + OpReturn + OpFunctionEnd)"; + + const std::string after = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %x %BaseColor %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %x "x" +OpName %BaseColor "BaseColor" +OpName %OutColor "OutColor" +OpDecorate %x Flat +OpDecorate %x Location 1 +OpDecorate %BaseColor Location 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%3 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%_ptr_Input_int = OpTypePointer Input %int +%x = OpVariable %_ptr_Input_int Input +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%float_1 = OpConstant %float 1 +%27 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +%main = OpFunction %void None %3 +%5 = OpLabel +OpBranch %11 +%11 = OpLabel +OpStore %OutColor %27 +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck(before, after, true, true); +} + +TEST_F(AggressiveDCETest, EliminateDeadIfThenElseNested) { + // #version 450 + // + // layout(location = 0) in vec4 BaseColor; + // layout(location = 0) out vec4 OutColor; + // + // void main() + // { + // float d; + // if (BaseColor.x == 0) + // if (BaseColor.y == 0) + // d = 0.0; + // else + // d = 0.25; + // else + // if (BaseColor.y == 0) + // d = 0.5; + // else + // d = 0.75; + // OutColor = vec4(1.0,1.0,1.0,1.0); + // } + + const std::string predefs_before = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %BaseColor "BaseColor" +OpName %d "d" +OpName %OutColor "OutColor" +OpDecorate %BaseColor Location 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%_ptr_Input_float = OpTypePointer Input %float +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%uint_1 = OpConstant %uint 1 +%_ptr_Function_float = OpTypePointer Function %float +%float_0_25 = OpConstant %float 0.25 +%float_0_5 = OpConstant %float 0.5 +%float_0_75 = OpConstant %float 0.75 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%float_1 = OpConstant %float 1 +%23 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +)"; + + const std::string predefs_after = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %BaseColor "BaseColor" +OpName %OutColor "OutColor" +OpDecorate %BaseColor Location 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%float_1 = OpConstant %float 1 +%23 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +)"; + + const std::string func_before = + R"(%main = OpFunction %void None %7 +%24 = OpLabel +%d = OpVariable %_ptr_Function_float Function +%25 = OpAccessChain %_ptr_Input_float %BaseColor %uint_0 +%26 = OpLoad %float %25 +%27 = OpFOrdEqual %bool %26 %float_0 +OpSelectionMerge %28 None +OpBranchConditional %27 %29 %30 +%29 = OpLabel +%31 = OpAccessChain %_ptr_Input_float %BaseColor %uint_1 +%32 = OpLoad %float %31 +%33 = OpFOrdEqual %bool %32 %float_0 +OpSelectionMerge %34 None +OpBranchConditional %33 %35 %36 +%35 = OpLabel +OpStore %d %float_0 +OpBranch %34 +%36 = OpLabel +OpStore %d %float_0_25 +OpBranch %34 +%34 = OpLabel +OpBranch %28 +%30 = OpLabel +%37 = OpAccessChain %_ptr_Input_float %BaseColor %uint_1 +%38 = OpLoad %float %37 +%39 = OpFOrdEqual %bool %38 %float_0 +OpSelectionMerge %40 None +OpBranchConditional %39 %41 %42 +%41 = OpLabel +OpStore %d %float_0_5 +OpBranch %40 +%42 = OpLabel +OpStore %d %float_0_75 +OpBranch %40 +%40 = OpLabel +OpBranch %28 +%28 = OpLabel +OpStore %OutColor %23 +OpReturn +OpFunctionEnd +)"; + + const std::string func_after = + R"(%main = OpFunction %void None %7 +%24 = OpLabel +OpBranch %28 +%28 = OpLabel +OpStore %OutColor %23 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs_before + func_before, predefs_after + func_after, true, true); +} + +TEST_F(AggressiveDCETest, NoEliminateLiveIfThenElse) { + // #version 450 + // + // layout(location = 0) in vec4 BaseColor; + // layout(location = 0) out vec4 OutColor; + // + // void main() + // { + // float t; + // if (BaseColor.x == 0) + // t = BaseColor.y; + // else + // t = BaseColor.z; + // OutColor = vec4(t); + // } + + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %BaseColor "BaseColor" +OpName %t "t" +OpName %OutColor "OutColor" +OpDecorate %BaseColor Location 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%_ptr_Input_float = OpTypePointer Input %float +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%_ptr_Function_float = OpTypePointer Function %float +%uint_1 = OpConstant %uint 1 +%uint_2 = OpConstant %uint 2 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %7 +%20 = OpLabel +%t = OpVariable %_ptr_Function_float Function +%21 = OpAccessChain %_ptr_Input_float %BaseColor %uint_0 +%22 = OpLoad %float %21 +%23 = OpFOrdEqual %bool %22 %float_0 +OpSelectionMerge %24 None +OpBranchConditional %23 %25 %26 +%25 = OpLabel +%27 = OpAccessChain %_ptr_Input_float %BaseColor %uint_1 +%28 = OpLoad %float %27 +OpStore %t %28 +OpBranch %24 +%26 = OpLabel +%29 = OpAccessChain %_ptr_Input_float %BaseColor %uint_2 +%30 = OpLoad %float %29 +OpStore %t %30 +OpBranch %24 +%24 = OpLabel +%31 = OpLoad %float %t +%32 = OpCompositeConstruct %v4float %31 %31 %31 %31 +OpStore %OutColor %32 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(assembly, assembly, true, true); +} + +TEST_F(AggressiveDCETest, NoEliminateLiveIfThenElseNested) { + // #version 450 + // + // layout(location = 0) in vec4 BaseColor; + // layout(location = 0) out vec4 OutColor; + // + // void main() + // { + // float t; + // if (BaseColor.x == 0) + // if (BaseColor.y == 0) + // t = 0.0; + // else + // t = 0.25; + // else + // if (BaseColor.y == 0) + // t = 0.5; + // else + // t = 0.75; + // OutColor = vec4(t); + // } + + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %BaseColor "BaseColor" +OpName %t "t" +OpName %OutColor "OutColor" +OpDecorate %BaseColor Location 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%_ptr_Input_float = OpTypePointer Input %float +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%uint_1 = OpConstant %uint 1 +%_ptr_Function_float = OpTypePointer Function %float +%float_0_25 = OpConstant %float 0.25 +%float_0_5 = OpConstant %float 0.5 +%float_0_75 = OpConstant %float 0.75 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %7 +%22 = OpLabel +%t = OpVariable %_ptr_Function_float Function +%23 = OpAccessChain %_ptr_Input_float %BaseColor %uint_0 +%24 = OpLoad %float %23 +%25 = OpFOrdEqual %bool %24 %float_0 +OpSelectionMerge %26 None +OpBranchConditional %25 %27 %28 +%27 = OpLabel +%29 = OpAccessChain %_ptr_Input_float %BaseColor %uint_1 +%30 = OpLoad %float %29 +%31 = OpFOrdEqual %bool %30 %float_0 +OpSelectionMerge %32 None +OpBranchConditional %31 %33 %34 +%33 = OpLabel +OpStore %t %float_0 +OpBranch %32 +%34 = OpLabel +OpStore %t %float_0_25 +OpBranch %32 +%32 = OpLabel +OpBranch %26 +%28 = OpLabel +%35 = OpAccessChain %_ptr_Input_float %BaseColor %uint_1 +%36 = OpLoad %float %35 +%37 = OpFOrdEqual %bool %36 %float_0 +OpSelectionMerge %38 None +OpBranchConditional %37 %39 %40 +%39 = OpLabel +OpStore %t %float_0_5 +OpBranch %38 +%40 = OpLabel +OpStore %t %float_0_75 +OpBranch %38 +%38 = OpLabel +OpBranch %26 +%26 = OpLabel +%41 = OpLoad %float %t +%42 = OpCompositeConstruct %v4float %41 %41 %41 %41 +OpStore %OutColor %42 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(assembly, assembly, true, true); +} + +TEST_F(AggressiveDCETest, NoEliminateIfWithPhi) { + // Note: Assembly hand-optimized from GLSL + // + // #version 450 + // + // layout(location = 0) in vec4 BaseColor; + // layout(location = 0) out vec4 OutColor; + // + // void main() + // { + // float t; + // if (BaseColor.x == 0) + // t = 0.0; + // else + // t = 1.0; + // OutColor = vec4(t); + // } + + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %BaseColor "BaseColor" +OpName %OutColor "OutColor" +OpDecorate %BaseColor Location 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%6 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%_ptr_Input_float = OpTypePointer Input %float +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%float_1 = OpConstant %float 1 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %6 +%17 = OpLabel +%18 = OpAccessChain %_ptr_Input_float %BaseColor %uint_0 +%19 = OpLoad %float %18 +%20 = OpFOrdEqual %bool %19 %float_0 +OpSelectionMerge %21 None +OpBranchConditional %20 %22 %23 +%22 = OpLabel +OpBranch %21 +%23 = OpLabel +OpBranch %21 +%21 = OpLabel +%24 = OpPhi %float %float_0 %22 %float_1 %23 +%25 = OpCompositeConstruct %v4float %24 %24 %24 %24 +OpStore %OutColor %25 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(assembly, assembly, true, true); +} + +TEST_F(AggressiveDCETest, NoEliminateIfBreak) { + // Note: Assembly optimized from GLSL + // + // #version 450 + // + // layout(location=0) in vec4 InColor; + // layout(location=0) out vec4 OutColor; + // + // void main() + // { + // float f = 0.0; + // for (;;) { + // f += 2.0; + // if (f > 20.0) + // break; + // } + // + // OutColor = InColor / f; + // } + + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %OutColor %InColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %f "f" +OpName %OutColor "OutColor" +OpName %InColor "InColor" +OpDecorate %OutColor Location 0 +OpDecorate %InColor Location 0 +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float +%float_0 = OpConstant %float 0 +%float_2 = OpConstant %float 2 +%float_20 = OpConstant %float 20 +%bool = OpTypeBool +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%_ptr_Input_v4float = OpTypePointer Input %v4float +%InColor = OpVariable %_ptr_Input_v4float Input +%main = OpFunction %void None %7 +%17 = OpLabel +%f = OpVariable %_ptr_Function_float Function +OpStore %f %float_0 +OpBranch %18 +%18 = OpLabel +OpLoopMerge %19 %20 None +OpBranch %21 +%21 = OpLabel +%22 = OpLoad %float %f +%23 = OpFAdd %float %22 %float_2 +OpStore %f %23 +%24 = OpLoad %float %f +%25 = OpFOrdGreaterThan %bool %24 %float_20 +OpSelectionMerge %26 None +OpBranchConditional %25 %27 %26 +%27 = OpLabel +OpBranch %19 +%26 = OpLabel +OpBranch %20 +%20 = OpLabel +OpBranch %18 +%19 = OpLabel +%28 = OpLoad %v4float %InColor +%29 = OpLoad %float %f +%30 = OpCompositeConstruct %v4float %29 %29 %29 %29 +%31 = OpFDiv %v4float %28 %30 +OpStore %OutColor %31 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(assembly, assembly, true, true); +} + +TEST_F(AggressiveDCETest, NoEliminateIfBreak2) { + // Do not eliminate break as conditional branch with merge instruction + // Note: SPIR-V edited to add merge instruction before break. + // + // #version 430 + // + // layout(std430) buffer U_t + // { + // float g_F[10]; + // }; + // + // layout(location = 0)out float o; + // + // void main(void) + // { + // float s = 0.0; + // for (int i=0; i<10; i++) + // s += g_F[i]; + // o = s; + // } + + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %o +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 430 +OpName %main "main" +OpName %s "s" +OpName %i "i" +OpName %U_t "U_t" +OpMemberName %U_t 0 "g_F" +OpName %_ "" +OpName %o "o" +OpDecorate %_arr_float_uint_10 ArrayStride 4 +OpMemberDecorate %U_t 0 Offset 0 +OpDecorate %U_t BufferBlock +OpDecorate %_ DescriptorSet 0 +OpDecorate %o Location 0 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float +%float_0 = OpConstant %float 0 +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_0 = OpConstant %int 0 +%int_10 = OpConstant %int 10 +%bool = OpTypeBool +%uint = OpTypeInt 32 0 +%uint_10 = OpConstant %uint 10 +%_arr_float_uint_10 = OpTypeArray %float %uint_10 +%U_t = OpTypeStruct %_arr_float_uint_10 +%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t +%_ = OpVariable %_ptr_Uniform_U_t Uniform +%_ptr_Uniform_float = OpTypePointer Uniform %float +%int_1 = OpConstant %int 1 +%_ptr_Output_float = OpTypePointer Output %float +%o = OpVariable %_ptr_Output_float Output +%main = OpFunction %void None %10 +%25 = OpLabel +%s = OpVariable %_ptr_Function_float Function +%i = OpVariable %_ptr_Function_int Function +OpStore %s %float_0 +OpStore %i %int_0 +OpBranch %26 +%26 = OpLabel +OpLoopMerge %27 %28 None +OpBranch %29 +%29 = OpLabel +%30 = OpLoad %int %i +%31 = OpSLessThan %bool %30 %int_10 +OpSelectionMerge %32 None +OpBranchConditional %31 %32 %27 +%32 = OpLabel +%33 = OpLoad %int %i +%34 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %33 +%35 = OpLoad %float %34 +%36 = OpLoad %float %s +%37 = OpFAdd %float %36 %35 +OpStore %s %37 +OpBranch %28 +%28 = OpLabel +%38 = OpLoad %int %i +%39 = OpIAdd %int %38 %int_1 +OpStore %i %39 +OpBranch %26 +%27 = OpLabel +%40 = OpLoad %float %s +OpStore %o %40 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(assembly, assembly, true, true); +} + +TEST_F(AggressiveDCETest, EliminateEntireUselessLoop) { + // #version 140 + // in vec4 BaseColor; + // + // layout(std140) uniform U_t + // { + // int g_I ; + // } ; + // + // void main() + // { + // vec4 v = BaseColor; + // float df = 0.0; + // int i = 0; + // while (i < g_I) { + // df = df * 0.5; + // i = i + 1; + // } + // gl_FragColor = v; + // } + + const std::string predefs1 = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +)"; + + const std::string names_before = + R"(OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %df "df" +OpName %i "i" +OpName %U_t "U_t" +OpMemberName %U_t 0 "g_I" +OpName %_ "" +OpName %gl_FragColor "gl_FragColor" +)"; + + const std::string names_after = + R"(OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %gl_FragColor "gl_FragColor" +)"; + + const std::string predefs2_before = + R"(OpMemberDecorate %U_t 0 Offset 0 +OpDecorate %U_t Block +OpDecorate %_ DescriptorSet 0 +%void = OpTypeVoid +%11 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Function_float = OpTypePointer Function %float +%float_0 = OpConstant %float 0 +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_0 = OpConstant %int 0 +%U_t = OpTypeStruct %int +%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t +%_ = OpVariable %_ptr_Uniform_U_t Uniform +%_ptr_Uniform_int = OpTypePointer Uniform %int +%bool = OpTypeBool +%float_0_5 = OpConstant %float 0.5 +%int_1 = OpConstant %int 1 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string predefs2_after = + R"(%void = OpTypeVoid +%11 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string func_before = + R"(%main = OpFunction %void None %11 +%27 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%df = OpVariable %_ptr_Function_float Function +%i = OpVariable %_ptr_Function_int Function +%28 = OpLoad %v4float %BaseColor +OpStore %v %28 +OpStore %df %float_0 +OpStore %i %int_0 +OpBranch %29 +%29 = OpLabel +OpLoopMerge %30 %31 None +OpBranch %32 +%32 = OpLabel +%33 = OpLoad %int %i +%34 = OpAccessChain %_ptr_Uniform_int %_ %int_0 +%35 = OpLoad %int %34 +%36 = OpSLessThan %bool %33 %35 +OpBranchConditional %36 %37 %30 +%37 = OpLabel +%38 = OpLoad %float %df +%39 = OpFMul %float %38 %float_0_5 +OpStore %df %39 +%40 = OpLoad %int %i +%41 = OpIAdd %int %40 %int_1 +OpStore %i %41 +OpBranch %31 +%31 = OpLabel +OpBranch %29 +%30 = OpLabel +%42 = OpLoad %v4float %v +OpStore %gl_FragColor %42 +OpReturn +OpFunctionEnd +)"; + + const std::string func_after = + R"(%main = OpFunction %void None %11 +%27 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%28 = OpLoad %v4float %BaseColor +OpStore %v %28 +OpBranch %29 +%29 = OpLabel +OpBranch %30 +%30 = OpLabel +%42 = OpLoad %v4float %v +OpStore %gl_FragColor %42 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs1 + names_before + predefs2_before + func_before, + predefs1 + names_after + predefs2_after + func_after, true, true); +} + +TEST_F(AggressiveDCETest, NoEliminateBusyLoop) { + // Note: SPIR-V edited to replace AtomicAdd(i,0) with AtomicLoad(i) + // + // #version 450 + // + // layout(std430) buffer I_t + // { + // int g_I; + // int g_I2; + // }; + // + // layout(location = 0) out int o; + // + // void main(void) + // { + // while (atomicAdd(g_I, 0) == 0) {} + // o = g_I2; + // } + + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %o +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %I_t "I_t" +OpMemberName %I_t 0 "g_I" +OpMemberName %I_t 1 "g_I2" +OpName %_ "" +OpName %o "o" +OpMemberDecorate %I_t 0 Offset 0 +OpMemberDecorate %I_t 1 Offset 4 +OpDecorate %I_t BufferBlock +OpDecorate %_ DescriptorSet 0 +OpDecorate %o Location 0 +%void = OpTypeVoid +%7 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%I_t = OpTypeStruct %int %int +%_ptr_Uniform_I_t = OpTypePointer Uniform %I_t +%_ = OpVariable %_ptr_Uniform_I_t Uniform +%int_0 = OpConstant %int 0 +%int_1 = OpConstant %int 1 +%_ptr_Uniform_int = OpTypePointer Uniform %int +%uint = OpTypeInt 32 0 +%uint_1 = OpConstant %uint 1 +%uint_0 = OpConstant %uint 0 +%bool = OpTypeBool +%_ptr_Output_int = OpTypePointer Output %int +%o = OpVariable %_ptr_Output_int Output +%main = OpFunction %void None %7 +%18 = OpLabel +OpBranch %19 +%19 = OpLabel +OpLoopMerge %20 %21 None +OpBranch %22 +%22 = OpLabel +%23 = OpAccessChain %_ptr_Uniform_int %_ %int_0 +%24 = OpAtomicLoad %int %23 %uint_1 %uint_0 +%25 = OpIEqual %bool %24 %int_0 +OpBranchConditional %25 %26 %20 +%26 = OpLabel +OpBranch %21 +%21 = OpLabel +OpBranch %19 +%20 = OpLabel +%27 = OpAccessChain %_ptr_Uniform_int %_ %int_1 +%28 = OpLoad %int %27 +OpStore %o %28 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(assembly, assembly, true, true); +} + +TEST_F(AggressiveDCETest, NoEliminateLiveLoop) { + // Note: SPIR-V optimized + // + // #version 430 + // + // layout(std430) buffer U_t + // { + // float g_F[10]; + // }; + // + // layout(location = 0)out float o; + // + // void main(void) + // { + // float s = 0.0; + // for (int i=0; i<10; i++) + // s += g_F[i]; + // o = s; + // } + + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %o +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 430 +OpName %main "main" +OpName %U_t "U_t" +OpMemberName %U_t 0 "g_F" +OpName %_ "" +OpName %o "o" +OpDecorate %_arr_float_uint_10 ArrayStride 4 +OpMemberDecorate %U_t 0 Offset 0 +OpDecorate %U_t BufferBlock +OpDecorate %_ DescriptorSet 0 +OpDecorate %o Location 0 +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%float_0 = OpConstant %float 0 +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%int_10 = OpConstant %int 10 +%bool = OpTypeBool +%uint = OpTypeInt 32 0 +%uint_10 = OpConstant %uint 10 +%_arr_float_uint_10 = OpTypeArray %float %uint_10 +%U_t = OpTypeStruct %_arr_float_uint_10 +%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t +%_ = OpVariable %_ptr_Uniform_U_t Uniform +%_ptr_Uniform_float = OpTypePointer Uniform %float +%int_1 = OpConstant %int 1 +%_ptr_Output_float = OpTypePointer Output %float +%o = OpVariable %_ptr_Output_float Output +%main = OpFunction %void None %8 +%21 = OpLabel +OpBranch %22 +%22 = OpLabel +%23 = OpPhi %float %float_0 %21 %24 %25 +%26 = OpPhi %int %int_0 %21 %27 %25 +OpLoopMerge %28 %25 None +OpBranch %29 +%29 = OpLabel +%30 = OpSLessThan %bool %26 %int_10 +OpBranchConditional %30 %31 %28 +%31 = OpLabel +%32 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %26 +%33 = OpLoad %float %32 +%24 = OpFAdd %float %23 %33 +OpBranch %25 +%25 = OpLabel +%27 = OpIAdd %int %26 %int_1 +OpBranch %22 +%28 = OpLabel +OpStore %o %23 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(assembly, assembly, true, true); +} + +TEST_F(AggressiveDCETest, EliminateEntireFunctionBody) { + // #version 450 + // + // layout(location = 0) in vec4 BaseColor; + // layout(location = 0) out vec4 OutColor; + // + // void main() + // { + // float d; + // if (BaseColor.x == 0) + // d = BaseColor.y; + // else + // d = BaseColor.z; + // } + + const std::string predefs_before = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %BaseColor "BaseColor" +OpName %d "d" +OpName %OutColor "OutColor" +OpDecorate %BaseColor Location 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%_ptr_Input_float = OpTypePointer Input %float +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%_ptr_Function_float = OpTypePointer Function %float +%uint_1 = OpConstant %uint 1 +%uint_2 = OpConstant %uint 2 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string predefs_after = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %BaseColor "BaseColor" +OpName %OutColor "OutColor" +OpDecorate %BaseColor Location 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string func_before = + R"(%main = OpFunction %void None %7 +%20 = OpLabel +%d = OpVariable %_ptr_Function_float Function +%21 = OpAccessChain %_ptr_Input_float %BaseColor %uint_0 +%22 = OpLoad %float %21 +%23 = OpFOrdEqual %bool %22 %float_0 +OpSelectionMerge %24 None +OpBranchConditional %23 %25 %26 +%25 = OpLabel +%27 = OpAccessChain %_ptr_Input_float %BaseColor %uint_1 +%28 = OpLoad %float %27 +OpStore %d %28 +OpBranch %24 +%26 = OpLabel +%29 = OpAccessChain %_ptr_Input_float %BaseColor %uint_2 +%30 = OpLoad %float %29 +OpStore %d %30 +OpBranch %24 +%24 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string func_after = + R"(%main = OpFunction %void None %7 +%20 = OpLabel +OpBranch %24 +%24 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs_before + func_before, predefs_after + func_after, true, true); +} + +TEST_F(AggressiveDCETest, EliminateUselessInnerLoop) { + // #version 430 + // + // layout(std430) buffer U_t + // { + // float g_F[10]; + // }; + // + // layout(location = 0)out float o; + // + // void main(void) + // { + // float s = 0.0; + // for (int i=0; i<10; i++) { + // for (int j=0; j<10; j++) { + // } + // s += g_F[i]; + // } + // o = s; + // } + + const std::string predefs_before = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %o +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 430 +OpName %main "main" +OpName %s "s" +OpName %i "i" +OpName %j "j" +OpName %U_t "U_t" +OpMemberName %U_t 0 "g_F" +OpName %_ "" +OpName %o "o" +OpDecorate %_arr_float_uint_10 ArrayStride 4 +OpMemberDecorate %U_t 0 Offset 0 +OpDecorate %U_t BufferBlock +OpDecorate %_ DescriptorSet 0 +OpDecorate %o Location 0 +%void = OpTypeVoid +%11 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float +%float_0 = OpConstant %float 0 +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_0 = OpConstant %int 0 +%int_10 = OpConstant %int 10 +%bool = OpTypeBool +%int_1 = OpConstant %int 1 +%uint = OpTypeInt 32 0 +%uint_10 = OpConstant %uint 10 +%_arr_float_uint_10 = OpTypeArray %float %uint_10 +%U_t = OpTypeStruct %_arr_float_uint_10 +%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t +%_ = OpVariable %_ptr_Uniform_U_t Uniform +%_ptr_Uniform_float = OpTypePointer Uniform %float +%_ptr_Output_float = OpTypePointer Output %float +%o = OpVariable %_ptr_Output_float Output +)"; + + const std::string predefs_after = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %o +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 430 +OpName %main "main" +OpName %s "s" +OpName %i "i" +OpName %U_t "U_t" +OpMemberName %U_t 0 "g_F" +OpName %_ "" +OpName %o "o" +OpDecorate %_arr_float_uint_10 ArrayStride 4 +OpMemberDecorate %U_t 0 Offset 0 +OpDecorate %U_t BufferBlock +OpDecorate %_ DescriptorSet 0 +OpDecorate %o Location 0 +%void = OpTypeVoid +%11 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float +%float_0 = OpConstant %float 0 +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_0 = OpConstant %int 0 +%int_10 = OpConstant %int 10 +%bool = OpTypeBool +%int_1 = OpConstant %int 1 +%uint = OpTypeInt 32 0 +%uint_10 = OpConstant %uint 10 +%_arr_float_uint_10 = OpTypeArray %float %uint_10 +%U_t = OpTypeStruct %_arr_float_uint_10 +%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t +%_ = OpVariable %_ptr_Uniform_U_t Uniform +%_ptr_Uniform_float = OpTypePointer Uniform %float +%_ptr_Output_float = OpTypePointer Output %float +%o = OpVariable %_ptr_Output_float Output +)"; + + const std::string func_before = + R"(%main = OpFunction %void None %11 +%26 = OpLabel +%s = OpVariable %_ptr_Function_float Function +%i = OpVariable %_ptr_Function_int Function +%j = OpVariable %_ptr_Function_int Function +OpStore %s %float_0 +OpStore %i %int_0 +OpBranch %27 +%27 = OpLabel +OpLoopMerge %28 %29 None +OpBranch %30 +%30 = OpLabel +%31 = OpLoad %int %i +%32 = OpSLessThan %bool %31 %int_10 +OpBranchConditional %32 %33 %28 +%33 = OpLabel +OpStore %j %int_0 +OpBranch %34 +%34 = OpLabel +OpLoopMerge %35 %36 None +OpBranch %37 +%37 = OpLabel +%38 = OpLoad %int %j +%39 = OpSLessThan %bool %38 %int_10 +OpBranchConditional %39 %40 %35 +%40 = OpLabel +OpBranch %36 +%36 = OpLabel +%41 = OpLoad %int %j +%42 = OpIAdd %int %41 %int_1 +OpStore %j %42 +OpBranch %34 +%35 = OpLabel +%43 = OpLoad %int %i +%44 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %43 +%45 = OpLoad %float %44 +%46 = OpLoad %float %s +%47 = OpFAdd %float %46 %45 +OpStore %s %47 +OpBranch %29 +%29 = OpLabel +%48 = OpLoad %int %i +%49 = OpIAdd %int %48 %int_1 +OpStore %i %49 +OpBranch %27 +%28 = OpLabel +%50 = OpLoad %float %s +OpStore %o %50 +OpReturn +OpFunctionEnd +)"; + + const std::string func_after = + R"(%main = OpFunction %void None %11 +%26 = OpLabel +%s = OpVariable %_ptr_Function_float Function +%i = OpVariable %_ptr_Function_int Function +OpStore %s %float_0 +OpStore %i %int_0 +OpBranch %27 +%27 = OpLabel +OpLoopMerge %28 %29 None +OpBranch %30 +%30 = OpLabel +%31 = OpLoad %int %i +%32 = OpSLessThan %bool %31 %int_10 +OpBranchConditional %32 %33 %28 +%33 = OpLabel +OpBranch %34 +%34 = OpLabel +OpBranch %35 +%35 = OpLabel +%43 = OpLoad %int %i +%44 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %43 +%45 = OpLoad %float %44 +%46 = OpLoad %float %s +%47 = OpFAdd %float %46 %45 +OpStore %s %47 +OpBranch %29 +%29 = OpLabel +%48 = OpLoad %int %i +%49 = OpIAdd %int %48 %int_1 +OpStore %i %49 +OpBranch %27 +%28 = OpLabel +%50 = OpLoad %float %s +OpStore %o %50 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs_before + func_before, predefs_after + func_after, true, true); +} + +TEST_F(AggressiveDCETest, EliminateUselessNestedLoopWithIf) { + // #version 430 + // + // layout(std430) buffer U_t + // { + // float g_F[10][10]; + // }; + // + // layout(location = 0)out float o; + // + // void main(void) + // { + // float s = 0.0; + // for (int i=0; i<10; i++) { + // for (int j=0; j<10; j++) { + // float t = g_F[i][j]; + // if (t > 0.0) + // s += t; + // } + // } + // o = 0.0; + // } + + const std::string predefs_before = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %o +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 430 +OpName %main "main" +OpName %s "s" +OpName %i "i" +OpName %j "j" +OpName %U_t "U_t" +OpMemberName %U_t 0 "g_F" +OpName %_ "" +OpName %o "o" +OpDecorate %_arr_float_uint_10 ArrayStride 4 +OpDecorate %_arr__arr_float_uint_10_uint_10 ArrayStride 40 +OpMemberDecorate %U_t 0 Offset 0 +OpDecorate %U_t BufferBlock +OpDecorate %_ DescriptorSet 0 +OpDecorate %o Location 0 +%void = OpTypeVoid +%12 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float +%float_0 = OpConstant %float 0 +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_0 = OpConstant %int 0 +%int_10 = OpConstant %int 10 +%bool = OpTypeBool +%uint = OpTypeInt 32 0 +%uint_10 = OpConstant %uint 10 +%_arr_float_uint_10 = OpTypeArray %float %uint_10 +%_arr__arr_float_uint_10_uint_10 = OpTypeArray %_arr_float_uint_10 %uint_10 +%U_t = OpTypeStruct %_arr__arr_float_uint_10_uint_10 +%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t +%_ = OpVariable %_ptr_Uniform_U_t Uniform +%_ptr_Uniform_float = OpTypePointer Uniform %float +%int_1 = OpConstant %int 1 +%_ptr_Output_float = OpTypePointer Output %float +%o = OpVariable %_ptr_Output_float Output +)"; + + const std::string predefs_after = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %o +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 430 +OpName %main "main" +OpName %o "o" +OpDecorate %o Location 0 +%void = OpTypeVoid +%12 = OpTypeFunction %void +%float = OpTypeFloat 32 +%float_0 = OpConstant %float 0 +%_ptr_Output_float = OpTypePointer Output %float +%o = OpVariable %_ptr_Output_float Output +)"; + + const std::string func_before = + R"(%main = OpFunction %void None %12 +%27 = OpLabel +%s = OpVariable %_ptr_Function_float Function +%i = OpVariable %_ptr_Function_int Function +%j = OpVariable %_ptr_Function_int Function +OpStore %s %float_0 +OpStore %i %int_0 +OpBranch %28 +%28 = OpLabel +OpLoopMerge %29 %30 None +OpBranch %31 +%31 = OpLabel +%32 = OpLoad %int %i +%33 = OpSLessThan %bool %32 %int_10 +OpBranchConditional %33 %34 %29 +%34 = OpLabel +OpStore %j %int_0 +OpBranch %35 +%35 = OpLabel +OpLoopMerge %36 %37 None +OpBranch %38 +%38 = OpLabel +%39 = OpLoad %int %j +%40 = OpSLessThan %bool %39 %int_10 +OpBranchConditional %40 %41 %36 +%41 = OpLabel +%42 = OpLoad %int %i +%43 = OpLoad %int %j +%44 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %42 %43 +%45 = OpLoad %float %44 +%46 = OpFOrdGreaterThan %bool %45 %float_0 +OpSelectionMerge %47 None +OpBranchConditional %46 %48 %47 +%48 = OpLabel +%49 = OpLoad %float %s +%50 = OpFAdd %float %49 %45 +OpStore %s %50 +OpBranch %47 +%47 = OpLabel +OpBranch %37 +%37 = OpLabel +%51 = OpLoad %int %j +%52 = OpIAdd %int %51 %int_1 +OpStore %j %52 +OpBranch %35 +%36 = OpLabel +OpBranch %30 +%30 = OpLabel +%53 = OpLoad %int %i +%54 = OpIAdd %int %53 %int_1 +OpStore %i %54 +OpBranch %28 +%29 = OpLabel +OpStore %o %float_0 +OpReturn +OpFunctionEnd +)"; + + const std::string func_after = + R"(%main = OpFunction %void None %12 +%27 = OpLabel +OpBranch %28 +%28 = OpLabel +OpBranch %29 +%29 = OpLabel +OpStore %o %float_0 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs_before + func_before, predefs_after + func_after, true, true); +} + +TEST_F(AggressiveDCETest, EliminateEmptyIfBeforeContinue) { + // #version 430 + // + // layout(location = 0)out float o; + // + // void main(void) + // { + // float s = 0.0; + // for (int i=0; i<10; i++) { + // s += 1.0; + // if (i > s) {} + // } + // o = s; + // } + + const std::string predefs_before = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %3 +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 430 +OpSourceExtension "GL_GOOGLE_cpp_style_line_directive" +OpSourceExtension "GL_GOOGLE_include_directive" +OpName %main "main" +OpDecorate %3 Location 0 +%void = OpTypeVoid +%5 = OpTypeFunction %void +%float = OpTypeFloat 32 +%float_0 = OpConstant %float 0 +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_0 = OpConstant %int 0 +%int_10 = OpConstant %int 10 +%bool = OpTypeBool +%float_1 = OpConstant %float 1 +%int_1 = OpConstant %int 1 +%_ptr_Output_float = OpTypePointer Output %float +%3 = OpVariable %_ptr_Output_float Output +)"; + + const std::string predefs_after = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %3 +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 430 +OpSourceExtension "GL_GOOGLE_cpp_style_line_directive" +OpSourceExtension "GL_GOOGLE_include_directive" +OpName %main "main" +OpDecorate %3 Location 0 +%void = OpTypeVoid +%5 = OpTypeFunction %void +%float = OpTypeFloat 32 +%float_0 = OpConstant %float 0 +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%int_10 = OpConstant %int 10 +%bool = OpTypeBool +%float_1 = OpConstant %float 1 +%int_1 = OpConstant %int 1 +%_ptr_Output_float = OpTypePointer Output %float +%3 = OpVariable %_ptr_Output_float Output +)"; + + const std::string func_before = + R"(%main = OpFunction %void None %5 +%16 = OpLabel +OpBranch %17 +%17 = OpLabel +%18 = OpPhi %float %float_0 %16 %19 %20 +%21 = OpPhi %int %int_0 %16 %22 %20 +OpLoopMerge %23 %20 None +OpBranch %24 +%24 = OpLabel +%25 = OpSLessThan %bool %21 %int_10 +OpBranchConditional %25 %26 %23 +%26 = OpLabel +%19 = OpFAdd %float %18 %float_1 +%27 = OpConvertFToS %int %19 +%28 = OpSGreaterThan %bool %21 %27 +OpSelectionMerge %20 None +OpBranchConditional %28 %29 %20 +%29 = OpLabel +OpBranch %20 +%20 = OpLabel +%22 = OpIAdd %int %21 %int_1 +OpBranch %17 +%23 = OpLabel +OpStore %3 %18 +OpReturn +OpFunctionEnd +)"; + + const std::string func_after = + R"(%main = OpFunction %void None %5 +%16 = OpLabel +OpBranch %17 +%17 = OpLabel +%18 = OpPhi %float %float_0 %16 %19 %20 +%21 = OpPhi %int %int_0 %16 %22 %20 +OpLoopMerge %23 %20 None +OpBranch %24 +%24 = OpLabel +%25 = OpSLessThan %bool %21 %int_10 +OpBranchConditional %25 %26 %23 +%26 = OpLabel +%19 = OpFAdd %float %18 %float_1 +OpBranch %20 +%20 = OpLabel +%22 = OpIAdd %int %21 %int_1 +OpBranch %17 +%23 = OpLabel +OpStore %3 %18 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs_before + func_before, predefs_after + func_after, true, true); +} + +TEST_F(AggressiveDCETest, NoEliminateLiveNestedLoopWithIf) { + // Note: SPIR-V optimized + // + // #version 430 + // + // layout(std430) buffer U_t + // { + // float g_F[10][10]; + // }; + // + // layout(location = 0)out float o; + // + // void main(void) + // { + // float s = 0.0; + // for (int i=0; i<10; i++) { + // for (int j=0; j<10; j++) { + // float t = g_F[i][j]; + // if (t > 0.0) + // s += t; + // } + // } + // o = s; + // } + + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %o +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 430 +OpName %main "main" +OpName %s "s" +OpName %i "i" +OpName %j "j" +OpName %U_t "U_t" +OpMemberName %U_t 0 "g_F" +OpName %_ "" +OpName %o "o" +OpDecorate %_arr_float_uint_10 ArrayStride 4 +OpDecorate %_arr__arr_float_uint_10_uint_10 ArrayStride 40 +OpMemberDecorate %U_t 0 Offset 0 +OpDecorate %U_t BufferBlock +OpDecorate %_ DescriptorSet 0 +OpDecorate %o Location 0 +%void = OpTypeVoid +%12 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float +%float_0 = OpConstant %float 0 +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_0 = OpConstant %int 0 +%int_10 = OpConstant %int 10 +%bool = OpTypeBool +%uint = OpTypeInt 32 0 +%uint_10 = OpConstant %uint 10 +%_arr_float_uint_10 = OpTypeArray %float %uint_10 +%_arr__arr_float_uint_10_uint_10 = OpTypeArray %_arr_float_uint_10 %uint_10 +%U_t = OpTypeStruct %_arr__arr_float_uint_10_uint_10 +%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t +%_ = OpVariable %_ptr_Uniform_U_t Uniform +%_ptr_Uniform_float = OpTypePointer Uniform %float +%int_1 = OpConstant %int 1 +%_ptr_Output_float = OpTypePointer Output %float +%o = OpVariable %_ptr_Output_float Output +%main = OpFunction %void None %12 +%27 = OpLabel +%s = OpVariable %_ptr_Function_float Function +%i = OpVariable %_ptr_Function_int Function +%j = OpVariable %_ptr_Function_int Function +OpStore %s %float_0 +OpStore %i %int_0 +OpBranch %28 +%28 = OpLabel +OpLoopMerge %29 %30 None +OpBranch %31 +%31 = OpLabel +%32 = OpLoad %int %i +%33 = OpSLessThan %bool %32 %int_10 +OpBranchConditional %33 %34 %29 +%34 = OpLabel +OpStore %j %int_0 +OpBranch %35 +%35 = OpLabel +OpLoopMerge %36 %37 None +OpBranch %38 +%38 = OpLabel +%39 = OpLoad %int %j +%40 = OpSLessThan %bool %39 %int_10 +OpBranchConditional %40 %41 %36 +%41 = OpLabel +%42 = OpLoad %int %i +%43 = OpLoad %int %j +%44 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %42 %43 +%45 = OpLoad %float %44 +%46 = OpFOrdGreaterThan %bool %45 %float_0 +OpSelectionMerge %47 None +OpBranchConditional %46 %48 %47 +%48 = OpLabel +%49 = OpLoad %float %s +%50 = OpFAdd %float %49 %45 +OpStore %s %50 +OpBranch %47 +%47 = OpLabel +OpBranch %37 +%37 = OpLabel +%51 = OpLoad %int %j +%52 = OpIAdd %int %51 %int_1 +OpStore %j %52 +OpBranch %35 +%36 = OpLabel +OpBranch %30 +%30 = OpLabel +%53 = OpLoad %int %i +%54 = OpIAdd %int %53 %int_1 +OpStore %i %54 +OpBranch %28 +%29 = OpLabel +%55 = OpLoad %float %s +OpStore %o %55 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(assembly, assembly, true, true); +} + +TEST_F(AggressiveDCETest, NoEliminateIfContinue) { + // Do not eliminate continue embedded in if construct + // + // #version 430 + // + // layout(std430) buffer U_t + // { + // float g_F[10]; + // }; + // + // layout(location = 0)out float o; + // + // void main(void) + // { + // float s = 0.0; + // for (int i=0; i<10; i++) { + // if (i % 2 == 0) continue; + // s += g_F[i]; + // } + // o = s; + // } + + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %o +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 430 +OpName %main "main" +OpName %s "s" +OpName %i "i" +OpName %U_t "U_t" +OpMemberName %U_t 0 "g_F" +OpName %_ "" +OpName %o "o" +OpDecorate %_arr_float_uint_10 ArrayStride 4 +OpMemberDecorate %U_t 0 Offset 0 +OpDecorate %U_t BufferBlock +OpDecorate %_ DescriptorSet 0 +OpDecorate %o Location 0 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float +%float_0 = OpConstant %float 0 +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_0 = OpConstant %int 0 +%int_10 = OpConstant %int 10 +%bool = OpTypeBool +%int_2 = OpConstant %int 2 +%uint = OpTypeInt 32 0 +%uint_10 = OpConstant %uint 10 +%_arr_float_uint_10 = OpTypeArray %float %uint_10 +%U_t = OpTypeStruct %_arr_float_uint_10 +%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t +%_ = OpVariable %_ptr_Uniform_U_t Uniform +%_ptr_Uniform_float = OpTypePointer Uniform %float +%int_1 = OpConstant %int 1 +%_ptr_Output_float = OpTypePointer Output %float +%o = OpVariable %_ptr_Output_float Output +%main = OpFunction %void None %10 +%26 = OpLabel +%s = OpVariable %_ptr_Function_float Function +%i = OpVariable %_ptr_Function_int Function +OpStore %s %float_0 +OpStore %i %int_0 +OpBranch %27 +%27 = OpLabel +OpLoopMerge %28 %29 None +OpBranch %30 +%30 = OpLabel +%31 = OpLoad %int %i +%32 = OpSLessThan %bool %31 %int_10 +OpBranchConditional %32 %33 %28 +%33 = OpLabel +%34 = OpLoad %int %i +%35 = OpSMod %int %34 %int_2 +%36 = OpIEqual %bool %35 %int_0 +OpSelectionMerge %37 None +OpBranchConditional %36 %38 %37 +%38 = OpLabel +OpBranch %29 +%37 = OpLabel +%39 = OpLoad %int %i +%40 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %39 +%41 = OpLoad %float %40 +%42 = OpLoad %float %s +%43 = OpFAdd %float %42 %41 +OpStore %s %43 +OpBranch %29 +%29 = OpLabel +%44 = OpLoad %int %i +%45 = OpIAdd %int %44 %int_1 +OpStore %i %45 +OpBranch %27 +%28 = OpLabel +%46 = OpLoad %float %s +OpStore %o %46 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(assembly, assembly, true, true); +} + +TEST_F(AggressiveDCETest, NoEliminateIfContinue2) { + // Do not eliminate continue not embedded in if construct + // + // #version 430 + // + // layout(std430) buffer U_t + // { + // float g_F[10]; + // }; + // + // layout(location = 0)out float o; + // + // void main(void) + // { + // float s = 0.0; + // for (int i=0; i<10; i++) { + // if (i % 2 == 0) continue; + // s += g_F[i]; + // } + // o = s; + // } + + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %o +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 430 +OpName %main "main" +OpName %s "s" +OpName %i "i" +OpName %U_t "U_t" +OpMemberName %U_t 0 "g_F" +OpName %_ "" +OpName %o "o" +OpDecorate %_arr_float_uint_10 ArrayStride 4 +OpMemberDecorate %U_t 0 Offset 0 +OpDecorate %U_t BufferBlock +OpDecorate %_ DescriptorSet 0 +OpDecorate %o Location 0 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float +%float_0 = OpConstant %float 0 +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_0 = OpConstant %int 0 +%int_10 = OpConstant %int 10 +%bool = OpTypeBool +%int_2 = OpConstant %int 2 +%uint = OpTypeInt 32 0 +%uint_10 = OpConstant %uint 10 +%_arr_float_uint_10 = OpTypeArray %float %uint_10 +%U_t = OpTypeStruct %_arr_float_uint_10 +%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t +%_ = OpVariable %_ptr_Uniform_U_t Uniform +%_ptr_Uniform_float = OpTypePointer Uniform %float +%int_1 = OpConstant %int 1 +%_ptr_Output_float = OpTypePointer Output %float +%o = OpVariable %_ptr_Output_float Output +%main = OpFunction %void None %10 +%26 = OpLabel +%s = OpVariable %_ptr_Function_float Function +%i = OpVariable %_ptr_Function_int Function +OpStore %s %float_0 +OpStore %i %int_0 +OpBranch %27 +%27 = OpLabel +OpLoopMerge %28 %29 None +OpBranch %30 +%30 = OpLabel +%31 = OpLoad %int %i +%32 = OpSLessThan %bool %31 %int_10 +OpBranchConditional %32 %33 %28 +%33 = OpLabel +%34 = OpLoad %int %i +%35 = OpSMod %int %34 %int_2 +%36 = OpIEqual %bool %35 %int_0 +OpBranchConditional %36 %29 %37 +%37 = OpLabel +%38 = OpLoad %int %i +%39 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %38 +%40 = OpLoad %float %39 +%41 = OpLoad %float %s +%42 = OpFAdd %float %41 %40 +OpStore %s %42 +OpBranch %29 +%29 = OpLabel +%43 = OpLoad %int %i +%44 = OpIAdd %int %43 %int_1 +OpStore %i %44 +OpBranch %27 +%28 = OpLabel +%45 = OpLoad %float %s +OpStore %o %45 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(assembly, assembly, true, true); +} + +TEST_F(AggressiveDCETest, NoEliminateIfContinue3) { + // Do not eliminate continue as conditional branch with merge instruction + // Note: SPIR-V edited to add merge instruction before continue. + // + // #version 430 + // + // layout(std430) buffer U_t + // { + // float g_F[10]; + // }; + // + // layout(location = 0)out float o; + // + // void main(void) + // { + // float s = 0.0; + // for (int i=0; i<10; i++) { + // if (i % 2 == 0) continue; + // s += g_F[i]; + // } + // o = s; + // } + + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %o +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 430 +OpName %main "main" +OpName %s "s" +OpName %i "i" +OpName %U_t "U_t" +OpMemberName %U_t 0 "g_F" +OpName %_ "" +OpName %o "o" +OpDecorate %_arr_float_uint_10 ArrayStride 4 +OpMemberDecorate %U_t 0 Offset 0 +OpDecorate %U_t BufferBlock +OpDecorate %_ DescriptorSet 0 +OpDecorate %o Location 0 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float +%float_0 = OpConstant %float 0 +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_0 = OpConstant %int 0 +%int_10 = OpConstant %int 10 +%bool = OpTypeBool +%int_2 = OpConstant %int 2 +%uint = OpTypeInt 32 0 +%uint_10 = OpConstant %uint 10 +%_arr_float_uint_10 = OpTypeArray %float %uint_10 +%U_t = OpTypeStruct %_arr_float_uint_10 +%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t +%_ = OpVariable %_ptr_Uniform_U_t Uniform +%_ptr_Uniform_float = OpTypePointer Uniform %float +%int_1 = OpConstant %int 1 +%_ptr_Output_float = OpTypePointer Output %float +%o = OpVariable %_ptr_Output_float Output +%main = OpFunction %void None %10 +%26 = OpLabel +%s = OpVariable %_ptr_Function_float Function +%i = OpVariable %_ptr_Function_int Function +OpStore %s %float_0 +OpStore %i %int_0 +OpBranch %27 +%27 = OpLabel +OpLoopMerge %28 %29 None +OpBranch %30 +%30 = OpLabel +%31 = OpLoad %int %i +%32 = OpSLessThan %bool %31 %int_10 +OpBranchConditional %32 %33 %28 +%33 = OpLabel +%34 = OpLoad %int %i +%35 = OpSMod %int %34 %int_2 +%36 = OpIEqual %bool %35 %int_0 +OpSelectionMerge %37 None +OpBranchConditional %36 %29 %37 +%37 = OpLabel +%38 = OpLoad %int %i +%39 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %38 +%40 = OpLoad %float %39 +%41 = OpLoad %float %s +%42 = OpFAdd %float %41 %40 +OpStore %s %42 +OpBranch %29 +%29 = OpLabel +%43 = OpLoad %int %i +%44 = OpIAdd %int %43 %int_1 +OpStore %i %44 +OpBranch %27 +%28 = OpLabel +%45 = OpLoad %float %s +OpStore %o %45 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(assembly, assembly, true, true); +} + +TEST_F(AggressiveDCETest, PointerVariable) { + // ADCE is able to handle code that contains a load whose base address + // comes from a load and not an OpVariable. I want to see an instruction + // removed to be sure that ADCE is not exiting early. + + const std::string before = + R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %1 "main" %2 +OpExecutionMode %1 OriginUpperLeft +OpMemberDecorate %_struct_3 0 Offset 0 +OpDecorate %_runtimearr__struct_3 ArrayStride 16 +OpMemberDecorate %_struct_5 0 Offset 0 +OpDecorate %_struct_5 BufferBlock +OpMemberDecorate %_struct_6 0 Offset 0 +OpDecorate %_struct_6 BufferBlock +OpDecorate %2 Location 0 +OpDecorate %7 DescriptorSet 0 +OpDecorate %7 Binding 0 +OpDecorate %8 DescriptorSet 0 +OpDecorate %8 Binding 1 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%uint = OpTypeInt 32 0 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float +%_struct_3 = OpTypeStruct %v4float +%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3 +%_struct_5 = OpTypeStruct %_runtimearr__struct_3 +%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5 +%_struct_6 = OpTypeStruct %int +%_ptr_Uniform__struct_6 = OpTypePointer Uniform %_struct_6 +%_ptr_Function__ptr_Uniform__struct_5 = OpTypePointer Function %_ptr_Uniform__struct_5 +%_ptr_Function__ptr_Uniform__struct_6 = OpTypePointer Function %_ptr_Uniform__struct_6 +%int_0 = OpConstant %int 0 +%uint_0 = OpConstant %uint 0 +%2 = OpVariable %_ptr_Output_v4float Output +%7 = OpVariable %_ptr_Uniform__struct_5 Uniform +%8 = OpVariable %_ptr_Uniform__struct_6 Uniform +%1 = OpFunction %void None %10 +%23 = OpLabel +%24 = OpVariable %_ptr_Function__ptr_Uniform__struct_5 Function +OpStore %24 %7 +%26 = OpLoad %_ptr_Uniform__struct_5 %24 +%27 = OpAccessChain %_ptr_Uniform_v4float %26 %int_0 %uint_0 %int_0 +%28 = OpLoad %v4float %27 +%29 = OpCopyObject %v4float %28 +OpStore %2 %28 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %1 "main" %2 +OpExecutionMode %1 OriginUpperLeft +OpMemberDecorate %_struct_3 0 Offset 0 +OpDecorate %_runtimearr__struct_3 ArrayStride 16 +OpMemberDecorate %_struct_5 0 Offset 0 +OpDecorate %_struct_5 BufferBlock +OpDecorate %2 Location 0 +OpDecorate %7 DescriptorSet 0 +OpDecorate %7 Binding 0 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%uint = OpTypeInt 32 0 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float +%_struct_3 = OpTypeStruct %v4float +%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3 +%_struct_5 = OpTypeStruct %_runtimearr__struct_3 +%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5 +%_ptr_Function__ptr_Uniform__struct_5 = OpTypePointer Function %_ptr_Uniform__struct_5 +%int_0 = OpConstant %int 0 +%uint_0 = OpConstant %uint 0 +%2 = OpVariable %_ptr_Output_v4float Output +%7 = OpVariable %_ptr_Uniform__struct_5 Uniform +%1 = OpFunction %void None %10 +%23 = OpLabel +%24 = OpVariable %_ptr_Function__ptr_Uniform__struct_5 Function +OpStore %24 %7 +%25 = OpLoad %_ptr_Uniform__struct_5 %24 +%26 = OpAccessChain %_ptr_Uniform_v4float %25 %int_0 %uint_0 %int_0 +%27 = OpLoad %v4float %26 +OpStore %2 %27 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(before, after, true, true); +} + +// %dead is unused. Make sure we remove it along with its name. +TEST_F(AggressiveDCETest, RemoveUnreferenced) { + const std::string before = + R"(OpCapability Shader +OpCapability Linkage +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 150 +OpName %main "main" +OpName %dead "dead" +%void = OpTypeVoid +%5 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Private_float = OpTypePointer Private %float +%dead = OpVariable %_ptr_Private_float Private +%main = OpFunction %void None %5 +%8 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(OpCapability Shader +OpCapability Linkage +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 150 +OpName %main "main" +%void = OpTypeVoid +%5 = OpTypeFunction %void +%main = OpFunction %void None %5 +%8 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck(before, after, true, true); +} + +// Delete %dead because it is unreferenced. Then %initializer becomes +// unreferenced, so remove it as well. +TEST_F(AggressiveDCETest, RemoveUnreferencedWithInit1) { + const std::string before = + R"(OpCapability Shader +OpCapability Linkage +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 150 +OpName %main "main" +OpName %dead "dead" +OpName %initializer "initializer" +%void = OpTypeVoid +%6 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Private_float = OpTypePointer Private %float +%initializer = OpVariable %_ptr_Private_float Private +%dead = OpVariable %_ptr_Private_float Private %initializer +%main = OpFunction %void None %6 +%9 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(OpCapability Shader +OpCapability Linkage +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 150 +OpName %main "main" +%void = OpTypeVoid +%6 = OpTypeFunction %void +%main = OpFunction %void None %6 +%9 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck(before, after, true, true); +} + +// Keep %live because it is used, and its initializer. +TEST_F(AggressiveDCETest, KeepReferenced) { + const std::string before = + R"(OpCapability Shader +OpCapability Linkage +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %output +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 150 +OpName %main "main" +OpName %live "live" +OpName %initializer "initializer" +OpName %output "output" +%void = OpTypeVoid +%6 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Private_float = OpTypePointer Private %float +%initializer = OpVariable %_ptr_Private_float Private +%live = OpVariable %_ptr_Private_float Private %initializer +%_ptr_Output_float = OpTypePointer Output %float +%output = OpVariable %_ptr_Output_float Output +%main = OpFunction %void None %6 +%9 = OpLabel +%10 = OpLoad %float %live +OpStore %output %10 +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck(before, before, true, true); +} + +// This test that the decoration associated with a variable are removed when the +// variable is removed. +TEST_F(AggressiveDCETest, RemoveVariableAndDecorations) { + const std::string before = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %main "main" +OpSource GLSL 450 +OpName %main "main" +OpName %B "B" +OpMemberName %B 0 "a" +OpName %Bdat "Bdat" +OpMemberDecorate %B 0 Offset 0 +OpDecorate %B BufferBlock +OpDecorate %Bdat DescriptorSet 0 +OpDecorate %Bdat Binding 0 +%void = OpTypeVoid +%6 = OpTypeFunction %void +%uint = OpTypeInt 32 0 +%B = OpTypeStruct %uint +%_ptr_Uniform_B = OpTypePointer Uniform %B +%Bdat = OpVariable %_ptr_Uniform_B Uniform +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%uint_1 = OpConstant %uint 1 +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%main = OpFunction %void None %6 +%13 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %main "main" +OpSource GLSL 450 +OpName %main "main" +%void = OpTypeVoid +%6 = OpTypeFunction %void +%main = OpFunction %void None %6 +%13 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck(before, after, true, true); +} + +#ifdef SPIRV_EFFCEE +TEST_F(AggressiveDCETest, DeadNestedSwitch) { + const std::string text = R"( +; CHECK: OpLabel +; CHECK: OpBranch [[block:%\w+]] +; CHECK-NOT: OpSwitch +; CHECK-NEXT: [[block]] = OpLabel +; CHECK: OpBranch [[block:%\w+]] +; CHECK-NOT: OpSwitch +; CHECK-NEXT: [[block]] = OpLabel +; CHECK-NEXT: OpStore +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %func "func" %x +OpExecutionMode %func OriginUpperLeft +OpName %func "func" +%void = OpTypeVoid +%1 = OpTypeFunction %void +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%uint_ptr_Output = OpTypePointer Output %uint +%uint_ptr_Input = OpTypePointer Input %uint +%x = OpVariable %uint_ptr_Output Output +%a = OpVariable %uint_ptr_Input Input +%func = OpFunction %void None %1 +%entry = OpLabel +OpBranch %header +%header = OpLabel +%ld = OpLoad %uint %a +OpLoopMerge %merge %continue None +OpBranch %postheader +%postheader = OpLabel +; This switch doesn't require an OpSelectionMerge and is nested in the dead loop. +OpSwitch %ld %merge 0 %extra 1 %continue +%extra = OpLabel +OpBranch %continue +%continue = OpLabel +OpBranch %header +%merge = OpLabel +OpStore %x %uint_0 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} +#endif // SPIRV_EFFCEE + +TEST_F(AggressiveDCETest, LiveNestedSwitch) { + const std::string text = R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %func "func" %3 +OpExecutionMode %func OriginUpperLeft +OpName %func "func" +%void = OpTypeVoid +%1 = OpTypeFunction %void +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%uint_1 = OpConstant %uint 1 +%_ptr_Output_uint = OpTypePointer Output %uint +%_ptr_Input_uint = OpTypePointer Input %uint +%3 = OpVariable %_ptr_Output_uint Output +%10 = OpVariable %_ptr_Input_uint Input +%func = OpFunction %void None %1 +%11 = OpLabel +OpBranch %12 +%12 = OpLabel +%13 = OpLoad %uint %10 +OpLoopMerge %14 %15 None +OpBranch %16 +%16 = OpLabel +OpSwitch %13 %14 0 %17 1 %15 +%17 = OpLabel +OpStore %3 %uint_1 +OpBranch %15 +%15 = OpLabel +OpBranch %12 +%14 = OpLabel +OpStore %3 %uint_0 +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck(text, text, false, true); +} + +TEST_F(AggressiveDCETest, BasicDeleteDeadFunction) { + // The function Dead should be removed because it is never called. + const std::vector common_code = { + // clang-format off + "OpCapability Shader", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Fragment %main \"main\"", + "OpName %main \"main\"", + "OpName %Live \"Live\"", + "%void = OpTypeVoid", + "%7 = OpTypeFunction %void", + "%main = OpFunction %void None %7", + "%15 = OpLabel", + "%16 = OpFunctionCall %void %Live", + "%17 = OpFunctionCall %void %Live", + "OpReturn", + "OpFunctionEnd", + "%Live = OpFunction %void None %7", + "%20 = OpLabel", + "OpReturn", + "OpFunctionEnd" + // clang-format on + }; + + const std::vector dead_function = { + // clang-format off + "%Dead = OpFunction %void None %7", + "%19 = OpLabel", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck( + JoinAllInsts(Concat(common_code, dead_function)), + JoinAllInsts(common_code), /* skip_nop = */ true); +} + +TEST_F(AggressiveDCETest, BasicKeepLiveFunction) { + // Everything is reachable from an entry point, so no functions should be + // deleted. + const std::vector text = { + // clang-format off + "OpCapability Shader", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Fragment %main \"main\"", + "OpName %main \"main\"", + "OpName %Live1 \"Live1\"", + "OpName %Live2 \"Live2\"", + "%void = OpTypeVoid", + "%7 = OpTypeFunction %void", + "%main = OpFunction %void None %7", + "%15 = OpLabel", + "%16 = OpFunctionCall %void %Live2", + "%17 = OpFunctionCall %void %Live1", + "OpReturn", + "OpFunctionEnd", + "%Live1 = OpFunction %void None %7", + "%19 = OpLabel", + "OpReturn", + "OpFunctionEnd", + "%Live2 = OpFunction %void None %7", + "%20 = OpLabel", + "OpReturn", + "OpFunctionEnd" + // clang-format on + }; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + std::string assembly = JoinAllInsts(text); + auto result = SinglePassRunAndDisassemble( + assembly, /* skip_nop = */ true, /* do_validation = */ false); + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); + EXPECT_EQ(assembly, std::get<0>(result)); +} + +TEST_F(AggressiveDCETest, BasicRemoveDecorationsAndNames) { + // We want to remove the names and decorations associated with results that + // are removed. This test will check for that. + const std::string text = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" + OpName %main "main" + OpName %Dead "Dead" + OpName %x "x" + OpName %y "y" + OpName %z "z" + OpDecorate %x RelaxedPrecision + OpDecorate %y RelaxedPrecision + OpDecorate %z RelaxedPrecision + OpDecorate %6 RelaxedPrecision + OpDecorate %7 RelaxedPrecision + OpDecorate %8 RelaxedPrecision + %void = OpTypeVoid + %10 = OpTypeFunction %void + %float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float + %float_1 = OpConstant %float 1 + %main = OpFunction %void None %10 + %14 = OpLabel + OpReturn + OpFunctionEnd + %Dead = OpFunction %void None %10 + %15 = OpLabel + %x = OpVariable %_ptr_Function_float Function + %y = OpVariable %_ptr_Function_float Function + %z = OpVariable %_ptr_Function_float Function + OpStore %x %float_1 + OpStore %y %float_1 + %6 = OpLoad %float %x + %7 = OpLoad %float %y + %8 = OpFAdd %float %6 %7 + OpStore %z %8 + OpReturn + OpFunctionEnd)"; + + const std::string expected_output = R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %main "main" +OpName %main "main" +%void = OpTypeVoid +%10 = OpTypeFunction %void +%main = OpFunction %void None %10 +%14 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck(text, expected_output, + /* skip_nop = */ true); +} + +#ifdef SPIRV_EFFCEE +TEST_F(AggressiveDCETest, BasicAllDeadConstants) { + const std::string text = R"( + ; CHECK-NOT: OpConstant + OpCapability Shader + OpCapability Float64 + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" + OpName %main "main" + %void = OpTypeVoid + %4 = OpTypeFunction %void + %bool = OpTypeBool + %true = OpConstantTrue %bool + %false = OpConstantFalse %bool + %int = OpTypeInt 32 1 + %9 = OpConstant %int 1 + %uint = OpTypeInt 32 0 + %11 = OpConstant %uint 2 + %float = OpTypeFloat 32 + %13 = OpConstant %float 3.1415 + %double = OpTypeFloat 64 + %15 = OpConstant %double 3.14159265358979 + %main = OpFunction %void None %4 + %16 = OpLabel + OpReturn + OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} +#endif // SPIRV_EFFCEE + +TEST_F(AggressiveDCETest, BasicNoneDeadConstants) { + const std::vector text = { + // clang-format off + "OpCapability Shader", + "OpCapability Float64", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Vertex %main \"main\" %btv %bfv %iv %uv %fv %dv", + "OpName %main \"main\"", + "OpName %btv \"btv\"", + "OpName %bfv \"bfv\"", + "OpName %iv \"iv\"", + "OpName %uv \"uv\"", + "OpName %fv \"fv\"", + "OpName %dv \"dv\"", + "%void = OpTypeVoid", + "%10 = OpTypeFunction %void", + "%bool = OpTypeBool", + "%_ptr_Output_bool = OpTypePointer Output %bool", + "%true = OpConstantTrue %bool", + "%false = OpConstantFalse %bool", + "%int = OpTypeInt 32 1", + "%_ptr_Output_int = OpTypePointer Output %int", + "%int_1 = OpConstant %int 1", + "%uint = OpTypeInt 32 0", + "%_ptr_Output_uint = OpTypePointer Output %uint", + "%uint_2 = OpConstant %uint 2", + "%float = OpTypeFloat 32", + "%_ptr_Output_float = OpTypePointer Output %float", + "%float_3_1415 = OpConstant %float 3.1415", + "%double = OpTypeFloat 64", + "%_ptr_Output_double = OpTypePointer Output %double", + "%double_3_14159265358979 = OpConstant %double 3.14159265358979", + "%btv = OpVariable %_ptr_Output_bool Output", + "%bfv = OpVariable %_ptr_Output_bool Output", + "%iv = OpVariable %_ptr_Output_int Output", + "%uv = OpVariable %_ptr_Output_uint Output", + "%fv = OpVariable %_ptr_Output_float Output", + "%dv = OpVariable %_ptr_Output_double Output", + "%main = OpFunction %void None %10", + "%27 = OpLabel", + "OpStore %btv %true", + "OpStore %bfv %false", + "OpStore %iv %int_1", + "OpStore %uv %uint_2", + "OpStore %fv %float_3_1415", + "OpStore %dv %double_3_14159265358979", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + // All constants are used, so none of them should be eliminated. + SinglePassRunAndCheck( + JoinAllInsts(text), JoinAllInsts(text), /* skip_nop = */ true); +} + +struct EliminateDeadConstantTestCase { + // Type declarations and constants that should be kept. + std::vector used_consts; + // Instructions that refer to constants, this is added to create uses for + // some constants so they won't be treated as dead constants. + std::vector main_insts; + // Dead constants that should be removed. + std::vector dead_consts; + // Expectations + std::vector checks; +}; + +// All types that are potentially required in EliminateDeadConstantTest. +const std::vector CommonTypes = { + // clang-format off + // scalar types + "%bool = OpTypeBool", + "%uint = OpTypeInt 32 0", + "%int = OpTypeInt 32 1", + "%float = OpTypeFloat 32", + "%double = OpTypeFloat 64", + // vector types + "%v2bool = OpTypeVector %bool 2", + "%v2uint = OpTypeVector %uint 2", + "%v2int = OpTypeVector %int 2", + "%v3int = OpTypeVector %int 3", + "%v4int = OpTypeVector %int 4", + "%v2float = OpTypeVector %float 2", + "%v3float = OpTypeVector %float 3", + "%v2double = OpTypeVector %double 2", + // variable pointer types + "%_pf_bool = OpTypePointer Output %bool", + "%_pf_uint = OpTypePointer Output %uint", + "%_pf_int = OpTypePointer Output %int", + "%_pf_float = OpTypePointer Output %float", + "%_pf_double = OpTypePointer Output %double", + "%_pf_v2int = OpTypePointer Output %v2int", + "%_pf_v3int = OpTypePointer Output %v3int", + "%_pf_v2float = OpTypePointer Output %v2float", + "%_pf_v3float = OpTypePointer Output %v3float", + "%_pf_v2double = OpTypePointer Output %v2double", + // struct types + "%inner_struct = OpTypeStruct %bool %int %float %double", + "%outer_struct = OpTypeStruct %inner_struct %int %double", + "%flat_struct = OpTypeStruct %bool %int %float %double", + // clang-format on +}; + +using EliminateDeadConstantTest = + PassTest<::testing::TestWithParam>; + +#ifdef SPIRV_EFFCEE +TEST_P(EliminateDeadConstantTest, Custom) { + auto& tc = GetParam(); + AssemblyBuilder builder; + builder.AppendTypesConstantsGlobals(CommonTypes) + .AppendTypesConstantsGlobals(tc.used_consts) + .AppendInMain(tc.main_insts); + const std::string expected = builder.GetCode(); + builder.AppendTypesConstantsGlobals(tc.dead_consts); + builder.PrependPreamble(tc.checks); + const std::string assembly_with_dead_const = builder.GetCode(); + + // Do not enable validation. As the input code is invalid from the base + // tests (ported from other passes). + SinglePassRunAndMatch(assembly_with_dead_const, + false); +} + +INSTANTIATE_TEST_CASE_P( + ScalarTypeConstants, EliminateDeadConstantTest, + ::testing::ValuesIn(std::vector({ + // clang-format off + // Scalar type constants, one dead constant and one used constant. + { + /* .used_consts = */ + { + "%used_const_int = OpConstant %int 1", + }, + /* .main_insts = */ + { + "%int_var = OpVariable %_pf_int Output", + "OpStore %int_var %used_const_int", + }, + /* .dead_consts = */ + { + "%dead_const_int = OpConstant %int 1", + }, + /* .checks = */ + { + "; CHECK: [[const:%\\w+]] = OpConstant %int 1", + "; CHECK-NOT: OpConstant", + "; CHECK: OpStore {{%\\w+}} [[const]]", + }, + }, + { + /* .used_consts = */ + { + "%used_const_uint = OpConstant %uint 1", + }, + /* .main_insts = */ + { + "%uint_var = OpVariable %_pf_uint Output", + "OpStore %uint_var %used_const_uint", + }, + /* .dead_consts = */ + { + "%dead_const_uint = OpConstant %uint 1", + }, + /* .checks = */ + { + "; CHECK: [[const:%\\w+]] = OpConstant %uint 1", + "; CHECK-NOT: OpConstant", + "; CHECK: OpStore {{%\\w+}} [[const]]", + }, + }, + { + /* .used_consts = */ + { + "%used_const_float = OpConstant %float 3.1415", + }, + /* .main_insts = */ + { + "%float_var = OpVariable %_pf_float Output", + "OpStore %float_var %used_const_float", + }, + /* .dead_consts = */ + { + "%dead_const_float = OpConstant %float 3.1415", + }, + /* .checks = */ + { + "; CHECK: [[const:%\\w+]] = OpConstant %float 3.1415", + "; CHECK-NOT: OpConstant", + "; CHECK: OpStore {{%\\w+}} [[const]]", + }, + }, + { + /* .used_consts = */ + { + "%used_const_double = OpConstant %double 3.14", + }, + /* .main_insts = */ + { + "%double_var = OpVariable %_pf_double Output", + "OpStore %double_var %used_const_double", + }, + /* .dead_consts = */ + { + "%dead_const_double = OpConstant %double 3.14", + }, + /* .checks = */ + { + "; CHECK: [[const:%\\w+]] = OpConstant %double 3.14", + "; CHECK-NOT: OpConstant", + "; CHECK: OpStore {{%\\w+}} [[const]]", + }, + }, + // clang-format on + }))); + +INSTANTIATE_TEST_CASE_P( + VectorTypeConstants, EliminateDeadConstantTest, + ::testing::ValuesIn(std::vector({ + // clang-format off + // Tests eliminating dead constant type ivec2. One dead constant vector + // and one used constant vector, each built from its own group of + // scalar constants. + { + /* .used_consts = */ + { + "%used_int_x = OpConstant %int 1", + "%used_int_y = OpConstant %int 2", + "%used_v2int = OpConstantComposite %v2int %used_int_x %used_int_y", + }, + /* .main_insts = */ + { + "%v2int_var = OpVariable %_pf_v2int Output", + "OpStore %v2int_var %used_v2int", + }, + /* .dead_consts = */ + { + "%dead_int_x = OpConstant %int 1", + "%dead_int_y = OpConstant %int 2", + "%dead_v2int = OpConstantComposite %v2int %dead_int_x %dead_int_y", + }, + /* .checks = */ + { + "; CHECK: [[constx:%\\w+]] = OpConstant %int 1", + "; CHECK: [[consty:%\\w+]] = OpConstant %int 2", + "; CHECK: [[const:%\\w+]] = OpConstantComposite %v2int [[constx]] [[consty]]", + "; CHECK-NOT: OpConstant", + "; CHECK: OpStore {{%\\w+}} [[const]]", + }, + }, + // Tests eliminating dead constant ivec3. One dead constant vector and + // one used constant vector. But both built from a same group of + // scalar constants. + { + /* .used_consts = */ + { + "%used_int_x = OpConstant %int 1", + "%used_int_y = OpConstant %int 2", + "%used_int_z = OpConstant %int 3", + "%used_v3int = OpConstantComposite %v3int %used_int_x %used_int_y %used_int_z", + }, + /* .main_insts = */ + { + "%v3int_var = OpVariable %_pf_v3int Output", + "OpStore %v3int_var %used_v3int", + }, + /* .dead_consts = */ + { + "%dead_v3int = OpConstantComposite %v3int %used_int_x %used_int_y %used_int_z", + }, + /* .checks = */ + { + "; CHECK: [[constx:%\\w+]] = OpConstant %int 1", + "; CHECK: [[consty:%\\w+]] = OpConstant %int 2", + "; CHECK: [[constz:%\\w+]] = OpConstant %int 3", + "; CHECK: [[const:%\\w+]] = OpConstantComposite %v3int [[constx]] [[consty]] [[constz]]", + "; CHECK-NOT: OpConstant", + "; CHECK: OpStore {{%\\w+}} [[const]]", + }, + }, + // Tests eliminating dead constant vec2. One dead constant vector and + // one used constant vector. Each built from its own group of scalar + // constants. + { + /* .used_consts = */ + { + "%used_float_x = OpConstant %float 3.1415", + "%used_float_y = OpConstant %float 4.13", + "%used_v2float = OpConstantComposite %v2float %used_float_x %used_float_y", + }, + /* .main_insts = */ + { + "%v2float_var = OpVariable %_pf_v2float Output", + "OpStore %v2float_var %used_v2float", + }, + /* .dead_consts = */ + { + "%dead_float_x = OpConstant %float 3.1415", + "%dead_float_y = OpConstant %float 4.13", + "%dead_v2float = OpConstantComposite %v2float %dead_float_x %dead_float_y", + }, + /* .checks = */ + { + "; CHECK: [[constx:%\\w+]] = OpConstant %float 3.1415", + "; CHECK: [[consty:%\\w+]] = OpConstant %float 4.13", + "; CHECK: [[const:%\\w+]] = OpConstantComposite %v2float [[constx]] [[consty]]", + "; CHECK-NOT: OpConstant", + "; CHECK: OpStore {{%\\w+}} [[const]]", + }, + }, + // Tests eliminating dead constant vec3. One dead constant vector and + // one used constant vector. Both built from a same group of scalar + // constants. + { + /* .used_consts = */ + { + "%used_float_x = OpConstant %float 3.1415", + "%used_float_y = OpConstant %float 4.25", + "%used_float_z = OpConstant %float 4.75", + "%used_v3float = OpConstantComposite %v3float %used_float_x %used_float_y %used_float_z", + }, + /* .main_insts = */ + { + "%v3float_var = OpVariable %_pf_v3float Output", + "OpStore %v3float_var %used_v3float", + }, + /* .dead_consts = */ + { + "%dead_v3float = OpConstantComposite %v3float %used_float_x %used_float_y %used_float_z", + }, + /* .checks = */ + { + "; CHECK: [[constx:%\\w+]] = OpConstant %float 3.1415", + "; CHECK: [[consty:%\\w+]] = OpConstant %float 4.25", + "; CHECK: [[constz:%\\w+]] = OpConstant %float 4.75", + "; CHECK: [[const:%\\w+]] = OpConstantComposite %v3float [[constx]] [[consty]]", + "; CHECK-NOT: OpConstant", + "; CHECK: OpStore {{%\\w+}} [[const]]", + }, + }, + // clang-format on + }))); + +INSTANTIATE_TEST_CASE_P( + StructTypeConstants, EliminateDeadConstantTest, + ::testing::ValuesIn(std::vector({ + // clang-format off + // A plain struct type dead constants. All of its components are dead + // constants too. + { + /* .used_consts = */ {}, + /* .main_insts = */ {}, + /* .dead_consts = */ + { + "%dead_bool = OpConstantTrue %bool", + "%dead_int = OpConstant %int 1", + "%dead_float = OpConstant %float 2.5", + "%dead_double = OpConstant %double 3.14159265358979", + "%dead_struct = OpConstantComposite %flat_struct %dead_bool %dead_int %dead_float %dead_double", + }, + /* .checks = */ + { + "; CHECK-NOT: OpConstant", + }, + }, + // A plain struct type dead constants. Some of its components are dead + // constants while others are not. + { + /* .used_consts = */ + { + "%used_int = OpConstant %int 1", + "%used_double = OpConstant %double 3.14159265358979", + }, + /* .main_insts = */ + { + "%int_var = OpVariable %_pf_int Output", + "OpStore %int_var %used_int", + "%double_var = OpVariable %_pf_double Output", + "OpStore %double_var %used_double", + }, + /* .dead_consts = */ + { + "%dead_bool = OpConstantTrue %bool", + "%dead_float = OpConstant %float 2.5", + "%dead_struct = OpConstantComposite %flat_struct %dead_bool %used_int %dead_float %used_double", + }, + /* .checks = */ + { + "; CHECK: [[int:%\\w+]] = OpConstant %int 1", + "; CHECK: [[double:%\\w+]] = OpConstant %double 3.14159265358979", + "; CHECK-NOT: OpConstant", + "; CHECK: OpStore {{%\\w+}} [[int]]", + "; CHECK: OpStore {{%\\w+}} [[double]]", + }, + }, + // A nesting struct type dead constants. All components of both outer + // and inner structs are dead and should be removed after dead constant + // elimination. + { + /* .used_consts = */ {}, + /* .main_insts = */ {}, + /* .dead_consts = */ + { + "%dead_bool = OpConstantTrue %bool", + "%dead_int = OpConstant %int 1", + "%dead_float = OpConstant %float 2.5", + "%dead_double = OpConstant %double 3.1415926535", + "%dead_inner_struct = OpConstantComposite %inner_struct %dead_bool %dead_int %dead_float %dead_double", + "%dead_int2 = OpConstant %int 2", + "%dead_double2 = OpConstant %double 1.428571428514", + "%dead_outer_struct = OpConstantComposite %outer_struct %dead_inner_struct %dead_int2 %dead_double2", + }, + /* .checks = */ + { + "; CHECK-NOT: OpConstant", + }, + }, + // A nesting struct type dead constants. Some of its components are + // dead constants while others are not. + { + /* .used_consts = */ + { + "%used_int = OpConstant %int 1", + "%used_double = OpConstant %double 3.14159265358979", + }, + /* .main_insts = */ + { + "%int_var = OpVariable %_pf_int Output", + "OpStore %int_var %used_int", + "%double_var = OpVariable %_pf_double Output", + "OpStore %double_var %used_double", + }, + /* .dead_consts = */ + { + "%dead_bool = OpConstantTrue %bool", + "%dead_float = OpConstant %float 2.5", + "%dead_inner_struct = OpConstantComposite %inner_struct %dead_bool %used_int %dead_float %used_double", + "%dead_int = OpConstant %int 2", + "%dead_outer_struct = OpConstantComposite %outer_struct %dead_inner_struct %dead_int %used_double", + }, + /* .checks = */ + { + "; CHECK: [[int:%\\w+]] = OpConstant %int 1", + "; CHECK: [[double:%\\w+]] = OpConstant %double 3.14159265358979", + "; CHECK-NOT: OpConstant", + "; CHECK: OpStore {{%\\w+}} [[int]]", + "; CHECK: OpStore {{%\\w+}} [[double]]", + }, + }, + // A nesting struct case. The inner struct is used while the outer struct is not + { + /* .used_const = */ + { + "%used_bool = OpConstantTrue %bool", + "%used_int = OpConstant %int 1", + "%used_float = OpConstant %float 1.23", + "%used_double = OpConstant %double 1.2345678901234", + "%used_inner_struct = OpConstantComposite %inner_struct %used_bool %used_int %used_float %used_double", + }, + /* .main_insts = */ + { + "%bool_var = OpVariable %_pf_bool Output", + "%bool_from_inner_struct = OpCompositeExtract %bool %used_inner_struct 0", + "OpStore %bool_var %bool_from_inner_struct", + }, + /* .dead_consts = */ + { + "%dead_int = OpConstant %int 2", + "%dead_outer_struct = OpConstantComposite %outer_struct %used_inner_struct %dead_int %used_double" + }, + /* .checks = */ + { + "; CHECK: [[bool:%\\w+]] = OpConstantTrue", + "; CHECK: [[int:%\\w+]] = OpConstant %int 1", + "; CHECK: [[float:%\\w+]] = OpConstant %float 1.23", + "; CHECK: [[double:%\\w+]] = OpConstant %double 1.2345678901234", + "; CHECK: [[struct:%\\w+]] = OpConstantComposite %inner_struct [[bool]] [[int]] [[float]] [[double]]", + "; CHECK-NOT: OpConstant", + "; CHECK: OpCompositeExtract %bool [[struct]]", + } + }, + // A nesting struct case. The outer struct is used, so the inner struct should not + // be removed even though it is not used anywhere. + { + /* .used_const = */ + { + "%used_bool = OpConstantTrue %bool", + "%used_int = OpConstant %int 1", + "%used_float = OpConstant %float 1.23", + "%used_double = OpConstant %double 1.2345678901234", + "%used_inner_struct = OpConstantComposite %inner_struct %used_bool %used_int %used_float %used_double", + "%used_outer_struct = OpConstantComposite %outer_struct %used_inner_struct %used_int %used_double" + }, + /* .main_insts = */ + { + "%int_var = OpVariable %_pf_int Output", + "%int_from_outer_struct = OpCompositeExtract %int %used_outer_struct 1", + "OpStore %int_var %int_from_outer_struct", + }, + /* .dead_consts = */ {}, + /* .checks = */ + { + "; CHECK: [[bool:%\\w+]] = OpConstantTrue %bool", + "; CHECK: [[int:%\\w+]] = OpConstant %int 1", + "; CHECK: [[float:%\\w+]] = OpConstant %float 1.23", + "; CHECK: [[double:%\\w+]] = OpConstant %double 1.2345678901234", + "; CHECK: [[inner_struct:%\\w+]] = OpConstantComposite %inner_struct %used_bool %used_int %used_float %used_double", + "; CHECK: [[outer_struct:%\\w+]] = OpConstantComposite %outer_struct %used_inner_struct %used_int %used_double", + "; CHECK: OpCompositeExtract %int [[outer_struct]]", + }, + }, + // clang-format on + }))); + +INSTANTIATE_TEST_CASE_P( + ScalarTypeSpecConstants, EliminateDeadConstantTest, + ::testing::ValuesIn(std::vector({ + // clang-format off + // All scalar type spec constants. + { + /* .used_consts = */ + { + "%used_bool = OpSpecConstantTrue %bool", + "%used_uint = OpSpecConstant %uint 2", + "%used_int = OpSpecConstant %int 2", + "%used_float = OpSpecConstant %float 2.5", + "%used_double = OpSpecConstant %double 1.428571428514", + }, + /* .main_insts = */ + { + "%bool_var = OpVariable %_pf_bool Output", + "%uint_var = OpVariable %_pf_uint Output", + "%int_var = OpVariable %_pf_int Output", + "%float_var = OpVariable %_pf_float Output", + "%double_var = OpVariable %_pf_double Output", + "OpStore %bool_var %used_bool", + "OpStore %uint_var %used_uint", + "OpStore %int_var %used_int", + "OpStore %float_var %used_float", + "OpStore %double_var %used_double", + }, + /* .dead_consts = */ + { + "%dead_bool = OpSpecConstantTrue %bool", + "%dead_uint = OpSpecConstant %uint 2", + "%dead_int = OpSpecConstant %int 2", + "%dead_float = OpSpecConstant %float 2.5", + "%dead_double = OpSpecConstant %double 1.428571428514", + }, + /* .checks = */ + { + "; CHECK: [[bool:%\\w+]] = OpSpecConstantTrue %bool", + "; CHECK: [[uint:%\\w+]] = OpSpecConstant %uint 2", + "; CHECK: [[int:%\\w+]] = OpSpecConstant %int 2", + "; CHECK: [[float:%\\w+]] = OpSpecConstant %float 2.5", + "; CHECK: [[double:%\\w+]] = OpSpecConstant %double 1.428571428514", + "; CHECK-NOT: OpSpecConstant", + "; CHECK: OpStore {{%\\w+}} [[bool]]", + "; CHECK: OpStore {{%\\w+}} [[uint]]", + "; CHECK: OpStore {{%\\w+}} [[int]]", + "; CHECK: OpStore {{%\\w+}} [[float]]", + "; CHECK: OpStore {{%\\w+}} [[double]]", + }, + }, + // clang-format on + }))); + +INSTANTIATE_TEST_CASE_P( + VectorTypeSpecConstants, EliminateDeadConstantTest, + ::testing::ValuesIn(std::vector({ + // clang-format off + // Bool vector type spec constants. One vector has all component dead, + // another vector has one dead boolean and one used boolean. + { + /* .used_consts = */ + { + "%used_bool = OpSpecConstantTrue %bool", + }, + /* .main_insts = */ + { + "%bool_var = OpVariable %_pf_bool Output", + "OpStore %bool_var %used_bool", + }, + /* .dead_consts = */ + { + "%dead_bool = OpSpecConstantFalse %bool", + "%dead_bool_vec1 = OpSpecConstantComposite %v2bool %dead_bool %dead_bool", + "%dead_bool_vec2 = OpSpecConstantComposite %v2bool %dead_bool %used_bool", + }, + /* .checks = */ + { + "; CHECK: [[bool:%\\w+]] = OpSpecConstantTrue %bool", + "; CHECK-NOT: OpSpecConstant", + "; CHECK: OpStore {{%\\w+}} [[bool]]", + }, + }, + + // Uint vector type spec constants. One vector has all component dead, + // another vector has one dead unsigend integer and one used unsigned + // integer. + { + /* .used_consts = */ + { + "%used_uint = OpSpecConstant %uint 3", + }, + /* .main_insts = */ + { + "%uint_var = OpVariable %_pf_uint Output", + "OpStore %uint_var %used_uint", + }, + /* .dead_consts = */ + { + "%dead_uint = OpSpecConstant %uint 1", + "%dead_uint_vec1 = OpSpecConstantComposite %v2uint %dead_uint %dead_uint", + "%dead_uint_vec2 = OpSpecConstantComposite %v2uint %dead_uint %used_uint", + }, + /* .checks = */ + { + "; CHECK: [[uint:%\\w+]] = OpSpecConstant %uint 3", + "; CHECK-NOT: OpSpecConstant", + "; CHECK: OpStore {{%\\w+}} [[uint]]", + }, + }, + + // Int vector type spec constants. One vector has all component dead, + // another vector has one dead integer and one used integer. + { + /* .used_consts = */ + { + "%used_int = OpSpecConstant %int 3", + }, + /* .main_insts = */ + { + "%int_var = OpVariable %_pf_int Output", + "OpStore %int_var %used_int", + }, + /* .dead_consts = */ + { + "%dead_int = OpSpecConstant %int 1", + "%dead_int_vec1 = OpSpecConstantComposite %v2int %dead_int %dead_int", + "%dead_int_vec2 = OpSpecConstantComposite %v2int %dead_int %used_int", + }, + /* .checks = */ + { + "; CHECK: [[int:%\\w+]] = OpSpecConstant %int 3", + "; CHECK-NOT: OpSpecConstant", + "; CHECK: OpStore {{%\\w+}} [[int]]", + }, + }, + + // Int vector type spec constants built with both spec constants and + // front-end constants. + { + /* .used_consts = */ + { + "%used_spec_int = OpSpecConstant %int 3", + "%used_front_end_int = OpConstant %int 3", + }, + /* .main_insts = */ + { + "%int_var1 = OpVariable %_pf_int Output", + "OpStore %int_var1 %used_spec_int", + "%int_var2 = OpVariable %_pf_int Output", + "OpStore %int_var2 %used_front_end_int", + }, + /* .dead_consts = */ + { + "%dead_spec_int = OpSpecConstant %int 1", + "%dead_front_end_int = OpConstant %int 1", + // Dead front-end and dead spec constants + "%dead_int_vec1 = OpSpecConstantComposite %v2int %dead_spec_int %dead_front_end_int", + // Used front-end and dead spec constants + "%dead_int_vec2 = OpSpecConstantComposite %v2int %dead_spec_int %used_front_end_int", + // Dead front-end and used spec constants + "%dead_int_vec3 = OpSpecConstantComposite %v2int %dead_front_end_int %used_spec_int", + }, + /* .checks = */ + { + "; CHECK: [[int1:%\\w+]] = OpSpecConstant %int 3", + "; CHECK: [[int2:%\\w+]] = OpConstant %int 3", + "; CHECK-NOT: OpSpecConstant", + "; CHECK-NOT: OpConstant", + "; CHECK: OpStore {{%\\w+}} [[int1]]", + "; CHECK: OpStore {{%\\w+}} [[int2]]", + }, + }, + // clang-format on + }))); + +INSTANTIATE_TEST_CASE_P( + SpecConstantOp, EliminateDeadConstantTest, + ::testing::ValuesIn(std::vector({ + // clang-format off + // Cast operations: uint <-> int <-> bool + { + /* .used_consts = */ {}, + /* .main_insts = */ {}, + /* .dead_consts = */ + { + // Assistant constants, only used in dead spec constant + // operations. + "%signed_zero = OpConstant %int 0", + "%signed_zero_vec = OpConstantComposite %v2int %signed_zero %signed_zero", + "%unsigned_zero = OpConstant %uint 0", + "%unsigned_zero_vec = OpConstantComposite %v2uint %unsigned_zero %unsigned_zero", + "%signed_one = OpConstant %int 1", + "%signed_one_vec = OpConstantComposite %v2int %signed_one %signed_one", + "%unsigned_one = OpConstant %uint 1", + "%unsigned_one_vec = OpConstantComposite %v2uint %unsigned_one %unsigned_one", + + // Spec constants that support casting to each other. + "%dead_bool = OpSpecConstantTrue %bool", + "%dead_uint = OpSpecConstant %uint 1", + "%dead_int = OpSpecConstant %int 2", + "%dead_bool_vec = OpSpecConstantComposite %v2bool %dead_bool %dead_bool", + "%dead_uint_vec = OpSpecConstantComposite %v2uint %dead_uint %dead_uint", + "%dead_int_vec = OpSpecConstantComposite %v2int %dead_int %dead_int", + + // Scalar cast to boolean spec constant. + "%int_to_bool = OpSpecConstantOp %bool INotEqual %dead_int %signed_zero", + "%uint_to_bool = OpSpecConstantOp %bool INotEqual %dead_uint %unsigned_zero", + + // Vector cast to boolean spec constant. + "%int_to_bool_vec = OpSpecConstantOp %v2bool INotEqual %dead_int_vec %signed_zero_vec", + "%uint_to_bool_vec = OpSpecConstantOp %v2bool INotEqual %dead_uint_vec %unsigned_zero_vec", + + // Scalar cast to int spec constant. + "%bool_to_int = OpSpecConstantOp %int Select %dead_bool %signed_one %signed_zero", + "%uint_to_int = OpSpecConstantOp %uint IAdd %dead_uint %unsigned_zero", + + // Vector cast to int spec constant. + "%bool_to_int_vec = OpSpecConstantOp %v2int Select %dead_bool_vec %signed_one_vec %signed_zero_vec", + "%uint_to_int_vec = OpSpecConstantOp %v2uint IAdd %dead_uint_vec %unsigned_zero_vec", + + // Scalar cast to uint spec constant. + "%bool_to_uint = OpSpecConstantOp %uint Select %dead_bool %unsigned_one %unsigned_zero", + "%int_to_uint_vec = OpSpecConstantOp %uint IAdd %dead_int %signed_zero", + + // Vector cast to uint spec constant. + "%bool_to_uint_vec = OpSpecConstantOp %v2uint Select %dead_bool_vec %unsigned_one_vec %unsigned_zero_vec", + "%int_to_uint = OpSpecConstantOp %v2uint IAdd %dead_int_vec %signed_zero_vec", + }, + /* .checks = */ + { + "; CHECK-NOT: OpConstant", + "; CHECK-NOT: OpSpecConstant", + }, + }, + + // Add, sub, mul, div, rem. + { + /* .used_consts = */ {}, + /* .main_insts = */ {}, + /* .dead_consts = */ + { + "%dead_spec_int_a = OpSpecConstant %int 1", + "%dead_spec_int_a_vec = OpSpecConstantComposite %v2int %dead_spec_int_a %dead_spec_int_a", + + "%dead_spec_int_b = OpSpecConstant %int 2", + "%dead_spec_int_b_vec = OpSpecConstantComposite %v2int %dead_spec_int_b %dead_spec_int_b", + + "%dead_const_int_c = OpConstant %int 3", + "%dead_const_int_c_vec = OpConstantComposite %v2int %dead_const_int_c %dead_const_int_c", + + // Add + "%add_a_b = OpSpecConstantOp %int IAdd %dead_spec_int_a %dead_spec_int_b", + "%add_a_b_vec = OpSpecConstantOp %v2int IAdd %dead_spec_int_a_vec %dead_spec_int_b_vec", + + // Sub + "%sub_a_b = OpSpecConstantOp %int ISub %dead_spec_int_a %dead_spec_int_b", + "%sub_a_b_vec = OpSpecConstantOp %v2int ISub %dead_spec_int_a_vec %dead_spec_int_b_vec", + + // Mul + "%mul_a_b = OpSpecConstantOp %int IMul %dead_spec_int_a %dead_spec_int_b", + "%mul_a_b_vec = OpSpecConstantOp %v2int IMul %dead_spec_int_a_vec %dead_spec_int_b_vec", + + // Div + "%div_a_b = OpSpecConstantOp %int SDiv %dead_spec_int_a %dead_spec_int_b", + "%div_a_b_vec = OpSpecConstantOp %v2int SDiv %dead_spec_int_a_vec %dead_spec_int_b_vec", + + // Bitwise Xor + "%xor_a_b = OpSpecConstantOp %int BitwiseXor %dead_spec_int_a %dead_spec_int_b", + "%xor_a_b_vec = OpSpecConstantOp %v2int BitwiseXor %dead_spec_int_a_vec %dead_spec_int_b_vec", + + // Scalar Comparison + "%less_a_b = OpSpecConstantOp %bool SLessThan %dead_spec_int_a %dead_spec_int_b", + }, + /* .checks = */ + { + "; CHECK-NOT: OpConstant", + "; CHECK-NOT: OpSpecConstant", + }, + }, + + // Vectors without used swizzles should be removed. + { + /* .used_consts = */ + { + "%used_int = OpConstant %int 3", + }, + /* .main_insts = */ + { + "%int_var = OpVariable %_pf_int Output", + "OpStore %int_var %used_int", + }, + /* .dead_consts = */ + { + "%dead_int = OpConstant %int 3", + + "%dead_spec_int_a = OpSpecConstant %int 1", + "%vec_a = OpSpecConstantComposite %v4int %dead_spec_int_a %dead_spec_int_a %dead_int %dead_int", + + "%dead_spec_int_b = OpSpecConstant %int 2", + "%vec_b = OpSpecConstantComposite %v4int %dead_spec_int_b %dead_spec_int_b %used_int %used_int", + + // Extract scalar + "%a_x = OpSpecConstantOp %int CompositeExtract %vec_a 0", + "%b_x = OpSpecConstantOp %int CompositeExtract %vec_b 0", + + // Extract vector + "%a_xy = OpSpecConstantOp %v2int VectorShuffle %vec_a %vec_a 0 1", + "%b_xy = OpSpecConstantOp %v2int VectorShuffle %vec_b %vec_b 0 1", + }, + /* .checks = */ + { + "; CHECK: [[int:%\\w+]] = OpConstant %int 3", + "; CHECK-NOT: OpConstant", + "; CHECK-NOT: OpSpecConstant", + "; CHECK: OpStore {{%\\w+}} [[int]]", + }, + }, + // Vectors with used swizzles should not be removed. + { + /* .used_consts = */ + { + "%used_int = OpConstant %int 3", + "%used_spec_int_a = OpSpecConstant %int 1", + "%used_spec_int_b = OpSpecConstant %int 2", + // Create vectors + "%vec_a = OpSpecConstantComposite %v4int %used_spec_int_a %used_spec_int_a %used_int %used_int", + "%vec_b = OpSpecConstantComposite %v4int %used_spec_int_b %used_spec_int_b %used_int %used_int", + // Extract vector + "%a_xy = OpSpecConstantOp %v2int VectorShuffle %vec_a %vec_a 0 1", + "%b_xy = OpSpecConstantOp %v2int VectorShuffle %vec_b %vec_b 0 1", + }, + /* .main_insts = */ + { + "%v2int_var_a = OpVariable %_pf_v2int Output", + "%v2int_var_b = OpVariable %_pf_v2int Output", + "OpStore %v2int_var_a %a_xy", + "OpStore %v2int_var_b %b_xy", + }, + /* .dead_consts = */ {}, + /* .checks = */ + { + "; CHECK: [[int:%\\w+]] = OpConstant %int 3", + "; CHECK: [[a:%\\w+]] = OpSpecConstant %int 1", + "; CHECK: [[b:%\\w+]] = OpSpecConstant %int 2", + "; CHECK: [[veca:%\\w+]] = OpSpecConstantComposite %v4int [[a]] [[a]] [[int]] [[int]]", + "; CHECK: [[vecb:%\\w+]] = OpSpecConstantComposite %v4int [[b]] [[b]] [[int]] [[int]]", + "; CHECK: [[exa:%\\w+]] = OpSpecConstantOp %v2int VectorShuffle [[veca]] [[veca]] 0 1", + "; CHECK: [[exb:%\\w+]] = OpSpecConstantOp %v2int VectorShuffle [[vecb]] [[vecb]] 0 1", + "; CHECK-NOT: OpConstant", + "; CHECK-NOT: OpSpecConstant", + "; CHECK: OpStore {{%\\w+}} [[exa]]", + "; CHECK: OpStore {{%\\w+}} [[exb]]", + }, + }, + // clang-format on + }))); + +INSTANTIATE_TEST_CASE_P( + LongDefUseChain, EliminateDeadConstantTest, + ::testing::ValuesIn(std::vector({ + // clang-format off + // Long Def-Use chain with binary operations. + { + /* .used_consts = */ + { + "%array_size = OpConstant %int 4", + "%type_arr_int_4 = OpTypeArray %int %array_size", + "%used_int_0 = OpConstant %int 100", + "%used_int_1 = OpConstant %int 1", + "%used_int_2 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_1", + "%used_int_3 = OpSpecConstantOp %int ISub %used_int_0 %used_int_2", + "%used_int_4 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_3", + "%used_int_5 = OpSpecConstantOp %int ISub %used_int_0 %used_int_4", + "%used_int_6 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_5", + "%used_int_7 = OpSpecConstantOp %int ISub %used_int_0 %used_int_6", + "%used_int_8 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_7", + "%used_int_9 = OpSpecConstantOp %int ISub %used_int_0 %used_int_8", + "%used_int_10 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_9", + "%used_int_11 = OpSpecConstantOp %int ISub %used_int_0 %used_int_10", + "%used_int_12 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_11", + "%used_int_13 = OpSpecConstantOp %int ISub %used_int_0 %used_int_12", + "%used_int_14 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_13", + "%used_int_15 = OpSpecConstantOp %int ISub %used_int_0 %used_int_14", + "%used_int_16 = OpSpecConstantOp %int ISub %used_int_0 %used_int_15", + "%used_int_17 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_16", + "%used_int_18 = OpSpecConstantOp %int ISub %used_int_0 %used_int_17", + "%used_int_19 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_18", + "%used_int_20 = OpSpecConstantOp %int ISub %used_int_0 %used_int_19", + "%used_vec_a = OpSpecConstantComposite %v2int %used_int_18 %used_int_19", + "%used_vec_b = OpSpecConstantOp %v2int IMul %used_vec_a %used_vec_a", + "%used_int_21 = OpSpecConstantOp %int CompositeExtract %used_vec_b 0", + "%used_array = OpConstantComposite %type_arr_int_4 %used_int_20 %used_int_20 %used_int_21 %used_int_21", + }, + /* .main_insts = */ + { + "%int_var = OpVariable %_pf_int Output", + "%used_array_2 = OpCompositeExtract %int %used_array 2", + "OpStore %int_var %used_array_2", + }, + /* .dead_consts = */ + { + "%dead_int_1 = OpConstant %int 2", + "%dead_int_2 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_1", + "%dead_int_3 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_2", + "%dead_int_4 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_3", + "%dead_int_5 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_4", + "%dead_int_6 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_5", + "%dead_int_7 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_6", + "%dead_int_8 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_7", + "%dead_int_9 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_8", + "%dead_int_10 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_9", + "%dead_int_11 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_10", + "%dead_int_12 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_11", + "%dead_int_13 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_12", + "%dead_int_14 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_13", + "%dead_int_15 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_14", + "%dead_int_16 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_15", + "%dead_int_17 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_16", + "%dead_int_18 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_17", + "%dead_int_19 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_18", + "%dead_int_20 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_19", + "%dead_vec_a = OpSpecConstantComposite %v2int %dead_int_18 %dead_int_19", + "%dead_vec_b = OpSpecConstantOp %v2int IMul %dead_vec_a %dead_vec_a", + "%dead_int_21 = OpSpecConstantOp %int CompositeExtract %dead_vec_b 0", + "%dead_array = OpConstantComposite %type_arr_int_4 %dead_int_20 %used_int_20 %dead_int_19 %used_int_19", + }, + /* .checks = */ + { + "; CHECK: OpConstant %int 4", + "; CHECK: [[array:%\\w+]] = OpConstantComposite %type_arr_int_4 %used_int_20 %used_int_20 %used_int_21 %used_int_21", + "; CHECK-NOT: OpConstant", + "; CHECK-NOT: OpSpecConstant", + "; CHECK: OpStore {{%\\w+}} [[array]]", + }, + }, + // Long Def-Use chain with swizzle + // clang-format on + }))); + +TEST_F(AggressiveDCETest, DeadDecorationGroup) { + // The decoration group should be eliminated because the target of group + // decorate is dead. + const std::string text = R"( +; CHECK-NOT: OpDecorat +; CHECK-NOT: OpGroupDecorate +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpDecorate %1 Restrict +OpDecorate %1 Aliased +%1 = OpDecorationGroup +OpGroupDecorate %1 %var +%void = OpTypeVoid +%func = OpTypeFunction %void +%uint = OpTypeInt 32 0 +%uint_ptr = OpTypePointer Function %uint +%main = OpFunction %void None %func +%2 = OpLabel +%var = OpVariable %uint_ptr Function +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(AggressiveDCETest, ParitallyDeadDecorationGroup) { + const std::string text = R"( +; CHECK: OpDecorate [[grp:%\w+]] Restrict +; CHECK: OpDecorate [[grp]] Aliased +; CHECK: [[grp]] = OpDecorationGroup +; CHECK: OpGroupDecorate [[grp]] [[output:%\w+]] +; CHECK: [[output]] = OpVariable {{%\w+}} Output +; CHECK-NOT: OpVariable {{%\w+}} Function +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %output +OpExecutionMode %main OriginUpperLeft +OpDecorate %1 Restrict +OpDecorate %1 Aliased +%1 = OpDecorationGroup +OpGroupDecorate %1 %var %output +%void = OpTypeVoid +%func = OpTypeFunction %void +%uint = OpTypeInt 32 0 +%uint_ptr_Function = OpTypePointer Function %uint +%uint_ptr_Output = OpTypePointer Output %uint +%uint_0 = OpConstant %uint 0 +%output = OpVariable %uint_ptr_Output Output +%main = OpFunction %void None %func +%2 = OpLabel +%var = OpVariable %uint_ptr_Function Function +OpStore %output %uint_0 +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(AggressiveDCETest, ParitallyDeadDecorationGroupDifferentGroupDecorate) { + const std::string text = R"( +; CHECK: OpDecorate [[grp:%\w+]] Restrict +; CHECK: OpDecorate [[grp]] Aliased +; CHECK: [[grp]] = OpDecorationGroup +; CHECK: OpGroupDecorate [[grp]] [[output:%\w+]] +; CHECK-NOT: OpGroupDecorate +; CHECK: [[output]] = OpVariable {{%\w+}} Output +; CHECK-NOT: OpVariable {{%\w+}} Function +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %output +OpExecutionMode %main OriginUpperLeft +OpDecorate %1 Restrict +OpDecorate %1 Aliased +%1 = OpDecorationGroup +OpGroupDecorate %1 %output +OpGroupDecorate %1 %var +%void = OpTypeVoid +%func = OpTypeFunction %void +%uint = OpTypeInt 32 0 +%uint_ptr_Function = OpTypePointer Function %uint +%uint_ptr_Output = OpTypePointer Output %uint +%uint_0 = OpConstant %uint 0 +%output = OpVariable %uint_ptr_Output Output +%main = OpFunction %void None %func +%2 = OpLabel +%var = OpVariable %uint_ptr_Function Function +OpStore %output %uint_0 +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(AggressiveDCETest, DeadGroupMemberDecorate) { + const std::string text = R"( +; CHECK-NOT: OpDec +; CHECK-NOT: OpGroup +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpDecorate %1 Offset 0 +OpDecorate %1 Uniform +%1 = OpDecorationGroup +OpGroupMemberDecorate %1 %var 0 +%void = OpTypeVoid +%func = OpTypeFunction %void +%uint = OpTypeInt 32 0 +%struct = OpTypeStruct %uint %uint +%struct_ptr = OpTypePointer Function %struct +%main = OpFunction %void None %func +%2 = OpLabel +%var = OpVariable %struct_ptr Function +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(AggressiveDCETest, PartiallyDeadGroupMemberDecorate) { + const std::string text = R"( +; CHECK: OpDecorate [[grp:%\w+]] Offset 0 +; CHECK: OpDecorate [[grp]] Uniform +; CHECK: [[grp]] = OpDecorationGroup +; CHECK: OpGroupMemberDecorate [[grp]] [[output:%\w+]] 1 +; CHECK: [[output]] = OpTypeStruct +; CHECK-NOT: OpTypeStruct +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %output +OpExecutionMode %main OriginUpperLeft +OpDecorate %1 Offset 0 +OpDecorate %1 Uniform +%1 = OpDecorationGroup +OpGroupMemberDecorate %1 %var_struct 0 %output_struct 1 +%void = OpTypeVoid +%func = OpTypeFunction %void +%uint = OpTypeInt 32 0 +%var_struct = OpTypeStruct %uint %uint +%output_struct = OpTypeStruct %uint %uint +%struct_ptr_Function = OpTypePointer Function %var_struct +%struct_ptr_Output = OpTypePointer Output %output_struct +%uint_ptr_Output = OpTypePointer Output %uint +%output = OpVariable %struct_ptr_Output Output +%uint_0 = OpConstant %uint 0 +%main = OpFunction %void None %func +%2 = OpLabel +%var = OpVariable %struct_ptr_Function Function +%3 = OpAccessChain %uint_ptr_Output %output %uint_0 +OpStore %3 %uint_0 +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(AggressiveDCETest, + PartiallyDeadGroupMemberDecorateDifferentGroupDecorate) { + const std::string text = R"( +; CHECK: OpDecorate [[grp:%\w+]] Offset 0 +; CHECK: OpDecorate [[grp]] Uniform +; CHECK: [[grp]] = OpDecorationGroup +; CHECK: OpGroupMemberDecorate [[grp]] [[output:%\w+]] 1 +; CHECK-NOT: OpGroupMemberDecorate +; CHECK: [[output]] = OpTypeStruct +; CHECK-NOT: OpTypeStruct +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %output +OpExecutionMode %main OriginUpperLeft +OpDecorate %1 Offset 0 +OpDecorate %1 Uniform +%1 = OpDecorationGroup +OpGroupMemberDecorate %1 %var_struct 0 +OpGroupMemberDecorate %1 %output_struct 1 +%void = OpTypeVoid +%func = OpTypeFunction %void +%uint = OpTypeInt 32 0 +%var_struct = OpTypeStruct %uint %uint +%output_struct = OpTypeStruct %uint %uint +%struct_ptr_Function = OpTypePointer Function %var_struct +%struct_ptr_Output = OpTypePointer Output %output_struct +%uint_ptr_Output = OpTypePointer Output %uint +%output = OpVariable %struct_ptr_Output Output +%uint_0 = OpConstant %uint 0 +%main = OpFunction %void None %func +%2 = OpLabel +%var = OpVariable %struct_ptr_Function Function +%3 = OpAccessChain %uint_ptr_Output %output %uint_0 +OpStore %3 %uint_0 +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +// Test for #1404 +TEST_F(AggressiveDCETest, DontRemoveWorkgroupSize) { + const std::string text = R"( +; CHECK: OpDecorate [[wgs:%\w+]] BuiltIn WorkgroupSize +; CHECK: [[wgs]] = OpSpecConstantComposite +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %func "func" +OpExecutionMode %func LocalSize 1 1 1 +OpDecorate %1 BuiltIn WorkgroupSize +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%functy = OpTypeFunction %void +%v3int = OpTypeVector %int 3 +%2 = OpSpecConstant %int 1 +%1 = OpSpecConstantComposite %v3int %2 %2 %2 +%func = OpFunction %void None %functy +%3 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} +#endif // SPIRV_EFFCEE + +// Test for #1214 +TEST_F(AggressiveDCETest, LoopHeaderIsAlsoAnotherLoopMerge) { + const std::string text = R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %1 "func" %2 +OpExecutionMode %1 OriginUpperLeft +%void = OpTypeVoid +%bool = OpTypeBool +%true = OpConstantTrue %bool +%uint = OpTypeInt 32 0 +%_ptr_Output_uint = OpTypePointer Output %uint +%2 = OpVariable %_ptr_Output_uint Output +%uint_0 = OpConstant %uint 0 +%9 = OpTypeFunction %void +%1 = OpFunction %void None %9 +%10 = OpLabel +OpBranch %11 +%11 = OpLabel +OpLoopMerge %12 %13 None +OpBranchConditional %true %14 %13 +%14 = OpLabel +OpStore %2 %uint_0 +OpLoopMerge %15 %16 None +OpBranchConditional %true %15 %16 +%16 = OpLabel +OpBranch %14 +%15 = OpLabel +OpBranchConditional %true %12 %13 +%13 = OpLabel +OpBranch %11 +%12 = OpLabel +%17 = OpPhi %uint %uint_0 %15 %uint_0 %18 +OpStore %2 %17 +OpLoopMerge %19 %18 None +OpBranchConditional %true %19 %18 +%18 = OpLabel +OpBranch %12 +%19 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(text, text, true, true); +} + +TEST_F(AggressiveDCETest, BreaksDontVisitPhis) { + const std::string text = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %func "func" %var +OpExecutionMode %func OriginUpperLeft +%void = OpTypeVoid +%bool = OpTypeBool +%true = OpConstantTrue %bool +%int = OpTypeInt 32 0 +%int_ptr_Output = OpTypePointer Output %int +%var = OpVariable %int_ptr_Output Output +%int0 = OpConstant %int 0 +%functy = OpTypeFunction %void +%func = OpFunction %void None %functy +%entry = OpLabel +OpBranch %outer_header +%outer_header = OpLabel +OpLoopMerge %outer_merge %outer_continue None +OpBranchConditional %true %inner_header %outer_continue +%inner_header = OpLabel +%phi = OpPhi %int %int0 %outer_header %int0 %inner_continue +OpStore %var %phi +OpLoopMerge %inner_merge %inner_continue None +OpBranchConditional %true %inner_merge %inner_continue +%inner_continue = OpLabel +OpBranch %inner_header +%inner_merge = OpLabel +OpBranch %outer_continue +%outer_continue = OpLabel +%p = OpPhi %int %int0 %outer_header %int0 %inner_merge +OpStore %var %p +OpBranch %outer_header +%outer_merge = OpLabel +OpReturn +OpFunctionEnd +)"; + + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, + std::get<1>(SinglePassRunAndDisassemble( + text, false, true))); +} + +// Test for #1212 +TEST_F(AggressiveDCETest, ConstStoreInnerLoop) { + const std::string text = R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %1 "main" %2 +%void = OpTypeVoid +%4 = OpTypeFunction %void +%float = OpTypeFloat 32 +%bool = OpTypeBool +%true = OpConstantTrue %bool +%_ptr_Output_float = OpTypePointer Output %float +%2 = OpVariable %_ptr_Output_float Output +%float_3 = OpConstant %float 3 +%1 = OpFunction %void None %4 +%13 = OpLabel +OpBranch %14 +%14 = OpLabel +OpLoopMerge %15 %16 None +OpBranchConditional %true %17 %15 +%17 = OpLabel +OpStore %2 %float_3 +OpLoopMerge %18 %17 None +OpBranchConditional %true %18 %17 +%18 = OpLabel +OpBranch %15 +%16 = OpLabel +OpBranch %14 +%15 = OpLabel +OpBranch %20 +%20 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck(text, text, true, true); +} + +// Test for #1212 +TEST_F(AggressiveDCETest, InnerLoopCopy) { + const std::string text = R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %1 "main" %2 %3 +%void = OpTypeVoid +%5 = OpTypeFunction %void +%float = OpTypeFloat 32 +%bool = OpTypeBool +%true = OpConstantTrue %bool +%_ptr_Output_float = OpTypePointer Output %float +%_ptr_Input_float = OpTypePointer Input %float +%2 = OpVariable %_ptr_Output_float Output +%3 = OpVariable %_ptr_Input_float Input +%1 = OpFunction %void None %5 +%14 = OpLabel +OpBranch %15 +%15 = OpLabel +OpLoopMerge %16 %17 None +OpBranchConditional %true %18 %16 +%18 = OpLabel +%19 = OpLoad %float %3 +OpStore %2 %19 +OpLoopMerge %20 %18 None +OpBranchConditional %true %20 %18 +%20 = OpLabel +OpBranch %16 +%17 = OpLabel +OpBranch %15 +%16 = OpLabel +OpBranch %22 +%22 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck(text, text, true, true); +} + +TEST_F(AggressiveDCETest, AtomicAdd) { + const std::string text = R"(OpCapability SampledBuffer +OpCapability StorageImageExtendedFormats +OpCapability ImageBuffer +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %2 "min" %gl_GlobalInvocationID +OpExecutionMode %2 LocalSize 64 1 1 +OpSource HLSL 600 +OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId +OpDecorate %4 DescriptorSet 4 +OpDecorate %4 Binding 70 +%uint = OpTypeInt 32 0 +%6 = OpTypeImage %uint Buffer 0 0 0 2 R32ui +%_ptr_UniformConstant_6 = OpTypePointer UniformConstant %6 +%_ptr_Private_6 = OpTypePointer Private %6 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%uint_0 = OpConstant %uint 0 +%uint_1 = OpConstant %uint 1 +%v3uint = OpTypeVector %uint 3 +%_ptr_Input_v3uint = OpTypePointer Input %v3uint +%_ptr_Image_uint = OpTypePointer Image %uint +%4 = OpVariable %_ptr_UniformConstant_6 UniformConstant +%16 = OpVariable %_ptr_Private_6 Private +%gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input +%2 = OpFunction %void None %10 +%17 = OpLabel +%18 = OpLoad %6 %4 +OpStore %16 %18 +%19 = OpImageTexelPointer %_ptr_Image_uint %16 %uint_0 %uint_0 +%20 = OpAtomicIAdd %uint %19 %uint_1 %uint_0 %uint_1 +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck(text, text, true, true); +} + +// TODO(greg-lunarg): Add tests to verify handling of these cases: +// +// Check that logical addressing required +// Check that function calls inhibit optimization +// Others? + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/assembly_builder.h b/3rdparty/spirv-tools/test/opt/assembly_builder.h new file mode 100644 index 000000000..2edd4a58c --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/assembly_builder.h @@ -0,0 +1,264 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_TEST_OPT_ASSEMBLY_BUILDER +#define LIBSPIRV_TEST_OPT_ASSEMBLY_BUILDER + +#include +#include +#include +#include +#include +#include + +namespace spvtools { + +// A simple SPIR-V assembly code builder for test uses. It builds an SPIR-V +// assembly module from vectors of assembly strings. It allows users to add +// instructions to the main function and the type-constants-globals section +// directly. It relies on OpName instructions and friendly-name disassembling +// to keep the ID names unchanged after assembling. +// +// An assembly module is divided into several sections, matching with the +// SPIR-V Logical Layout: +// Global Preamble: +// OpCapability instructions; +// OpExtension instructions and OpExtInstImport instructions; +// OpMemoryModel instruction; +// OpEntryPoint and OpExecutionMode instruction; +// OpString, OpSourceExtension, OpSource and OpSourceContinued instructions. +// Names: +// OpName instructions. +// Annotations: +// OpDecorate, OpMemberDecorate, OpGroupDecorate, OpGroupMemberDecorate and +// OpDecorationGroup. +// Types, Constants and Global variables: +// Types, constants and global variables declaration instructions. +// Main Function: +// Main function instructions. +// Main Function Postamble: +// The return and function end instructions. +// +// The assembly code is built by concatenating all the strings in the above +// sections. +// +// Users define the contents in section +// and
. The section is to hold the names for IDs to +// keep them unchanged before and after assembling. All defined IDs to be added +// to this code builder will be assigned with a global name through OpName +// instruction. The name is extracted from the definition instruction. +// E.g. adding instruction: %var_a = OpConstant %int 2, will also add an +// instruction: OpName %var_a, "var_a". +// +// Note that the name must not be used on more than one defined IDs and +// friendly-name disassembling must be enabled so that OpName instructions will +// be respected. +class AssemblyBuilder { + // The base ID value for spec constants. + static const uint32_t SPEC_ID_BASE = 200; + + public: + // Initalize a minimal SPIR-V assembly code as the template. The minimal + // module contains an empty main function and some predefined names for the + // main function. + AssemblyBuilder() + : spec_id_counter_(SPEC_ID_BASE), + global_preamble_({ + // clang-format off + "OpCapability Shader", + "OpCapability Float64", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Vertex %main \"main\"", + // clang-format on + }), + names_(), + annotations_(), + types_consts_globals_(), + main_func_(), + main_func_postamble_({ + "OpReturn", + "OpFunctionEnd", + }) { + AppendTypesConstantsGlobals({ + "%void = OpTypeVoid", + "%main_func_type = OpTypeFunction %void", + }); + AppendInMain({ + "%main = OpFunction %void None %main_func_type", + "%main_func_entry_block = OpLabel", + }); + } + + // Appends OpName instructions to this builder. Instrcution strings that do + // not start with 'OpName ' will be skipped. Returns the references of this + // assembly builder. + AssemblyBuilder& AppendNames(const std::vector& vec_asm_code) { + for (auto& inst_str : vec_asm_code) { + if (inst_str.find("OpName ") == 0) { + names_.push_back(inst_str); + } + } + return *this; + } + + // Appends instructions to the types-constants-globals section and returns + // the reference of this assembly builder. IDs defined in the given code will + // be added to the Names section and then be registered with OpName + // instruction. Corresponding decoration instruction will be added for spec + // constants defined with opcode: 'OpSpecConstant'. + AssemblyBuilder& AppendTypesConstantsGlobals( + const std::vector& vec_asm_code) { + AddNamesForResultIDsIn(vec_asm_code); + // Check spec constants defined with OpSpecConstant. + for (auto& inst_str : vec_asm_code) { + if (inst_str.find("= OpSpecConstant ") != std::string::npos || + inst_str.find("= OpSpecConstantTrue ") != std::string::npos || + inst_str.find("= OpSpecConstantFalse ") != std::string::npos) { + AddSpecIDFor(GetResultIDName(inst_str)); + } + } + types_consts_globals_.insert(types_consts_globals_.end(), + vec_asm_code.begin(), vec_asm_code.end()); + return *this; + } + + // Appends instructions to the main function block, which is already labelled + // with "main_func_entry_block". Returns the reference of this assembly + // builder. IDs defined in the given code will be added to the Names section + // and then be registered with OpName instruction. + AssemblyBuilder& AppendInMain(const std::vector& vec_asm_code) { + AddNamesForResultIDsIn(vec_asm_code); + main_func_.insert(main_func_.end(), vec_asm_code.begin(), + vec_asm_code.end()); + return *this; + } + + // Appends annotation instructions to the annotation section, and returns the + // reference of this assembly builder. + AssemblyBuilder& AppendAnnotations( + const std::vector& vec_annotations) { + annotations_.insert(annotations_.end(), vec_annotations.begin(), + vec_annotations.end()); + return *this; + } + + // Pre-pends string to the preamble of the module. Useful for EFFCEE checks. + AssemblyBuilder& PrependPreamble(const std::vector& preamble) { + preamble_.insert(preamble_.end(), preamble.begin(), preamble.end()); + return *this; + } + + // Get the SPIR-V assembly code as string. + std::string GetCode() const { + std::ostringstream ss; + for (const auto& line : preamble_) { + ss << line << std::endl; + } + for (const auto& line : global_preamble_) { + ss << line << std::endl; + } + for (const auto& line : names_) { + ss << line << std::endl; + } + for (const auto& line : annotations_) { + ss << line << std::endl; + } + for (const auto& line : types_consts_globals_) { + ss << line << std::endl; + } + for (const auto& line : main_func_) { + ss << line << std::endl; + } + for (const auto& line : main_func_postamble_) { + ss << line << std::endl; + } + return ss.str(); + } + + private: + // Adds a given name to the Name section with OpName. If the given name has + // been added before, does nothing. + void AddOpNameIfNotExist(const std::string& id_name) { + if (!used_names_.count(id_name)) { + std::stringstream opname_inst; + opname_inst << "OpName " + << "%" << id_name << " \"" << id_name << "\""; + names_.emplace_back(opname_inst.str()); + used_names_.insert(id_name); + } + } + + // Adds the names in a vector of assembly code strings to the Names section. + // If a '=' sign is found in an instruction, this instruction will be treated + // as an ID defining instruction. The ID name used in the instruction will be + // extracted and added to the Names section. + void AddNamesForResultIDsIn(const std::vector& vec_asm_code) { + for (const auto& line : vec_asm_code) { + std::string name = GetResultIDName(line); + if (!name.empty()) { + AddOpNameIfNotExist(name); + } + } + } + + // Adds an OpDecorate SpecId instruction for the given ID name. + void AddSpecIDFor(const std::string& id_name) { + std::stringstream decorate_inst; + decorate_inst << "OpDecorate " + << "%" << id_name << " SpecId " << spec_id_counter_; + spec_id_counter_ += 1; + annotations_.emplace_back(decorate_inst.str()); + } + + // Extracts the ID name from a SPIR-V assembly instruction string. If the + // instruction is an ID-defining instruction (has result ID), returns the + // name of the result ID in string. If the instruction does not have result + // ID, returns an empty string. + std::string GetResultIDName(const std::string inst_str) { + std::string name; + if (inst_str.find('=') != std::string::npos) { + size_t assign_sign = inst_str.find('='); + name = inst_str.substr(0, assign_sign); + name.erase(remove_if(name.begin(), name.end(), + [](char c) { return c == ' ' || c == '%'; }), + name.end()); + } + return name; + } + + uint32_t spec_id_counter_; + // User-defined preamble. + std::vector preamble_; + // The vector that contains common preambles shared across all test SPIR-V + // code. + std::vector global_preamble_; + // The vector that contains OpName instructions. + std::vector names_; + // The vector that contains annotation instructions. + std::vector annotations_; + // The vector that contains the code to declare types, constants and global + // variables (aka. the Types-Constants-Globals section). + std::vector types_consts_globals_; + // The vector that contains the code in main function's entry block. + std::vector main_func_; + // The vector that contains the postamble of main function body. + std::vector main_func_postamble_; + // All of the defined variable names. + std::unordered_set used_names_; +}; + +} // namespace spvtools + +#endif // LIBSPIRV_TEST_OPT_ASSEMBLY_BUILDER diff --git a/3rdparty/spirv-tools/test/opt/assembly_builder_test.cpp b/3rdparty/spirv-tools/test/opt/assembly_builder_test.cpp new file mode 100644 index 000000000..12c796392 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/assembly_builder_test.cpp @@ -0,0 +1,284 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "assembly_builder.h" + +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; +using AssemblyBuilderTest = PassTest<::testing::Test>; + +TEST_F(AssemblyBuilderTest, MinimalShader) { + AssemblyBuilder builder; + std::vector expected = { + // clang-format off + "OpCapability Shader", + "OpCapability Float64", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Vertex %main \"main\"", + "OpName %void \"void\"", + "OpName %main_func_type \"main_func_type\"", + "OpName %main \"main\"", + "OpName %main_func_entry_block \"main_func_entry_block\"", + "%void = OpTypeVoid", + "%main_func_type = OpTypeFunction %void", + "%main = OpFunction %void None %main_func_type", +"%main_func_entry_block = OpLabel", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + + SinglePassRunAndCheck(builder.GetCode(), + JoinAllInsts(expected), + /* skip_nop = */ false); +} + +TEST_F(AssemblyBuilderTest, ShaderWithConstants) { + AssemblyBuilder builder; + builder + .AppendTypesConstantsGlobals({ + // clang-format off + "%bool = OpTypeBool", + "%_PF_bool = OpTypePointer Function %bool", + "%bt = OpConstantTrue %bool", + "%bf = OpConstantFalse %bool", + "%int = OpTypeInt 32 1", + "%_PF_int = OpTypePointer Function %int", + "%si = OpConstant %int 1", + "%uint = OpTypeInt 32 0", + "%_PF_uint = OpTypePointer Function %uint", + "%ui = OpConstant %uint 2", + "%float = OpTypeFloat 32", + "%_PF_float = OpTypePointer Function %float", + "%f = OpConstant %float 3.1415", + "%double = OpTypeFloat 64", + "%_PF_double = OpTypePointer Function %double", + "%d = OpConstant %double 3.14159265358979", + // clang-format on + }) + .AppendInMain({ + // clang-format off + "%btv = OpVariable %_PF_bool Function", + "%bfv = OpVariable %_PF_bool Function", + "%iv = OpVariable %_PF_int Function", + "%uv = OpVariable %_PF_uint Function", + "%fv = OpVariable %_PF_float Function", + "%dv = OpVariable %_PF_double Function", + "OpStore %btv %bt", + "OpStore %bfv %bf", + "OpStore %iv %si", + "OpStore %uv %ui", + "OpStore %fv %f", + "OpStore %dv %d", + // clang-format on + }); + + std::vector expected = { + // clang-format off + "OpCapability Shader", + "OpCapability Float64", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Vertex %main \"main\"", + "OpName %void \"void\"", + "OpName %main_func_type \"main_func_type\"", + "OpName %main \"main\"", + "OpName %main_func_entry_block \"main_func_entry_block\"", + "OpName %bool \"bool\"", + "OpName %_PF_bool \"_PF_bool\"", + "OpName %bt \"bt\"", + "OpName %bf \"bf\"", + "OpName %int \"int\"", + "OpName %_PF_int \"_PF_int\"", + "OpName %si \"si\"", + "OpName %uint \"uint\"", + "OpName %_PF_uint \"_PF_uint\"", + "OpName %ui \"ui\"", + "OpName %float \"float\"", + "OpName %_PF_float \"_PF_float\"", + "OpName %f \"f\"", + "OpName %double \"double\"", + "OpName %_PF_double \"_PF_double\"", + "OpName %d \"d\"", + "OpName %btv \"btv\"", + "OpName %bfv \"bfv\"", + "OpName %iv \"iv\"", + "OpName %uv \"uv\"", + "OpName %fv \"fv\"", + "OpName %dv \"dv\"", + "%void = OpTypeVoid", +"%main_func_type = OpTypeFunction %void", + "%bool = OpTypeBool", + "%_PF_bool = OpTypePointer Function %bool", + "%bt = OpConstantTrue %bool", + "%bf = OpConstantFalse %bool", + "%int = OpTypeInt 32 1", + "%_PF_int = OpTypePointer Function %int", + "%si = OpConstant %int 1", + "%uint = OpTypeInt 32 0", + "%_PF_uint = OpTypePointer Function %uint", + "%ui = OpConstant %uint 2", + "%float = OpTypeFloat 32", + "%_PF_float = OpTypePointer Function %float", + "%f = OpConstant %float 3.1415", + "%double = OpTypeFloat 64", + "%_PF_double = OpTypePointer Function %double", + "%d = OpConstant %double 3.14159265358979", + "%main = OpFunction %void None %main_func_type", +"%main_func_entry_block = OpLabel", + "%btv = OpVariable %_PF_bool Function", + "%bfv = OpVariable %_PF_bool Function", + "%iv = OpVariable %_PF_int Function", + "%uv = OpVariable %_PF_uint Function", + "%fv = OpVariable %_PF_float Function", + "%dv = OpVariable %_PF_double Function", + "OpStore %btv %bt", + "OpStore %bfv %bf", + "OpStore %iv %si", + "OpStore %uv %ui", + "OpStore %fv %f", + "OpStore %dv %d", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + SinglePassRunAndCheck(builder.GetCode(), + JoinAllInsts(expected), + /* skip_nop = */ false); +} + +TEST_F(AssemblyBuilderTest, SpecConstants) { + AssemblyBuilder builder; + builder.AppendTypesConstantsGlobals({ + "%bool = OpTypeBool", + "%uint = OpTypeInt 32 0", + "%int = OpTypeInt 32 1", + "%float = OpTypeFloat 32", + "%double = OpTypeFloat 64", + "%v2int = OpTypeVector %int 2", + + "%spec_true = OpSpecConstantTrue %bool", + "%spec_false = OpSpecConstantFalse %bool", + "%spec_uint = OpSpecConstant %uint 1", + "%spec_int = OpSpecConstant %int 1", + "%spec_float = OpSpecConstant %float 1.25", + "%spec_double = OpSpecConstant %double 1.2345678", + + // Spec constants defined below should not have SpecID. + "%spec_add_op = OpSpecConstantOp %int IAdd %spec_int %spec_int", + "%spec_vec = OpSpecConstantComposite %v2int %spec_int %spec_int", + "%spec_vec_x = OpSpecConstantOp %int CompositeExtract %spec_vec 0", + }); + std::vector expected = { + // clang-format off + "OpCapability Shader", + "OpCapability Float64", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Vertex %main \"main\"", + "OpName %void \"void\"", + "OpName %main_func_type \"main_func_type\"", + "OpName %main \"main\"", + "OpName %main_func_entry_block \"main_func_entry_block\"", + "OpName %bool \"bool\"", + "OpName %uint \"uint\"", + "OpName %int \"int\"", + "OpName %float \"float\"", + "OpName %double \"double\"", + "OpName %v2int \"v2int\"", + "OpName %spec_true \"spec_true\"", + "OpName %spec_false \"spec_false\"", + "OpName %spec_uint \"spec_uint\"", + "OpName %spec_int \"spec_int\"", + "OpName %spec_float \"spec_float\"", + "OpName %spec_double \"spec_double\"", + "OpName %spec_add_op \"spec_add_op\"", + "OpName %spec_vec \"spec_vec\"", + "OpName %spec_vec_x \"spec_vec_x\"", + "OpDecorate %spec_true SpecId 200", + "OpDecorate %spec_false SpecId 201", + "OpDecorate %spec_uint SpecId 202", + "OpDecorate %spec_int SpecId 203", + "OpDecorate %spec_float SpecId 204", + "OpDecorate %spec_double SpecId 205", + "%void = OpTypeVoid", + "%main_func_type = OpTypeFunction %void", + "%bool = OpTypeBool", + "%uint = OpTypeInt 32 0", + "%int = OpTypeInt 32 1", + "%float = OpTypeFloat 32", + "%double = OpTypeFloat 64", + "%v2int = OpTypeVector %int 2", + "%spec_true = OpSpecConstantTrue %bool", + "%spec_false = OpSpecConstantFalse %bool", + "%spec_uint = OpSpecConstant %uint 1", + "%spec_int = OpSpecConstant %int 1", + "%spec_float = OpSpecConstant %float 1.25", + "%spec_double = OpSpecConstant %double 1.2345678", + "%spec_add_op = OpSpecConstantOp %int IAdd %spec_int %spec_int", + "%spec_vec = OpSpecConstantComposite %v2int %spec_int %spec_int", + "%spec_vec_x = OpSpecConstantOp %int CompositeExtract %spec_vec 0", + "%main = OpFunction %void None %main_func_type", +"%main_func_entry_block = OpLabel", + "OpReturn", + "OpFunctionEnd", + + // clang-format on + }; + + SinglePassRunAndCheck(builder.GetCode(), + JoinAllInsts(expected), + /* skip_nop = */ false); +} + +TEST_F(AssemblyBuilderTest, AppendNames) { + AssemblyBuilder builder; + builder.AppendNames({ + "OpName %void \"another_name_for_void\"", + "I am an invalid OpName instruction and should not be added", + "OpName %main \"another name for main\"", + }); + std::vector expected = { + // clang-format off + "OpCapability Shader", + "OpCapability Float64", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Vertex %main \"main\"", + "OpName %void \"void\"", + "OpName %main_func_type \"main_func_type\"", + "OpName %main \"main\"", + "OpName %main_func_entry_block \"main_func_entry_block\"", + "OpName %void \"another_name_for_void\"", + "OpName %main \"another name for main\"", + "%void = OpTypeVoid", + "%main_func_type = OpTypeFunction %void", + "%main = OpFunction %void None %main_func_type", +"%main_func_entry_block = OpLabel", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + + SinglePassRunAndCheck(builder.GetCode(), + JoinAllInsts(expected), + /* skip_nop = */ false); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/block_merge_test.cpp b/3rdparty/spirv-tools/test/opt/block_merge_test.cpp new file mode 100644 index 000000000..11399e6ce --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/block_merge_test.cpp @@ -0,0 +1,699 @@ +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; + +using BlockMergeTest = PassTest<::testing::Test>; + +TEST_F(BlockMergeTest, Simple) { + // Note: SPIR-V hand edited to insert block boundary + // between two statements in main. + // + // #version 140 + // + // in vec4 BaseColor; + // + // void main() + // { + // vec4 v = BaseColor; + // gl_FragColor = v; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %7 +%13 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%14 = OpLoad %v4float %BaseColor +OpStore %v %14 +OpBranch %15 +%15 = OpLabel +%16 = OpLoad %v4float %v +OpStore %gl_FragColor %16 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %7 +%13 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%14 = OpLoad %v4float %BaseColor +OpStore %v %14 +%16 = OpLoad %v4float %v +OpStore %gl_FragColor %16 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(predefs + before, predefs + after, + true, true); +} + +TEST_F(BlockMergeTest, EmptyBlock) { + // Note: SPIR-V hand edited to insert empty block + // after two statements in main. + // + // #version 140 + // + // in vec4 BaseColor; + // + // void main() + // { + // vec4 v = BaseColor; + // gl_FragColor = v; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %7 +%13 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%14 = OpLoad %v4float %BaseColor +OpStore %v %14 +OpBranch %15 +%15 = OpLabel +%16 = OpLoad %v4float %v +OpStore %gl_FragColor %16 +OpBranch %17 +%17 = OpLabel +OpBranch %18 +%18 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %7 +%13 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%14 = OpLoad %v4float %BaseColor +OpStore %v %14 +%16 = OpLoad %v4float %v +OpStore %gl_FragColor %16 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(predefs + before, predefs + after, + true, true); +} + +TEST_F(BlockMergeTest, NestedInControlFlow) { + // Note: SPIR-V hand edited to insert block boundary + // between OpFMul and OpStore in then-part. + // + // #version 140 + // in vec4 BaseColor; + // + // layout(std140) uniform U_t + // { + // bool g_B ; + // } ; + // + // void main() + // { + // vec4 v = BaseColor; + // if (g_B) + // vec4 v = v * 0.25; + // gl_FragColor = v; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %U_t "U_t" +OpMemberName %U_t 0 "g_B" +OpName %_ "" +OpName %v_0 "v" +OpName %gl_FragColor "gl_FragColor" +OpMemberDecorate %U_t 0 Offset 0 +OpDecorate %U_t Block +OpDecorate %_ DescriptorSet 0 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%uint = OpTypeInt 32 0 +%U_t = OpTypeStruct %uint +%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t +%_ = OpVariable %_ptr_Uniform_U_t Uniform +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%bool = OpTypeBool +%uint_0 = OpConstant %uint 0 +%float_0_25 = OpConstant %float 0.25 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %10 +%24 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%v_0 = OpVariable %_ptr_Function_v4float Function +%25 = OpLoad %v4float %BaseColor +OpStore %v %25 +%26 = OpAccessChain %_ptr_Uniform_uint %_ %int_0 +%27 = OpLoad %uint %26 +%28 = OpINotEqual %bool %27 %uint_0 +OpSelectionMerge %29 None +OpBranchConditional %28 %30 %29 +%30 = OpLabel +%31 = OpLoad %v4float %v +%32 = OpVectorTimesScalar %v4float %31 %float_0_25 +OpBranch %33 +%33 = OpLabel +OpStore %v_0 %32 +OpBranch %29 +%29 = OpLabel +%34 = OpLoad %v4float %v +OpStore %gl_FragColor %34 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %10 +%24 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%v_0 = OpVariable %_ptr_Function_v4float Function +%25 = OpLoad %v4float %BaseColor +OpStore %v %25 +%26 = OpAccessChain %_ptr_Uniform_uint %_ %int_0 +%27 = OpLoad %uint %26 +%28 = OpINotEqual %bool %27 %uint_0 +OpSelectionMerge %29 None +OpBranchConditional %28 %30 %29 +%30 = OpLabel +%31 = OpLoad %v4float %v +%32 = OpVectorTimesScalar %v4float %31 %float_0_25 +OpStore %v_0 %32 +OpBranch %29 +%29 = OpLabel +%34 = OpLoad %v4float %v +OpStore %gl_FragColor %34 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(predefs + before, predefs + after, + true, true); +} + +#ifdef SPIRV_EFFCEE +TEST_F(BlockMergeTest, PhiInSuccessorOfMergedBlock) { + const std::string text = R"( +; CHECK: OpSelectionMerge [[merge:%\w+]] None +; CHECK-NEXT: OpBranchConditional {{%\w+}} [[then:%\w+]] [[else:%\w+]] +; CHECK: [[then]] = OpLabel +; CHECK-NEXT: OpBranch [[merge]] +; CHECK: [[else]] = OpLabel +; CHECK-NEXT: OpBranch [[merge]] +; CHECK: [[merge]] = OpLabel +; CHECK-NEXT: OpPhi {{%\w+}} %true [[then]] %false [[else]] +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %func "func" +%void = OpTypeVoid +%bool = OpTypeBool +%true = OpConstantTrue %bool +%false = OpConstantFalse %bool +%functy = OpTypeFunction %void +%func = OpFunction %void None %functy +%entry = OpLabel +OpSelectionMerge %merge None +OpBranchConditional %true %then %else +%then = OpLabel +OpBranch %then_next +%then_next = OpLabel +OpBranch %merge +%else = OpLabel +OpBranch %merge +%merge = OpLabel +%phi = OpPhi %bool %true %then_next %false %else +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(BlockMergeTest, UpdateMergeInstruction) { + const std::string text = R"( +; CHECK: OpSelectionMerge [[merge:%\w+]] None +; CHECK-NEXT: OpBranchConditional {{%\w+}} [[then:%\w+]] [[else:%\w+]] +; CHECK: [[then]] = OpLabel +; CHECK-NEXT: OpBranch [[merge]] +; CHECK: [[else]] = OpLabel +; CHECK-NEXT: OpBranch [[merge]] +; CHECK: [[merge]] = OpLabel +; CHECK-NEXT: OpReturn +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %func "func" +%void = OpTypeVoid +%bool = OpTypeBool +%true = OpConstantTrue %bool +%false = OpConstantFalse %bool +%functy = OpTypeFunction %void +%func = OpFunction %void None %functy +%entry = OpLabel +OpSelectionMerge %real_merge None +OpBranchConditional %true %then %else +%then = OpLabel +OpBranch %merge +%else = OpLabel +OpBranch %merge +%merge = OpLabel +OpBranch %real_merge +%real_merge = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(BlockMergeTest, TwoMergeBlocksCannotBeMerged) { + const std::string text = R"( +; CHECK: OpSelectionMerge [[outer_merge:%\w+]] None +; CHECK: OpSelectionMerge [[inner_merge:%\w+]] None +; CHECK: [[inner_merge]] = OpLabel +; CHECK-NEXT: OpBranch [[outer_merge]] +; CHECK: [[outer_merge]] = OpLabel +; CHECK-NEXT: OpReturn +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %func "func" +%void = OpTypeVoid +%bool = OpTypeBool +%true = OpConstantTrue %bool +%false = OpConstantFalse %bool +%functy = OpTypeFunction %void +%func = OpFunction %void None %functy +%entry = OpLabel +OpSelectionMerge %outer_merge None +OpBranchConditional %true %then %else +%then = OpLabel +OpBranch %inner_header +%else = OpLabel +OpBranch %inner_header +%inner_header = OpLabel +OpSelectionMerge %inner_merge None +OpBranchConditional %true %inner_then %inner_else +%inner_then = OpLabel +OpBranch %inner_merge +%inner_else = OpLabel +OpBranch %inner_merge +%inner_merge = OpLabel +OpBranch %outer_merge +%outer_merge = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(BlockMergeTest, MergeContinue) { + const std::string text = R"( +; CHECK: OpBranch [[header:%\w+]] +; CHECK: [[header]] = OpLabel +; CHECK-NEXT: OpLogicalAnd +; CHECK-NEXT: OpLoopMerge {{%\w+}} [[header]] None +; CHECK-NEXT: OpBranch [[header]] +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %func "func" +%void = OpTypeVoid +%bool = OpTypeBool +%true = OpConstantTrue %bool +%false = OpConstantFalse %bool +%functy = OpTypeFunction %void +%func = OpFunction %void None %functy +%entry = OpLabel +OpBranch %header +%header = OpLabel +OpLoopMerge %merge %continue None +OpBranch %continue +%continue = OpLabel +%op = OpLogicalAnd %bool %true %false +OpBranch %header +%merge = OpLabel +OpUnreachable +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(BlockMergeTest, TwoHeadersCannotBeMerged) { + const std::string text = R"( +; CHECK: OpBranch [[loop_header:%\w+]] +; CHECK: [[loop_header]] = OpLabel +; CHECK-NEXT: OpLoopMerge +; CHECK-NEXT: OpBranch [[if_header:%\w+]] +; CHECK: [[if_header]] = OpLabel +; CHECK-NEXT: OpSelectionMerge +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %func "func" +%void = OpTypeVoid +%bool = OpTypeBool +%true = OpConstantTrue %bool +%false = OpConstantFalse %bool +%functy = OpTypeFunction %void +%func = OpFunction %void None %functy +%entry = OpLabel +OpBranch %header +%header = OpLabel +OpLoopMerge %merge %continue None +OpBranch %inner_header +%inner_header = OpLabel +OpSelectionMerge %continue None +OpBranchConditional %true %then %continue +%then = OpLabel +OpBranch %continue +%continue = OpLabel +OpBranchConditional %false %merge %header +%merge = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(BlockMergeTest, RemoveStructuredDeclaration) { + // Note: SPIR-V hand edited remove dead branch and add block + // before continue block + // + // #version 140 + // in vec4 BaseColor; + // + // void main() + // { + // while (true) { + // break; + // } + // gl_FragColor = BaseColor; + // } + + const std::string assembly = + R"( +; CHECK: OpLabel +; CHECK: [[header:%\w+]] = OpLabel +; CHECK-NOT: OpLoopMerge +; CHECK: OpReturn +; CHECK: [[continue:%\w+]] = OpLabel +; CHECK-NEXT: OpBranch [[header]] +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %gl_FragColor %BaseColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %gl_FragColor "gl_FragColor" +OpName %BaseColor "BaseColor" +%void = OpTypeVoid +%6 = OpTypeFunction %void +%bool = OpTypeBool +%true = OpConstantTrue %bool +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%main = OpFunction %void None %6 +%13 = OpLabel +OpBranch %14 +%14 = OpLabel +OpLoopMerge %15 %16 None +OpBranch %17 +%17 = OpLabel +OpBranch %15 +%18 = OpLabel +OpBranch %16 +%16 = OpLabel +OpBranch %14 +%15 = OpLabel +%19 = OpLoad %v4float %BaseColor +OpStore %gl_FragColor %19 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(assembly, true); +} + +TEST_F(BlockMergeTest, DontMergeKill) { + const std::string text = R"( +; CHECK: OpLoopMerge [[merge:%\w+]] [[cont:%\w+]] None +; CHECK-NEXT: OpBranch [[ret:%\w+]] +; CHECK: [[ret:%\w+]] = OpLabel +; CHECK-NEXT: OpKill +; CHECK-DAG: [[cont]] = OpLabel +; CHECK-DAG: [[merge]] = OpLabel +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %func "func" +%void = OpTypeVoid +%bool = OpTypeBool +%functy = OpTypeFunction %void +%func = OpFunction %void None %functy +%1 = OpLabel +OpBranch %2 +%2 = OpLabel +OpLoopMerge %3 %4 None +OpBranch %5 +%5 = OpLabel +OpKill +%4 = OpLabel +OpBranch %2 +%3 = OpLabel +OpUnreachable +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(BlockMergeTest, DontMergeUnreachable) { + const std::string text = R"( +; CHECK: OpLoopMerge [[merge:%\w+]] [[cont:%\w+]] None +; CHECK-NEXT: OpBranch [[ret:%\w+]] +; CHECK: [[ret:%\w+]] = OpLabel +; CHECK-NEXT: OpUnreachable +; CHECK-DAG: [[cont]] = OpLabel +; CHECK-DAG: [[merge]] = OpLabel +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %func "func" +%void = OpTypeVoid +%bool = OpTypeBool +%functy = OpTypeFunction %void +%func = OpFunction %void None %functy +%1 = OpLabel +OpBranch %2 +%2 = OpLabel +OpLoopMerge %3 %4 None +OpBranch %5 +%5 = OpLabel +OpUnreachable +%4 = OpLabel +OpBranch %2 +%3 = OpLabel +OpUnreachable +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(BlockMergeTest, DontMergeReturn) { + const std::string text = R"( +; CHECK: OpLoopMerge [[merge:%\w+]] [[cont:%\w+]] None +; CHECK-NEXT: OpBranch [[ret:%\w+]] +; CHECK: [[ret:%\w+]] = OpLabel +; CHECK-NEXT: OpReturn +; CHECK-DAG: [[cont]] = OpLabel +; CHECK-DAG: [[merge]] = OpLabel +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %func "func" +%void = OpTypeVoid +%bool = OpTypeBool +%functy = OpTypeFunction %void +%func = OpFunction %void None %functy +%1 = OpLabel +OpBranch %2 +%2 = OpLabel +OpLoopMerge %3 %4 None +OpBranch %5 +%5 = OpLabel +OpReturn +%4 = OpLabel +OpBranch %2 +%3 = OpLabel +OpUnreachable +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(BlockMergeTest, DontMergeSwitch) { + const std::string text = R"( +; CHECK: OpLoopMerge [[merge:%\w+]] [[cont:%\w+]] None +; CHECK-NEXT: OpBranch [[ret:%\w+]] +; CHECK: [[ret:%\w+]] = OpLabel +; CHECK-NEXT: OpSwitch +; CHECK-DAG: [[cont]] = OpLabel +; CHECK-DAG: [[merge]] = OpLabel +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %func "func" +%void = OpTypeVoid +%bool = OpTypeBool +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%functy = OpTypeFunction %void +%func = OpFunction %void None %functy +%1 = OpLabel +OpBranch %2 +%2 = OpLabel +OpLoopMerge %3 %4 None +OpBranch %5 +%5 = OpLabel +OpSwitch %int_0 %6 +%6 = OpLabel +OpReturn +%4 = OpLabel +OpBranch %2 +%3 = OpLabel +OpUnreachable +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(BlockMergeTest, DontMergeReturnValue) { + const std::string text = R"( +; CHECK: OpLoopMerge [[merge:%\w+]] [[cont:%\w+]] None +; CHECK-NEXT: OpBranch [[ret:%\w+]] +; CHECK: [[ret:%\w+]] = OpLabel +; CHECK-NEXT: OpReturn +; CHECK-DAG: [[cont]] = OpLabel +; CHECK-DAG: [[merge]] = OpLabel +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %func "func" +%void = OpTypeVoid +%bool = OpTypeBool +%functy = OpTypeFunction %void +%otherfuncty = OpTypeFunction %bool +%true = OpConstantTrue %bool +%func = OpFunction %void None %functy +%1 = OpLabel +%2 = OpFunctionCall %bool %3 +OpReturn +OpFunctionEnd +%3 = OpFunction %bool None %otherfuncty +%4 = OpLabel +OpBranch %5 +%5 = OpLabel +OpLoopMerge %6 %7 None +OpBranch %8 +%8 = OpLabel +OpReturnValue %true +%7 = OpLabel +OpBranch %5 +%6 = OpLabel +OpUnreachable +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} +#endif // SPIRV_EFFCEE + +// TODO(greg-lunarg): Add tests to verify handling of these cases: +// +// More complex control flow +// Others? + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/ccp_test.cpp b/3rdparty/spirv-tools/test/opt/ccp_test.cpp new file mode 100644 index 000000000..47ae9cc00 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/ccp_test.cpp @@ -0,0 +1,895 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "pass_fixture.h" +#include "pass_utils.h" + +#include "opt/ccp_pass.h" + +namespace { + +using namespace spvtools; + +using CCPTest = PassTest<::testing::Test>; + +// TODO(dneto): Add Effcee as required dependency, and make this unconditional. +#ifdef SPIRV_EFFCEE +TEST_F(CCPTest, PropagateThroughPhis) { + const std::string spv_asm = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %x %outparm + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 450 + OpName %main "main" + OpName %x "x" + OpName %outparm "outparm" + OpDecorate %x Flat + OpDecorate %x Location 0 + OpDecorate %outparm Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 + %bool = OpTypeBool +%_ptr_Function_int = OpTypePointer Function %int + %int_4 = OpConstant %int 4 + %int_3 = OpConstant %int 3 + %int_1 = OpConstant %int 1 +%_ptr_Input_int = OpTypePointer Input %int + %x = OpVariable %_ptr_Input_int Input +%_ptr_Output_int = OpTypePointer Output %int + %outparm = OpVariable %_ptr_Output_int Output + %main = OpFunction %void None %3 + %4 = OpLabel + %5 = OpLoad %int %x + %9 = OpIAdd %int %int_1 %int_3 + %6 = OpSGreaterThan %bool %5 %int_3 + OpSelectionMerge %25 None + OpBranchConditional %6 %22 %23 + %22 = OpLabel + +; CHECK: OpCopyObject %int %int_4 + %7 = OpCopyObject %int %9 + + OpBranch %25 + %23 = OpLabel + %8 = OpCopyObject %int %int_4 + OpBranch %25 + %25 = OpLabel + +; %int_4 should have propagated to both OpPhi operands. +; CHECK: OpPhi %int %int_4 {{%\d+}} %int_4 {{%\d+}} + %35 = OpPhi %int %7 %22 %8 %23 + +; This function always returns 4. DCE should get rid of everything else. +; CHECK OpStore %outparm %int_4 + OpStore %outparm %35 + OpReturn + OpFunctionEnd + )"; + + SinglePassRunAndMatch(spv_asm, true); +} + +TEST_F(CCPTest, SimplifyConditionals) { + const std::string spv_asm = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %outparm + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 450 + OpName %main "main" + OpName %outparm "outparm" + OpDecorate %outparm Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 + %bool = OpTypeBool +%_ptr_Function_int = OpTypePointer Function %int + %int_4 = OpConstant %int 4 + %int_3 = OpConstant %int 3 + %int_1 = OpConstant %int 1 +%_ptr_Output_int = OpTypePointer Output %int + %outparm = OpVariable %_ptr_Output_int Output + %main = OpFunction %void None %3 + %4 = OpLabel + %9 = OpIAdd %int %int_4 %int_3 + %6 = OpSGreaterThan %bool %9 %int_3 + OpSelectionMerge %25 None +; CHECK: OpBranchConditional %true [[bb_taken:%\d+]] [[bb_not_taken:%\d+]] + OpBranchConditional %6 %22 %23 +; CHECK: [[bb_taken]] = OpLabel + %22 = OpLabel +; CHECK: OpCopyObject %int %int_7 + %7 = OpCopyObject %int %9 + OpBranch %25 +; CHECK: [[bb_not_taken]] = OpLabel + %23 = OpLabel +; CHECK: [[id_not_evaluated:%\d+]] = OpCopyObject %int %int_4 + %8 = OpCopyObject %int %int_4 + OpBranch %25 + %25 = OpLabel + +; %int_7 should have propagated to the first OpPhi operand. But the else branch +; is not executable (conditional is always true), so no values should be +; propagated there and the value of the OpPhi should always be %int_7. +; CHECK: OpPhi %int %int_7 [[bb_taken]] [[id_not_evaluated]] [[bb_not_taken]] + %35 = OpPhi %int %7 %22 %8 %23 + +; Only the true path of the conditional is ever executed. The output of this +; function is always %int_7. +; CHECK: OpStore %outparm %int_7 + OpStore %outparm %35 + OpReturn + OpFunctionEnd + )"; + + SinglePassRunAndMatch(spv_asm, true); +} + +TEST_F(CCPTest, SimplifySwitches) { + const std::string spv_asm = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %outparm + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 450 + OpName %main "main" + OpName %outparm "outparm" + OpDecorate %outparm Location 0 + %void = OpTypeVoid + %6 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int + %int_23 = OpConstant %int 23 + %int_42 = OpConstant %int 42 + %int_14 = OpConstant %int 14 + %int_15 = OpConstant %int 15 + %int_4 = OpConstant %int 4 +%_ptr_Output_int = OpTypePointer Output %int + %outparm = OpVariable %_ptr_Output_int Output + %main = OpFunction %void None %6 + %15 = OpLabel + OpSelectionMerge %17 None + OpSwitch %int_23 %17 10 %18 13 %19 23 %20 + %18 = OpLabel + OpBranch %17 + %19 = OpLabel + OpBranch %17 + %20 = OpLabel + OpBranch %17 + %17 = OpLabel + %24 = OpPhi %int %int_23 %15 %int_42 %18 %int_14 %19 %int_15 %20 + +; The switch will always jump to label %20, which carries the value %int_15. +; CHECK: OpIAdd %int %int_15 %int_4 + %22 = OpIAdd %int %24 %int_4 + +; Consequently, the return value will always be %int_19. +; CHECK: OpStore %outparm %int_19 + OpStore %outparm %22 + OpReturn + OpFunctionEnd + )"; + + SinglePassRunAndMatch(spv_asm, true); +} + +TEST_F(CCPTest, SimplifySwitchesDefaultBranch) { + const std::string spv_asm = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %outparm + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 450 + OpName %main "main" + OpName %outparm "outparm" + OpDecorate %outparm Location 0 + %void = OpTypeVoid + %6 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int + %int_42 = OpConstant %int 42 + %int_4 = OpConstant %int 4 + %int_1 = OpConstant %int 1 +%_ptr_Output_int = OpTypePointer Output %int + %outparm = OpVariable %_ptr_Output_int Output + %main = OpFunction %void None %6 + %13 = OpLabel + %15 = OpIAdd %int %int_42 %int_4 + OpSelectionMerge %16 None + +; CHECK: OpSwitch %int_46 {{%\d+}} 10 {{%\d+}} + OpSwitch %15 %17 10 %18 + %18 = OpLabel + OpBranch %16 + %17 = OpLabel + OpBranch %16 + %16 = OpLabel + %22 = OpPhi %int %int_42 %18 %int_1 %17 + +; The switch will always jump to the default label %17. This carries the value +; %int_1. +; CHECK: OpIAdd %int %int_1 %int_4 + %20 = OpIAdd %int %22 %int_4 + +; Resulting in a return value of %int_5. +; CHECK: OpStore %outparm %int_5 + OpStore %outparm %20 + OpReturn + OpFunctionEnd + )"; + + SinglePassRunAndMatch(spv_asm, true); +} + +TEST_F(CCPTest, SimplifyIntVector) { + const std::string spv_asm = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %OutColor + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 450 + OpName %main "main" + OpName %v "v" + OpName %OutColor "OutColor" + OpDecorate %OutColor Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 + %v4int = OpTypeVector %int 4 +%_ptr_Function_v4int = OpTypePointer Function %v4int + %int_1 = OpConstant %int 1 + %int_2 = OpConstant %int 2 + %int_3 = OpConstant %int 3 + %int_4 = OpConstant %int 4 + %14 = OpConstantComposite %v4int %int_1 %int_2 %int_3 %int_4 + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 +%_ptr_Function_int = OpTypePointer Function %int +%_ptr_Output_v4int = OpTypePointer Output %v4int + %OutColor = OpVariable %_ptr_Output_v4int Output + %main = OpFunction %void None %3 + %5 = OpLabel + %v = OpVariable %_ptr_Function_v4int Function + OpStore %v %14 + %18 = OpAccessChain %_ptr_Function_int %v %uint_0 + %19 = OpLoad %int %18 + +; The constant folder does not see through access chains. To get this, the +; vector would have to be scalarized. +; CHECK: [[result_id:%\d+]] = OpIAdd %int {{%\d+}} %int_1 + %20 = OpIAdd %int %19 %int_1 + %21 = OpAccessChain %_ptr_Function_int %v %uint_0 + +; CHECK: OpStore {{%\d+}} [[result_id]] + OpStore %21 %20 + %24 = OpLoad %v4int %v + OpStore %OutColor %24 + OpReturn + OpFunctionEnd + )"; + + SinglePassRunAndMatch(spv_asm, true); +} + +TEST_F(CCPTest, BadSimplifyFloatVector) { + const std::string spv_asm = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %OutColor + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 450 + OpName %main "main" + OpName %v "v" + OpName %OutColor "OutColor" + OpDecorate %OutColor Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float + %float_1 = OpConstant %float 1 + %float_2 = OpConstant %float 2 + %float_3 = OpConstant %float 3 + %float_4 = OpConstant %float 4 + %14 = OpConstantComposite %v4float %float_1 %float_2 %float_3 %float_4 + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 +%_ptr_Function_float = OpTypePointer Function %float +%_ptr_Output_v4float = OpTypePointer Output %v4float + %OutColor = OpVariable %_ptr_Output_v4float Output + %main = OpFunction %void None %3 + %5 = OpLabel + %v = OpVariable %_ptr_Function_v4float Function + OpStore %v %14 + %18 = OpAccessChain %_ptr_Function_float %v %uint_0 + %19 = OpLoad %float %18 + +; NOTE: This test should start failing once floating point folding is +; implemented (https://github.com/KhronosGroup/SPIRV-Tools/issues/943). +; This should be checking that we are adding %float_1 + %float_1. +; CHECK: [[result_id:%\d+]] = OpFAdd %float {{%\d+}} %float_1 + %20 = OpFAdd %float %19 %float_1 + %21 = OpAccessChain %_ptr_Function_float %v %uint_0 + +; This should be checkint that we are storing %float_2 instead of result_it. +; CHECK: OpStore {{%\d+}} [[result_id]] + OpStore %21 %20 + %24 = OpLoad %v4float %v + OpStore %OutColor %24 + OpReturn + OpFunctionEnd + )"; + + SinglePassRunAndMatch(spv_asm, true); +} + +TEST_F(CCPTest, NoLoadStorePropagation) { + const std::string spv_asm = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %outparm + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 450 + OpName %main "main" + OpName %x "x" + OpName %outparm "outparm" + OpDecorate %outparm Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int + %int_23 = OpConstant %int 23 +%_ptr_Output_int = OpTypePointer Output %int + %outparm = OpVariable %_ptr_Output_int Output + %main = OpFunction %void None %3 + %5 = OpLabel + %x = OpVariable %_ptr_Function_int Function + OpStore %x %int_23 + +; int_23 should not propagate into this load. +; CHECK: [[load_id:%\d+]] = OpLoad %int %x + %12 = OpLoad %int %x + +; Nor into this copy operation. +; CHECK: [[copy_id:%\d+]] = OpCopyObject %int [[load_id]] + %13 = OpCopyObject %int %12 + +; Likewise here. +; CHECK: OpStore %outparm [[copy_id]] + OpStore %outparm %13 + OpReturn + OpFunctionEnd + )"; + + SinglePassRunAndMatch(spv_asm, true); +} + +TEST_F(CCPTest, HandleAbortInstructions) { + const std::string spv_asm = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" + OpExecutionMode %main OriginUpperLeft + OpSource HLSL 500 + OpName %main "main" + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 + %bool = OpTypeBool +; CHECK: %true = OpConstantTrue %bool + %int_3 = OpConstant %int 3 + %int_1 = OpConstant %int 1 + %main = OpFunction %void None %3 + %4 = OpLabel + %9 = OpIAdd %int %int_3 %int_1 + %6 = OpSGreaterThan %bool %9 %int_3 + OpSelectionMerge %23 None +; CHECK: OpBranchConditional %true {{%\d+}} {{%\d+}} + OpBranchConditional %6 %22 %23 + %22 = OpLabel + OpKill + %23 = OpLabel + OpReturn + OpFunctionEnd + )"; + + SinglePassRunAndMatch(spv_asm, true); +} + +TEST_F(CCPTest, SSAWebCycles) { + // Test reduced from https://github.com/KhronosGroup/SPIRV-Tools/issues/1159 + // When there is a cycle in the SSA def-use web, the propagator was getting + // into an infinite loop. SSA edges for Phi instructions should not be + // added to the edges to simulate. + const std::string spv_asm = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 450 + OpName %main "main" + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int + %int_0 = OpConstant %int 0 + %int_4 = OpConstant %int 4 + %bool = OpTypeBool + %int_1 = OpConstant %int 1 +%_ptr_Output_int = OpTypePointer Output %int + %main = OpFunction %void None %3 + %5 = OpLabel + OpBranch %11 + %11 = OpLabel + %29 = OpPhi %int %int_0 %5 %22 %14 + %30 = OpPhi %int %int_0 %5 %25 %14 + OpLoopMerge %13 %14 None + OpBranch %15 + %15 = OpLabel + %19 = OpSLessThan %bool %30 %int_4 +; CHECK: OpBranchConditional %true {{%\d+}} {{%\d+}} + OpBranchConditional %19 %12 %13 + %12 = OpLabel +; CHECK: OpIAdd %int %int_0 %int_0 + %22 = OpIAdd %int %29 %30 + OpBranch %14 + %14 = OpLabel +; CHECK: OpPhi %int %int_0 {{%\d+}} + %25 = OpPhi %int %30 %12 + OpBranch %11 + %13 = OpLabel + OpReturn + OpFunctionEnd + )"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndMatch(spv_asm, true); +} + +TEST_F(CCPTest, LoopInductionVariables) { + // Test reduced from https://github.com/KhronosGroup/SPIRV-Tools/issues/1143 + // We are failing to properly consider the induction variable for this loop + // as Varying. + const std::string spv_asm = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 430 + OpName %main "main" + %void = OpTypeVoid + %5 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int + %int_0 = OpConstant %int 0 + %int_10 = OpConstant %int 10 + %bool = OpTypeBool + %int_1 = OpConstant %int 1 + %main = OpFunction %void None %5 + %12 = OpLabel + OpBranch %13 + %13 = OpLabel + +; This Phi should not have all constant arguments: +; CHECK: [[phi_id:%\d+]] = OpPhi %int %int_0 {{%\d+}} {{%\d+}} {{%\d+}} + %22 = OpPhi %int %int_0 %12 %21 %15 + OpLoopMerge %14 %15 None + OpBranch %16 + %16 = OpLabel + +; The Phi should never be considered to have the value %int_0. +; CHECK: [[branch_selector:%\d+]] = OpSLessThan %bool [[phi_id]] %int_10 + %18 = OpSLessThan %bool %22 %int_10 + +; This conditional was wrongly converted into an always-true jump due to the +; bad meet evaluation of %22. +; CHECK: OpBranchConditional [[branch_selector]] {{%\d+}} {{%\d+}} + OpBranchConditional %18 %19 %14 + %19 = OpLabel + OpBranch %15 + %15 = OpLabel +; CHECK: OpIAdd %int [[phi_id]] %int_1 + %21 = OpIAdd %int %22 %int_1 + OpBranch %13 + %14 = OpLabel + OpReturn + OpFunctionEnd + )"; + + SinglePassRunAndMatch(spv_asm, true); +} + +TEST_F(CCPTest, HandleCompositeWithUndef) { + // Check to make sure that CCP does not crash when given a "constant" struct + // with an undef. If at a later time CCP is enhanced to optimize this case, + // it is not wrong. + const std::string spv_asm = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" + OpExecutionMode %main OriginUpperLeft + OpSource HLSL 500 + OpName %main "main" + %void = OpTypeVoid + %4 = OpTypeFunction %void + %int = OpTypeInt 32 1 + %bool = OpTypeBool + %_struct_7 = OpTypeStruct %int %int + %int_1 = OpConstant %int 1 + %9 = OpUndef %int + %10 = OpConstantComposite %_struct_7 %int_1 %9 + %main = OpFunction %void None %4 + %11 = OpLabel + %12 = OpCompositeExtract %int %10 0 + %13 = OpCopyObject %int %12 + OpReturn + OpFunctionEnd + )"; + + auto res = SinglePassRunToBinary(spv_asm, true); + EXPECT_EQ(std::get<1>(res), opt::Pass::Status::SuccessWithoutChange); +} + +TEST_F(CCPTest, SkipSpecConstantInstrucitons) { + const std::string spv_asm = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" + OpExecutionMode %main OriginUpperLeft + OpSource HLSL 500 + OpName %main "main" + %void = OpTypeVoid + %4 = OpTypeFunction %void + %bool = OpTypeBool + %10 = OpSpecConstantFalse %bool + %main = OpFunction %void None %4 + %11 = OpLabel + %12 = OpBranchConditional %10 %l1 %l2 + %l1 = OpLabel + OpReturn + %l2 = OpLabel + OpReturn + OpFunctionEnd + )"; + + auto res = SinglePassRunToBinary(spv_asm, true); + EXPECT_EQ(std::get<1>(res), opt::Pass::Status::SuccessWithoutChange); +} + +TEST_F(CCPTest, UpdateSubsequentPhisToVarying) { + const std::string text = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %func "func" %in +%void = OpTypeVoid +%bool = OpTypeBool +%int = OpTypeInt 32 1 +%false = OpConstantFalse %bool +%int0 = OpConstant %int 0 +%int1 = OpConstant %int 1 +%int6 = OpConstant %int 6 +%int_ptr_Input = OpTypePointer Input %int +%in = OpVariable %int_ptr_Input Input +%undef = OpUndef %int +%functy = OpTypeFunction %void +%func = OpFunction %void None %functy +%1 = OpLabel +OpBranch %2 +%2 = OpLabel +%outer_phi = OpPhi %int %int0 %1 %outer_add %15 +%cond1 = OpSLessThanEqual %bool %outer_phi %int6 +OpLoopMerge %3 %15 None +OpBranchConditional %cond1 %4 %3 +%4 = OpLabel +%ld = OpLoad %int %in +%cond2 = OpSGreaterThanEqual %bool %int1 %ld +OpSelectionMerge %10 None +OpBranchConditional %cond2 %8 %9 +%8 = OpLabel +OpBranch %10 +%9 = OpLabel +OpBranch %10 +%10 = OpLabel +%extra_phi = OpPhi %int %outer_phi %8 %outer_phi %9 +OpBranch %11 +%11 = OpLabel +%inner_phi = OpPhi %int %int0 %10 %inner_add %13 +%cond3 = OpSLessThanEqual %bool %inner_phi %int6 +OpLoopMerge %14 %13 None +OpBranchConditional %cond3 %12 %14 +%12 = OpLabel +OpBranch %13 +%13 = OpLabel +%inner_add = OpIAdd %int %inner_phi %int1 +OpBranch %11 +%14 = OpLabel +OpBranch %15 +%15 = OpLabel +%outer_add = OpIAdd %int %extra_phi %int1 +OpBranch %2 +%3 = OpLabel +OpReturn +OpFunctionEnd +)"; + + auto res = SinglePassRunToBinary(text, true); + EXPECT_EQ(std::get<1>(res), opt::Pass::Status::SuccessWithoutChange); +} + +TEST_F(CCPTest, UndefInPhi) { + const std::string text = R"( +; CHECK: [[uint1:%\w+]] = OpConstant {{%\w+}} 1 +; CHECK: [[phi:%\w+]] = OpPhi +; CHECK: OpIAdd {{%\w+}} [[phi]] [[uint1]] + OpCapability Kernel + OpCapability Linkage + OpMemoryModel Logical OpenCL + OpDecorate %1 LinkageAttributes "func" Export + %void = OpTypeVoid + %bool = OpTypeBool + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %uint_1 = OpConstant %uint 1 + %7 = OpUndef %uint + %8 = OpTypeFunction %void %bool + %1 = OpFunction %void None %8 + %9 = OpFunctionParameter %bool + %10 = OpLabel + OpBranchConditional %9 %11 %12 + %11 = OpLabel + OpBranch %13 + %12 = OpLabel + OpBranch %14 + %14 = OpLabel + OpBranchConditional %9 %13 %15 + %15 = OpLabel + OpBranch %13 + %13 = OpLabel + %16 = OpPhi %uint %uint_0 %11 %7 %14 %uint_1 %15 + %17 = OpIAdd %uint %16 %uint_1 + OpReturn + OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +// Just test to make sure the constant fold rules are being used. Will rely on +// the folding test for specific testing of specific rules. +TEST_F(CCPTest, UseConstantFoldingRules) { + const std::string text = R"( +; CHECK: [[float1:%\w+]] = OpConstant {{%\w+}} 1 +; CHECK: OpReturnValue [[float1]] + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpDecorate %1 LinkageAttributes "func" Export + %void = OpTypeVoid + %bool = OpTypeBool + %float = OpTypeFloat 32 + %float_0 = OpConstant %float 0 + %float_1 = OpConstant %float 1 + %8 = OpTypeFunction %float + %1 = OpFunction %float None %8 + %10 = OpLabel + %17 = OpFAdd %float %float_0 %float_1 + OpReturnValue %17 + OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +// Test for #1300. Previously value for %5 would not settle during simulation. +TEST_F(CCPTest, SettlePhiLatticeValue) { + const std::string text = R"( +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical OpenCL +OpDecorate %func LinkageAttributes "func" Export +%void = OpTypeVoid +%bool = OpTypeBool +%true = OpConstantTrue %bool +%false = OpConstantFalse %bool +%functy = OpTypeFunction %void +%func = OpFunction %void None %functy +%1 = OpLabel +OpBranchConditional %true %2 %3 +%3 = OpLabel +OpBranch %2 +%2 = OpLabel +%5 = OpPhi %bool %true %1 %false %3 +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunToBinary(text, true); +} + +TEST_F(CCPTest, NullBranchCondition) { + const std::string text = R"( +; CHECK: [[int1:%\w+]] = OpConstant {{%\w+}} 1 +; CHECK: [[int2:%\w+]] = OpConstant {{%\w+}} 2 +; CHECK: OpIAdd {{%\w+}} [[int1]] [[int2]] +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %func "func" +%void = OpTypeVoid +%bool = OpTypeBool +%int = OpTypeInt 32 1 +%null = OpConstantNull %bool +%int_1 = OpConstant %int 1 +%int_2 = OpConstant %int 2 +%functy = OpTypeFunction %void +%func = OpFunction %void None %functy +%1 = OpLabel +OpSelectionMerge %2 None +OpBranchConditional %null %2 %3 +%3 = OpLabel +OpBranch %2 +%2 = OpLabel +%phi = OpPhi %int %int_1 %1 %int_2 %3 +%add = OpIAdd %int %int_1 %phi +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(CCPTest, UndefBranchCondition) { + const std::string text = R"( +; CHECK: [[int1:%\w+]] = OpConstant {{%\w+}} 1 +; CHECK: [[phi:%\w+]] = OpPhi +; CHECK: OpIAdd {{%\w+}} [[int1]] [[phi]] +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %func "func" +%void = OpTypeVoid +%bool = OpTypeBool +%int = OpTypeInt 32 1 +%undef = OpUndef %bool +%int_1 = OpConstant %int 1 +%int_2 = OpConstant %int 2 +%functy = OpTypeFunction %void +%func = OpFunction %void None %functy +%1 = OpLabel +OpSelectionMerge %2 None +OpBranchConditional %undef %2 %3 +%3 = OpLabel +OpBranch %2 +%2 = OpLabel +%phi = OpPhi %int %int_1 %1 %int_2 %3 +%add = OpIAdd %int %int_1 %phi +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(CCPTest, NullSwitchCondition) { + const std::string text = R"( +; CHECK: [[int1:%\w+]] = OpConstant {{%\w+}} 1 +; CHECK: [[int2:%\w+]] = OpConstant {{%\w+}} 2 +; CHECK: OpIAdd {{%\w+}} [[int1]] [[int2]] +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %func "func" +%void = OpTypeVoid +%int = OpTypeInt 32 1 +%null = OpConstantNull %int +%int_1 = OpConstant %int 1 +%int_2 = OpConstant %int 2 +%functy = OpTypeFunction %void +%func = OpFunction %void None %functy +%1 = OpLabel +OpSelectionMerge %2 None +OpSwitch %null %2 0 %3 +%3 = OpLabel +OpBranch %2 +%2 = OpLabel +%phi = OpPhi %int %int_1 %1 %int_2 %3 +%add = OpIAdd %int %int_1 %phi +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(CCPTest, UndefSwitchCondition) { + const std::string text = R"( +; CHECK: [[int1:%\w+]] = OpConstant {{%\w+}} 1 +; CHECK: [[phi:%\w+]] = OpPhi +; CHECK: OpIAdd {{%\w+}} [[int1]] [[phi]] +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %func "func" +%void = OpTypeVoid +%int = OpTypeInt 32 1 +%undef = OpUndef %int +%int_1 = OpConstant %int 1 +%int_2 = OpConstant %int 2 +%functy = OpTypeFunction %void +%func = OpFunction %void None %functy +%1 = OpLabel +OpSelectionMerge %2 None +OpSwitch %undef %2 0 %3 +%3 = OpLabel +OpBranch %2 +%2 = OpLabel +%phi = OpPhi %int %int_1 %1 %int_2 %3 +%add = OpIAdd %int %int_1 %phi +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +// Test for #1361. +TEST_F(CCPTest, CompositeConstructOfGlobalValue) { + const std::string text = R"( +; CHECK: [[phi:%\w+]] = OpPhi +; CHECK-NEXT: OpCompositeExtract {{%\w+}} [[phi]] 0 +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %func "func" %in +%void = OpTypeVoid +%int = OpTypeInt 32 1 +%bool = OpTypeBool +%functy = OpTypeFunction %void +%ptr_int_Input = OpTypePointer Input %int +%in = OpVariable %ptr_int_Input Input +%struct = OpTypeStruct %ptr_int_Input %ptr_int_Input +%struct_null = OpConstantNull %struct +%func = OpFunction %void None %functy +%1 = OpLabel +OpBranch %2 +%2 = OpLabel +%phi = OpPhi %struct %struct_null %1 %5 %4 +%extract = OpCompositeExtract %ptr_int_Input %phi 0 +OpLoopMerge %3 %4 None +OpBranch %4 +%4 = OpLabel +%5 = OpCompositeConstruct %struct %in %in +OpBranch %2 +%3 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} +#endif + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/cfg_cleanup_test.cpp b/3rdparty/spirv-tools/test/opt/cfg_cleanup_test.cpp new file mode 100644 index 000000000..0f6082e17 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/cfg_cleanup_test.cpp @@ -0,0 +1,443 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; + +using CFGCleanupTest = PassTest<::testing::Test>; + +TEST_F(CFGCleanupTest, RemoveUnreachableBlocks) { + const std::string declarations = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %inf %outf4 +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %inf "inf" +OpName %outf4 "outf4" +OpDecorate %inf Location 0 +OpDecorate %outf4 Location 0 +%void = OpTypeVoid +%6 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Input_float = OpTypePointer Input %float +%inf = OpVariable %_ptr_Input_float Input +%float_2 = OpConstant %float 2 +%bool = OpTypeBool +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%outf4 = OpVariable %_ptr_Output_v4float Output +%float_n0_5 = OpConstant %float -0.5 +)"; + + const std::string body_before = R"(%main = OpFunction %void None %6 +%14 = OpLabel +OpBranch %18 +%19 = OpLabel +%20 = OpLoad %float %inf +%21 = OpCompositeConstruct %v4float %20 %20 %20 %20 +OpStore %outf4 %21 +OpBranch %17 +%18 = OpLabel +%22 = OpLoad %float %inf +%23 = OpFAdd %float %22 %float_n0_5 +%24 = OpCompositeConstruct %v4float %23 %23 %23 %23 +OpStore %outf4 %24 +OpBranch %17 +%17 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string body_after = R"(%main = OpFunction %void None %6 +%14 = OpLabel +OpBranch %15 +%15 = OpLabel +%20 = OpLoad %float %inf +%21 = OpFAdd %float %20 %float_n0_5 +%22 = OpCompositeConstruct %v4float %21 %21 %21 %21 +OpStore %outf4 %22 +OpBranch %19 +%19 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + declarations + body_before, declarations + body_after, true, true); +} + +TEST_F(CFGCleanupTest, RemoveDecorations) { + const std::string before = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" + OpName %main "main" + OpName %x "x" + OpName %dead "dead" + OpDecorate %x RelaxedPrecision + OpDecorate %dead RelaxedPrecision + %void = OpTypeVoid + %6 = OpTypeFunction %void + %float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float + %float_2 = OpConstant %float 2 + %float_4 = OpConstant %float 4 + + %main = OpFunction %void None %6 + %14 = OpLabel + %x = OpVariable %_ptr_Function_float Function + OpBranch %18 + %19 = OpLabel + %dead = OpVariable %_ptr_Function_float Function + OpStore %dead %float_2 + OpBranch %17 + %18 = OpLabel + OpStore %x %float_4 + OpBranch %17 + %17 = OpLabel + OpReturn + OpFunctionEnd +)"; + + const std::string after = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpName %main "main" +OpName %x "x" +OpDecorate %x RelaxedPrecision +%void = OpTypeVoid +%6 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float +%float_2 = OpConstant %float 2 +%float_4 = OpConstant %float 4 +%main = OpFunction %void None %6 +%11 = OpLabel +%x = OpVariable %_ptr_Function_float Function +OpBranch %12 +%12 = OpLabel +OpStore %x %float_4 +OpBranch %14 +%14 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(before, after, true, true); +} + +TEST_F(CFGCleanupTest, UpdatePhis) { + const std::string before = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %y %outparm + OpName %main "main" + OpName %y "y" + OpName %outparm "outparm" + OpDecorate %y Flat + OpDecorate %y Location 0 + OpDecorate %outparm Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%_ptr_Input_int = OpTypePointer Input %int + %y = OpVariable %_ptr_Input_int Input + %int_10 = OpConstant %int 10 + %bool = OpTypeBool + %int_42 = OpConstant %int 42 + %int_23 = OpConstant %int 23 + %int_5 = OpConstant %int 5 +%_ptr_Output_int = OpTypePointer Output %int + %outparm = OpVariable %_ptr_Output_int Output + %main = OpFunction %void None %3 + %5 = OpLabel + %11 = OpLoad %int %y + OpBranch %21 + %16 = OpLabel + %20 = OpIAdd %int %11 %int_42 + OpBranch %17 + %21 = OpLabel + %24 = OpISub %int %11 %int_23 + OpBranch %17 + %17 = OpLabel + %31 = OpPhi %int %20 %16 %24 %21 + %27 = OpIAdd %int %31 %int_5 + OpStore %outparm %27 + OpReturn + OpFunctionEnd +)"; + + const std::string after = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %y %outparm +OpName %main "main" +OpName %y "y" +OpName %outparm "outparm" +OpDecorate %y Flat +OpDecorate %y Location 0 +OpDecorate %outparm Location 0 +%void = OpTypeVoid +%6 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%_ptr_Input_int = OpTypePointer Input %int +%y = OpVariable %_ptr_Input_int Input +%int_10 = OpConstant %int 10 +%bool = OpTypeBool +%int_42 = OpConstant %int 42 +%int_23 = OpConstant %int 23 +%int_5 = OpConstant %int 5 +%_ptr_Output_int = OpTypePointer Output %int +%outparm = OpVariable %_ptr_Output_int Output +%main = OpFunction %void None %6 +%16 = OpLabel +%17 = OpLoad %int %y +OpBranch %18 +%18 = OpLabel +%22 = OpISub %int %17 %int_23 +OpBranch %21 +%21 = OpLabel +%23 = OpPhi %int %22 %18 +%24 = OpIAdd %int %23 %int_5 +OpStore %outparm %24 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(before, after, true, true); +} + +TEST_F(CFGCleanupTest, RemoveNamedLabels) { + const std::string before = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" + OpSource GLSL 430 + OpName %main "main" + OpName %dead "dead" + %void = OpTypeVoid + %5 = OpTypeFunction %void + %main = OpFunction %void None %5 + %6 = OpLabel + OpReturn + %dead = OpLabel + OpReturn + OpFunctionEnd)"; + + const std::string after = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %main "main" +OpSource GLSL 430 +OpName %main "main" +%void = OpTypeVoid +%5 = OpTypeFunction %void +%main = OpFunction %void None %5 +%6 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(before, after, true, true); +} + +TEST_F(CFGCleanupTest, RemovePhiArgsFromFarBlocks) { + const std::string before = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %y %outparm + OpName %main "main" + OpName %y "y" + OpName %outparm "outparm" + OpDecorate %y Flat + OpDecorate %y Location 0 + OpDecorate %outparm Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%_ptr_Input_int = OpTypePointer Input %int + %y = OpVariable %_ptr_Input_int Input + %int_42 = OpConstant %int 42 +%_ptr_Output_int = OpTypePointer Output %int + %outparm = OpVariable %_ptr_Output_int Output + %int_14 = OpConstant %int 14 + %int_15 = OpConstant %int 15 + %int_5 = OpConstant %int 5 + %main = OpFunction %void None %3 + %5 = OpLabel + OpBranch %40 + %41 = OpLabel + %11 = OpLoad %int %y + OpBranch %40 + %40 = OpLabel + %12 = OpLoad %int %y + OpSelectionMerge %16 None + OpSwitch %12 %16 10 %13 13 %14 18 %15 + %13 = OpLabel + OpBranch %16 + %14 = OpLabel + OpStore %outparm %int_14 + OpBranch %16 + %15 = OpLabel + OpStore %outparm %int_15 + OpBranch %16 + %16 = OpLabel + %30 = OpPhi %int %11 %40 %int_42 %13 %11 %14 %11 %15 + %28 = OpIAdd %int %30 %int_5 + OpStore %outparm %28 + OpReturn + OpFunctionEnd)"; + + const std::string after = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %y %outparm +OpName %main "main" +OpName %y "y" +OpName %outparm "outparm" +OpDecorate %y Flat +OpDecorate %y Location 0 +OpDecorate %outparm Location 0 +%void = OpTypeVoid +%6 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%_ptr_Input_int = OpTypePointer Input %int +%y = OpVariable %_ptr_Input_int Input +%int_42 = OpConstant %int 42 +%_ptr_Output_int = OpTypePointer Output %int +%outparm = OpVariable %_ptr_Output_int Output +%int_14 = OpConstant %int 14 +%int_15 = OpConstant %int 15 +%int_5 = OpConstant %int 5 +%26 = OpUndef %int +%main = OpFunction %void None %6 +%15 = OpLabel +OpBranch %16 +%16 = OpLabel +%19 = OpLoad %int %y +OpSelectionMerge %20 None +OpSwitch %19 %20 10 %21 13 %22 18 %23 +%21 = OpLabel +OpBranch %20 +%22 = OpLabel +OpStore %outparm %int_14 +OpBranch %20 +%23 = OpLabel +OpStore %outparm %int_15 +OpBranch %20 +%20 = OpLabel +%24 = OpPhi %int %26 %16 %int_42 %21 %26 %22 %26 %23 +%25 = OpIAdd %int %24 %int_5 +OpStore %outparm %25 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(before, after, true, true); +} + +TEST_F(CFGCleanupTest, RemovePhiConstantArgs) { + const std::string before = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %y %outparm + OpName %main "main" + OpName %y "y" + OpName %outparm "outparm" + OpDecorate %y Flat + OpDecorate %y Location 0 + OpDecorate %outparm Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Input_int = OpTypePointer Input %int + %y = OpVariable %_ptr_Input_int Input + %int_10 = OpConstant %int 10 + %bool = OpTypeBool +%_ptr_Function_int = OpTypePointer Function %int + %int_23 = OpConstant %int 23 + %int_5 = OpConstant %int 5 +%_ptr_Output_int = OpTypePointer Output %int + %outparm = OpVariable %_ptr_Output_int Output + %24 = OpUndef %int + %main = OpFunction %void None %3 + %5 = OpLabel + OpBranch %14 + %40 = OpLabel + %9 = OpLoad %int %y + %12 = OpSGreaterThan %bool %9 %int_10 + OpSelectionMerge %14 None + OpBranchConditional %12 %13 %14 + %13 = OpLabel + OpBranch %14 + %14 = OpLabel + %25 = OpPhi %int %24 %5 %int_23 %13 + %20 = OpIAdd %int %25 %int_5 + OpStore %outparm %20 + OpReturn + OpFunctionEnd)"; + + const std::string after = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %y %outparm +OpName %main "main" +OpName %y "y" +OpName %outparm "outparm" +OpDecorate %y Flat +OpDecorate %y Location 0 +OpDecorate %outparm Location 0 +%void = OpTypeVoid +%6 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%_ptr_Input_int = OpTypePointer Input %int +%y = OpVariable %_ptr_Input_int Input +%int_10 = OpConstant %int 10 +%bool = OpTypeBool +%_ptr_Function_int = OpTypePointer Function %int +%int_23 = OpConstant %int 23 +%int_5 = OpConstant %int 5 +%_ptr_Output_int = OpTypePointer Output %int +%outparm = OpVariable %_ptr_Output_int Output +%15 = OpUndef %int +%main = OpFunction %void None %6 +%16 = OpLabel +OpBranch %17 +%17 = OpLabel +%22 = OpPhi %int %15 %16 +%23 = OpIAdd %int %22 %int_5 +OpStore %outparm %23 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(before, after, true, true); +} +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/common_uniform_elim_test.cpp b/3rdparty/spirv-tools/test/opt/common_uniform_elim_test.cpp new file mode 100644 index 000000000..cf9cc1bb8 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/common_uniform_elim_test.cpp @@ -0,0 +1,1162 @@ +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "pass_fixture.h" + +namespace { + +using namespace spvtools; + +using CommonUniformElimTest = PassTest<::testing::Test>; + +TEST_F(CommonUniformElimTest, Basic1) { + // Note: This test exemplifies the following: + // - Common uniform (%_) load floated to nearest non-controlled block + // - Common extract (g_F) floated to non-controlled block + // - Non-common extract (g_F2) not floated, but common uniform load shared + // + // #version 140 + // in vec4 BaseColor; + // in float fi; + // + // layout(std140) uniform U_t + // { + // float g_F; + // float g_F2; + // } ; + // + // void main() + // { + // vec4 v = BaseColor; + // if (fi > 0) { + // v = v * g_F; + // } + // else { + // float f2 = g_F2 - g_F; + // v = v * f2; + // } + // gl_FragColor = v; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %fi %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %fi "fi" +OpName %U_t "U_t" +OpMemberName %U_t 0 "g_F" +OpMemberName %U_t 1 "g_F2" +OpName %_ "" +OpName %f2 "f2" +OpName %gl_FragColor "gl_FragColor" +OpMemberDecorate %U_t 0 Offset 0 +OpMemberDecorate %U_t 1 Offset 4 +OpDecorate %U_t Block +OpDecorate %_ DescriptorSet 0 +%void = OpTypeVoid +%11 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Input_float = OpTypePointer Input %float +%fi = OpVariable %_ptr_Input_float Input +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%U_t = OpTypeStruct %float %float +%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t +%_ = OpVariable %_ptr_Uniform_U_t Uniform +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%_ptr_Uniform_float = OpTypePointer Uniform %float +%_ptr_Function_float = OpTypePointer Function %float +%int_1 = OpConstant %int 1 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %11 +%26 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%f2 = OpVariable %_ptr_Function_float Function +%27 = OpLoad %v4float %BaseColor +OpStore %v %27 +%28 = OpLoad %float %fi +%29 = OpFOrdGreaterThan %bool %28 %float_0 +OpSelectionMerge %30 None +OpBranchConditional %29 %31 %32 +%31 = OpLabel +%33 = OpLoad %v4float %v +%34 = OpAccessChain %_ptr_Uniform_float %_ %int_0 +%35 = OpLoad %float %34 +%36 = OpVectorTimesScalar %v4float %33 %35 +OpStore %v %36 +OpBranch %30 +%32 = OpLabel +%37 = OpAccessChain %_ptr_Uniform_float %_ %int_1 +%38 = OpLoad %float %37 +%39 = OpAccessChain %_ptr_Uniform_float %_ %int_0 +%40 = OpLoad %float %39 +%41 = OpFSub %float %38 %40 +OpStore %f2 %41 +%42 = OpLoad %v4float %v +%43 = OpLoad %float %f2 +%44 = OpVectorTimesScalar %v4float %42 %43 +OpStore %v %44 +OpBranch %30 +%30 = OpLabel +%45 = OpLoad %v4float %v +OpStore %gl_FragColor %45 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %11 +%26 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%f2 = OpVariable %_ptr_Function_float Function +%52 = OpLoad %U_t %_ +%53 = OpCompositeExtract %float %52 0 +%27 = OpLoad %v4float %BaseColor +OpStore %v %27 +%28 = OpLoad %float %fi +%29 = OpFOrdGreaterThan %bool %28 %float_0 +OpSelectionMerge %30 None +OpBranchConditional %29 %31 %32 +%31 = OpLabel +%33 = OpLoad %v4float %v +%36 = OpVectorTimesScalar %v4float %33 %53 +OpStore %v %36 +OpBranch %30 +%32 = OpLabel +%49 = OpCompositeExtract %float %52 1 +%41 = OpFSub %float %49 %53 +OpStore %f2 %41 +%42 = OpLoad %v4float %v +%43 = OpLoad %float %f2 +%44 = OpVectorTimesScalar %v4float %42 %43 +OpStore %v %44 +OpBranch %30 +%30 = OpLabel +%45 = OpLoad %v4float %v +OpStore %gl_FragColor %45 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(CommonUniformElimTest, Basic2) { + // Note: This test exemplifies the following: + // - Common uniform (%_) load floated to nearest non-controlled block + // - Common extract (g_F) floated to non-controlled block + // - Non-common extract (g_F2) not floated, but common uniform load shared + // + // #version 140 + // in vec4 BaseColor; + // in float fi; + // in float fi2; + // + // layout(std140) uniform U_t + // { + // float g_F; + // float g_F2; + // } ; + // + // void main() + // { + // float f = fi; + // if (f < 0) + // f = -f; + // if (fi2 > 0) { + // f = f * g_F; + // } + // else { + // f = g_F2 - g_F; + // } + // gl_FragColor = f * BaseColor; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %fi %fi2 %gl_FragColor %BaseColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %f "f" +OpName %fi "fi" +OpName %fi2 "fi2" +OpName %U_t "U_t" +OpMemberName %U_t 0 "g_F" +OpMemberName %U_t 1 "g_F2" +OpName %_ "" +OpName %gl_FragColor "gl_FragColor" +OpName %BaseColor "BaseColor" +OpMemberDecorate %U_t 0 Offset 0 +OpMemberDecorate %U_t 1 Offset 4 +OpDecorate %U_t Block +OpDecorate %_ DescriptorSet 0 +%void = OpTypeVoid +%11 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float +%_ptr_Input_float = OpTypePointer Input %float +%fi = OpVariable %_ptr_Input_float Input +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%fi2 = OpVariable %_ptr_Input_float Input +%U_t = OpTypeStruct %float %float +%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t +%_ = OpVariable %_ptr_Uniform_U_t Uniform +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%_ptr_Uniform_float = OpTypePointer Uniform %float +%int_1 = OpConstant %int 1 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +)"; + + const std::string before = + R"(%main = OpFunction %void None %11 +%25 = OpLabel +%f = OpVariable %_ptr_Function_float Function +%26 = OpLoad %float %fi +OpStore %f %26 +%27 = OpLoad %float %f +%28 = OpFOrdLessThan %bool %27 %float_0 +OpSelectionMerge %29 None +OpBranchConditional %28 %30 %29 +%30 = OpLabel +%31 = OpLoad %float %f +%32 = OpFNegate %float %31 +OpStore %f %32 +OpBranch %29 +%29 = OpLabel +%33 = OpLoad %float %fi2 +%34 = OpFOrdGreaterThan %bool %33 %float_0 +OpSelectionMerge %35 None +OpBranchConditional %34 %36 %37 +%36 = OpLabel +%38 = OpLoad %float %f +%39 = OpAccessChain %_ptr_Uniform_float %_ %int_0 +%40 = OpLoad %float %39 +%41 = OpFMul %float %38 %40 +OpStore %f %41 +OpBranch %35 +%37 = OpLabel +%42 = OpAccessChain %_ptr_Uniform_float %_ %int_1 +%43 = OpLoad %float %42 +%44 = OpAccessChain %_ptr_Uniform_float %_ %int_0 +%45 = OpLoad %float %44 +%46 = OpFSub %float %43 %45 +OpStore %f %46 +OpBranch %35 +%35 = OpLabel +%47 = OpLoad %v4float %BaseColor +%48 = OpLoad %float %f +%49 = OpVectorTimesScalar %v4float %47 %48 +OpStore %gl_FragColor %49 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %11 +%25 = OpLabel +%f = OpVariable %_ptr_Function_float Function +%26 = OpLoad %float %fi +OpStore %f %26 +%27 = OpLoad %float %f +%28 = OpFOrdLessThan %bool %27 %float_0 +OpSelectionMerge %29 None +OpBranchConditional %28 %30 %29 +%30 = OpLabel +%31 = OpLoad %float %f +%32 = OpFNegate %float %31 +OpStore %f %32 +OpBranch %29 +%29 = OpLabel +%56 = OpLoad %U_t %_ +%57 = OpCompositeExtract %float %56 0 +%33 = OpLoad %float %fi2 +%34 = OpFOrdGreaterThan %bool %33 %float_0 +OpSelectionMerge %35 None +OpBranchConditional %34 %36 %37 +%36 = OpLabel +%38 = OpLoad %float %f +%41 = OpFMul %float %38 %57 +OpStore %f %41 +OpBranch %35 +%37 = OpLabel +%53 = OpCompositeExtract %float %56 1 +%46 = OpFSub %float %53 %57 +OpStore %f %46 +OpBranch %35 +%35 = OpLabel +%47 = OpLoad %v4float %BaseColor +%48 = OpLoad %float %f +%49 = OpVectorTimesScalar %v4float %47 %48 +OpStore %gl_FragColor %49 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(CommonUniformElimTest, Basic3) { + // Note: This test exemplifies the following: + // - Existing common uniform (%_) load kept in place and shared + // + // #version 140 + // in vec4 BaseColor; + // in float fi; + // + // layout(std140) uniform U_t + // { + // bool g_B; + // float g_F; + // } ; + // + // void main() + // { + // vec4 v = BaseColor; + // if (g_B) + // v = v * g_F; + // gl_FragColor = v; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor %fi +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %U_t "U_t" +OpMemberName %U_t 0 "g_B" +OpMemberName %U_t 1 "g_F" +OpName %_ "" +OpName %gl_FragColor "gl_FragColor" +OpName %fi "fi" +OpMemberDecorate %U_t 0 Offset 0 +OpMemberDecorate %U_t 1 Offset 4 +OpDecorate %U_t Block +OpDecorate %_ DescriptorSet 0 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%uint = OpTypeInt 32 0 +%U_t = OpTypeStruct %uint %float +%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t +%_ = OpVariable %_ptr_Uniform_U_t Uniform +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%bool = OpTypeBool +%uint_0 = OpConstant %uint 0 +%int_1 = OpConstant %int 1 +%_ptr_Uniform_float = OpTypePointer Uniform %float +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +%_ptr_Input_float = OpTypePointer Input %float +%fi = OpVariable %_ptr_Input_float Input +)"; + + const std::string before = + R"(%main = OpFunction %void None %10 +%26 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%27 = OpLoad %v4float %BaseColor +OpStore %v %27 +%28 = OpAccessChain %_ptr_Uniform_uint %_ %int_0 +%29 = OpLoad %uint %28 +%30 = OpINotEqual %bool %29 %uint_0 +OpSelectionMerge %31 None +OpBranchConditional %30 %32 %31 +%32 = OpLabel +%33 = OpLoad %v4float %v +%34 = OpAccessChain %_ptr_Uniform_float %_ %int_1 +%35 = OpLoad %float %34 +%36 = OpVectorTimesScalar %v4float %33 %35 +OpStore %v %36 +OpBranch %31 +%31 = OpLabel +%37 = OpLoad %v4float %v +OpStore %gl_FragColor %37 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %10 +%26 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%27 = OpLoad %v4float %BaseColor +OpStore %v %27 +%38 = OpLoad %U_t %_ +%39 = OpCompositeExtract %uint %38 0 +%30 = OpINotEqual %bool %39 %uint_0 +OpSelectionMerge %31 None +OpBranchConditional %30 %32 %31 +%32 = OpLabel +%33 = OpLoad %v4float %v +%41 = OpCompositeExtract %float %38 1 +%36 = OpVectorTimesScalar %v4float %33 %41 +OpStore %v %36 +OpBranch %31 +%31 = OpLabel +%37 = OpLoad %v4float %v +OpStore %gl_FragColor %37 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(CommonUniformElimTest, Loop) { + // Note: This test exemplifies the following: + // - Common extract (g_F) shared between two loops + // #version 140 + // in vec4 BC; + // in vec4 BC2; + // + // layout(std140) uniform U_t + // { + // float g_F; + // } ; + // + // void main() + // { + // vec4 v = BC; + // for (int i = 0; i < 4; i++) + // v[i] = v[i] / g_F; + // vec4 v2 = BC2; + // for (int i = 0; i < 4; i++) + // v2[i] = v2[i] * g_F; + // gl_FragColor = v + v2; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BC %BC2 %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %v "v" +OpName %BC "BC" +OpName %i "i" +OpName %U_t "U_t" +OpMemberName %U_t 0 "g_F" +OpName %_ "" +OpName %v2 "v2" +OpName %BC2 "BC2" +OpName %i_0 "i" +OpName %gl_FragColor "gl_FragColor" +OpMemberDecorate %U_t 0 Offset 0 +OpDecorate %U_t Block +OpDecorate %_ DescriptorSet 0 +%void = OpTypeVoid +%13 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BC = OpVariable %_ptr_Input_v4float Input +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_0 = OpConstant %int 0 +%int_4 = OpConstant %int 4 +%bool = OpTypeBool +%_ptr_Function_float = OpTypePointer Function %float +%U_t = OpTypeStruct %float +%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t +%_ = OpVariable %_ptr_Uniform_U_t Uniform +%_ptr_Uniform_float = OpTypePointer Uniform %float +%int_1 = OpConstant %int 1 +%BC2 = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %13 +%28 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%i = OpVariable %_ptr_Function_int Function +%v2 = OpVariable %_ptr_Function_v4float Function +%i_0 = OpVariable %_ptr_Function_int Function +%29 = OpLoad %v4float %BC +OpStore %v %29 +OpStore %i %int_0 +OpBranch %30 +%30 = OpLabel +OpLoopMerge %31 %32 None +OpBranch %33 +%33 = OpLabel +%34 = OpLoad %int %i +%35 = OpSLessThan %bool %34 %int_4 +OpBranchConditional %35 %36 %31 +%36 = OpLabel +%37 = OpLoad %int %i +%38 = OpLoad %int %i +%39 = OpAccessChain %_ptr_Function_float %v %38 +%40 = OpLoad %float %39 +%41 = OpAccessChain %_ptr_Uniform_float %_ %int_0 +%42 = OpLoad %float %41 +%43 = OpFDiv %float %40 %42 +%44 = OpAccessChain %_ptr_Function_float %v %37 +OpStore %44 %43 +OpBranch %32 +%32 = OpLabel +%45 = OpLoad %int %i +%46 = OpIAdd %int %45 %int_1 +OpStore %i %46 +OpBranch %30 +%31 = OpLabel +%47 = OpLoad %v4float %BC2 +OpStore %v2 %47 +OpStore %i_0 %int_0 +OpBranch %48 +%48 = OpLabel +OpLoopMerge %49 %50 None +OpBranch %51 +%51 = OpLabel +%52 = OpLoad %int %i_0 +%53 = OpSLessThan %bool %52 %int_4 +OpBranchConditional %53 %54 %49 +%54 = OpLabel +%55 = OpLoad %int %i_0 +%56 = OpLoad %int %i_0 +%57 = OpAccessChain %_ptr_Function_float %v2 %56 +%58 = OpLoad %float %57 +%59 = OpAccessChain %_ptr_Uniform_float %_ %int_0 +%60 = OpLoad %float %59 +%61 = OpFMul %float %58 %60 +%62 = OpAccessChain %_ptr_Function_float %v2 %55 +OpStore %62 %61 +OpBranch %50 +%50 = OpLabel +%63 = OpLoad %int %i_0 +%64 = OpIAdd %int %63 %int_1 +OpStore %i_0 %64 +OpBranch %48 +%49 = OpLabel +%65 = OpLoad %v4float %v +%66 = OpLoad %v4float %v2 +%67 = OpFAdd %v4float %65 %66 +OpStore %gl_FragColor %67 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %13 +%28 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%i = OpVariable %_ptr_Function_int Function +%v2 = OpVariable %_ptr_Function_v4float Function +%i_0 = OpVariable %_ptr_Function_int Function +%72 = OpLoad %U_t %_ +%73 = OpCompositeExtract %float %72 0 +%29 = OpLoad %v4float %BC +OpStore %v %29 +OpStore %i %int_0 +OpBranch %30 +%30 = OpLabel +OpLoopMerge %31 %32 None +OpBranch %33 +%33 = OpLabel +%34 = OpLoad %int %i +%35 = OpSLessThan %bool %34 %int_4 +OpBranchConditional %35 %36 %31 +%36 = OpLabel +%37 = OpLoad %int %i +%38 = OpLoad %int %i +%39 = OpAccessChain %_ptr_Function_float %v %38 +%40 = OpLoad %float %39 +%43 = OpFDiv %float %40 %73 +%44 = OpAccessChain %_ptr_Function_float %v %37 +OpStore %44 %43 +OpBranch %32 +%32 = OpLabel +%45 = OpLoad %int %i +%46 = OpIAdd %int %45 %int_1 +OpStore %i %46 +OpBranch %30 +%31 = OpLabel +%47 = OpLoad %v4float %BC2 +OpStore %v2 %47 +OpStore %i_0 %int_0 +OpBranch %48 +%48 = OpLabel +OpLoopMerge %49 %50 None +OpBranch %51 +%51 = OpLabel +%52 = OpLoad %int %i_0 +%53 = OpSLessThan %bool %52 %int_4 +OpBranchConditional %53 %54 %49 +%54 = OpLabel +%55 = OpLoad %int %i_0 +%56 = OpLoad %int %i_0 +%57 = OpAccessChain %_ptr_Function_float %v2 %56 +%58 = OpLoad %float %57 +%61 = OpFMul %float %58 %73 +%62 = OpAccessChain %_ptr_Function_float %v2 %55 +OpStore %62 %61 +OpBranch %50 +%50 = OpLabel +%63 = OpLoad %int %i_0 +%64 = OpIAdd %int %63 %int_1 +OpStore %i_0 %64 +OpBranch %48 +%49 = OpLabel +%65 = OpLoad %v4float %v +%66 = OpLoad %v4float %v2 +%67 = OpFAdd %v4float %65 %66 +OpStore %gl_FragColor %67 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(CommonUniformElimTest, Volatile1) { + // Note: This test exemplifies the following: + // - Same test as Basic1 with the exception that + // the Load of g_F in else-branch is volatile + // - Common uniform (%_) load floated to nearest non-controlled block + // + // #version 140 + // in vec4 BaseColor; + // in float fi; + // + // layout(std140) uniform U_t + // { + // float g_F; + // float g_F2; + // } ; + // + // void main() + // { + // vec4 v = BaseColor; + // if (fi > 0) { + // v = v * g_F; + // } + // else { + // float f2 = g_F2 - g_F; + // v = v * f2; + // } + // gl_FragColor = v; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %fi %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %fi "fi" +OpName %U_t "U_t" +OpMemberName %U_t 0 "g_F" +OpMemberName %U_t 1 "g_F2" +OpName %_ "" +OpName %f2 "f2" +OpName %gl_FragColor "gl_FragColor" +OpMemberDecorate %U_t 0 Offset 0 +OpMemberDecorate %U_t 1 Offset 4 +OpDecorate %U_t Block +OpDecorate %_ DescriptorSet 0 +%void = OpTypeVoid +%11 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Input_float = OpTypePointer Input %float +%fi = OpVariable %_ptr_Input_float Input +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%U_t = OpTypeStruct %float %float +%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t +%_ = OpVariable %_ptr_Uniform_U_t Uniform +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%_ptr_Uniform_float = OpTypePointer Uniform %float +%_ptr_Function_float = OpTypePointer Function %float +%int_1 = OpConstant %int 1 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %11 +%26 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%f2 = OpVariable %_ptr_Function_float Function +%27 = OpLoad %v4float %BaseColor +OpStore %v %27 +%28 = OpLoad %float %fi +%29 = OpFOrdGreaterThan %bool %28 %float_0 +OpSelectionMerge %30 None +OpBranchConditional %29 %31 %32 +%31 = OpLabel +%33 = OpLoad %v4float %v +%34 = OpAccessChain %_ptr_Uniform_float %_ %int_0 +%35 = OpLoad %float %34 +%36 = OpVectorTimesScalar %v4float %33 %35 +OpStore %v %36 +OpBranch %30 +%32 = OpLabel +%37 = OpAccessChain %_ptr_Uniform_float %_ %int_1 +%38 = OpLoad %float %37 +%39 = OpAccessChain %_ptr_Uniform_float %_ %int_0 +%40 = OpLoad %float %39 Volatile +%41 = OpFSub %float %38 %40 +OpStore %f2 %41 +%42 = OpLoad %v4float %v +%43 = OpLoad %float %f2 +%44 = OpVectorTimesScalar %v4float %42 %43 +OpStore %v %44 +OpBranch %30 +%30 = OpLabel +%45 = OpLoad %v4float %v +OpStore %gl_FragColor %45 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %11 +%26 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%f2 = OpVariable %_ptr_Function_float Function +%50 = OpLoad %U_t %_ +%27 = OpLoad %v4float %BaseColor +OpStore %v %27 +%28 = OpLoad %float %fi +%29 = OpFOrdGreaterThan %bool %28 %float_0 +OpSelectionMerge %30 None +OpBranchConditional %29 %31 %32 +%31 = OpLabel +%33 = OpLoad %v4float %v +%47 = OpCompositeExtract %float %50 0 +%36 = OpVectorTimesScalar %v4float %33 %47 +OpStore %v %36 +OpBranch %30 +%32 = OpLabel +%49 = OpCompositeExtract %float %50 1 +%39 = OpAccessChain %_ptr_Uniform_float %_ %int_0 +%40 = OpLoad %float %39 Volatile +%41 = OpFSub %float %49 %40 +OpStore %f2 %41 +%42 = OpLoad %v4float %v +%43 = OpLoad %float %f2 +%44 = OpVectorTimesScalar %v4float %42 %43 +OpStore %v %44 +OpBranch %30 +%30 = OpLabel +%45 = OpLoad %v4float %v +OpStore %gl_FragColor %45 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(CommonUniformElimTest, Volatile2) { + // Note: This test exemplifies the following: + // - Same test as Basic1 with the exception that + // U_t is Volatile. + // - No optimizations are applied + // + // #version 430 + // in vec4 BaseColor; + // in float fi; + // + // layout(std430) volatile buffer U_t + // { + // float g_F; + // float g_F2; + // }; + // + // + // void main(void) + // { + // vec4 v = BaseColor; + // if (fi > 0) { + // v = v * g_F; + // } else { + // float f2 = g_F2 - g_F; + // v = v * f2; + // } + // } + + const std::string text = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %fi +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 430 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %fi "fi" +OpName %U_t "U_t" +OpMemberName %U_t 0 "g_F" +OpMemberName %U_t 1 "g_F2" +OpName %_ "" +OpName %f2 "f2" +OpDecorate %BaseColor Location 0 +OpDecorate %fi Location 0 +OpMemberDecorate %U_t 0 Volatile +OpMemberDecorate %U_t 0 Offset 0 +OpMemberDecorate %U_t 1 Volatile +OpMemberDecorate %U_t 1 Offset 4 +OpDecorate %U_t BufferBlock +OpDecorate %_ DescriptorSet 0 +%void = OpTypeVoid +%3 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Input_float = OpTypePointer Input %float +%fi = OpVariable %_ptr_Input_float Input +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%U_t = OpTypeStruct %float %float +%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t +%_ = OpVariable %_ptr_Uniform_U_t Uniform +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%_ptr_Uniform_float = OpTypePointer Uniform %float +%_ptr_Function_float = OpTypePointer Function %float +%int_1 = OpConstant %int 1 +%main = OpFunction %void None %3 +%5 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%f2 = OpVariable %_ptr_Function_float Function +%12 = OpLoad %v4float %BaseColor +OpStore %v %12 +%15 = OpLoad %float %fi +%18 = OpFOrdGreaterThan %bool %15 %float_0 +OpSelectionMerge %20 None +OpBranchConditional %18 %19 %31 +%19 = OpLabel +%21 = OpLoad %v4float %v +%28 = OpAccessChain %_ptr_Uniform_float %_ %int_0 +%29 = OpLoad %float %28 +%30 = OpVectorTimesScalar %v4float %21 %29 +OpStore %v %30 +OpBranch %20 +%31 = OpLabel +%35 = OpAccessChain %_ptr_Uniform_float %_ %int_1 +%36 = OpLoad %float %35 +%37 = OpAccessChain %_ptr_Uniform_float %_ %int_0 +%38 = OpLoad %float %37 +%39 = OpFSub %float %36 %38 +OpStore %f2 %39 +%40 = OpLoad %v4float %v +%41 = OpLoad %float %f2 +%42 = OpVectorTimesScalar %v4float %40 %41 +OpStore %v %42 +OpBranch %20 +%20 = OpLabel +OpReturn +OpFunctionEnd +)"; + + opt::Pass::Status res = + std::get<1>(SinglePassRunAndDisassemble( + text, true, false)); + EXPECT_EQ(res, opt::Pass::Status::SuccessWithoutChange); +} + +TEST_F(CommonUniformElimTest, Volatile3) { + // Note: This test exemplifies the following: + // - Same test as Volatile2 with the exception that + // the nested struct S is volatile + // - No optimizations are applied + // + // #version 430 + // in vec4 BaseColor; + // in float fi; + // + // struct S { + // volatile float a; + // }; + // + // layout(std430) buffer U_t + // { + // S g_F; + // S g_F2; + // }; + // + // + // void main(void) + // { + // vec4 v = BaseColor; + // if (fi > 0) { + // v = v * g_F.a; + // } else { + // float f2 = g_F2.a - g_F.a; + // v = v * f2; + // } + // } + + const std::string text = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %fi +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 430 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %fi "fi" +OpName %S "S" +OpMemberName %S 0 "a" +OpName %U_t "U_t" +OpMemberName %U_t 0 "g_F" +OpMemberName %U_t 1 "g_F2" +OpName %_ "" +OpName %f2 "f2" +OpDecorate %BaseColor Location 0 +OpDecorate %fi Location 0 +OpMemberDecorate %S 0 Offset 0 +OpMemberDecorate %S 0 Volatile +OpMemberDecorate %U_t 0 Offset 0 +OpMemberDecorate %U_t 1 Offset 4 +OpDecorate %U_t BufferBlock +OpDecorate %_ DescriptorSet 0 +%void = OpTypeVoid +%3 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Input_float = OpTypePointer Input %float +%fi = OpVariable %_ptr_Input_float Input +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%S = OpTypeStruct %float +%U_t = OpTypeStruct %S %S +%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t +%_ = OpVariable %_ptr_Uniform_U_t Uniform +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%_ptr_Uniform_float = OpTypePointer Uniform %float +%_ptr_Function_float = OpTypePointer Function %float +%int_1 = OpConstant %int 1 +%main = OpFunction %void None %3 +%5 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%f2 = OpVariable %_ptr_Function_float Function +%12 = OpLoad %v4float %BaseColor +OpStore %v %12 +%15 = OpLoad %float %fi +%18 = OpFOrdGreaterThan %bool %15 %float_0 +OpSelectionMerge %20 None +OpBranchConditional %18 %19 %32 +%19 = OpLabel +%21 = OpLoad %v4float %v +%29 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %int_0 +%30 = OpLoad %float %29 +%31 = OpVectorTimesScalar %v4float %21 %30 +OpStore %v %31 +OpBranch %20 +%32 = OpLabel +%36 = OpAccessChain %_ptr_Uniform_float %_ %int_1 %int_0 +%37 = OpLoad %float %36 +%38 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %int_0 +%39 = OpLoad %float %38 +%40 = OpFSub %float %37 %39 +OpStore %f2 %40 +%41 = OpLoad %v4float %v +%42 = OpLoad %float %f2 +%43 = OpVectorTimesScalar %v4float %41 %42 +OpStore %v %43 +OpBranch %20 +%20 = OpLabel +OpReturn +OpFunctionEnd +)"; + + opt::Pass::Status res = + std::get<1>(SinglePassRunAndDisassemble( + text, true, false)); + EXPECT_EQ(res, opt::Pass::Status::SuccessWithoutChange); +} + +#ifdef SPIRV_EFFCEE +TEST_F(CommonUniformElimTest, MixedConstantAndNonConstantIndexes) { + const std::string text = R"( +; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Uniform +; CHECK: %501 = OpLabel +; CHECK: [[ld:%\w+]] = OpLoad +; CHECK-NOT: OpCompositeExtract {{%\w+}} {{%\w+}} 0 2 484 +; CHECK: OpAccessChain {{%\w+}} [[var]] %int_0 %int_2 [[ld]] + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "ringeffectLayer_px" %gl_FragCoord %178 %182 + OpExecutionMode %4 OriginUpperLeft + OpSource HLSL 500 + OpDecorate %_arr_v4float_uint_10 ArrayStride 16 + OpMemberDecorate %_struct_20 0 Offset 0 + OpMemberDecorate %_struct_20 1 Offset 16 + OpMemberDecorate %_struct_20 2 Offset 32 + OpMemberDecorate %_struct_21 0 Offset 0 + OpDecorate %_struct_21 Block + OpDecorate %23 DescriptorSet 0 + OpDecorate %gl_FragCoord BuiltIn FragCoord + OpDecorate %178 Location 0 + OpDecorate %182 Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 + %v2float = OpTypeVector %float 2 +%_ptr_Function_v2float = OpTypePointer Function %v2float + %uint = OpTypeInt 32 0 + %uint_10 = OpConstant %uint 10 +%_arr_v4float_uint_10 = OpTypeArray %v4float %uint_10 + %_struct_20 = OpTypeStruct %v4float %v4float %_arr_v4float_uint_10 + %_struct_21 = OpTypeStruct %_struct_20 +%_ptr_Uniform__struct_21 = OpTypePointer Uniform %_struct_21 + %23 = OpVariable %_ptr_Uniform__struct_21 Uniform + %int = OpTypeInt 32 1 + %int_0 = OpConstant %int 0 +%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float +%_ptr_Uniform_float = OpTypePointer Uniform %float + %uint_3 = OpConstant %uint 3 +%_ptr_Function_v4float = OpTypePointer Function %v4float + %float_0 = OpConstant %float 0 + %43 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 +%_ptr_Function_int = OpTypePointer Function %int + %int_5 = OpConstant %int 5 + %bool = OpTypeBool + %int_1 = OpConstant %int 1 + %int_2 = OpConstant %int 2 + %uint_5 = OpConstant %uint 5 +%_arr_v2float_uint_5 = OpTypeArray %v2float %uint_5 +%_ptr_Function__arr_v2float_uint_5 = OpTypePointer Function %_arr_v2float_uint_5 + %82 = OpTypeImage %float 2D 0 0 0 1 Unknown +%_ptr_UniformConstant_82 = OpTypePointer UniformConstant %82 + %86 = OpTypeSampler +%_ptr_UniformConstant_86 = OpTypePointer UniformConstant %86 + %90 = OpTypeSampledImage %82 + %v3float = OpTypeVector %float 3 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%gl_FragCoord = OpVariable %_ptr_Input_v4float Input + %178 = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float + %182 = OpVariable %_ptr_Output_v4float Output + %4 = OpFunction %void None %3 + %5 = OpLabel + %483 = OpVariable %_ptr_Function_v4float Function + %484 = OpVariable %_ptr_Function_int Function + %486 = OpVariable %_ptr_Function__arr_v2float_uint_5 Function + %179 = OpLoad %v4float %178 + %493 = OpAccessChain %_ptr_Uniform_float %23 %int_0 %int_0 %uint_3 + %494 = OpLoad %float %493 + OpStore %483 %43 + OpStore %484 %int_0 + OpBranch %495 + %495 = OpLabel + OpLoopMerge %496 %497 None + OpBranch %498 + %498 = OpLabel + %499 = OpLoad %int %484 + %500 = OpSLessThan %bool %499 %int_5 + OpBranchConditional %500 %501 %496 + %501 = OpLabel + %504 = OpVectorShuffle %v2float %179 %179 0 1 + %505 = OpLoad %int %484 + %506 = OpAccessChain %_ptr_Uniform_v4float %23 %int_0 %int_2 %505 + %507 = OpLoad %v4float %506 + %508 = OpVectorShuffle %v2float %507 %507 0 1 + %509 = OpFAdd %v2float %504 %508 + %512 = OpAccessChain %_ptr_Uniform_v4float %23 %int_0 %int_1 + %513 = OpLoad %v4float %512 + %514 = OpVectorShuffle %v2float %513 %513 0 1 + %517 = OpVectorShuffle %v2float %513 %513 2 3 + %518 = OpExtInst %v2float %1 FClamp %509 %514 %517 + %519 = OpAccessChain %_ptr_Function_v2float %486 %505 + OpStore %519 %518 + OpBranch %497 + %497 = OpLabel + %520 = OpLoad %int %484 + %521 = OpIAdd %int %520 %int_1 + OpStore %484 %521 + OpBranch %495 + %496 = OpLabel + OpReturn + OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndMatch(text, true); +} +#endif // SPIRV_EFFCEE +// TODO(greg-lunarg): Add tests to verify handling of these cases: +// +// Disqualifying cases: extensions, decorations, non-logical addressing, +// non-structured control flow +// Others? + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/compact_ids_test.cpp b/3rdparty/spirv-tools/test/opt/compact_ids_test.cpp new file mode 100644 index 000000000..d5fcfb95c --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/compact_ids_test.cpp @@ -0,0 +1,195 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "spirv-tools/libspirv.hpp" +#include "spirv-tools/optimizer.hpp" + +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; + +using CompactIdsTest = PassTest<::testing::Test>; + +TEST_F(CompactIdsTest, PassOff) { + const std::string before = + R"(OpCapability Addresses +OpCapability Kernel +OpCapability GenericPointer +OpCapability Linkage +OpMemoryModel Physical32 OpenCL +%99 = OpTypeInt 32 0 +%10 = OpTypeVector %99 2 +%20 = OpConstant %99 2 +%30 = OpTypeArray %99 %20 +)"; + + const std::string after = before; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndCheck(before, after, false, false); +} + +TEST_F(CompactIdsTest, PassOn) { + const std::string before = + R"(OpCapability Addresses +OpCapability Kernel +OpCapability GenericPointer +OpCapability Linkage +OpMemoryModel Physical32 OpenCL +OpEntryPoint Kernel %3 "simple_kernel" +%99 = OpTypeInt 32 0 +%10 = OpTypeVector %99 2 +%20 = OpConstant %99 2 +%30 = OpTypeArray %99 %20 +%40 = OpTypeVoid +%50 = OpTypeFunction %40 + %3 = OpFunction %40 None %50 +%70 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(OpCapability Addresses +OpCapability Kernel +OpCapability GenericPointer +OpCapability Linkage +OpMemoryModel Physical32 OpenCL +OpEntryPoint Kernel %1 "simple_kernel" +%2 = OpTypeInt 32 0 +%3 = OpTypeVector %2 2 +%4 = OpConstant %2 2 +%5 = OpTypeArray %2 %4 +%6 = OpTypeVoid +%7 = OpTypeFunction %6 +%1 = OpFunction %6 None %7 +%8 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndCheck(before, after, false, false); +} + +TEST(CompactIds, InstructionResultIsUpdated) { + // For https://github.com/KhronosGroup/SPIRV-Tools/issues/827 + // In that bug, the compact Ids pass was directly updating the result Id + // word for an OpFunction instruction, but not updating the cached + // result_id_ in that Instruction object. + // + // This test is a bit cheesy. We don't expose internal interfaces enough + // to see the inconsistency. So reproduce the original scenario, with + // compact ids followed by a pass that trips up on the inconsistency. + + const std::string input(R"(OpCapability Shader +OpMemoryModel Logical Simple +OpEntryPoint GLCompute %100 "main" +%200 = OpTypeVoid +%300 = OpTypeFunction %200 +%100 = OpFunction %300 None %200 +%400 = OpLabel +OpReturn +OpFunctionEnd +)"); + + std::vector binary; + const spv_target_env env = SPV_ENV_UNIVERSAL_1_0; + spvtools::SpirvTools tools(env); + auto assembled = tools.Assemble( + input, &binary, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + EXPECT_TRUE(assembled); + + spvtools::Optimizer optimizer(env); + optimizer.RegisterPass(CreateCompactIdsPass()); + // The exhaustive inliner will use the result_id + optimizer.RegisterPass(CreateInlineExhaustivePass()); + + // This should not crash! + optimizer.Run(binary.data(), binary.size(), &binary); + + std::string disassembly; + tools.Disassemble(binary, &disassembly, SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + + const std::string expected(R"(OpCapability Shader +OpMemoryModel Logical Simple +OpEntryPoint GLCompute %1 "main" +%2 = OpTypeVoid +%3 = OpTypeFunction %2 +%1 = OpFunction %3 None %2 +%4 = OpLabel +OpReturn +OpFunctionEnd +)"); + + EXPECT_THAT(disassembly, ::testing::Eq(expected)); +} + +TEST(CompactIds, HeaderIsUpdated) { + const std::string input(R"(OpCapability Shader +OpMemoryModel Logical Simple +OpEntryPoint GLCompute %100 "main" +%200 = OpTypeVoid +%300 = OpTypeFunction %200 +%100 = OpFunction %300 None %200 +%400 = OpLabel +OpReturn +OpFunctionEnd +)"); + + std::vector binary; + const spv_target_env env = SPV_ENV_UNIVERSAL_1_0; + spvtools::SpirvTools tools(env); + auto assembled = tools.Assemble( + input, &binary, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + EXPECT_TRUE(assembled); + + spvtools::Optimizer optimizer(env); + optimizer.RegisterPass(CreateCompactIdsPass()); + // The exhaustive inliner will use the result_id + optimizer.RegisterPass(CreateInlineExhaustivePass()); + + // This should not crash! + optimizer.Run(binary.data(), binary.size(), &binary); + + std::string disassembly; + tools.Disassemble(binary, &disassembly, SPV_BINARY_TO_TEXT_OPTION_NONE); + + const std::string expected(R"(; SPIR-V +; Version: 1.0 +; Generator: Khronos SPIR-V Tools Assembler; 0 +; Bound: 5 +; Schema: 0 +OpCapability Shader +OpMemoryModel Logical Simple +OpEntryPoint GLCompute %1 "main" +%2 = OpTypeVoid +%3 = OpTypeFunction %2 +%1 = OpFunction %3 None %2 +%4 = OpLabel +OpReturn +OpFunctionEnd +)"); + + EXPECT_THAT(disassembly, ::testing::Eq(expected)); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/copy_prop_array_test.cpp b/3rdparty/spirv-tools/test/opt/copy_prop_array_test.cpp new file mode 100644 index 000000000..a58acdfd7 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/copy_prop_array_test.cpp @@ -0,0 +1,1184 @@ +// Copyright (c) 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include + +#include "assembly_builder.h" +#include "pass_fixture.h" + +namespace { + +using namespace spvtools; +using ir::Instruction; +using ir::IRContext; +using opt::PassManager; + +using CopyPropArrayPassTest = PassTest<::testing::Test>; + +#ifdef SPIRV_EFFCEE +TEST_F(CopyPropArrayPassTest, BasicPropagateArray) { + const std::string before = + R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target +OpExecutionMode %main OriginUpperLeft +OpSource HLSL 600 +OpName %type_MyCBuffer "type.MyCBuffer" +OpMemberName %type_MyCBuffer 0 "Data" +OpName %MyCBuffer "MyCBuffer" +OpName %main "main" +OpName %in_var_INDEX "in.var.INDEX" +OpName %out_var_SV_Target "out.var.SV_Target" +OpDecorate %_arr_v4float_uint_8 ArrayStride 16 +OpMemberDecorate %type_MyCBuffer 0 Offset 0 +OpDecorate %type_MyCBuffer Block +OpDecorate %in_var_INDEX Flat +OpDecorate %in_var_INDEX Location 0 +OpDecorate %out_var_SV_Target Location 0 +OpDecorate %MyCBuffer DescriptorSet 0 +OpDecorate %MyCBuffer Binding 0 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%uint = OpTypeInt 32 0 +%uint_8 = OpConstant %uint 8 +%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8 +%type_MyCBuffer = OpTypeStruct %_arr_v4float_uint_8 +%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer +%void = OpTypeVoid +%13 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%_ptr_Input_int = OpTypePointer Input %int +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_arr_v4float_uint_8_0 = OpTypeArray %v4float %uint_8 +%_ptr_Function__arr_v4float_uint_8_0 = OpTypePointer Function %_arr_v4float_uint_8_0 +%int_0 = OpConstant %int 0 +%_ptr_Uniform__arr_v4float_uint_8 = OpTypePointer Uniform %_arr_v4float_uint_8 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform +%in_var_INDEX = OpVariable %_ptr_Input_int Input +%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output +; CHECK: OpFunction +; CHECK: OpLabel +; CHECK: OpVariable +; CHECK: OpAccessChain +; CHECK: [[new_address:%\w+]] = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0 +; CHECK: [[element_ptr:%\w+]] = OpAccessChain %_ptr_Uniform_v4float [[new_address]] %24 +; CHECK: [[load:%\w+]] = OpLoad %v4float [[element_ptr]] +; CHECK: OpStore %out_var_SV_Target [[load]] +%main = OpFunction %void None %13 +%22 = OpLabel +%23 = OpVariable %_ptr_Function__arr_v4float_uint_8_0 Function +%24 = OpLoad %int %in_var_INDEX +%25 = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0 +%26 = OpLoad %_arr_v4float_uint_8 %25 +%27 = OpCompositeExtract %v4float %26 0 +%28 = OpCompositeExtract %v4float %26 1 +%29 = OpCompositeExtract %v4float %26 2 +%30 = OpCompositeExtract %v4float %26 3 +%31 = OpCompositeExtract %v4float %26 4 +%32 = OpCompositeExtract %v4float %26 5 +%33 = OpCompositeExtract %v4float %26 6 +%34 = OpCompositeExtract %v4float %26 7 +%35 = OpCompositeConstruct %_arr_v4float_uint_8_0 %27 %28 %29 %30 %31 %32 %33 %34 +OpStore %23 %35 +%36 = OpAccessChain %_ptr_Function_v4float %23 %24 +%37 = OpLoad %v4float %36 +OpStore %out_var_SV_Target %37 +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER | + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES); + SinglePassRunAndMatch(before, false); +} + +TEST_F(CopyPropArrayPassTest, BasicPropagateArrayWithName) { + const std::string before = + R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target +OpExecutionMode %main OriginUpperLeft +OpSource HLSL 600 +OpName %type_MyCBuffer "type.MyCBuffer" +OpMemberName %type_MyCBuffer 0 "Data" +OpName %MyCBuffer "MyCBuffer" +OpName %main "main" +OpName %local "local" +OpName %in_var_INDEX "in.var.INDEX" +OpName %out_var_SV_Target "out.var.SV_Target" +OpDecorate %_arr_v4float_uint_8 ArrayStride 16 +OpMemberDecorate %type_MyCBuffer 0 Offset 0 +OpDecorate %type_MyCBuffer Block +OpDecorate %in_var_INDEX Flat +OpDecorate %in_var_INDEX Location 0 +OpDecorate %out_var_SV_Target Location 0 +OpDecorate %MyCBuffer DescriptorSet 0 +OpDecorate %MyCBuffer Binding 0 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%uint = OpTypeInt 32 0 +%uint_8 = OpConstant %uint 8 +%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8 +%type_MyCBuffer = OpTypeStruct %_arr_v4float_uint_8 +%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer +%void = OpTypeVoid +%13 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%_ptr_Input_int = OpTypePointer Input %int +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_arr_v4float_uint_8_0 = OpTypeArray %v4float %uint_8 +%_ptr_Function__arr_v4float_uint_8_0 = OpTypePointer Function %_arr_v4float_uint_8_0 +%int_0 = OpConstant %int 0 +%_ptr_Uniform__arr_v4float_uint_8 = OpTypePointer Uniform %_arr_v4float_uint_8 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform +%in_var_INDEX = OpVariable %_ptr_Input_int Input +%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output +; CHECK: OpFunction +; CHECK: OpLabel +; CHECK: OpVariable +; CHECK: OpAccessChain +; CHECK: [[new_address:%\w+]] = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0 +; CHECK: [[element_ptr:%\w+]] = OpAccessChain %_ptr_Uniform_v4float [[new_address]] %24 +; CHECK: [[load:%\w+]] = OpLoad %v4float [[element_ptr]] +; CHECK: OpStore %out_var_SV_Target [[load]] +%main = OpFunction %void None %13 +%22 = OpLabel +%local = OpVariable %_ptr_Function__arr_v4float_uint_8_0 Function +%24 = OpLoad %int %in_var_INDEX +%25 = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0 +%26 = OpLoad %_arr_v4float_uint_8 %25 +%27 = OpCompositeExtract %v4float %26 0 +%28 = OpCompositeExtract %v4float %26 1 +%29 = OpCompositeExtract %v4float %26 2 +%30 = OpCompositeExtract %v4float %26 3 +%31 = OpCompositeExtract %v4float %26 4 +%32 = OpCompositeExtract %v4float %26 5 +%33 = OpCompositeExtract %v4float %26 6 +%34 = OpCompositeExtract %v4float %26 7 +%35 = OpCompositeConstruct %_arr_v4float_uint_8_0 %27 %28 %29 %30 %31 %32 %33 %34 +OpStore %local %35 +%36 = OpAccessChain %_ptr_Function_v4float %local %24 +%37 = OpLoad %v4float %36 +OpStore %out_var_SV_Target %37 +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER | + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES); + SinglePassRunAndMatch(before, false); +} + +// Propagate 2d array. This test identifing a copy through multiple levels. +// Also has to traverse multiple OpAccessChains. +TEST_F(CopyPropArrayPassTest, Propagate2DArray) { + const std::string text = + R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target +OpExecutionMode %main OriginUpperLeft +OpSource HLSL 600 +OpName %type_MyCBuffer "type.MyCBuffer" +OpMemberName %type_MyCBuffer 0 "Data" +OpName %MyCBuffer "MyCBuffer" +OpName %main "main" +OpName %in_var_INDEX "in.var.INDEX" +OpName %out_var_SV_Target "out.var.SV_Target" +OpDecorate %_arr_v4float_uint_2 ArrayStride 16 +OpDecorate %_arr__arr_v4float_uint_2_uint_2 ArrayStride 32 +OpMemberDecorate %type_MyCBuffer 0 Offset 0 +OpDecorate %type_MyCBuffer Block +OpDecorate %in_var_INDEX Flat +OpDecorate %in_var_INDEX Location 0 +OpDecorate %out_var_SV_Target Location 0 +OpDecorate %MyCBuffer DescriptorSet 0 +OpDecorate %MyCBuffer Binding 0 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%uint = OpTypeInt 32 0 +%uint_2 = OpConstant %uint 2 +%_arr_v4float_uint_2 = OpTypeArray %v4float %uint_2 +%_arr__arr_v4float_uint_2_uint_2 = OpTypeArray %_arr_v4float_uint_2 %uint_2 +%type_MyCBuffer = OpTypeStruct %_arr__arr_v4float_uint_2_uint_2 +%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer +%void = OpTypeVoid +%14 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%_ptr_Input_int = OpTypePointer Input %int +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_arr_v4float_uint_2_0 = OpTypeArray %v4float %uint_2 +%_arr__arr_v4float_uint_2_0_uint_2 = OpTypeArray %_arr_v4float_uint_2_0 %uint_2 +%_ptr_Function__arr__arr_v4float_uint_2_0_uint_2 = OpTypePointer Function %_arr__arr_v4float_uint_2_0_uint_2 +%int_0 = OpConstant %int 0 +%_ptr_Uniform__arr__arr_v4float_uint_2_uint_2 = OpTypePointer Uniform %_arr__arr_v4float_uint_2_uint_2 +%_ptr_Function__arr_v4float_uint_2_0 = OpTypePointer Function %_arr_v4float_uint_2_0 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform +%in_var_INDEX = OpVariable %_ptr_Input_int Input +%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output +; CHECK: OpFunction +; CHECK: OpLabel +; CHECK: OpVariable +; CHECK: OpVariable +; CHECK: OpAccessChain +; CHECK: [[new_address:%\w+]] = OpAccessChain %_ptr_Uniform__arr__arr_v4float_uint_2_uint_2 %MyCBuffer %int_0 +%main = OpFunction %void None %14 +%25 = OpLabel +%26 = OpVariable %_ptr_Function__arr_v4float_uint_2_0 Function +%27 = OpVariable %_ptr_Function__arr__arr_v4float_uint_2_0_uint_2 Function +%28 = OpLoad %int %in_var_INDEX +%29 = OpAccessChain %_ptr_Uniform__arr__arr_v4float_uint_2_uint_2 %MyCBuffer %int_0 +%30 = OpLoad %_arr__arr_v4float_uint_2_uint_2 %29 +%31 = OpCompositeExtract %_arr_v4float_uint_2 %30 0 +%32 = OpCompositeExtract %v4float %31 0 +%33 = OpCompositeExtract %v4float %31 1 +%34 = OpCompositeConstruct %_arr_v4float_uint_2_0 %32 %33 +%35 = OpCompositeExtract %_arr_v4float_uint_2 %30 1 +%36 = OpCompositeExtract %v4float %35 0 +%37 = OpCompositeExtract %v4float %35 1 +%38 = OpCompositeConstruct %_arr_v4float_uint_2_0 %36 %37 +%39 = OpCompositeConstruct %_arr__arr_v4float_uint_2_0_uint_2 %34 %38 +; CHECK: OpStore +OpStore %27 %39 +%40 = OpAccessChain %_ptr_Function__arr_v4float_uint_2_0 %27 %28 +%42 = OpAccessChain %_ptr_Function_v4float %40 %28 +%43 = OpLoad %v4float %42 +; CHECK: [[ac1:%\w+]] = OpAccessChain %_ptr_Uniform__arr_v4float_uint_2 [[new_address]] %28 +; CHECK: [[ac2:%\w+]] = OpAccessChain %_ptr_Uniform_v4float [[ac1]] %28 +; CHECK: [[load:%\w+]] = OpLoad %v4float [[ac2]] +; CHECK: OpStore %out_var_SV_Target [[load]] +OpStore %out_var_SV_Target %43 +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER | + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES); + SinglePassRunAndMatch(text, false); +} + +// Test decomposing an object when we need to "rewrite" a store. +TEST_F(CopyPropArrayPassTest, DecomposeObjectForArrayStore) { + const std::string text = + R"( OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target + OpExecutionMode %main OriginUpperLeft + OpSource HLSL 600 + OpName %type_MyCBuffer "type.MyCBuffer" + OpMemberName %type_MyCBuffer 0 "Data" + OpName %MyCBuffer "MyCBuffer" + OpName %main "main" + OpName %in_var_INDEX "in.var.INDEX" + OpName %out_var_SV_Target "out.var.SV_Target" + OpDecorate %_arr_v4float_uint_2 ArrayStride 16 + OpDecorate %_arr__arr_v4float_uint_2_uint_2 ArrayStride 32 + OpMemberDecorate %type_MyCBuffer 0 Offset 0 + OpDecorate %type_MyCBuffer Block + OpDecorate %in_var_INDEX Flat + OpDecorate %in_var_INDEX Location 0 + OpDecorate %out_var_SV_Target Location 0 + OpDecorate %MyCBuffer DescriptorSet 0 + OpDecorate %MyCBuffer Binding 0 + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 + %uint = OpTypeInt 32 0 + %uint_2 = OpConstant %uint 2 +%_arr_v4float_uint_2 = OpTypeArray %v4float %uint_2 +%_arr__arr_v4float_uint_2_uint_2 = OpTypeArray %_arr_v4float_uint_2 %uint_2 +%type_MyCBuffer = OpTypeStruct %_arr__arr_v4float_uint_2_uint_2 +%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer + %void = OpTypeVoid + %14 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Input_int = OpTypePointer Input %int +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_arr_v4float_uint_2_0 = OpTypeArray %v4float %uint_2 +%_arr__arr_v4float_uint_2_0_uint_2 = OpTypeArray %_arr_v4float_uint_2_0 %uint_2 +%_ptr_Function__arr__arr_v4float_uint_2_0_uint_2 = OpTypePointer Function %_arr__arr_v4float_uint_2_0_uint_2 + %int_0 = OpConstant %int 0 +%_ptr_Uniform__arr__arr_v4float_uint_2_uint_2 = OpTypePointer Uniform %_arr__arr_v4float_uint_2_uint_2 +%_ptr_Function__arr_v4float_uint_2_0 = OpTypePointer Function %_arr_v4float_uint_2_0 +%_ptr_Function_v4float = OpTypePointer Function %v4float + %MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform +%in_var_INDEX = OpVariable %_ptr_Input_int Input +%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output + %main = OpFunction %void None %14 + %25 = OpLabel + %26 = OpVariable %_ptr_Function__arr_v4float_uint_2_0 Function + %27 = OpVariable %_ptr_Function__arr__arr_v4float_uint_2_0_uint_2 Function + %28 = OpLoad %int %in_var_INDEX + %29 = OpAccessChain %_ptr_Uniform__arr__arr_v4float_uint_2_uint_2 %MyCBuffer %int_0 + %30 = OpLoad %_arr__arr_v4float_uint_2_uint_2 %29 + %31 = OpCompositeExtract %_arr_v4float_uint_2 %30 0 + %32 = OpCompositeExtract %v4float %31 0 + %33 = OpCompositeExtract %v4float %31 1 + %34 = OpCompositeConstruct %_arr_v4float_uint_2_0 %32 %33 + %35 = OpCompositeExtract %_arr_v4float_uint_2 %30 1 + %36 = OpCompositeExtract %v4float %35 0 + %37 = OpCompositeExtract %v4float %35 1 + %38 = OpCompositeConstruct %_arr_v4float_uint_2_0 %36 %37 + %39 = OpCompositeConstruct %_arr__arr_v4float_uint_2_0_uint_2 %34 %38 + OpStore %27 %39 +; CHECK: [[access_chain:%\w+]] = OpAccessChain %_ptr_Uniform__arr_v4float_uint_2 + %40 = OpAccessChain %_ptr_Function__arr_v4float_uint_2_0 %27 %28 +; CHECK: [[load:%\w+]] = OpLoad %_arr_v4float_uint_2 [[access_chain]] + %41 = OpLoad %_arr_v4float_uint_2_0 %40 +; CHECK: [[extract1:%\w+]] = OpCompositeExtract %v4float [[load]] 0 +; CHECK: [[extract2:%\w+]] = OpCompositeExtract %v4float [[load]] 1 +; CHECK: [[construct:%\w+]] = OpCompositeConstruct %_arr_v4float_uint_2_0 [[extract1]] [[extract2]] +; CHEKC: OpStore %26 [[construct]] + OpStore %26 %41 + %42 = OpAccessChain %_ptr_Function_v4float %26 %28 + %43 = OpLoad %v4float %42 + OpStore %out_var_SV_Target %43 + OpReturn + OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER | + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES); + SinglePassRunAndMatch(text, false); +} + +// Test decomposing an object when we need to "rewrite" a store. +TEST_F(CopyPropArrayPassTest, DecomposeObjectForStructStore) { + const std::string text = + R"( OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target + OpExecutionMode %main OriginUpperLeft + OpSource HLSL 600 + OpName %type_MyCBuffer "type.MyCBuffer" + OpMemberName %type_MyCBuffer 0 "Data" + OpName %MyCBuffer "MyCBuffer" + OpName %main "main" + OpName %in_var_INDEX "in.var.INDEX" + OpName %out_var_SV_Target "out.var.SV_Target" + OpMemberDecorate %type_MyCBuffer 0 Offset 0 + OpDecorate %type_MyCBuffer Block + OpDecorate %in_var_INDEX Flat + OpDecorate %in_var_INDEX Location 0 + OpDecorate %out_var_SV_Target Location 0 + OpDecorate %MyCBuffer DescriptorSet 0 + OpDecorate %MyCBuffer Binding 0 +; CHECK: OpDecorate [[decorated_type:%\w+]] GLSLPacked + OpDecorate %struct GLSLPacked + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 + %uint = OpTypeInt 32 0 + %uint_2 = OpConstant %uint 2 +; CHECK: [[decorated_type]] = OpTypeStruct +%struct = OpTypeStruct %float %uint +%_arr_struct_uint_2 = OpTypeArray %struct %uint_2 +%type_MyCBuffer = OpTypeStruct %_arr_struct_uint_2 +%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer + %void = OpTypeVoid + %14 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Input_int = OpTypePointer Input %int +%_ptr_Output_v4float = OpTypePointer Output %v4float +; CHECK: [[struct:%\w+]] = OpTypeStruct %float %uint +%struct_0 = OpTypeStruct %float %uint +%_arr_struct_0_uint_2 = OpTypeArray %struct_0 %uint_2 +%_ptr_Function__arr_struct_0_uint_2 = OpTypePointer Function %_arr_struct_0_uint_2 + %int_0 = OpConstant %int 0 +%_ptr_Uniform__arr_struct_uint_2 = OpTypePointer Uniform %_arr_struct_uint_2 +; CHECK: [[decorated_ptr:%\w+]] = OpTypePointer Uniform [[decorated_type]] +%_ptr_Function_struct_0 = OpTypePointer Function %struct_0 +%_ptr_Function_v4float = OpTypePointer Function %v4float + %MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform +%in_var_INDEX = OpVariable %_ptr_Input_int Input +%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output + %main = OpFunction %void None %14 + %25 = OpLabel + %26 = OpVariable %_ptr_Function_struct_0 Function + %27 = OpVariable %_ptr_Function__arr_struct_0_uint_2 Function + %28 = OpLoad %int %in_var_INDEX + %29 = OpAccessChain %_ptr_Uniform__arr_struct_uint_2 %MyCBuffer %int_0 + %30 = OpLoad %_arr_struct_uint_2 %29 + %31 = OpCompositeExtract %struct %30 0 + %32 = OpCompositeExtract %v4float %31 0 + %33 = OpCompositeExtract %v4float %31 1 + %34 = OpCompositeConstruct %struct_0 %32 %33 + %35 = OpCompositeExtract %struct %30 1 + %36 = OpCompositeExtract %float %35 0 + %37 = OpCompositeExtract %uint %35 1 + %38 = OpCompositeConstruct %struct_0 %36 %37 + %39 = OpCompositeConstruct %_arr_struct_0_uint_2 %34 %38 + OpStore %27 %39 +; CHECK: [[access_chain:%\w+]] = OpAccessChain [[decorated_ptr]] + %40 = OpAccessChain %_ptr_Function_struct_0 %27 %28 +; CHECK: [[load:%\w+]] = OpLoad [[decorated_type]] [[access_chain]] + %41 = OpLoad %struct_0 %40 +; CHECK: [[extract1:%\w+]] = OpCompositeExtract %float [[load]] 0 +; CHECK: [[extract2:%\w+]] = OpCompositeExtract %uint [[load]] 1 +; CHECK: [[construct:%\w+]] = OpCompositeConstruct [[struct]] [[extract1]] [[extract2]] +; CHEKC: OpStore %26 [[construct]] + OpStore %26 %41 + %42 = OpAccessChain %_ptr_Function_v4float %26 %28 + %43 = OpLoad %v4float %42 + OpStore %out_var_SV_Target %43 + OpReturn + OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER | + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES); + SinglePassRunAndMatch(text, false); +} + +TEST_F(CopyPropArrayPassTest, CopyViaInserts) { + const std::string before = + R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target +OpExecutionMode %main OriginUpperLeft +OpSource HLSL 600 +OpName %type_MyCBuffer "type.MyCBuffer" +OpMemberName %type_MyCBuffer 0 "Data" +OpName %MyCBuffer "MyCBuffer" +OpName %main "main" +OpName %in_var_INDEX "in.var.INDEX" +OpName %out_var_SV_Target "out.var.SV_Target" +OpDecorate %_arr_v4float_uint_8 ArrayStride 16 +OpMemberDecorate %type_MyCBuffer 0 Offset 0 +OpDecorate %type_MyCBuffer Block +OpDecorate %in_var_INDEX Flat +OpDecorate %in_var_INDEX Location 0 +OpDecorate %out_var_SV_Target Location 0 +OpDecorate %MyCBuffer DescriptorSet 0 +OpDecorate %MyCBuffer Binding 0 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%uint = OpTypeInt 32 0 +%uint_8 = OpConstant %uint 8 +%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8 +%type_MyCBuffer = OpTypeStruct %_arr_v4float_uint_8 +%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer +%void = OpTypeVoid +%13 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%_ptr_Input_int = OpTypePointer Input %int +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_arr_v4float_uint_8_0 = OpTypeArray %v4float %uint_8 +%_ptr_Function__arr_v4float_uint_8_0 = OpTypePointer Function %_arr_v4float_uint_8_0 +%int_0 = OpConstant %int 0 +%_ptr_Uniform__arr_v4float_uint_8 = OpTypePointer Uniform %_arr_v4float_uint_8 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform +%in_var_INDEX = OpVariable %_ptr_Input_int Input +%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output +; CHECK: OpFunction +; CHECK: OpLabel +; CHECK: OpVariable +; CHECK: OpAccessChain +; CHECK: [[new_address:%\w+]] = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0 +; CHECK: [[element_ptr:%\w+]] = OpAccessChain %_ptr_Uniform_v4float [[new_address]] %24 +; CHECK: [[load:%\w+]] = OpLoad %v4float [[element_ptr]] +; CHECK: OpStore %out_var_SV_Target [[load]] +%main = OpFunction %void None %13 +%22 = OpLabel +%23 = OpVariable %_ptr_Function__arr_v4float_uint_8_0 Function +%undef = OpUndef %_arr_v4float_uint_8_0 +%24 = OpLoad %int %in_var_INDEX +%25 = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0 +%26 = OpLoad %_arr_v4float_uint_8 %25 +%27 = OpCompositeExtract %v4float %26 0 +%i0 = OpCompositeInsert %_arr_v4float_uint_8_0 %27 %undef 0 +%28 = OpCompositeExtract %v4float %26 1 +%i1 = OpCompositeInsert %_arr_v4float_uint_8_0 %28 %i0 1 +%29 = OpCompositeExtract %v4float %26 2 +%i2 = OpCompositeInsert %_arr_v4float_uint_8_0 %29 %i1 2 +%30 = OpCompositeExtract %v4float %26 3 +%i3 = OpCompositeInsert %_arr_v4float_uint_8_0 %30 %i2 3 +%31 = OpCompositeExtract %v4float %26 4 +%i4 = OpCompositeInsert %_arr_v4float_uint_8_0 %31 %i3 4 +%32 = OpCompositeExtract %v4float %26 5 +%i5 = OpCompositeInsert %_arr_v4float_uint_8_0 %32 %i4 5 +%33 = OpCompositeExtract %v4float %26 6 +%i6 = OpCompositeInsert %_arr_v4float_uint_8_0 %33 %i5 6 +%34 = OpCompositeExtract %v4float %26 7 +%i7 = OpCompositeInsert %_arr_v4float_uint_8_0 %34 %i6 7 +OpStore %23 %i7 +%36 = OpAccessChain %_ptr_Function_v4float %23 %24 +%37 = OpLoad %v4float %36 +OpStore %out_var_SV_Target %37 +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER | + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES); + SinglePassRunAndMatch(before, false); +} +#endif // SPIRV_EFFCEE + +// This test will place a load before the store. We cannot propagate in this +// case. +TEST_F(CopyPropArrayPassTest, LoadBeforeStore) { + const std::string text = + R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target +OpExecutionMode %main OriginUpperLeft +OpSource HLSL 600 +OpName %type_MyCBuffer "type.MyCBuffer" +OpMemberName %type_MyCBuffer 0 "Data" +OpName %MyCBuffer "MyCBuffer" +OpName %main "main" +OpName %in_var_INDEX "in.var.INDEX" +OpName %out_var_SV_Target "out.var.SV_Target" +OpDecorate %_arr_v4float_uint_8 ArrayStride 16 +OpMemberDecorate %type_MyCBuffer 0 Offset 0 +OpDecorate %type_MyCBuffer Block +OpDecorate %in_var_INDEX Flat +OpDecorate %in_var_INDEX Location 0 +OpDecorate %out_var_SV_Target Location 0 +OpDecorate %MyCBuffer DescriptorSet 0 +OpDecorate %MyCBuffer Binding 0 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%uint = OpTypeInt 32 0 +%uint_8 = OpConstant %uint 8 +%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8 +%type_MyCBuffer = OpTypeStruct %_arr_v4float_uint_8 +%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer +%void = OpTypeVoid +%13 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%_ptr_Input_int = OpTypePointer Input %int +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_arr_v4float_uint_8_0 = OpTypeArray %v4float %uint_8 +%_ptr_Function__arr_v4float_uint_8_0 = OpTypePointer Function %_arr_v4float_uint_8_0 +%int_0 = OpConstant %int 0 +%_ptr_Uniform__arr_v4float_uint_8 = OpTypePointer Uniform %_arr_v4float_uint_8 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform +%in_var_INDEX = OpVariable %_ptr_Input_int Input +%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %13 +%22 = OpLabel +%23 = OpVariable %_ptr_Function__arr_v4float_uint_8_0 Function +%38 = OpAccessChain %_ptr_Function_v4float %23 %24 +%39 = OpLoad %v4float %36 +%24 = OpLoad %int %in_var_INDEX +%25 = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0 +%26 = OpLoad %_arr_v4float_uint_8 %25 +%27 = OpCompositeExtract %v4float %26 0 +%28 = OpCompositeExtract %v4float %26 1 +%29 = OpCompositeExtract %v4float %26 2 +%30 = OpCompositeExtract %v4float %26 3 +%31 = OpCompositeExtract %v4float %26 4 +%32 = OpCompositeExtract %v4float %26 5 +%33 = OpCompositeExtract %v4float %26 6 +%34 = OpCompositeExtract %v4float %26 7 +%35 = OpCompositeConstruct %_arr_v4float_uint_8_0 %27 %28 %29 %30 %31 %32 %33 %34 +OpStore %23 %35 +%36 = OpAccessChain %_ptr_Function_v4float %23 %24 +%37 = OpLoad %v4float %36 +OpStore %out_var_SV_Target %37 +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER | + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES); + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ false); + + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +// This test will place a load where it is not dominated by the store. We +// cannot propagate in this case. +TEST_F(CopyPropArrayPassTest, LoadNotDominated) { + const std::string text = + R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target +OpExecutionMode %main OriginUpperLeft +OpSource HLSL 600 +OpName %type_MyCBuffer "type.MyCBuffer" +OpMemberName %type_MyCBuffer 0 "Data" +OpName %MyCBuffer "MyCBuffer" +OpName %main "main" +OpName %in_var_INDEX "in.var.INDEX" +OpName %out_var_SV_Target "out.var.SV_Target" +OpDecorate %_arr_v4float_uint_8 ArrayStride 16 +OpMemberDecorate %type_MyCBuffer 0 Offset 0 +OpDecorate %type_MyCBuffer Block +OpDecorate %in_var_INDEX Flat +OpDecorate %in_var_INDEX Location 0 +OpDecorate %out_var_SV_Target Location 0 +OpDecorate %MyCBuffer DescriptorSet 0 +OpDecorate %MyCBuffer Binding 0 +%bool = OpTypeBool +%true = OpConstantTrue %bool +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%uint = OpTypeInt 32 0 +%uint_8 = OpConstant %uint 8 +%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8 +%type_MyCBuffer = OpTypeStruct %_arr_v4float_uint_8 +%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer +%void = OpTypeVoid +%13 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%_ptr_Input_int = OpTypePointer Input %int +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_arr_v4float_uint_8_0 = OpTypeArray %v4float %uint_8 +%_ptr_Function__arr_v4float_uint_8_0 = OpTypePointer Function %_arr_v4float_uint_8_0 +%int_0 = OpConstant %int 0 +%_ptr_Uniform__arr_v4float_uint_8 = OpTypePointer Uniform %_arr_v4float_uint_8 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform +%in_var_INDEX = OpVariable %_ptr_Input_int Input +%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %13 +%22 = OpLabel +%23 = OpVariable %_ptr_Function__arr_v4float_uint_8_0 Function +OpSelectionMerge %merge None +OpBranchConditional %true %if %else +%if = OpLabel +%24 = OpLoad %int %in_var_INDEX +%25 = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0 +%26 = OpLoad %_arr_v4float_uint_8 %25 +%27 = OpCompositeExtract %v4float %26 0 +%28 = OpCompositeExtract %v4float %26 1 +%29 = OpCompositeExtract %v4float %26 2 +%30 = OpCompositeExtract %v4float %26 3 +%31 = OpCompositeExtract %v4float %26 4 +%32 = OpCompositeExtract %v4float %26 5 +%33 = OpCompositeExtract %v4float %26 6 +%34 = OpCompositeExtract %v4float %26 7 +%35 = OpCompositeConstruct %_arr_v4float_uint_8_0 %27 %28 %29 %30 %31 %32 %33 %34 +OpStore %23 %35 +%38 = OpAccessChain %_ptr_Function_v4float %23 %24 +%39 = OpLoad %v4float %36 +OpBranch %merge +%else = OpLabel +%36 = OpAccessChain %_ptr_Function_v4float %23 %24 +%37 = OpLoad %v4float %36 +OpBranch %merge +%merge = OpLabel +%phi = OpPhi %out_var_SV_Target %39 %if %37 %else +OpStore %out_var_SV_Target %phi +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER | + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES); + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ false); + + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +// This test has a partial store to the variable. We cannot propagate in this +// case. +TEST_F(CopyPropArrayPassTest, PartialStore) { + const std::string text = + R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target +OpExecutionMode %main OriginUpperLeft +OpSource HLSL 600 +OpName %type_MyCBuffer "type.MyCBuffer" +OpMemberName %type_MyCBuffer 0 "Data" +OpName %MyCBuffer "MyCBuffer" +OpName %main "main" +OpName %in_var_INDEX "in.var.INDEX" +OpName %out_var_SV_Target "out.var.SV_Target" +OpDecorate %_arr_v4float_uint_8 ArrayStride 16 +OpMemberDecorate %type_MyCBuffer 0 Offset 0 +OpDecorate %type_MyCBuffer Block +OpDecorate %in_var_INDEX Flat +OpDecorate %in_var_INDEX Location 0 +OpDecorate %out_var_SV_Target Location 0 +OpDecorate %MyCBuffer DescriptorSet 0 +OpDecorate %MyCBuffer Binding 0 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%uint = OpTypeInt 32 0 +%uint_8 = OpConstant %uint 8 +%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8 +%type_MyCBuffer = OpTypeStruct %_arr_v4float_uint_8 +%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer +%void = OpTypeVoid +%13 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%_ptr_Input_int = OpTypePointer Input %int +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_arr_v4float_uint_8_0 = OpTypeArray %v4float %uint_8 +%_ptr_Function__arr_v4float_uint_8_0 = OpTypePointer Function %_arr_v4float_uint_8_0 +%int_0 = OpConstant %int 0 +%f0 = OpConstant %float 0 +%v4const = OpConstantComposite %v4float %f0 %f0 %f0 %f0 +%_ptr_Uniform__arr_v4float_uint_8 = OpTypePointer Uniform %_arr_v4float_uint_8 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform +%in_var_INDEX = OpVariable %_ptr_Input_int Input +%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %13 +%22 = OpLabel +%23 = OpVariable %_ptr_Function__arr_v4float_uint_8_0 Function +%24 = OpLoad %int %in_var_INDEX +%25 = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0 +%26 = OpLoad %_arr_v4float_uint_8 %25 +%27 = OpCompositeExtract %v4float %26 0 +%28 = OpCompositeExtract %v4float %26 1 +%29 = OpCompositeExtract %v4float %26 2 +%30 = OpCompositeExtract %v4float %26 3 +%31 = OpCompositeExtract %v4float %26 4 +%32 = OpCompositeExtract %v4float %26 5 +%33 = OpCompositeExtract %v4float %26 6 +%34 = OpCompositeExtract %v4float %26 7 +%35 = OpCompositeConstruct %_arr_v4float_uint_8_0 %27 %28 %29 %30 %31 %32 %33 %34 +OpStore %23 %35 +%36 = OpAccessChain %_ptr_Function_v4float %23 %24 +%37 = OpLoad %v4float %36 +%39 = OpStore %36 %v4const +OpStore %out_var_SV_Target %37 +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER | + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES); + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ false); + + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +// This test does not have a proper copy of an object. We cannot propagate in +// this case. +TEST_F(CopyPropArrayPassTest, NotACopy) { + const std::string text = + R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target +OpExecutionMode %main OriginUpperLeft +OpSource HLSL 600 +OpName %type_MyCBuffer "type.MyCBuffer" +OpMemberName %type_MyCBuffer 0 "Data" +OpName %MyCBuffer "MyCBuffer" +OpName %main "main" +OpName %in_var_INDEX "in.var.INDEX" +OpName %out_var_SV_Target "out.var.SV_Target" +OpDecorate %_arr_v4float_uint_8 ArrayStride 16 +OpMemberDecorate %type_MyCBuffer 0 Offset 0 +OpDecorate %type_MyCBuffer Block +OpDecorate %in_var_INDEX Flat +OpDecorate %in_var_INDEX Location 0 +OpDecorate %out_var_SV_Target Location 0 +OpDecorate %MyCBuffer DescriptorSet 0 +OpDecorate %MyCBuffer Binding 0 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%uint = OpTypeInt 32 0 +%uint_8 = OpConstant %uint 8 +%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8 +%type_MyCBuffer = OpTypeStruct %_arr_v4float_uint_8 +%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer +%void = OpTypeVoid +%13 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%_ptr_Input_int = OpTypePointer Input %int +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_arr_v4float_uint_8_0 = OpTypeArray %v4float %uint_8 +%_ptr_Function__arr_v4float_uint_8_0 = OpTypePointer Function %_arr_v4float_uint_8_0 +%int_0 = OpConstant %int 0 +%f0 = OpConstant %float 0 +%v4const = OpConstantComposite %v4float %f0 %f0 %f0 %f0 +%_ptr_Uniform__arr_v4float_uint_8 = OpTypePointer Uniform %_arr_v4float_uint_8 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform +%in_var_INDEX = OpVariable %_ptr_Input_int Input +%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %13 +%22 = OpLabel +%23 = OpVariable %_ptr_Function__arr_v4float_uint_8_0 Function +%24 = OpLoad %int %in_var_INDEX +%25 = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0 +%26 = OpLoad %_arr_v4float_uint_8 %25 +%27 = OpCompositeExtract %v4float %26 0 +%28 = OpCompositeExtract %v4float %26 0 +%29 = OpCompositeExtract %v4float %26 2 +%30 = OpCompositeExtract %v4float %26 3 +%31 = OpCompositeExtract %v4float %26 4 +%32 = OpCompositeExtract %v4float %26 5 +%33 = OpCompositeExtract %v4float %26 6 +%34 = OpCompositeExtract %v4float %26 7 +%35 = OpCompositeConstruct %_arr_v4float_uint_8_0 %27 %28 %29 %30 %31 %32 %33 %34 +OpStore %23 %35 +%36 = OpAccessChain %_ptr_Function_v4float %23 %24 +%37 = OpLoad %v4float %36 +OpStore %out_var_SV_Target %37 +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER | + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES); + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ false); + + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +TEST_F(CopyPropArrayPassTest, BadCopyViaInserts1) { + const std::string text = + R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target +OpExecutionMode %main OriginUpperLeft +OpSource HLSL 600 +OpName %type_MyCBuffer "type.MyCBuffer" +OpMemberName %type_MyCBuffer 0 "Data" +OpName %MyCBuffer "MyCBuffer" +OpName %main "main" +OpName %in_var_INDEX "in.var.INDEX" +OpName %out_var_SV_Target "out.var.SV_Target" +OpDecorate %_arr_v4float_uint_8 ArrayStride 16 +OpMemberDecorate %type_MyCBuffer 0 Offset 0 +OpDecorate %type_MyCBuffer Block +OpDecorate %in_var_INDEX Flat +OpDecorate %in_var_INDEX Location 0 +OpDecorate %out_var_SV_Target Location 0 +OpDecorate %MyCBuffer DescriptorSet 0 +OpDecorate %MyCBuffer Binding 0 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%uint = OpTypeInt 32 0 +%uint_8 = OpConstant %uint 8 +%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8 +%type_MyCBuffer = OpTypeStruct %_arr_v4float_uint_8 +%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer +%void = OpTypeVoid +%13 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%_ptr_Input_int = OpTypePointer Input %int +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_arr_v4float_uint_8_0 = OpTypeArray %v4float %uint_8 +%_ptr_Function__arr_v4float_uint_8_0 = OpTypePointer Function %_arr_v4float_uint_8_0 +%int_0 = OpConstant %int 0 +%_ptr_Uniform__arr_v4float_uint_8 = OpTypePointer Uniform %_arr_v4float_uint_8 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform +%in_var_INDEX = OpVariable %_ptr_Input_int Input +%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %13 +%22 = OpLabel +%23 = OpVariable %_ptr_Function__arr_v4float_uint_8_0 Function +%undef = OpUndef %_arr_v4float_uint_8_0 +%24 = OpLoad %int %in_var_INDEX +%25 = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0 +%26 = OpLoad %_arr_v4float_uint_8 %25 +%27 = OpCompositeExtract %v4float %26 0 +%i0 = OpCompositeInsert %_arr_v4float_uint_8_0 %27 %undef 0 +%28 = OpCompositeExtract %v4float %26 1 +%i1 = OpCompositeInsert %_arr_v4float_uint_8_0 %28 %i0 1 +%29 = OpCompositeExtract %v4float %26 2 +%i2 = OpCompositeInsert %_arr_v4float_uint_8_0 %29 %i1 3 +%30 = OpCompositeExtract %v4float %26 3 +%i3 = OpCompositeInsert %_arr_v4float_uint_8_0 %30 %i2 3 +%31 = OpCompositeExtract %v4float %26 4 +%i4 = OpCompositeInsert %_arr_v4float_uint_8_0 %31 %i3 4 +%32 = OpCompositeExtract %v4float %26 5 +%i5 = OpCompositeInsert %_arr_v4float_uint_8_0 %32 %i4 5 +%33 = OpCompositeExtract %v4float %26 6 +%i6 = OpCompositeInsert %_arr_v4float_uint_8_0 %33 %i5 6 +%34 = OpCompositeExtract %v4float %26 7 +%i7 = OpCompositeInsert %_arr_v4float_uint_8_0 %34 %i6 7 +OpStore %23 %i7 +%36 = OpAccessChain %_ptr_Function_v4float %23 %24 +%37 = OpLoad %v4float %36 +OpStore %out_var_SV_Target %37 +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER | + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES); + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ false); + + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +TEST_F(CopyPropArrayPassTest, BadCopyViaInserts2) { + const std::string text = + R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target +OpExecutionMode %main OriginUpperLeft +OpSource HLSL 600 +OpName %type_MyCBuffer "type.MyCBuffer" +OpMemberName %type_MyCBuffer 0 "Data" +OpName %MyCBuffer "MyCBuffer" +OpName %main "main" +OpName %in_var_INDEX "in.var.INDEX" +OpName %out_var_SV_Target "out.var.SV_Target" +OpDecorate %_arr_v4float_uint_8 ArrayStride 16 +OpMemberDecorate %type_MyCBuffer 0 Offset 0 +OpDecorate %type_MyCBuffer Block +OpDecorate %in_var_INDEX Flat +OpDecorate %in_var_INDEX Location 0 +OpDecorate %out_var_SV_Target Location 0 +OpDecorate %MyCBuffer DescriptorSet 0 +OpDecorate %MyCBuffer Binding 0 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%uint = OpTypeInt 32 0 +%uint_8 = OpConstant %uint 8 +%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8 +%type_MyCBuffer = OpTypeStruct %_arr_v4float_uint_8 +%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer +%void = OpTypeVoid +%13 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%_ptr_Input_int = OpTypePointer Input %int +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_arr_v4float_uint_8_0 = OpTypeArray %v4float %uint_8 +%_ptr_Function__arr_v4float_uint_8_0 = OpTypePointer Function %_arr_v4float_uint_8_0 +%int_0 = OpConstant %int 0 +%_ptr_Uniform__arr_v4float_uint_8 = OpTypePointer Uniform %_arr_v4float_uint_8 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform +%in_var_INDEX = OpVariable %_ptr_Input_int Input +%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %13 +%22 = OpLabel +%23 = OpVariable %_ptr_Function__arr_v4float_uint_8_0 Function +%undef = OpUndef %_arr_v4float_uint_8_0 +%24 = OpLoad %int %in_var_INDEX +%25 = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0 +%26 = OpLoad %_arr_v4float_uint_8 %25 +%27 = OpCompositeExtract %v4float %26 0 +%i0 = OpCompositeInsert %_arr_v4float_uint_8_0 %27 %undef 0 +%28 = OpCompositeExtract %v4float %26 1 +%i1 = OpCompositeInsert %_arr_v4float_uint_8_0 %28 %i0 1 +%29 = OpCompositeExtract %v4float %26 3 +%i2 = OpCompositeInsert %_arr_v4float_uint_8_0 %29 %i1 2 +%30 = OpCompositeExtract %v4float %26 3 +%i3 = OpCompositeInsert %_arr_v4float_uint_8_0 %30 %i2 3 +%31 = OpCompositeExtract %v4float %26 4 +%i4 = OpCompositeInsert %_arr_v4float_uint_8_0 %31 %i3 4 +%32 = OpCompositeExtract %v4float %26 5 +%i5 = OpCompositeInsert %_arr_v4float_uint_8_0 %32 %i4 5 +%33 = OpCompositeExtract %v4float %26 6 +%i6 = OpCompositeInsert %_arr_v4float_uint_8_0 %33 %i5 6 +%34 = OpCompositeExtract %v4float %26 7 +%i7 = OpCompositeInsert %_arr_v4float_uint_8_0 %34 %i6 7 +OpStore %23 %i7 +%36 = OpAccessChain %_ptr_Function_v4float %23 %24 +%37 = OpLoad %v4float %36 +OpStore %out_var_SV_Target %37 +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER | + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES); + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ false); + + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +TEST_F(CopyPropArrayPassTest, BadCopyViaInserts3) { + const std::string text = + R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target +OpExecutionMode %main OriginUpperLeft +OpSource HLSL 600 +OpName %type_MyCBuffer "type.MyCBuffer" +OpMemberName %type_MyCBuffer 0 "Data" +OpName %MyCBuffer "MyCBuffer" +OpName %main "main" +OpName %in_var_INDEX "in.var.INDEX" +OpName %out_var_SV_Target "out.var.SV_Target" +OpDecorate %_arr_v4float_uint_8 ArrayStride 16 +OpMemberDecorate %type_MyCBuffer 0 Offset 0 +OpDecorate %type_MyCBuffer Block +OpDecorate %in_var_INDEX Flat +OpDecorate %in_var_INDEX Location 0 +OpDecorate %out_var_SV_Target Location 0 +OpDecorate %MyCBuffer DescriptorSet 0 +OpDecorate %MyCBuffer Binding 0 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%uint = OpTypeInt 32 0 +%uint_8 = OpConstant %uint 8 +%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8 +%type_MyCBuffer = OpTypeStruct %_arr_v4float_uint_8 +%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer +%void = OpTypeVoid +%13 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%_ptr_Input_int = OpTypePointer Input %int +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_arr_v4float_uint_8_0 = OpTypeArray %v4float %uint_8 +%_ptr_Function__arr_v4float_uint_8_0 = OpTypePointer Function %_arr_v4float_uint_8_0 +%int_0 = OpConstant %int 0 +%_ptr_Uniform__arr_v4float_uint_8 = OpTypePointer Uniform %_arr_v4float_uint_8 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform +%in_var_INDEX = OpVariable %_ptr_Input_int Input +%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %13 +%22 = OpLabel +%23 = OpVariable %_ptr_Function__arr_v4float_uint_8_0 Function +%undef = OpUndef %_arr_v4float_uint_8_0 +%24 = OpLoad %int %in_var_INDEX +%25 = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0 +%26 = OpLoad %_arr_v4float_uint_8 %25 +%28 = OpCompositeExtract %v4float %26 1 +%i1 = OpCompositeInsert %_arr_v4float_uint_8_0 %28 %undef 1 +%29 = OpCompositeExtract %v4float %26 2 +%i2 = OpCompositeInsert %_arr_v4float_uint_8_0 %29 %i1 2 +%30 = OpCompositeExtract %v4float %26 3 +%i3 = OpCompositeInsert %_arr_v4float_uint_8_0 %30 %i2 3 +%31 = OpCompositeExtract %v4float %26 4 +%i4 = OpCompositeInsert %_arr_v4float_uint_8_0 %31 %i3 4 +%32 = OpCompositeExtract %v4float %26 5 +%i5 = OpCompositeInsert %_arr_v4float_uint_8_0 %32 %i4 5 +%33 = OpCompositeExtract %v4float %26 6 +%i6 = OpCompositeInsert %_arr_v4float_uint_8_0 %33 %i5 6 +%34 = OpCompositeExtract %v4float %26 7 +%i7 = OpCompositeInsert %_arr_v4float_uint_8_0 %34 %i6 7 +OpStore %23 %i7 +%36 = OpAccessChain %_ptr_Function_v4float %23 %24 +%37 = OpLoad %v4float %36 +OpStore %out_var_SV_Target %37 +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER | + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES); + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ false); + + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +TEST_F(CopyPropArrayPassTest, AtomicAdd) { + const std::string before = R"(OpCapability SampledBuffer +OpCapability StorageImageExtendedFormats +OpCapability ImageBuffer +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %2 "min" %gl_GlobalInvocationID +OpExecutionMode %2 LocalSize 64 1 1 +OpSource HLSL 600 +OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId +OpDecorate %4 DescriptorSet 4 +OpDecorate %4 Binding 70 +%uint = OpTypeInt 32 0 +%6 = OpTypeImage %uint Buffer 0 0 0 2 R32ui +%_ptr_UniformConstant_6 = OpTypePointer UniformConstant %6 +%_ptr_Function_6 = OpTypePointer Function %6 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%uint_0 = OpConstant %uint 0 +%uint_1 = OpConstant %uint 1 +%v3uint = OpTypeVector %uint 3 +%_ptr_Input_v3uint = OpTypePointer Input %v3uint +%_ptr_Image_uint = OpTypePointer Image %uint +%4 = OpVariable %_ptr_UniformConstant_6 UniformConstant +%gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input +%2 = OpFunction %void None %10 +%17 = OpLabel +%16 = OpVariable %_ptr_Function_6 Function +%18 = OpLoad %6 %4 +OpStore %16 %18 +%19 = OpImageTexelPointer %_ptr_Image_uint %16 %uint_0 %uint_0 +%20 = OpAtomicIAdd %uint %19 %uint_1 %uint_0 %uint_1 +OpReturn +OpFunctionEnd +)"; + + const std::string after = R"(OpCapability SampledBuffer +OpCapability StorageImageExtendedFormats +OpCapability ImageBuffer +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %2 "min" %gl_GlobalInvocationID +OpExecutionMode %2 LocalSize 64 1 1 +OpSource HLSL 600 +OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId +OpDecorate %4 DescriptorSet 4 +OpDecorate %4 Binding 70 +%uint = OpTypeInt 32 0 +%6 = OpTypeImage %uint Buffer 0 0 0 2 R32ui +%_ptr_UniformConstant_6 = OpTypePointer UniformConstant %6 +%_ptr_Function_6 = OpTypePointer Function %6 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%uint_0 = OpConstant %uint 0 +%uint_1 = OpConstant %uint 1 +%v3uint = OpTypeVector %uint 3 +%_ptr_Input_v3uint = OpTypePointer Input %v3uint +%_ptr_Image_uint = OpTypePointer Image %uint +%4 = OpVariable %_ptr_UniformConstant_6 UniformConstant +%gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input +%2 = OpFunction %void None %10 +%17 = OpLabel +%16 = OpVariable %_ptr_Function_6 Function +%18 = OpLoad %6 %4 +OpStore %16 %18 +%19 = OpImageTexelPointer %_ptr_Image_uint %4 %uint_0 %uint_0 +%20 = OpAtomicIAdd %uint %19 %uint_1 %uint_0 %uint_1 +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck(before, after, true, true); +} +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/dead_branch_elim_test.cpp b/3rdparty/spirv-tools/test/opt/dead_branch_elim_test.cpp new file mode 100644 index 000000000..3ad5fc315 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/dead_branch_elim_test.cpp @@ -0,0 +1,2012 @@ +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; + +using DeadBranchElimTest = PassTest<::testing::Test>; + +TEST_F(DeadBranchElimTest, IfThenElseTrue) { + // #version 140 + // + // in vec4 BaseColor; + // + // void main() + // { + // vec4 v; + // if (true) + // v = vec4(0.0,0.0,0.0,0.0); + // else + // v = vec4(1.0,1.0,1.0,1.0); + // gl_FragColor = v; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %gl_FragColor %BaseColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %v "v" +OpName %gl_FragColor "gl_FragColor" +OpName %BaseColor "BaseColor" +%void = OpTypeVoid +%7 = OpTypeFunction %void +%bool = OpTypeBool +%true = OpConstantTrue %bool +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%float_0 = OpConstant %float 0 +%14 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 +%float_1 = OpConstant %float 1 +%16 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +)"; + + const std::string before = + R"(%main = OpFunction %void None %7 +%19 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +OpSelectionMerge %20 None +OpBranchConditional %true %21 %22 +%21 = OpLabel +OpStore %v %14 +OpBranch %20 +%22 = OpLabel +OpStore %v %16 +OpBranch %20 +%20 = OpLabel +%23 = OpLoad %v4float %v +OpStore %gl_FragColor %23 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %7 +%19 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +OpBranch %21 +%21 = OpLabel +OpStore %v %14 +OpBranch %20 +%20 = OpLabel +%23 = OpLoad %v4float %v +OpStore %gl_FragColor %23 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(predefs + before, + predefs + after, true, true); +} + +TEST_F(DeadBranchElimTest, IfThenElseFalse) { + // #version 140 + // + // in vec4 BaseColor; + // + // void main() + // { + // vec4 v; + // if (false) + // v = vec4(0.0,0.0,0.0,0.0); + // else + // v = vec4(1.0,1.0,1.0,1.0); + // gl_FragColor = v; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %gl_FragColor %BaseColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %v "v" +OpName %gl_FragColor "gl_FragColor" +OpName %BaseColor "BaseColor" +%void = OpTypeVoid +%7 = OpTypeFunction %void +%bool = OpTypeBool +%false = OpConstantFalse %bool +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%float_0 = OpConstant %float 0 +%14 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 +%float_1 = OpConstant %float 1 +%16 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +)"; + + const std::string before = + R"(%main = OpFunction %void None %7 +%19 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +OpSelectionMerge %20 None +OpBranchConditional %false %21 %22 +%21 = OpLabel +OpStore %v %14 +OpBranch %20 +%22 = OpLabel +OpStore %v %16 +OpBranch %20 +%20 = OpLabel +%23 = OpLoad %v4float %v +OpStore %gl_FragColor %23 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %7 +%19 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +OpBranch %22 +%22 = OpLabel +OpStore %v %16 +OpBranch %20 +%20 = OpLabel +%23 = OpLoad %v4float %v +OpStore %gl_FragColor %23 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(predefs + before, + predefs + after, true, true); +} + +TEST_F(DeadBranchElimTest, IfThenTrue) { + // #version 140 + // + // in vec4 BaseColor; + // + // void main() + // { + // vec4 v = BaseColor; + // if (true) + // v = v * vec4(0.5,0.5,0.5,0.5); + // gl_FragColor = v; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%bool = OpTypeBool +%true = OpConstantTrue %bool +%float_0_5 = OpConstant %float 0.5 +%15 = OpConstantComposite %v4float %float_0_5 %float_0_5 %float_0_5 %float_0_5 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %7 +%17 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%18 = OpLoad %v4float %BaseColor +OpStore %v %18 +OpSelectionMerge %19 None +OpBranchConditional %true %20 %19 +%20 = OpLabel +%21 = OpLoad %v4float %v +%22 = OpFMul %v4float %21 %15 +OpStore %v %22 +OpBranch %19 +%19 = OpLabel +%23 = OpLoad %v4float %v +OpStore %gl_FragColor %23 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %7 +%17 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%18 = OpLoad %v4float %BaseColor +OpStore %v %18 +OpBranch %20 +%20 = OpLabel +%21 = OpLoad %v4float %v +%22 = OpFMul %v4float %21 %15 +OpStore %v %22 +OpBranch %19 +%19 = OpLabel +%23 = OpLoad %v4float %v +OpStore %gl_FragColor %23 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(predefs + before, + predefs + after, true, true); +} + +TEST_F(DeadBranchElimTest, IfThenFalse) { + // #version 140 + // + // in vec4 BaseColor; + // + // void main() + // { + // vec4 v = BaseColor; + // if (false) + // v = v * vec4(0.5,0.5,0.5,0.5); + // gl_FragColor = v; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%bool = OpTypeBool +%false = OpConstantFalse %bool +%float_0_5 = OpConstant %float 0.5 +%15 = OpConstantComposite %v4float %float_0_5 %float_0_5 %float_0_5 %float_0_5 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %7 +%17 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%18 = OpLoad %v4float %BaseColor +OpStore %v %18 +OpSelectionMerge %19 None +OpBranchConditional %false %20 %19 +%20 = OpLabel +%21 = OpLoad %v4float %v +%22 = OpFMul %v4float %21 %15 +OpStore %v %22 +OpBranch %19 +%19 = OpLabel +%23 = OpLoad %v4float %v +OpStore %gl_FragColor %23 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %7 +%17 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%18 = OpLoad %v4float %BaseColor +OpStore %v %18 +OpBranch %19 +%19 = OpLabel +%23 = OpLoad %v4float %v +OpStore %gl_FragColor %23 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(predefs + before, + predefs + after, true, true); +} + +TEST_F(DeadBranchElimTest, IfThenElsePhiTrue) { + // Test handling of phi in merge block after dead branch elimination. + // Note: The SPIR-V has had store/load elimination and phi insertion + // + // #version 140 + // + // void main() + // { + // vec4 v; + // if (true) + // v = vec4(0.0,0.0,0.0,0.0); + // else + // v = vec4(1.0,1.0,1.0,1.0); + // gl_FragColor = v; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%5 = OpTypeFunction %void +%bool = OpTypeBool +%true = OpConstantTrue %bool +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%float_0 = OpConstant %float 0 +%12 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 +%float_1 = OpConstant %float 1 +%14 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +%_ptr_Input_v4float = OpTypePointer Input %v4float +)"; + + const std::string before = + R"(%main = OpFunction %void None %5 +%17 = OpLabel +OpSelectionMerge %18 None +OpBranchConditional %true %19 %20 +%19 = OpLabel +OpBranch %18 +%20 = OpLabel +OpBranch %18 +%18 = OpLabel +%21 = OpPhi %v4float %12 %19 %14 %20 +OpStore %gl_FragColor %21 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %5 +%17 = OpLabel +OpBranch %19 +%19 = OpLabel +OpBranch %18 +%18 = OpLabel +OpStore %gl_FragColor %12 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(predefs + before, + predefs + after, true, true); +} + +TEST_F(DeadBranchElimTest, IfThenElsePhiFalse) { + // Test handling of phi in merge block after dead branch elimination. + // Note: The SPIR-V has had store/load elimination and phi insertion + // + // #version 140 + // + // void main() + // { + // vec4 v; + // if (true) + // v = vec4(0.0,0.0,0.0,0.0); + // else + // v = vec4(1.0,1.0,1.0,1.0); + // gl_FragColor = v; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%5 = OpTypeFunction %void +%bool = OpTypeBool +%false = OpConstantFalse %bool +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%float_0 = OpConstant %float 0 +%12 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 +%float_1 = OpConstant %float 1 +%14 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +%_ptr_Input_v4float = OpTypePointer Input %v4float +)"; + + const std::string before = + R"(%main = OpFunction %void None %5 +%17 = OpLabel +OpSelectionMerge %18 None +OpBranchConditional %false %19 %20 +%19 = OpLabel +OpBranch %18 +%20 = OpLabel +OpBranch %18 +%18 = OpLabel +%21 = OpPhi %v4float %12 %19 %14 %20 +OpStore %gl_FragColor %21 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %5 +%17 = OpLabel +OpBranch %20 +%20 = OpLabel +OpBranch %18 +%18 = OpLabel +OpStore %gl_FragColor %14 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(predefs + before, + predefs + after, true, true); +} + +TEST_F(DeadBranchElimTest, CompoundIfThenElseFalse) { + // #version 140 + // + // layout(std140) uniform U_t + // { + // bool g_B ; + // } ; + // + // void main() + // { + // vec4 v; + // if (false) { + // if (g_B) + // v = vec4(0.0,0.0,0.0,0.0); + // else + // v = vec4(1.0,1.0,1.0,1.0); + // } else { + // if (g_B) + // v = vec4(1.0,1.0,1.0,1.0); + // else + // v = vec4(0.0,0.0,0.0,0.0); + // } + // gl_FragColor = v; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %U_t "U_t" +OpMemberName %U_t 0 "g_B" +OpName %_ "" +OpName %v "v" +OpName %gl_FragColor "gl_FragColor" +OpMemberDecorate %U_t 0 Offset 0 +OpDecorate %U_t Block +OpDecorate %_ DescriptorSet 0 +%void = OpTypeVoid +%8 = OpTypeFunction %void +%bool = OpTypeBool +%false = OpConstantFalse %bool +%uint = OpTypeInt 32 0 +%U_t = OpTypeStruct %uint +%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t +%_ = OpVariable %_ptr_Uniform_U_t Uniform +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%uint_0 = OpConstant %uint 0 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%float_0 = OpConstant %float 0 +%21 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 +%float_1 = OpConstant %float 1 +%23 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %8 +%25 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +OpSelectionMerge %26 None +OpBranchConditional %false %27 %28 +%27 = OpLabel +%29 = OpAccessChain %_ptr_Uniform_uint %_ %int_0 +%30 = OpLoad %uint %29 +%31 = OpINotEqual %bool %30 %uint_0 +OpSelectionMerge %32 None +OpBranchConditional %31 %33 %34 +%33 = OpLabel +OpStore %v %21 +OpBranch %32 +%34 = OpLabel +OpStore %v %23 +OpBranch %32 +%32 = OpLabel +OpBranch %26 +%28 = OpLabel +%35 = OpAccessChain %_ptr_Uniform_uint %_ %int_0 +%36 = OpLoad %uint %35 +%37 = OpINotEqual %bool %36 %uint_0 +OpSelectionMerge %38 None +OpBranchConditional %37 %39 %40 +%39 = OpLabel +OpStore %v %23 +OpBranch %38 +%40 = OpLabel +OpStore %v %21 +OpBranch %38 +%38 = OpLabel +OpBranch %26 +%26 = OpLabel +%41 = OpLoad %v4float %v +OpStore %gl_FragColor %41 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %8 +%25 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +OpBranch %28 +%28 = OpLabel +%35 = OpAccessChain %_ptr_Uniform_uint %_ %int_0 +%36 = OpLoad %uint %35 +%37 = OpINotEqual %bool %36 %uint_0 +OpSelectionMerge %38 None +OpBranchConditional %37 %39 %40 +%39 = OpLabel +OpStore %v %23 +OpBranch %38 +%40 = OpLabel +OpStore %v %21 +OpBranch %38 +%38 = OpLabel +OpBranch %26 +%26 = OpLabel +%41 = OpLoad %v4float %v +OpStore %gl_FragColor %41 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(predefs + before, + predefs + after, true, true); +} + +TEST_F(DeadBranchElimTest, PreventOrphanMerge) { + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%bool = OpTypeBool +%true = OpConstantTrue %bool +%float_0_5 = OpConstant %float 0.5 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %7 +%16 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%17 = OpLoad %v4float %BaseColor +OpStore %v %17 +OpSelectionMerge %18 None +OpBranchConditional %true %19 %20 +%19 = OpLabel +OpKill +%20 = OpLabel +%21 = OpLoad %v4float %v +%22 = OpVectorTimesScalar %v4float %21 %float_0_5 +OpStore %v %22 +OpBranch %18 +%18 = OpLabel +%23 = OpLoad %v4float %v +OpStore %gl_FragColor %23 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %7 +%16 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%17 = OpLoad %v4float %BaseColor +OpStore %v %17 +OpBranch %19 +%19 = OpLabel +OpKill +OpFunctionEnd +)"; + + SinglePassRunAndCheck(predefs + before, + predefs + after, true, true); +} + +TEST_F(DeadBranchElimTest, HandleOrphanMerge) { + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %foo_ "foo(" +OpName %gl_FragColor "gl_FragColor" +OpDecorate %gl_FragColor Location 0 +%void = OpTypeVoid +%6 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%9 = OpTypeFunction %v4float +%bool = OpTypeBool +%true = OpConstantTrue %bool +%float_0 = OpConstant %float 0 +%13 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 +%float_1 = OpConstant %float 1 +%15 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %6 +%17 = OpLabel +%18 = OpFunctionCall %v4float %foo_ +OpStore %gl_FragColor %18 +OpReturn +OpFunctionEnd +)"; + + const std::string before = + R"(%foo_ = OpFunction %v4float None %9 +%19 = OpLabel +OpSelectionMerge %20 None +OpBranchConditional %true %21 %22 +%21 = OpLabel +OpReturnValue %13 +%22 = OpLabel +OpReturnValue %15 +%20 = OpLabel +%23 = OpUndef %v4float +OpReturnValue %23 +OpFunctionEnd +)"; + + const std::string after = + R"(%foo_ = OpFunction %v4float None %9 +%19 = OpLabel +OpBranch %21 +%21 = OpLabel +OpReturnValue %13 +OpFunctionEnd +)"; + + SinglePassRunAndCheck(predefs + before, + predefs + after, true, true); +} + +TEST_F(DeadBranchElimTest, KeepContinueTargetWhenKillAfterMerge) { + // #version 450 + // void main() { + // bool c; + // bool d; + // while(c) { + // if(d) { + // continue; + // } + // if(false) { + // continue; + // } + // discard; + // } + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %c "c" +OpName %d "d" +%void = OpTypeVoid +%6 = OpTypeFunction %void +%bool = OpTypeBool +%_ptr_Function_bool = OpTypePointer Function %bool +%false = OpConstantFalse %bool +)"; + + const std::string before = + R"(%main = OpFunction %void None %6 +%10 = OpLabel +%c = OpVariable %_ptr_Function_bool Function +%d = OpVariable %_ptr_Function_bool Function +OpBranch %11 +%11 = OpLabel +OpLoopMerge %12 %13 None +OpBranch %14 +%14 = OpLabel +%15 = OpLoad %bool %c +OpBranchConditional %15 %16 %12 +%16 = OpLabel +%17 = OpLoad %bool %d +OpSelectionMerge %18 None +OpBranchConditional %17 %19 %18 +%19 = OpLabel +OpBranch %13 +%18 = OpLabel +OpSelectionMerge %20 None +OpBranchConditional %false %21 %20 +%21 = OpLabel +OpBranch %13 +%20 = OpLabel +OpKill +%13 = OpLabel +OpBranch %11 +%12 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %6 +%10 = OpLabel +%c = OpVariable %_ptr_Function_bool Function +%d = OpVariable %_ptr_Function_bool Function +OpBranch %11 +%11 = OpLabel +OpLoopMerge %12 %13 None +OpBranch %14 +%14 = OpLabel +%15 = OpLoad %bool %c +OpBranchConditional %15 %16 %12 +%16 = OpLabel +%17 = OpLoad %bool %d +OpSelectionMerge %18 None +OpBranchConditional %17 %19 %18 +%19 = OpLabel +OpBranch %13 +%18 = OpLabel +OpBranch %20 +%20 = OpLabel +OpKill +%13 = OpLabel +OpBranch %11 +%12 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(predefs + before, + predefs + after, true, true); +} + +TEST_F(DeadBranchElimTest, DecorateDeleted) { + // Note: SPIR-V hand-edited to add decoration + // #version 140 + // + // in vec4 BaseColor; + // + // void main() + // { + // vec4 v = BaseColor; + // if (false) + // v = v * vec4(0.5,0.5,0.5,0.5); + // gl_FragColor = v; + // } + + const std::string predefs_before = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %gl_FragColor "gl_FragColor" +OpDecorate %22 RelaxedPrecision +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%bool = OpTypeBool +%false = OpConstantFalse %bool +%float_0_5 = OpConstant %float 0.5 +%15 = OpConstantComposite %v4float %float_0_5 %float_0_5 %float_0_5 %float_0_5 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string predefs_after = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%bool = OpTypeBool +%false = OpConstantFalse %bool +%float_0_5 = OpConstant %float 0.5 +%16 = OpConstantComposite %v4float %float_0_5 %float_0_5 %float_0_5 %float_0_5 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %7 +%17 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%18 = OpLoad %v4float %BaseColor +OpStore %v %18 +OpSelectionMerge %19 None +OpBranchConditional %false %20 %19 +%20 = OpLabel +%21 = OpLoad %v4float %v +%22 = OpFMul %v4float %21 %15 +OpStore %v %22 +OpBranch %19 +%19 = OpLabel +%23 = OpLoad %v4float %v +OpStore %gl_FragColor %23 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %8 +%18 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%19 = OpLoad %v4float %BaseColor +OpStore %v %19 +OpBranch %20 +%20 = OpLabel +%23 = OpLoad %v4float %v +OpStore %gl_FragColor %23 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs_before + before, predefs_after + after, true, true); +} + +TEST_F(DeadBranchElimTest, LoopInDeadBranch) { + // #version 450 + // + // layout(location = 0) in vec4 BaseColor; + // layout(location = 0) out vec4 OutColor; + // + // void main() + // { + // vec4 v = BaseColor; + // if (false) + // for (int i=0; i<3; i++) + // v = v * 0.5; + // OutColor = v; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %i "i" +OpName %OutColor "OutColor" +OpDecorate %BaseColor Location 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%bool = OpTypeBool +%false = OpConstantFalse %bool +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_0 = OpConstant %int 0 +%int_3 = OpConstant %int 3 +%float_0_5 = OpConstant %float 0.5 +%int_1 = OpConstant %int 1 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %8 +%22 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%i = OpVariable %_ptr_Function_int Function +%23 = OpLoad %v4float %BaseColor +OpStore %v %23 +OpSelectionMerge %24 None +OpBranchConditional %false %25 %24 +%25 = OpLabel +OpStore %i %int_0 +OpBranch %26 +%26 = OpLabel +OpLoopMerge %27 %28 None +OpBranch %29 +%29 = OpLabel +%30 = OpLoad %int %i +%31 = OpSLessThan %bool %30 %int_3 +OpBranchConditional %31 %32 %27 +%32 = OpLabel +%33 = OpLoad %v4float %v +%34 = OpVectorTimesScalar %v4float %33 %float_0_5 +OpStore %v %34 +OpBranch %28 +%28 = OpLabel +%35 = OpLoad %int %i +%36 = OpIAdd %int %35 %int_1 +OpStore %i %36 +OpBranch %26 +%27 = OpLabel +OpBranch %24 +%24 = OpLabel +%37 = OpLoad %v4float %v +OpStore %OutColor %37 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %8 +%22 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%i = OpVariable %_ptr_Function_int Function +%23 = OpLoad %v4float %BaseColor +OpStore %v %23 +OpBranch %24 +%24 = OpLabel +%37 = OpLoad %v4float %v +OpStore %OutColor %37 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(predefs + before, + predefs + after, true, true); +} + +TEST_F(DeadBranchElimTest, SwitchLiveCase) { + // #version 450 + // + // layout (location=0) in vec4 BaseColor; + // layout (location=0) out vec4 OutColor; + // + // void main() + // { + // switch (1) { + // case 0: + // OutColor = vec4(0.0,0.0,0.0,0.0); + // break; + // case 1: + // OutColor = vec4(0.125,0.125,0.125,0.125); + // break; + // case 2: + // OutColor = vec4(0.25,0.25,0.25,0.25); + // break; + // default: + // OutColor = vec4(1.0,1.0,1.0,1.0); + // } + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %OutColor %BaseColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %OutColor "OutColor" +OpName %BaseColor "BaseColor" +OpDecorate %OutColor Location 0 +OpDecorate %BaseColor Location 0 +%void = OpTypeVoid +%6 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%int_1 = OpConstant %int 1 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%float_0 = OpConstant %float 0 +%13 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 +%float_0_125 = OpConstant %float 0.125 +%15 = OpConstantComposite %v4float %float_0_125 %float_0_125 %float_0_125 %float_0_125 +%float_0_25 = OpConstant %float 0.25 +%17 = OpConstantComposite %v4float %float_0_25 %float_0_25 %float_0_25 %float_0_25 +%float_1 = OpConstant %float 1 +%19 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +)"; + + const std::string before = + R"(%main = OpFunction %void None %6 +%21 = OpLabel +OpSelectionMerge %22 None +OpSwitch %int_1 %23 0 %24 1 %25 2 %26 +%23 = OpLabel +OpStore %OutColor %19 +OpBranch %22 +%24 = OpLabel +OpStore %OutColor %13 +OpBranch %22 +%25 = OpLabel +OpStore %OutColor %15 +OpBranch %22 +%26 = OpLabel +OpStore %OutColor %17 +OpBranch %22 +%22 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %6 +%21 = OpLabel +OpBranch %25 +%25 = OpLabel +OpStore %OutColor %15 +OpBranch %22 +%22 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(predefs + before, + predefs + after, true, true); +} + +TEST_F(DeadBranchElimTest, SwitchLiveDefault) { + // #version 450 + // + // layout (location=0) in vec4 BaseColor; + // layout (location=0) out vec4 OutColor; + // + // void main() + // { + // switch (7) { + // case 0: + // OutColor = vec4(0.0,0.0,0.0,0.0); + // break; + // case 1: + // OutColor = vec4(0.125,0.125,0.125,0.125); + // break; + // case 2: + // OutColor = vec4(0.25,0.25,0.25,0.25); + // break; + // default: + // OutColor = vec4(1.0,1.0,1.0,1.0); + // } + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %OutColor %BaseColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %OutColor "OutColor" +OpName %BaseColor "BaseColor" +OpDecorate %OutColor Location 0 +OpDecorate %BaseColor Location 0 +%void = OpTypeVoid +%6 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%int_7 = OpConstant %int 7 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%float_0 = OpConstant %float 0 +%13 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 +%float_0_125 = OpConstant %float 0.125 +%15 = OpConstantComposite %v4float %float_0_125 %float_0_125 %float_0_125 %float_0_125 +%float_0_25 = OpConstant %float 0.25 +%17 = OpConstantComposite %v4float %float_0_25 %float_0_25 %float_0_25 %float_0_25 +%float_1 = OpConstant %float 1 +%19 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +)"; + + const std::string before = + R"(%main = OpFunction %void None %6 +%21 = OpLabel +OpSelectionMerge %22 None +OpSwitch %int_7 %23 0 %24 1 %25 2 %26 +%23 = OpLabel +OpStore %OutColor %19 +OpBranch %22 +%24 = OpLabel +OpStore %OutColor %13 +OpBranch %22 +%25 = OpLabel +OpStore %OutColor %15 +OpBranch %22 +%26 = OpLabel +OpStore %OutColor %17 +OpBranch %22 +%22 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %6 +%21 = OpLabel +OpBranch %23 +%23 = OpLabel +OpStore %OutColor %19 +OpBranch %22 +%22 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(predefs + before, + predefs + after, true, true); +} + +TEST_F(DeadBranchElimTest, SwitchLiveCaseBreakFromLoop) { + // This sample does not directly translate to GLSL/HLSL as + // direct breaks from a loop cannot be made from a switch. + // This construct is currently formed by inlining a function + // containing early returns from the cases of a switch. The + // function is wrapped in a one-trip loop and returns are + // translated to branches to the loop's merge block. + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %OutColor %BaseColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %oc "oc" +OpName %OutColor "OutColor" +OpName %BaseColor "BaseColor" +OpDecorate %OutColor Location 0 +OpDecorate %BaseColor Location 0 +%void = OpTypeVoid +%7 = OpTypeFunction %void +%bool = OpTypeBool +%true = OpConstantTrue %bool +%false = OpConstantFalse %bool +%int = OpTypeInt 32 1 +%int_1 = OpConstant %int 1 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%float_0 = OpConstant %float 0 +%17 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 +%float_0_125 = OpConstant %float 0.125 +%19 = OpConstantComposite %v4float %float_0_125 %float_0_125 %float_0_125 %float_0_125 +%float_0_25 = OpConstant %float 0.25 +%21 = OpConstantComposite %v4float %float_0_25 %float_0_25 %float_0_25 %float_0_25 +%float_1 = OpConstant %float 1 +%23 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +)"; + + const std::string before = + R"(%main = OpFunction %void None %7 +%26 = OpLabel +%oc = OpVariable %_ptr_Function_v4float Function +OpBranch %27 +%27 = OpLabel +OpLoopMerge %28 %29 None +OpBranch %30 +%30 = OpLabel +OpSelectionMerge %31 None +OpSwitch %int_1 %31 0 %32 1 %33 2 %34 +%32 = OpLabel +OpStore %oc %17 +OpBranch %28 +%33 = OpLabel +OpStore %oc %19 +OpBranch %28 +%34 = OpLabel +OpStore %oc %21 +OpBranch %28 +%31 = OpLabel +OpStore %oc %23 +OpBranch %28 +%29 = OpLabel +OpBranchConditional %false %27 %28 +%28 = OpLabel +%35 = OpLoad %v4float %oc +OpStore %OutColor %35 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %7 +%26 = OpLabel +%oc = OpVariable %_ptr_Function_v4float Function +OpBranch %27 +%27 = OpLabel +OpLoopMerge %28 %29 None +OpBranch %30 +%30 = OpLabel +OpBranch %33 +%33 = OpLabel +OpStore %oc %19 +OpBranch %28 +%29 = OpLabel +OpBranch %27 +%28 = OpLabel +%35 = OpLoad %v4float %oc +OpStore %OutColor %35 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(predefs + before, + predefs + after, true, true); +} + +#ifdef SPIRV_EFFCEE +TEST_F(DeadBranchElimTest, LeaveContinueBackedge) { + const std::string text = R"( +; CHECK: OpLoopMerge [[merge:%\w+]] [[continue:%\w+]] None +; CHECK: [[continue]] = OpLabel +; CHECK-NEXT: OpBranchConditional {{%\w+}} {{%\w+}} [[merge]] +; CHECK-NEXT: [[merge]] = OpLabel +; CHECK-NEXT: OpReturn +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical OpenCL +%bool = OpTypeBool +%false = OpConstantFalse %bool +%void = OpTypeVoid +%funcTy = OpTypeFunction %void +%func = OpFunction %void None %funcTy +%1 = OpLabel +OpBranch %2 +%2 = OpLabel +OpLoopMerge %3 %4 None +OpBranch %4 +%4 = OpLabel +; Be careful we don't remove the backedge to %2 despite never taking it. +OpBranchConditional %false %2 %3 +%3 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} +TEST_F(DeadBranchElimTest, LeaveContinueBackedgeExtraBlock) { + const std::string text = R"( +; CHECK: OpBranch [[header:%\w+]] +; CHECK: OpLoopMerge [[merge:%\w+]] [[continue:%\w+]] None +; CHECK-NEXT: OpBranch [[continue]] +; CHECK-NEXT: [[continue]] = OpLabel +; CHECK-NEXT: OpBranchConditional {{%\w+}} [[extra:%\w+]] [[merge]] +; CHECK-NEXT: [[extra]] = OpLabel +; CHECK-NEXT: OpBranch [[header]] +; CHECK-NEXT: [[merge]] = OpLabel +; CHECK-NEXT: OpReturn +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical OpenCL +%bool = OpTypeBool +%false = OpConstantFalse %bool +%void = OpTypeVoid +%funcTy = OpTypeFunction %void +%func = OpFunction %void None %funcTy +%1 = OpLabel +OpBranch %2 +%2 = OpLabel +OpLoopMerge %3 %4 None +OpBranch %4 +%4 = OpLabel +; Be careful we don't remove the backedge to %2 despite never taking it. +OpBranchConditional %false %5 %3 +; This block remains live despite being unreachable. +%5 = OpLabel +OpBranch %2 +%3 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(DeadBranchElimTest, RemovePhiWithUnreachableContinue) { + const std::string text = R"( +; CHECK: [[entry:%\w+]] = OpLabel +; CHECK-NEXT: OpBranch [[header:%\w+]] +; CHECK: OpLoopMerge [[merge:%\w+]] [[continue:%\w+]] None +; CHECK-NEXT: OpBranch [[ret:%\w+]] +; CHECK-NEXT: [[ret]] = OpLabel +; CHECK-NEXT: OpReturn +; CHECK: [[continue]] = OpLabel +; CHECK-NEXT: OpBranch [[header]] +; CHECK: [[merge]] = OpLabel +; CHECK-NEXT: OpUnreachable +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical OpenCL +OpName %func "func" +OpDecorate %func LinkageAttributes "func" Export +%bool = OpTypeBool +%false = OpConstantFalse %bool +%true = OpConstantTrue %bool +%void = OpTypeVoid +%funcTy = OpTypeFunction %void +%func = OpFunction %void None %funcTy +%1 = OpLabel +OpBranch %2 +%2 = OpLabel +%phi = OpPhi %bool %false %1 %true %continue +OpLoopMerge %merge %continue None +OpBranch %3 +%3 = OpLabel +OpReturn +%continue = OpLabel +OpBranch %2 +%merge = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(DeadBranchElimTest, UnreachableLoopMergeAndContinueTargets) { + const std::string text = R"( +; CHECK: [[undef:%\w+]] = OpUndef %bool +; CHECK: OpSelectionMerge [[header:%\w+]] +; CHECK-NEXT: OpBranchConditional {{%\w+}} [[if_lab:%\w+]] [[else_lab:%\w+]] +; CHECK: OpPhi %bool %false [[if_lab]] %false [[else_lab]] [[undef]] [[continue:%\w+]] +; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None +; CHECK-NEXT: OpBranch [[ret:%\w+]] +; CHECK-NEXT: [[ret]] = OpLabel +; CHECK-NEXT: OpReturn +; CHECK: [[continue]] = OpLabel +; CHECK-NEXT: OpBranch [[header]] +; CHECK: [[merge]] = OpLabel +; CHECK-NEXT: OpUnreachable +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical OpenCL +OpName %func "func" +OpDecorate %func LinkageAttributes "func" Export +%bool = OpTypeBool +%false = OpConstantFalse %bool +%true = OpConstantTrue %bool +%void = OpTypeVoid +%funcTy = OpTypeFunction %void +%func = OpFunction %void None %funcTy +%1 = OpLabel +%c = OpUndef %bool +OpSelectionMerge %2 None +OpBranchConditional %c %if %else +%if = OpLabel +OpBranch %2 +%else = OpLabel +OpBranch %2 +%2 = OpLabel +%phi = OpPhi %bool %false %if %false %else %true %continue +OpLoopMerge %merge %continue None +OpBranch %3 +%3 = OpLabel +OpReturn +%continue = OpLabel +OpBranch %2 +%merge = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} +TEST_F(DeadBranchElimTest, EarlyReconvergence) { + const std::string text = R"( +; CHECK-NOT: OpBranchConditional +; CHECK: [[logical:%\w+]] = OpLogicalOr +; CHECK-NOT: OpPhi +; CHECK: OpLogicalAnd {{%\w+}} {{%\w+}} [[logical]] +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %func "func" +OpExecutionMode %func OriginUpperLeft +%void = OpTypeVoid +%bool = OpTypeBool +%false = OpConstantFalse %bool +%true = OpConstantTrue %bool +%func_ty = OpTypeFunction %void +%func = OpFunction %void None %func_ty +%1 = OpLabel +OpSelectionMerge %2 None +OpBranchConditional %false %3 %4 +%3 = OpLabel +%12 = OpLogicalNot %bool %true +OpBranch %2 +%4 = OpLabel +OpSelectionMerge %14 None +OpBranchConditional %false %5 %6 +%5 = OpLabel +%10 = OpLogicalAnd %bool %true %false +OpBranch %7 +%6 = OpLabel +%11 = OpLogicalOr %bool %true %false +OpBranch %7 +%7 = OpLabel +; This phi is in a block preceeding the merge %14! +%8 = OpPhi %bool %10 %5 %11 %6 +OpBranch %14 +%14 = OpLabel +OpBranch %2 +%2 = OpLabel +%9 = OpPhi %bool %12 %3 %8 %14 +%13 = OpLogicalAnd %bool %true %9 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(DeadBranchElimTest, RemoveUnreachableBlocksFloating) { + const std::string text = R"( +; CHECK: OpFunction +; CHECK-NEXT: OpLabel +; CHECK-NEXT: OpReturn +; CHECK-NEXT: OpFunctionEnd +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical OpenCL +OpName %func "func" +OpDecorate %func LinkageAttributes "func" Export +%void = OpTypeVoid +%1 = OpTypeFunction %void +%func = OpFunction %void None %1 +%2 = OpLabel +OpReturn +%3 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(DeadBranchElimTest, RemoveUnreachableBlocksFloatingJoin) { + const std::string text = R"( +; CHECK: OpFunction +; CHECK-NEXT: OpFunctionParameter +; CHECK-NEXT: OpLabel +; CHECK-NEXT: OpReturn +; CHECK-NEXT: OpFunctionEnd +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical OpenCL +OpName %func "func" +OpDecorate %func LinkageAttributes "func" Export +%void = OpTypeVoid +%bool = OpTypeBool +%false = OpConstantFalse %bool +%true = OpConstantTrue %bool +%1 = OpTypeFunction %void %bool +%func = OpFunction %void None %1 +%bool_param = OpFunctionParameter %bool +%2 = OpLabel +OpReturn +%3 = OpLabel +OpSelectionMerge %6 None +OpBranchConditional %bool_param %4 %5 +%4 = OpLabel +OpBranch %6 +%5 = OpLabel +OpBranch %6 +%6 = OpLabel +%7 = OpPhi %bool %true %4 %false %6 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(DeadBranchElimTest, RemoveUnreachableBlocksDeadPhi) { + const std::string text = R"( +; CHECK: OpFunction +; CHECK-NEXT: OpFunctionParameter +; CHECK-NEXT: OpLabel +; CHECK-NEXT: OpBranch [[label:%\w+]] +; CHECK-NEXT: [[label]] = OpLabel +; CHECK-NEXT: OpLogicalNot %bool %true +; CHECK-NEXT: OpReturn +; CHECK-NEXT: OpFunctionEnd +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical OpenCL +OpName %func "func" +OpDecorate %func LinkageAttributes "func" Export +%void = OpTypeVoid +%bool = OpTypeBool +%false = OpConstantFalse %bool +%true = OpConstantTrue %bool +%1 = OpTypeFunction %void %bool +%func = OpFunction %void None %1 +%bool_param = OpFunctionParameter %bool +%2 = OpLabel +OpBranch %3 +%4 = OpLabel +OpBranch %3 +%3 = OpLabel +%5 = OpPhi %bool %true %2 %false %4 +%6 = OpLogicalNot %bool %5 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(DeadBranchElimTest, RemoveUnreachableBlocksPartiallyDeadPhi) { + const std::string text = R"( +; CHECK: OpFunction +; CHECK-NEXT: [[param:%\w+]] = OpFunctionParameter +; CHECK-NEXT: OpLabel +; CHECK-NEXT: OpBranchConditional [[param]] [[merge:%\w+]] [[br:%\w+]] +; CHECK-NEXT: [[br]] = OpLabel +; CHECK-NEXT: OpBranch [[merge]] +; CHECK-NEXT: [[merge]] = OpLabel +; CHECK-NEXT: [[phi:%\w+]] = OpPhi %bool %true %2 %false [[br]] +; CHECK-NEXT: OpLogicalNot %bool [[phi]] +; CHECK-NEXT: OpReturn +; CHECK-NEXT: OpFunctionEnd +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical OpenCL +OpName %func "func" +OpDecorate %func LinkageAttributes "func" Export +%void = OpTypeVoid +%bool = OpTypeBool +%false = OpConstantFalse %bool +%true = OpConstantTrue %bool +%1 = OpTypeFunction %void %bool +%func = OpFunction %void None %1 +%bool_param = OpFunctionParameter %bool +%2 = OpLabel +OpBranchConditional %bool_param %3 %7 +%7 = OpLabel +OpBranch %3 +%4 = OpLabel +OpBranch %3 +%3 = OpLabel +%5 = OpPhi %bool %true %2 %false %7 %false %4 +%6 = OpLogicalNot %bool %5 +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndMatch(text, true); +} + +TEST_F(DeadBranchElimTest, LiveHeaderDeadPhi) { + const std::string text = R"( +; CHECK: OpLabel +; CHECK-NOT: OpBranchConditional +; CHECK-NOT: OpPhi +; CHECK: OpLogicalNot %bool %false +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical OpenCL +OpName %func "func" +OpDecorate %func LinkageAttributes "func" Export +%void = OpTypeVoid +%bool = OpTypeBool +%true = OpConstantTrue %bool +%false = OpConstantFalse %bool +%func_ty = OpTypeFunction %void +%func = OpFunction %void None %func_ty +%1 = OpLabel +OpSelectionMerge %3 None +OpBranchConditional %true %2 %3 +%2 = OpLabel +OpBranch %3 +%3 = OpLabel +%5 = OpPhi %bool %true %3 %false %2 +%6 = OpLogicalNot %bool %5 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(DeadBranchElimTest, ExtraBackedgeBlocksLive) { + const std::string text = R"( +; CHECK: [[entry:%\w+]] = OpLabel +; CHECK-NOT: OpSelectionMerge +; CHECK: OpBranch [[header:%\w+]] +; CHECK-NEXT: [[header]] = OpLabel +; CHECK-NEXT: OpPhi %bool %true [[entry]] %false [[backedge:%\w+]] +; CHECK-NEXT: OpLoopMerge +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical OpenCL +OpName %func "func" +OpDecorate %func LinkageAttributes "func" Export +%void = OpTypeVoid +%bool = OpTypeBool +%true = OpConstantTrue %bool +%false = OpConstantFalse %bool +%func_ty = OpTypeFunction %void %bool +%func = OpFunction %void None %func_ty +%param = OpFunctionParameter %bool +%entry = OpLabel +OpSelectionMerge %if_merge None +; This dead branch is included to ensure the pass does work. +OpBranchConditional %false %if_merge %loop_header +%loop_header = OpLabel +; Both incoming edges are live, so the phi should be untouched. +%phi = OpPhi %bool %true %entry %false %backedge +OpLoopMerge %loop_merge %continue None +OpBranchConditional %param %loop_merge %continue +%continue = OpLabel +OpBranch %backedge +%backedge = OpLabel +OpBranch %loop_header +%loop_merge = OpLabel +OpBranch %if_merge +%if_merge = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(DeadBranchElimTest, ExtraBackedgeBlocksUnreachable) { + const std::string text = R"( +; CHECK: [[entry:%\w+]] = OpLabel +; CHECK-NEXT: OpBranch [[header:%\w+]] +; CHECK-NEXT: [[header]] = OpLabel +; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue:%\w+]] None +; CHECK-NEXT: OpBranch [[merge]] +; CHECK-NEXT: [[continue]] = OpLabel +; CHECK-NEXT: OpBranch [[header]] +; CHECK-NEXT: [[merge]] = OpLabel +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical OpenCL +OpName %func "func" +OpDecorate %func LinkageAttributes "func" Export +%void = OpTypeVoid +%bool = OpTypeBool +%true = OpConstantTrue %bool +%false = OpConstantFalse %bool +%func_ty = OpTypeFunction %void %bool +%func = OpFunction %void None %func_ty +%param = OpFunctionParameter %bool +%entry = OpLabel +OpBranch %loop_header +%loop_header = OpLabel +; Since the continue is unreachable, %backedge will be removed. The phi will +; instead require an edge from %continue. +%phi = OpPhi %bool %true %entry %false %backedge +OpLoopMerge %merge %continue None +OpBranch %merge +%continue = OpLabel +OpBranch %backedge +%backedge = OpLabel +OpBranch %loop_header +%merge = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(DeadBranchElimTest, NoUnnecessaryChanges) { + const std::string text = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %func "func" +%void = OpTypeVoid +%bool = OpTypeBool +%true = OpConstantTrue %bool +%undef = OpUndef %bool +%functy = OpTypeFunction %void +%func = OpFunction %void None %functy +%1 = OpLabel +OpBranch %2 +%2 = OpLabel +OpLoopMerge %4 %5 None +OpBranch %6 +%6 = OpLabel +OpReturn +%5 = OpLabel +OpBranch %2 +%4 = OpLabel +OpUnreachable +OpFunctionEnd +)"; + + auto result = SinglePassRunToBinary(text, true); + EXPECT_EQ(std::get<1>(result), opt::Pass::Status::SuccessWithoutChange); +} + +TEST_F(DeadBranchElimTest, ExtraBackedgePartiallyDead) { + const std::string text = R"( +; CHECK: OpLabel +; CHECK: [[header:%\w+]] = OpLabel +; CHECK: OpLoopMerge [[merge:%\w+]] [[continue:%\w+]] None +; CHECK: [[continue]] = OpLabel +; CHECK: OpBranch [[extra:%\w+]] +; CHECK: [[extra]] = OpLabel +; CHECK-NOT: OpSelectionMerge +; CHECK-NEXT: OpBranch [[else:%\w+]] +; CHECK-NEXT: [[else]] = OpLabel +; CHECK-NEXT: OpLogicalOr +; CHECK-NEXT: OpBranch [[backedge:%\w+]] +; CHECK-NEXT: [[backedge:%\w+]] = OpLabel +; CHECK-NEXT: OpBranch [[header]] +; CHECK-NEXT: [[merge]] = OpLabel +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical OpenCL +OpName %func "func" +OpDecorate %func LinkageAttributes "func" Export +%void = OpTypeVoid +%bool = OpTypeBool +%true = OpConstantTrue %bool +%false = OpConstantFalse %bool +%func_ty = OpTypeFunction %void %bool +%func = OpFunction %void None %func_ty +%param = OpFunctionParameter %bool +%entry = OpLabel +OpBranch %loop_header +%loop_header = OpLabel +OpLoopMerge %loop_merge %continue None +OpBranchConditional %param %loop_merge %continue +%continue = OpLabel +OpBranch %extra +%extra = OpLabel +OpSelectionMerge %backedge None +OpBranchConditional %false %then %else +%then = OpLabel +%and = OpLogicalAnd %bool %true %false +OpBranch %backedge +%else = OpLabel +%or = OpLogicalOr %bool %true %false +OpBranch %backedge +%backedge = OpLabel +OpBranch %loop_header +%loop_merge = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(DeadBranchElimTest, UnreachableContinuePhiInMerge) { + const std::string text = R"( +; CHECK: [[entry:%\w+]] = OpLabel +; CHECK-NEXT: OpBranch [[header:%\w+]] +; CHECK-NEXT: [[header]] = OpLabel +; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue:%\w+]] None +; CHECK-NEXT: OpBranch [[label:%\w+]] +; CHECK-NEXT: [[label]] = OpLabel +; CHECK-NEXT: [[fadd:%\w+]] = OpFAdd +; CHECK-NEXT: OpBranch [[label:%\w+]] +; CHECK-NEXT: [[label]] = OpLabel +; CHECK-NEXT: OpBranch [[merge]] +; CHECK-NEXT: [[continue]] = OpLabel +; CHECK-NEXT: OpBranch [[header]] +; CHECK-NEXT: [[merge]] = OpLabel +; CHECK-NEXT: OpStore {{%\w+}} [[fadd]] + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %o + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 430 + OpSourceExtension "GL_GOOGLE_cpp_style_line_directive" + OpSourceExtension "GL_GOOGLE_include_directive" + OpName %main "main" + OpName %o "o" + OpName %S "S" + OpMemberName %S 0 "a" + OpName %U_t "U_t" + OpMemberName %U_t 0 "g_F" + OpMemberName %U_t 1 "g_F2" + OpDecorate %o Location 0 + OpMemberDecorate %S 0 Offset 0 + OpMemberDecorate %U_t 0 Volatile + OpMemberDecorate %U_t 0 Offset 0 + OpMemberDecorate %U_t 1 Offset 4 + OpDecorate %U_t BufferBlock + %void = OpTypeVoid + %7 = OpTypeFunction %void + %float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float + %float_0 = OpConstant %float 0 + %int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int + %int_0 = OpConstant %int 0 + %int_10 = OpConstant %int 10 + %bool = OpTypeBool + %true = OpConstantTrue %bool + %float_1 = OpConstant %float 1 + %float_5 = OpConstant %float 5 + %int_1 = OpConstant %int 1 +%_ptr_Output_float = OpTypePointer Output %float + %o = OpVariable %_ptr_Output_float Output + %S = OpTypeStruct %float + %U_t = OpTypeStruct %S %S +%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t + %main = OpFunction %void None %7 + %22 = OpLabel + OpBranch %23 + %23 = OpLabel + %24 = OpPhi %float %float_0 %22 %25 %26 + %27 = OpPhi %int %int_0 %22 %28 %26 + OpLoopMerge %29 %26 None + OpBranch %40 + %40 = OpLabel + %25 = OpFAdd %float %24 %float_1 + OpSelectionMerge %30 None + OpBranchConditional %true %31 %30 + %31 = OpLabel + OpBranch %29 + %30 = OpLabel + OpBranch %26 + %26 = OpLabel + %28 = OpIAdd %int %27 %int_1 + %32 = OpSLessThan %bool %27 %int_10 +; continue block branches to the header or another none dead block. + OpBranchConditional %32 %23 %29 + %29 = OpLabel + %33 = OpPhi %float %24 %26 %25 %31 + OpStore %o %33 + OpReturn + OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(DeadBranchElimTest, NonStructuredIf) { + const std::string text = R"( +; CHECK-NOT: OpBranchConditional +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical OpenCL +OpDecorate %func LinkageAttributes "func" Export +%void = OpTypeVoid +%bool = OpTypeBool +%true = OpConstantTrue %bool +%functy = OpTypeFunction %void +%func = OpFunction %void None %functy +%entry = OpLabel +OpBranchConditional %true %then %else +%then = OpLabel +OpBranch %final +%else = OpLabel +OpBranch %final +%final = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} +#endif + +// TODO(greg-lunarg): Add tests to verify handling of these cases: +// +// More complex control flow +// Others? + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/dead_insert_elim_test.cpp b/3rdparty/spirv-tools/test/opt/dead_insert_elim_test.cpp new file mode 100644 index 000000000..7a434363a --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/dead_insert_elim_test.cpp @@ -0,0 +1,685 @@ +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; + +using DeadInsertElimTest = PassTest<::testing::Test>; + +TEST_F(DeadInsertElimTest, InsertAfterInsertElim) { + // With two insertions to the same offset, the first is dead. + // + // Note: The SPIR-V assembly has had store/load elimination + // performed to allow the inserts and extracts to directly + // reference each other. + // + // #version 450 + // + // layout (location=0) in float In0; + // layout (location=1) in float In1; + // layout (location=2) in vec2 In2; + // layout (location=0) out vec4 OutColor; + // + // void main() + // { + // vec2 v = In2; + // v.x = In0 + In1; // dead + // v.x = 0.0; + // OutColor = v.xyxy; + // } + + const std::string before_predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %In2 %In0 %In1 %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %In2 "In2" +OpName %In0 "In0" +OpName %In1 "In1" +OpName %OutColor "OutColor" +OpName %_Globals_ "_Globals_" +OpMemberName %_Globals_ 0 "g_b" +OpMemberName %_Globals_ 1 "g_n" +OpName %_ "" +OpDecorate %In2 Location 2 +OpDecorate %In0 Location 0 +OpDecorate %In1 Location 1 +OpDecorate %OutColor Location 0 +OpMemberDecorate %_Globals_ 0 Offset 0 +OpMemberDecorate %_Globals_ 1 Offset 4 +OpDecorate %_Globals_ Block +OpDecorate %_ DescriptorSet 0 +OpDecorate %_ Binding 0 +%void = OpTypeVoid +%11 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v2float = OpTypeVector %float 2 +%_ptr_Function_v2float = OpTypePointer Function %v2float +%_ptr_Input_v2float = OpTypePointer Input %v2float +%In2 = OpVariable %_ptr_Input_v2float Input +%_ptr_Input_float = OpTypePointer Input %float +%In0 = OpVariable %_ptr_Input_float Input +%In1 = OpVariable %_ptr_Input_float Input +%uint = OpTypeInt 32 0 +%_ptr_Function_float = OpTypePointer Function %float +%float_0 = OpConstant %float 0 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%int = OpTypeInt 32 1 +%_Globals_ = OpTypeStruct %uint %int +%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_ +%_ = OpVariable %_ptr_Uniform__Globals_ Uniform +)"; + + const std::string after_predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %In2 %In0 %In1 %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %In2 "In2" +OpName %In0 "In0" +OpName %In1 "In1" +OpName %OutColor "OutColor" +OpName %_Globals_ "_Globals_" +OpMemberName %_Globals_ 0 "g_b" +OpMemberName %_Globals_ 1 "g_n" +OpName %_ "" +OpDecorate %In2 Location 2 +OpDecorate %In0 Location 0 +OpDecorate %In1 Location 1 +OpDecorate %OutColor Location 0 +OpMemberDecorate %_Globals_ 0 Offset 0 +OpMemberDecorate %_Globals_ 1 Offset 4 +OpDecorate %_Globals_ Block +OpDecorate %_ DescriptorSet 0 +OpDecorate %_ Binding 0 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v2float = OpTypeVector %float 2 +%_ptr_Function_v2float = OpTypePointer Function %v2float +%_ptr_Input_v2float = OpTypePointer Input %v2float +%In2 = OpVariable %_ptr_Input_v2float Input +%_ptr_Input_float = OpTypePointer Input %float +%In0 = OpVariable %_ptr_Input_float Input +%In1 = OpVariable %_ptr_Input_float Input +%uint = OpTypeInt 32 0 +%_ptr_Function_float = OpTypePointer Function %float +%float_0 = OpConstant %float 0 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%int = OpTypeInt 32 1 +%_Globals_ = OpTypeStruct %uint %int +%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_ +%_ = OpVariable %_ptr_Uniform__Globals_ Uniform +)"; + + const std::string before = + R"(%main = OpFunction %void None %11 +%25 = OpLabel +%26 = OpLoad %v2float %In2 +%27 = OpLoad %float %In0 +%28 = OpLoad %float %In1 +%29 = OpFAdd %float %27 %28 +%35 = OpCompositeInsert %v2float %29 %26 0 +%37 = OpCompositeInsert %v2float %float_0 %35 0 +%33 = OpVectorShuffle %v4float %37 %37 0 1 0 1 +OpStore %OutColor %33 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %10 +%23 = OpLabel +%24 = OpLoad %v2float %In2 +%29 = OpCompositeInsert %v2float %float_0 %24 0 +%30 = OpVectorShuffle %v4float %29 %29 0 1 0 1 +OpStore %OutColor %30 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + before_predefs + before, after_predefs + after, true, true); +} + +TEST_F(DeadInsertElimTest, DeadInsertInChainWithPhi) { + // Dead insert eliminated with phi in insertion chain. + // + // Note: The SPIR-V assembly has had store/load elimination + // performed to allow the inserts and extracts to directly + // reference each other. + // + // #version 450 + // + // layout (location=0) in vec4 In0; + // layout (location=1) in float In1; + // layout (location=2) in float In2; + // layout (location=0) out vec4 OutColor; + // + // layout(std140, binding = 0 ) uniform _Globals_ + // { + // bool g_b; + // }; + // + // void main() + // { + // vec4 v = In0; + // v.z = In1 + In2; + // if (g_b) v.w = 1.0; + // OutColor = vec4(v.x,v.y,0.0,v.w); + // } + + const std::string before_predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %In0 %In1 %In2 %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %In0 "In0" +OpName %In1 "In1" +OpName %In2 "In2" +OpName %_Globals_ "_Globals_" +OpMemberName %_Globals_ 0 "g_b" +OpName %_ "" +OpName %OutColor "OutColor" +OpDecorate %In0 Location 0 +OpDecorate %In1 Location 1 +OpDecorate %In2 Location 2 +OpMemberDecorate %_Globals_ 0 Offset 0 +OpDecorate %_Globals_ Block +OpDecorate %_ DescriptorSet 0 +OpDecorate %_ Binding 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%11 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%In0 = OpVariable %_ptr_Input_v4float Input +%_ptr_Input_float = OpTypePointer Input %float +%In1 = OpVariable %_ptr_Input_float Input +%In2 = OpVariable %_ptr_Input_float Input +%uint = OpTypeInt 32 0 +%_ptr_Function_float = OpTypePointer Function %float +%_Globals_ = OpTypeStruct %uint +%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_ +%_ = OpVariable %_ptr_Uniform__Globals_ Uniform +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%bool = OpTypeBool +%uint_0 = OpConstant %uint 0 +%float_1 = OpConstant %float 1 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%float_0 = OpConstant %float 0 +)"; + + const std::string after_predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %In0 %In1 %In2 %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %In0 "In0" +OpName %In1 "In1" +OpName %In2 "In2" +OpName %_Globals_ "_Globals_" +OpMemberName %_Globals_ 0 "g_b" +OpName %_ "" +OpName %OutColor "OutColor" +OpDecorate %In0 Location 0 +OpDecorate %In1 Location 1 +OpDecorate %In2 Location 2 +OpMemberDecorate %_Globals_ 0 Offset 0 +OpDecorate %_Globals_ Block +OpDecorate %_ DescriptorSet 0 +OpDecorate %_ Binding 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%In0 = OpVariable %_ptr_Input_v4float Input +%_ptr_Input_float = OpTypePointer Input %float +%In1 = OpVariable %_ptr_Input_float Input +%In2 = OpVariable %_ptr_Input_float Input +%uint = OpTypeInt 32 0 +%_ptr_Function_float = OpTypePointer Function %float +%_Globals_ = OpTypeStruct %uint +%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_ +%_ = OpVariable %_ptr_Uniform__Globals_ Uniform +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%bool = OpTypeBool +%uint_0 = OpConstant %uint 0 +%float_1 = OpConstant %float 1 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%float_0 = OpConstant %float 0 +)"; + + const std::string before = + R"(%main = OpFunction %void None %11 +%31 = OpLabel +%32 = OpLoad %v4float %In0 +%33 = OpLoad %float %In1 +%34 = OpLoad %float %In2 +%35 = OpFAdd %float %33 %34 +%51 = OpCompositeInsert %v4float %35 %32 2 +%37 = OpAccessChain %_ptr_Uniform_uint %_ %int_0 +%38 = OpLoad %uint %37 +%39 = OpINotEqual %bool %38 %uint_0 +OpSelectionMerge %40 None +OpBranchConditional %39 %41 %40 +%41 = OpLabel +%53 = OpCompositeInsert %v4float %float_1 %51 3 +OpBranch %40 +%40 = OpLabel +%60 = OpPhi %v4float %51 %31 %53 %41 +%55 = OpCompositeExtract %float %60 0 +%57 = OpCompositeExtract %float %60 1 +%59 = OpCompositeExtract %float %60 3 +%49 = OpCompositeConstruct %v4float %55 %57 %float_0 %59 +OpStore %OutColor %49 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %10 +%27 = OpLabel +%28 = OpLoad %v4float %In0 +%33 = OpAccessChain %_ptr_Uniform_uint %_ %int_0 +%34 = OpLoad %uint %33 +%35 = OpINotEqual %bool %34 %uint_0 +OpSelectionMerge %36 None +OpBranchConditional %35 %37 %36 +%37 = OpLabel +%38 = OpCompositeInsert %v4float %float_1 %28 3 +OpBranch %36 +%36 = OpLabel +%39 = OpPhi %v4float %28 %27 %38 %37 +%40 = OpCompositeExtract %float %39 0 +%41 = OpCompositeExtract %float %39 1 +%42 = OpCompositeExtract %float %39 3 +%43 = OpCompositeConstruct %v4float %40 %41 %float_0 %42 +OpStore %OutColor %43 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + before_predefs + before, after_predefs + after, true, true); +} + +TEST_F(DeadInsertElimTest, DeadInsertTwoPasses) { + // Dead insert which requires two passes to eliminate + // + // Note: The SPIR-V assembly has had store/load elimination + // performed to allow the inserts and extracts to directly + // reference each other. + // + // #version 450 + // + // layout (location=0) in vec4 In0; + // layout (location=1) in float In1; + // layout (location=2) in float In2; + // layout (location=0) out vec4 OutColor; + // + // layout(std140, binding = 0 ) uniform _Globals_ + // { + // bool g_b; + // bool g_b2; + // }; + // + // void main() + // { + // vec4 v1, v2; + // v1 = In0; + // v1.y = In1 + In2; // dead, second pass + // if (g_b) v1.x = 1.0; + // v2.x = v1.x; + // v2.y = v1.y; // dead, first pass + // if (g_b2) v2.x = 0.0; + // OutColor = vec4(v2.x,v2.x,0.0,1.0); + // } + + const std::string before_predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %In0 %In1 %In2 %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %In0 "In0" +OpName %In1 "In1" +OpName %In2 "In2" +OpName %_Globals_ "_Globals_" +OpMemberName %_Globals_ 0 "g_b" +OpMemberName %_Globals_ 1 "g_b2" +OpName %_ "" +OpName %OutColor "OutColor" +OpDecorate %In0 Location 0 +OpDecorate %In1 Location 1 +OpDecorate %In2 Location 2 +OpMemberDecorate %_Globals_ 0 Offset 0 +OpMemberDecorate %_Globals_ 1 Offset 4 +OpDecorate %_Globals_ Block +OpDecorate %_ DescriptorSet 0 +OpDecorate %_ Binding 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%In0 = OpVariable %_ptr_Input_v4float Input +%_ptr_Input_float = OpTypePointer Input %float +%In1 = OpVariable %_ptr_Input_float Input +%In2 = OpVariable %_ptr_Input_float Input +%uint = OpTypeInt 32 0 +%_Globals_ = OpTypeStruct %uint %uint +%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_ +%_ = OpVariable %_ptr_Uniform__Globals_ Uniform +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%bool = OpTypeBool +%uint_0 = OpConstant %uint 0 +%float_1 = OpConstant %float 1 +%int_1 = OpConstant %int 1 +%float_0 = OpConstant %float 0 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%27 = OpUndef %v4float +)"; + + const std::string after_predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %In0 %In1 %In2 %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %In0 "In0" +OpName %In1 "In1" +OpName %In2 "In2" +OpName %_Globals_ "_Globals_" +OpMemberName %_Globals_ 0 "g_b" +OpMemberName %_Globals_ 1 "g_b2" +OpName %_ "" +OpName %OutColor "OutColor" +OpDecorate %In0 Location 0 +OpDecorate %In1 Location 1 +OpDecorate %In2 Location 2 +OpMemberDecorate %_Globals_ 0 Offset 0 +OpMemberDecorate %_Globals_ 1 Offset 4 +OpDecorate %_Globals_ Block +OpDecorate %_ DescriptorSet 0 +OpDecorate %_ Binding 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%In0 = OpVariable %_ptr_Input_v4float Input +%_ptr_Input_float = OpTypePointer Input %float +%In1 = OpVariable %_ptr_Input_float Input +%In2 = OpVariable %_ptr_Input_float Input +%uint = OpTypeInt 32 0 +%_Globals_ = OpTypeStruct %uint %uint +%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_ +%_ = OpVariable %_ptr_Uniform__Globals_ Uniform +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%bool = OpTypeBool +%uint_0 = OpConstant %uint 0 +%float_1 = OpConstant %float 1 +%int_1 = OpConstant %int 1 +%float_0 = OpConstant %float 0 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%27 = OpUndef %v4float +)"; + + const std::string before = + R"(%main = OpFunction %void None %10 +%28 = OpLabel +%29 = OpLoad %v4float %In0 +%30 = OpLoad %float %In1 +%31 = OpLoad %float %In2 +%32 = OpFAdd %float %30 %31 +%33 = OpCompositeInsert %v4float %32 %29 1 +%34 = OpAccessChain %_ptr_Uniform_uint %_ %int_0 +%35 = OpLoad %uint %34 +%36 = OpINotEqual %bool %35 %uint_0 +OpSelectionMerge %37 None +OpBranchConditional %36 %38 %37 +%38 = OpLabel +%39 = OpCompositeInsert %v4float %float_1 %33 0 +OpBranch %37 +%37 = OpLabel +%40 = OpPhi %v4float %33 %28 %39 %38 +%41 = OpCompositeExtract %float %40 0 +%42 = OpCompositeInsert %v4float %41 %27 0 +%43 = OpCompositeExtract %float %40 1 +%44 = OpCompositeInsert %v4float %43 %42 1 +%45 = OpAccessChain %_ptr_Uniform_uint %_ %int_1 +%46 = OpLoad %uint %45 +%47 = OpINotEqual %bool %46 %uint_0 +OpSelectionMerge %48 None +OpBranchConditional %47 %49 %48 +%49 = OpLabel +%50 = OpCompositeInsert %v4float %float_0 %44 0 +OpBranch %48 +%48 = OpLabel +%51 = OpPhi %v4float %44 %37 %50 %49 +%52 = OpCompositeExtract %float %51 0 +%53 = OpCompositeExtract %float %51 0 +%54 = OpCompositeConstruct %v4float %52 %53 %float_0 %float_1 +OpStore %OutColor %54 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %10 +%28 = OpLabel +%29 = OpLoad %v4float %In0 +%34 = OpAccessChain %_ptr_Uniform_uint %_ %int_0 +%35 = OpLoad %uint %34 +%36 = OpINotEqual %bool %35 %uint_0 +OpSelectionMerge %37 None +OpBranchConditional %36 %38 %37 +%38 = OpLabel +%39 = OpCompositeInsert %v4float %float_1 %29 0 +OpBranch %37 +%37 = OpLabel +%40 = OpPhi %v4float %29 %28 %39 %38 +%41 = OpCompositeExtract %float %40 0 +%42 = OpCompositeInsert %v4float %41 %27 0 +%45 = OpAccessChain %_ptr_Uniform_uint %_ %int_1 +%46 = OpLoad %uint %45 +%47 = OpINotEqual %bool %46 %uint_0 +OpSelectionMerge %48 None +OpBranchConditional %47 %49 %48 +%49 = OpLabel +%50 = OpCompositeInsert %v4float %float_0 %42 0 +OpBranch %48 +%48 = OpLabel +%51 = OpPhi %v4float %42 %37 %50 %49 +%52 = OpCompositeExtract %float %51 0 +%53 = OpCompositeExtract %float %51 0 +%54 = OpCompositeConstruct %v4float %52 %53 %float_0 %float_1 +OpStore %OutColor %54 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + before_predefs + before, after_predefs + after, true, true); +} + +TEST_F(DeadInsertElimTest, DeadInsertInCycleToDo) { + // Dead insert in chain with cycle. Demonstrates analysis can handle + // cycles in chains. + // + // TODO(greg-lunarg): Improve algorithm to remove dead insert into v.y. Will + // likely require similar logic to ADCE. + // + // Note: The SPIR-V assembly has had store/load elimination + // performed to allow the inserts and extracts to directly + // reference each other. + // + // #version 450 + // + // layout (location=0) in vec4 In0; + // layout (location=1) in float In1; + // layout (location=2) in float In2; + // layout (location=0) out vec4 OutColor; + // + // layout(std140, binding = 0 ) uniform _Globals_ + // { + // int g_n ; + // }; + // + // void main() + // { + // vec2 v = vec2(0.0, 1.0); + // for (int i = 0; i < g_n; i++) { + // v.x = v.x + 1; + // v.y = v.y * 0.9; // dead + // } + // OutColor = vec4(v.x); + // } + + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %OutColor %In0 %In1 %In2 +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %_Globals_ "_Globals_" +OpMemberName %_Globals_ 0 "g_n" +OpName %_ "" +OpName %OutColor "OutColor" +OpName %In0 "In0" +OpName %In1 "In1" +OpName %In2 "In2" +OpMemberDecorate %_Globals_ 0 Offset 0 +OpDecorate %_Globals_ Block +OpDecorate %_ DescriptorSet 0 +OpDecorate %_ Binding 0 +OpDecorate %OutColor Location 0 +OpDecorate %In0 Location 0 +OpDecorate %In1 Location 1 +OpDecorate %In2 Location 2 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v2float = OpTypeVector %float 2 +%_ptr_Function_v2float = OpTypePointer Function %v2float +%float_0 = OpConstant %float 0 +%float_1 = OpConstant %float 1 +%16 = OpConstantComposite %v2float %float_0 %float_1 +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_0 = OpConstant %int 0 +%_Globals_ = OpTypeStruct %int +%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_ +%_ = OpVariable %_ptr_Uniform__Globals_ Uniform +%_ptr_Uniform_int = OpTypePointer Uniform %int +%bool = OpTypeBool +%float_0_75 = OpConstant %float 0.75 +%int_1 = OpConstant %int 1 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%_ptr_Input_v4float = OpTypePointer Input %v4float +%In0 = OpVariable %_ptr_Input_v4float Input +%_ptr_Input_float = OpTypePointer Input %float +%In1 = OpVariable %_ptr_Input_float Input +%In2 = OpVariable %_ptr_Input_float Input +%main = OpFunction %void None %10 +%29 = OpLabel +OpBranch %30 +%30 = OpLabel +%31 = OpPhi %v2float %16 %29 %32 %33 +%34 = OpPhi %int %int_0 %29 %35 %33 +OpLoopMerge %36 %33 None +OpBranch %37 +%37 = OpLabel +%38 = OpAccessChain %_ptr_Uniform_int %_ %int_0 +%39 = OpLoad %int %38 +%40 = OpSLessThan %bool %34 %39 +OpBranchConditional %40 %41 %36 +%41 = OpLabel +%42 = OpCompositeExtract %float %31 0 +%43 = OpFAdd %float %42 %float_1 +%44 = OpCompositeInsert %v2float %43 %31 0 +%45 = OpCompositeExtract %float %44 1 +%46 = OpFMul %float %45 %float_0_75 +%32 = OpCompositeInsert %v2float %46 %44 1 +OpBranch %33 +%33 = OpLabel +%35 = OpIAdd %int %34 %int_1 +OpBranch %30 +%36 = OpLabel +%47 = OpCompositeExtract %float %31 0 +%48 = OpCompositeConstruct %v4float %47 %47 %47 %47 +OpStore %OutColor %48 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(assembly, assembly, true, + true); +} + +// TODO(greg-lunarg): Add tests to verify handling of these cases: +// + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/dead_variable_elim_test.cpp b/3rdparty/spirv-tools/test/opt/dead_variable_elim_test.cpp new file mode 100644 index 000000000..676719cf4 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/dead_variable_elim_test.cpp @@ -0,0 +1,299 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; + +using DeadVariableElimTest = PassTest<::testing::Test>; + +// %dead is unused. Make sure we remove it along with its name. +TEST_F(DeadVariableElimTest, RemoveUnreferenced) { + const std::string before = + R"(OpCapability Shader +OpCapability Linkage +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 150 +OpName %main "main" +OpName %dead "dead" +%void = OpTypeVoid +%5 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Private_float = OpTypePointer Private %float +%dead = OpVariable %_ptr_Private_float Private +%main = OpFunction %void None %5 +%8 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(OpCapability Shader +OpCapability Linkage +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 150 +OpName %main "main" +%void = OpTypeVoid +%5 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Private_float = OpTypePointer Private %float +%main = OpFunction %void None %5 +%8 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck(before, after, true, + true); +} + +// Since %dead is exported, make sure we keep it. It could be referenced +// somewhere else. +TEST_F(DeadVariableElimTest, KeepExported) { + const std::string before = + R"(OpCapability Shader +OpCapability Linkage +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 150 +OpName %main "main" +OpName %dead "dead" +OpDecorate %dead LinkageAttributes "dead" Export +%void = OpTypeVoid +%5 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Private_float = OpTypePointer Private %float +%dead = OpVariable %_ptr_Private_float Private +%main = OpFunction %void None %5 +%8 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck(before, before, true, + true); +} + +// Delete %dead because it is unreferenced. Then %initializer becomes +// unreferenced, so remove it as well. +TEST_F(DeadVariableElimTest, RemoveUnreferencedWithInit1) { + const std::string before = + R"(OpCapability Shader +OpCapability Linkage +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 150 +OpName %main "main" +OpName %dead "dead" +OpName %initializer "initializer" +%void = OpTypeVoid +%6 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Private_float = OpTypePointer Private %float +%initializer = OpVariable %_ptr_Private_float Private +%dead = OpVariable %_ptr_Private_float Private %initializer +%main = OpFunction %void None %6 +%9 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(OpCapability Shader +OpCapability Linkage +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 150 +OpName %main "main" +%void = OpTypeVoid +%6 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Private_float = OpTypePointer Private %float +%main = OpFunction %void None %6 +%9 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck(before, after, true, + true); +} + +// Delete %dead because it is unreferenced. In this case, the initialized has +// another reference, and should not be removed. +TEST_F(DeadVariableElimTest, RemoveUnreferencedWithInit2) { + const std::string before = + R"(OpCapability Shader +OpCapability Linkage +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 150 +OpName %main "main" +OpName %dead "dead" +OpName %initializer "initializer" +%void = OpTypeVoid +%6 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Private_float = OpTypePointer Private %float +%initializer = OpVariable %_ptr_Private_float Private +%dead = OpVariable %_ptr_Private_float Private %initializer +%main = OpFunction %void None %6 +%9 = OpLabel +%10 = OpLoad %float %initializer +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(OpCapability Shader +OpCapability Linkage +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 150 +OpName %main "main" +OpName %initializer "initializer" +%void = OpTypeVoid +%6 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Private_float = OpTypePointer Private %float +%initializer = OpVariable %_ptr_Private_float Private +%main = OpFunction %void None %6 +%9 = OpLabel +%10 = OpLoad %float %initializer +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck(before, after, true, + true); +} + +// Keep %live because it is used, and its initializer. +TEST_F(DeadVariableElimTest, KeepReferenced) { + const std::string before = + R"(OpCapability Shader +OpCapability Linkage +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 150 +OpName %main "main" +OpName %live "live" +OpName %initializer "initializer" +%void = OpTypeVoid +%6 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Private_float = OpTypePointer Private %float +%initializer = OpVariable %_ptr_Private_float Private +%live = OpVariable %_ptr_Private_float Private %initializer +%main = OpFunction %void None %6 +%9 = OpLabel +%10 = OpLoad %float %live +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck(before, before, true, + true); +} + +// This test that the decoration associated with a variable are removed when the +// variable is removed. +TEST_F(DeadVariableElimTest, RemoveVariableAndDecorations) { + const std::string before = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %main "main" +OpSource GLSL 450 +OpName %main "main" +OpName %B "B" +OpMemberName %B 0 "a" +OpName %Bdat "Bdat" +OpMemberDecorate %B 0 Offset 0 +OpDecorate %B BufferBlock +OpDecorate %Bdat DescriptorSet 0 +OpDecorate %Bdat Binding 0 +%void = OpTypeVoid +%6 = OpTypeFunction %void +%uint = OpTypeInt 32 0 +%B = OpTypeStruct %uint +%_ptr_Uniform_B = OpTypePointer Uniform %B +%Bdat = OpVariable %_ptr_Uniform_B Uniform +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%uint_1 = OpConstant %uint 1 +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%main = OpFunction %void None %6 +%13 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %main "main" +OpSource GLSL 450 +OpName %main "main" +OpName %B "B" +OpMemberName %B 0 "a" +OpMemberDecorate %B 0 Offset 0 +OpDecorate %B BufferBlock +%void = OpTypeVoid +%6 = OpTypeFunction %void +%uint = OpTypeInt 32 0 +%B = OpTypeStruct %uint +%_ptr_Uniform_B = OpTypePointer Uniform %B +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%uint_1 = OpConstant %uint 1 +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%main = OpFunction %void None %6 +%13 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck(before, after, true, + true); +} +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/decoration_manager_test.cpp b/3rdparty/spirv-tools/test/opt/decoration_manager_test.cpp new file mode 100644 index 000000000..b503316fd --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/decoration_manager_test.cpp @@ -0,0 +1,1162 @@ +// Copyright (c) 2017 Pierre Moreau +// +// 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/opt/build_module.h" +#include "source/opt/decoration_manager.h" +#include "source/opt/ir_context.h" +#include "source/spirv_constant.h" +#include "unit_spirv.h" + +namespace { + +using spvtest::MakeVector; +using spvtools::ir::Instruction; +using spvtools::ir::IRContext; +using spvtools::opt::analysis::DecorationManager; + +class DecorationManagerTest : public ::testing::Test { + public: + DecorationManagerTest() + : tools_(SPV_ENV_UNIVERSAL_1_2), + context_(), + consumer_([this](spv_message_level_t level, const char*, + const spv_position_t& position, const char* message) { + if (!error_message_.empty()) error_message_ += "\n"; + switch (level) { + case SPV_MSG_FATAL: + case SPV_MSG_INTERNAL_ERROR: + case SPV_MSG_ERROR: + error_message_ += "ERROR"; + break; + case SPV_MSG_WARNING: + error_message_ += "WARNING"; + break; + case SPV_MSG_INFO: + error_message_ += "INFO"; + break; + case SPV_MSG_DEBUG: + error_message_ += "DEBUG"; + break; + } + error_message_ += + ": " + std::to_string(position.index) + ": " + message; + }), + disassemble_options_(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER), + error_message_() { + tools_.SetMessageConsumer(consumer_); + } + + virtual void TearDown() override { error_message_.clear(); } + + DecorationManager* GetDecorationManager(const std::string& text) { + context_ = spvtools::BuildModule(SPV_ENV_UNIVERSAL_1_2, consumer_, text); + if (context_.get()) + return context_->get_decoration_mgr(); + else + return nullptr; + } + + // Disassembles |binary| and outputs the result in |text|. If |text| is a + // null pointer, SPV_ERROR_INVALID_POINTER is returned. + spv_result_t Disassemble(const std::vector& binary, + std::string* text) { + if (!text) return SPV_ERROR_INVALID_POINTER; + return tools_.Disassemble(binary, text, disassemble_options_) + ? SPV_SUCCESS + : SPV_ERROR_INVALID_BINARY; + } + + // Returns the accumulated error messages for the test. + std::string GetErrorMessage() const { return error_message_; } + + std::string ToText(const std::vector& inst) { + std::vector binary = {SpvMagicNumber, 0x10200, 0u, 2u, 0u}; + for (const Instruction* i : inst) + i->ToBinaryWithoutAttachedDebugInsts(&binary); + std::string text; + Disassemble(binary, &text); + return text; + } + + std::string ModuleToText() { + std::vector binary; + context_->module()->ToBinary(&binary, false); + std::string text; + Disassemble(binary, &text); + return text; + } + + spvtools::MessageConsumer GetConsumer() { return consumer_; } + + private: + spvtools::SpirvTools + tools_; // An instance for calling SPIRV-Tools functionalities. + std::unique_ptr context_; + spvtools::MessageConsumer consumer_; + uint32_t disassemble_options_; + std::string error_message_; +}; + +TEST_F(DecorationManagerTest, + ComparingDecorationsWithDiffOpcodesDecorateDecorateId) { + spvtools::ir::IRContext ir_context(SPV_ENV_UNIVERSAL_1_2, GetConsumer()); + // This parameter can be interprated both as { SpvDecorationConstant } + // and also as a list of IDs: { 22 } + const std::vector param{SpvDecorationConstant}; + // OpDecorate %1 Constant + Instruction inst1( + &ir_context, SpvOpDecorate, 0u, 0u, + {{SPV_OPERAND_TYPE_ID, {1u}}, {SPV_OPERAND_TYPE_DECORATION, param}}); + // OpDecorateId %1 %22 ; 'Constant' is decoration number 22 + Instruction inst2( + &ir_context, SpvOpDecorateId, 0u, 0u, + {{SPV_OPERAND_TYPE_ID, {1u}}, {SPV_OPERAND_TYPE_ID, param}}); + DecorationManager* decoManager = ir_context.get_decoration_mgr(); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_FALSE(decoManager->AreDecorationsTheSame(&inst1, &inst2, true)); +} + +TEST_F(DecorationManagerTest, + ComparingDecorationsWithDiffOpcodesDecorateDecorateString) { + spvtools::ir::IRContext ir_context(SPV_ENV_UNIVERSAL_1_2, GetConsumer()); + // This parameter can be interprated both as { SpvDecorationConstant } + // and also as a null-terminated string with a single character with value 22. + const std::vector param{SpvDecorationConstant}; + // OpDecorate %1 Constant + Instruction inst1( + &ir_context, SpvOpDecorate, 0u, 0u, + {{SPV_OPERAND_TYPE_ID, {1u}}, {SPV_OPERAND_TYPE_DECORATION, param}}); + // OpDecorateStringGOOGLE %1 !22 + Instruction inst2( + &ir_context, SpvOpDecorateStringGOOGLE, 0u, 0u, + {{SPV_OPERAND_TYPE_ID, {1u}}, {SPV_OPERAND_TYPE_LITERAL_STRING, param}}); + DecorationManager* decoManager = ir_context.get_decoration_mgr(); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_FALSE(decoManager->AreDecorationsTheSame(&inst1, &inst2, true)); +} + +TEST_F(DecorationManagerTest, ComparingDecorationsWithDiffDecorateParam) { + spvtools::ir::IRContext ir_context(SPV_ENV_UNIVERSAL_1_2, GetConsumer()); + // OpDecorate %1 Constant + Instruction inst1(&ir_context, SpvOpDecorate, 0u, 0u, + {{SPV_OPERAND_TYPE_ID, {1u}}, + {SPV_OPERAND_TYPE_DECORATION, {SpvDecorationConstant}}}); + // OpDecorate %1 Restrict + Instruction inst2(&ir_context, SpvOpDecorate, 0u, 0u, + {{SPV_OPERAND_TYPE_ID, {1u}}, + {SPV_OPERAND_TYPE_DECORATION, {SpvDecorationRestrict}}}); + DecorationManager* decoManager = ir_context.get_decoration_mgr(); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_FALSE(decoManager->AreDecorationsTheSame(&inst1, &inst2, true)); +} + +TEST_F(DecorationManagerTest, ComparingDecorationsWithDiffDecorateIdParam) { + spvtools::ir::IRContext ir_context(SPV_ENV_UNIVERSAL_1_2, GetConsumer()); + // OpDecorate %1 Constant + Instruction inst1( + &ir_context, SpvOpDecorateId, 0u, 0u, + {{SPV_OPERAND_TYPE_ID, {1u}}, {SPV_OPERAND_TYPE_ID, {555}}}); + // OpDecorate %1 Restrict + Instruction inst2( + &ir_context, SpvOpDecorateId, 0u, 0u, + {{SPV_OPERAND_TYPE_ID, {1u}}, {SPV_OPERAND_TYPE_ID, {666}}}); + DecorationManager* decoManager = ir_context.get_decoration_mgr(); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_FALSE(decoManager->AreDecorationsTheSame(&inst1, &inst2, true)); +} + +TEST_F(DecorationManagerTest, ComparingDecorationsWithDiffDecorateStringParam) { + spvtools::ir::IRContext ir_context(SPV_ENV_UNIVERSAL_1_2, GetConsumer()); + // OpDecorate %1 Constant + Instruction inst1(&ir_context, SpvOpDecorateStringGOOGLE, 0u, 0u, + {{SPV_OPERAND_TYPE_ID, {1u}}, + {SPV_OPERAND_TYPE_LITERAL_STRING, MakeVector("Hello!")}}); + // OpDecorate %1 Restrict + Instruction inst2(&ir_context, SpvOpDecorateStringGOOGLE, 0u, 0u, + {{SPV_OPERAND_TYPE_ID, {1u}}, + {SPV_OPERAND_TYPE_LITERAL_STRING, MakeVector("Hellx")}}); + DecorationManager* decoManager = ir_context.get_decoration_mgr(); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_FALSE(decoManager->AreDecorationsTheSame(&inst1, &inst2, true)); +} + +TEST_F(DecorationManagerTest, ComparingSameDecorationsOnDiffTargetAllowed) { + spvtools::ir::IRContext ir_context(SPV_ENV_UNIVERSAL_1_2, GetConsumer()); + // OpDecorate %1 Constant + Instruction inst1(&ir_context, SpvOpDecorate, 0u, 0u, + {{SPV_OPERAND_TYPE_ID, {1u}}, + {SPV_OPERAND_TYPE_DECORATION, {SpvDecorationConstant}}}); + // OpDecorate %2 Constant + Instruction inst2(&ir_context, SpvOpDecorate, 0u, 0u, + {{SPV_OPERAND_TYPE_ID, {2u}}, + {SPV_OPERAND_TYPE_DECORATION, {SpvDecorationConstant}}}); + DecorationManager* decoManager = ir_context.get_decoration_mgr(); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_TRUE(decoManager->AreDecorationsTheSame(&inst1, &inst2, true)); +} + +TEST_F(DecorationManagerTest, ComparingSameDecorationIdsOnDiffTargetAllowed) { + spvtools::ir::IRContext ir_context(SPV_ENV_UNIVERSAL_1_2, GetConsumer()); + Instruction inst1( + &ir_context, SpvOpDecorateId, 0u, 0u, + {{SPV_OPERAND_TYPE_ID, {1u}}, {SPV_OPERAND_TYPE_DECORATION, {44}}}); + Instruction inst2( + &ir_context, SpvOpDecorateId, 0u, 0u, + {{SPV_OPERAND_TYPE_ID, {2u}}, {SPV_OPERAND_TYPE_DECORATION, {44}}}); + DecorationManager* decoManager = ir_context.get_decoration_mgr(); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_TRUE(decoManager->AreDecorationsTheSame(&inst1, &inst2, true)); +} + +TEST_F(DecorationManagerTest, + ComparingSameDecorationStringsOnDiffTargetAllowed) { + spvtools::ir::IRContext ir_context(SPV_ENV_UNIVERSAL_1_2, GetConsumer()); + Instruction inst1(&ir_context, SpvOpDecorateStringGOOGLE, 0u, 0u, + {{SPV_OPERAND_TYPE_ID, {1u}}, + {SPV_OPERAND_TYPE_LITERAL_STRING, MakeVector("hello")}}); + Instruction inst2(&ir_context, SpvOpDecorateStringGOOGLE, 0u, 0u, + {{SPV_OPERAND_TYPE_ID, {2u}}, + {SPV_OPERAND_TYPE_LITERAL_STRING, MakeVector("hello")}}); + DecorationManager* decoManager = ir_context.get_decoration_mgr(); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_TRUE(decoManager->AreDecorationsTheSame(&inst1, &inst2, true)); +} + +TEST_F(DecorationManagerTest, ComparingSameDecorationsOnDiffTargetDisallowed) { + spvtools::ir::IRContext ir_context(SPV_ENV_UNIVERSAL_1_2, GetConsumer()); + // OpDecorate %1 Constant + Instruction inst1(&ir_context, SpvOpDecorate, 0u, 0u, + {{SPV_OPERAND_TYPE_ID, {1u}}, + {SPV_OPERAND_TYPE_DECORATION, {SpvDecorationConstant}}}); + // OpDecorate %2 Constant + Instruction inst2(&ir_context, SpvOpDecorate, 0u, 0u, + {{SPV_OPERAND_TYPE_ID, {2u}}, + {SPV_OPERAND_TYPE_DECORATION, {SpvDecorationConstant}}}); + DecorationManager* decoManager = ir_context.get_decoration_mgr(); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_FALSE(decoManager->AreDecorationsTheSame(&inst1, &inst2, false)); +} + +TEST_F(DecorationManagerTest, ComparingMemberDecorationsOnSameTypeDiffMember) { + spvtools::ir::IRContext ir_context(SPV_ENV_UNIVERSAL_1_2, GetConsumer()); + // OpMemberDecorate %1 0 Constant + Instruction inst1(&ir_context, SpvOpMemberDecorate, 0u, 0u, + {{SPV_OPERAND_TYPE_ID, {1u}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {0u}}, + {SPV_OPERAND_TYPE_DECORATION, {SpvDecorationConstant}}}); + // OpMemberDecorate %1 1 Constant + Instruction inst2(&ir_context, SpvOpMemberDecorate, 0u, 0u, + {{SPV_OPERAND_TYPE_ID, {1u}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {1u}}, + {SPV_OPERAND_TYPE_DECORATION, {SpvDecorationConstant}}}); + DecorationManager* decoManager = ir_context.get_decoration_mgr(); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_FALSE(decoManager->AreDecorationsTheSame(&inst1, &inst2, true)); +} + +TEST_F(DecorationManagerTest, + ComparingSameMemberDecorationsOnDiffTargetAllowed) { + spvtools::ir::IRContext ir_context(SPV_ENV_UNIVERSAL_1_2, GetConsumer()); + // OpMemberDecorate %1 0 Constant + Instruction inst1(&ir_context, SpvOpMemberDecorate, 0u, 0u, + {{SPV_OPERAND_TYPE_ID, {1u}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {0u}}, + {SPV_OPERAND_TYPE_DECORATION, {SpvDecorationConstant}}}); + // OpMemberDecorate %2 0 Constant + Instruction inst2(&ir_context, SpvOpMemberDecorate, 0u, 0u, + {{SPV_OPERAND_TYPE_ID, {2u}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {0u}}, + {SPV_OPERAND_TYPE_DECORATION, {SpvDecorationConstant}}}); + DecorationManager* decoManager = ir_context.get_decoration_mgr(); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_TRUE(decoManager->AreDecorationsTheSame(&inst1, &inst2, true)); +} + +TEST_F(DecorationManagerTest, + ComparingSameMemberDecorationsOnDiffTargetDisallowed) { + spvtools::ir::IRContext ir_context(SPV_ENV_UNIVERSAL_1_2, GetConsumer()); + // OpMemberDecorate %1 0 Constant + Instruction inst1(&ir_context, SpvOpMemberDecorate, 0u, 0u, + {{SPV_OPERAND_TYPE_ID, {1u}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {0u}}, + {SPV_OPERAND_TYPE_DECORATION, {SpvDecorationConstant}}}); + // OpMemberDecorate %2 0 Constant + Instruction inst2(&ir_context, SpvOpMemberDecorate, 0u, 0u, + {{SPV_OPERAND_TYPE_ID, {2u}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {0u}}, + {SPV_OPERAND_TYPE_DECORATION, {SpvDecorationConstant}}}); + DecorationManager* decoManager = ir_context.get_decoration_mgr(); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_FALSE(decoManager->AreDecorationsTheSame(&inst1, &inst2, false)); +} + +TEST_F(DecorationManagerTest, RemoveDecorationFromVariable) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 Constant +OpDecorate %2 Restrict +%2 = OpDecorationGroup +OpGroupDecorate %2 %1 %3 +%4 = OpTypeInt 32 0 +%1 = OpVariable %4 Uniform +%3 = OpVariable %4 Uniform +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + decoManager->RemoveDecorationsFrom(1u); + auto decorations = decoManager->GetDecorationsFor(1u, false); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_TRUE(decorations.empty()); + decorations = decoManager->GetDecorationsFor(3u, false); + EXPECT_THAT(GetErrorMessage(), ""); + + const std::string expected_decorations = R"(OpDecorate %2 Restrict +)"; + EXPECT_THAT(ToText(decorations), expected_decorations); + + const std::string expected_binary = R"(OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %2 Restrict +%2 = OpDecorationGroup +OpGroupDecorate %2 %3 +%4 = OpTypeInt 32 0 +%1 = OpVariable %4 Uniform +%3 = OpVariable %4 Uniform +)"; + EXPECT_THAT(ModuleToText(), expected_binary); +} + +TEST_F(DecorationManagerTest, RemoveDecorationStringFromVariable) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpExtension "SPV_GOOGLE_hlsl_functionality1" +OpExtension "SPV_GOOGLE_decorate_string" +OpMemoryModel Logical GLSL450 +OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE "hello world" +OpDecorate %2 Restrict +%2 = OpDecorationGroup +OpGroupDecorate %2 %1 %3 +%4 = OpTypeInt 32 0 +%1 = OpVariable %4 Uniform +%3 = OpVariable %4 Uniform +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + decoManager->RemoveDecorationsFrom(1u); + auto decorations = decoManager->GetDecorationsFor(1u, false); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_TRUE(decorations.empty()); + decorations = decoManager->GetDecorationsFor(3u, false); + EXPECT_THAT(GetErrorMessage(), ""); + + const std::string expected_decorations = R"(OpDecorate %2 Restrict +)"; + EXPECT_THAT(ToText(decorations), expected_decorations); + + const std::string expected_binary = R"(OpCapability Shader +OpCapability Linkage +OpExtension "SPV_GOOGLE_hlsl_functionality1" +OpExtension "SPV_GOOGLE_decorate_string" +OpMemoryModel Logical GLSL450 +OpDecorate %2 Restrict +%2 = OpDecorationGroup +OpGroupDecorate %2 %3 +%4 = OpTypeInt 32 0 +%1 = OpVariable %4 Uniform +%3 = OpVariable %4 Uniform +)"; + EXPECT_THAT(ModuleToText(), expected_binary); +} + +TEST_F(DecorationManagerTest, RemoveDecorationFromDecorationGroup) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 Constant +OpDecorate %2 Restrict +%2 = OpDecorationGroup +OpGroupDecorate %2 %1 %3 +%4 = OpTypeInt 32 0 +%1 = OpVariable %4 Uniform +%3 = OpVariable %4 Uniform +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + decoManager->RemoveDecorationsFrom(2u); + auto decorations = decoManager->GetDecorationsFor(2u, false); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_TRUE(decorations.empty()); + decorations = decoManager->GetDecorationsFor(1u, false); + EXPECT_THAT(GetErrorMessage(), ""); + + const std::string expected_decorations = R"(OpDecorate %1 Constant +)"; + EXPECT_THAT(ToText(decorations), expected_decorations); + decorations = decoManager->GetDecorationsFor(3u, false); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_THAT(ToText(decorations), ""); + + const std::string expected_binary = R"(OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 Constant +%4 = OpTypeInt 32 0 +%1 = OpVariable %4 Uniform +%3 = OpVariable %4 Uniform +)"; + EXPECT_THAT(ModuleToText(), expected_binary); +} + +TEST_F(DecorationManagerTest, + RemoveDecorationFromDecorationGroupKeepDeadDecorations) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 Constant +OpDecorate %2 Restrict +%2 = OpDecorationGroup +OpGroupDecorate %2 %1 +%3 = OpTypeInt 32 0 +%1 = OpVariable %3 Uniform +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + decoManager->RemoveDecorationsFrom(1u); + auto decorations = decoManager->GetDecorationsFor(1u, false); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_TRUE(decorations.empty()); + decorations = decoManager->GetDecorationsFor(2u, false); + EXPECT_THAT(GetErrorMessage(), ""); + + const std::string expected_decorations = R"(OpDecorate %2 Restrict +)"; + EXPECT_THAT(ToText(decorations), expected_decorations); + + const std::string expected_binary = R"(OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %2 Restrict +%2 = OpDecorationGroup +%3 = OpTypeInt 32 0 +%1 = OpVariable %3 Uniform +)"; + EXPECT_THAT(ModuleToText(), expected_binary); +} + +TEST_F(DecorationManagerTest, RemoveAllDecorationsAppliedByGroup) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 Constant +OpDecorate %2 Restrict +%2 = OpDecorationGroup +OpGroupDecorate %2 %1 +OpDecorate %3 BuiltIn VertexId +%3 = OpDecorationGroup +OpGroupDecorate %3 %1 +%4 = OpTypeInt 32 0 +%1 = OpVariable %4 Input +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + decoManager->RemoveDecorationsFrom( + 1u, [](const spvtools::ir::Instruction& inst) { + return inst.opcode() == SpvOpDecorate && + inst.GetSingleWordInOperand(0u) == 3u; + }); + auto decorations = decoManager->GetDecorationsFor(1u, false); + EXPECT_THAT(GetErrorMessage(), ""); + + std::string expected_decorations = R"(OpDecorate %1 Constant +OpDecorate %2 Restrict +)"; + EXPECT_THAT(ToText(decorations), expected_decorations); + decorations = decoManager->GetDecorationsFor(2u, false); + EXPECT_THAT(GetErrorMessage(), ""); + + expected_decorations = R"(OpDecorate %2 Restrict +)"; + EXPECT_THAT(ToText(decorations), expected_decorations); + + const std::string expected_binary = R"(OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 Constant +OpDecorate %2 Restrict +%2 = OpDecorationGroup +OpGroupDecorate %2 %1 +OpDecorate %3 BuiltIn VertexId +%3 = OpDecorationGroup +%4 = OpTypeInt 32 0 +%1 = OpVariable %4 Input +)"; + EXPECT_THAT(ModuleToText(), expected_binary); +} + +TEST_F(DecorationManagerTest, RemoveSomeDecorationsAppliedByGroup) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 Constant +OpDecorate %2 Restrict +%2 = OpDecorationGroup +OpGroupDecorate %2 %1 +OpDecorate %3 BuiltIn VertexId +OpDecorate %3 Invariant +%3 = OpDecorationGroup +OpGroupDecorate %3 %1 +%uint = OpTypeInt 32 0 +%1 = OpVariable %uint Input +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + decoManager->RemoveDecorationsFrom( + 1u, [](const spvtools::ir::Instruction& inst) { + return inst.opcode() == SpvOpDecorate && + inst.GetSingleWordInOperand(0u) == 3u && + inst.GetSingleWordInOperand(1u) == SpvDecorationBuiltIn; + }); + auto decorations = decoManager->GetDecorationsFor(1u, false); + EXPECT_THAT(GetErrorMessage(), ""); + + std::string expected_decorations = R"(OpDecorate %1 Constant +OpDecorate %1 Invariant +OpDecorate %2 Restrict +)"; + EXPECT_THAT(ToText(decorations), expected_decorations); + decorations = decoManager->GetDecorationsFor(2u, false); + EXPECT_THAT(GetErrorMessage(), ""); + + expected_decorations = R"(OpDecorate %2 Restrict +)"; + EXPECT_THAT(ToText(decorations), expected_decorations); + + const std::string expected_binary = R"(OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 Constant +OpDecorate %2 Restrict +%2 = OpDecorationGroup +OpGroupDecorate %2 %1 +OpDecorate %3 BuiltIn VertexId +OpDecorate %3 Invariant +%3 = OpDecorationGroup +OpDecorate %1 Invariant +%4 = OpTypeInt 32 0 +%1 = OpVariable %4 Input +)"; + EXPECT_THAT(ModuleToText(), expected_binary); +} + +TEST_F(DecorationManagerTest, RemoveDecorationDecorate) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 Constant +OpDecorate %1 Restrict +%2 = OpTypeInt 32 0 +%1 = OpVariable %2 Uniform +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + auto decorations = decoManager->GetDecorationsFor(1u, false); + decoManager->RemoveDecoration(decorations.front()); + decorations = decoManager->GetDecorationsFor(1u, false); + EXPECT_THAT(GetErrorMessage(), ""); + + const std::string expected_decorations = R"(OpDecorate %1 Restrict +)"; + EXPECT_THAT(ToText(decorations), expected_decorations); +} + +TEST_F(DecorationManagerTest, RemoveDecorationStringDecorate) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpExtension "SPV_GOOGLE_hlsl_functionality1" +OpExtension "SPV_GOOGLE_decorate_string" +OpMemoryModel Logical GLSL450 +OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE "foobar" +OpDecorate %1 Restrict +%2 = OpTypeInt 32 0 +%1 = OpVariable %2 Uniform +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + auto decorations = decoManager->GetDecorationsFor(1u, false); + decoManager->RemoveDecoration(decorations.front()); + decorations = decoManager->GetDecorationsFor(1u, false); + EXPECT_THAT(GetErrorMessage(), ""); + + const std::string expected_decorations = R"(OpDecorate %1 Restrict +)"; + EXPECT_THAT(ToText(decorations), expected_decorations); +} + +TEST_F(DecorationManagerTest, CloneDecorations) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 Constant +OpDecorate %2 Restrict +%2 = OpDecorationGroup +OpGroupDecorate %2 %1 +OpDecorate %3 BuiltIn VertexId +OpDecorate %3 Invariant +%3 = OpDecorationGroup +OpGroupDecorate %3 %1 +%4 = OpTypeInt 32 0 +%1 = OpVariable %4 Input +%5 = OpVariable %4 Input +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + + // Check cloning OpDecorate including group decorations. + auto decorations = decoManager->GetDecorationsFor(5u, false); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_TRUE(decorations.empty()); + + decoManager->CloneDecorations(1u, 5u); + decorations = decoManager->GetDecorationsFor(5u, false); + EXPECT_THAT(GetErrorMessage(), ""); + + std::string expected_decorations = R"(OpDecorate %5 Constant +OpDecorate %2 Restrict +OpDecorate %3 BuiltIn VertexId +OpDecorate %3 Invariant +)"; + EXPECT_THAT(ToText(decorations), expected_decorations); + + // Check that bookkeeping for ID 2 remains the same. + decorations = decoManager->GetDecorationsFor(2u, false); + EXPECT_THAT(GetErrorMessage(), ""); + + expected_decorations = R"(OpDecorate %2 Restrict +)"; + EXPECT_THAT(ToText(decorations), expected_decorations); + + const std::string expected_binary = R"(OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 Constant +OpDecorate %2 Restrict +%2 = OpDecorationGroup +OpGroupDecorate %2 %1 %5 +OpDecorate %3 BuiltIn VertexId +OpDecorate %3 Invariant +%3 = OpDecorationGroup +OpGroupDecorate %3 %1 %5 +OpDecorate %5 Constant +%4 = OpTypeInt 32 0 +%1 = OpVariable %4 Input +%5 = OpVariable %4 Input +)"; + EXPECT_THAT(ModuleToText(), expected_binary); +} + +TEST_F(DecorationManagerTest, CloneDecorationsStringAndId) { + const std::string spirv = R"(OpCapability Shader +OpCapability Linkage +OpExtension "SPV_GOOGLE_hlsl_functionality1" +OpExtension "SPV_GOOGLE_decorate_string" +OpMemoryModel Logical GLSL450 +OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE "blah" +OpDecorateId %1 HlslCounterBufferGOOGLE %2 +OpDecorate %1 Aliased +%3 = OpTypeInt 32 0 +%4 = OpTypePointer Uniform %3 +%1 = OpVariable %4 Uniform +%2 = OpVariable %4 Uniform +%5 = OpVariable %4 Uniform +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + + // Check cloning OpDecorate including group decorations. + auto decorations = decoManager->GetDecorationsFor(5u, false); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_TRUE(decorations.empty()); + + decoManager->CloneDecorations(1u, 5u); + decorations = decoManager->GetDecorationsFor(5u, false); + EXPECT_THAT(GetErrorMessage(), ""); + + std::string expected_decorations = + R"(OpDecorateStringGOOGLE %5 HlslSemanticGOOGLE "blah" +OpDecorateId %5 HlslCounterBufferGOOGLE %2 +OpDecorate %5 Aliased +)"; + EXPECT_THAT(ToText(decorations), expected_decorations); + + const std::string expected_binary = R"(OpCapability Shader +OpCapability Linkage +OpExtension "SPV_GOOGLE_hlsl_functionality1" +OpExtension "SPV_GOOGLE_decorate_string" +OpMemoryModel Logical GLSL450 +OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE "blah" +OpDecorateId %1 HlslCounterBufferGOOGLE %2 +OpDecorate %1 Aliased +OpDecorateStringGOOGLE %5 HlslSemanticGOOGLE "blah" +OpDecorateId %5 HlslCounterBufferGOOGLE %2 +OpDecorate %5 Aliased +%3 = OpTypeInt 32 0 +%4 = OpTypePointer Uniform %3 +%1 = OpVariable %4 Uniform +%2 = OpVariable %4 Uniform +%5 = OpVariable %4 Uniform +)"; + EXPECT_THAT(ModuleToText(), expected_binary); +} + +TEST_F(DecorationManagerTest, HaveTheSameDecorationsWithoutGroupsTrue) { + 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->HaveTheSameDecorations(1u, 2u)); +} + +TEST_F(DecorationManagerTest, HaveTheSameDecorationsWithoutGroupsFalse) { + 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->HaveTheSameDecorations(1u, 2u)); +} + +TEST_F(DecorationManagerTest, HaveTheSameDecorationsIdWithoutGroupsTrue) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorateId %1 AlignmentId %nine +OpDecorateId %3 MaxByteOffsetId %nine +OpDecorateId %3 AlignmentId %nine +OpDecorateId %1 MaxByteOffsetId %nine +%u32 = OpTypeInt 32 0 +%nine = OpConstant %u32 9 +%1 = OpVariable %u32 Uniform +%3 = OpVariable %u32 Uniform +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_TRUE(decoManager->HaveTheSameDecorations(1u, 3u)); +} + +TEST_F(DecorationManagerTest, HaveTheSameDecorationsIdWithoutGroupsFalse) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorateId %1 AlignmentId %nine +OpDecorateId %2 MaxByteOffsetId %nine +OpDecorateId %2 AlignmentId %nine +%u32 = OpTypeInt 32 0 +%nine = OpConstant %u32 9 +%1 = OpVariable %u32 Uniform +%2 = OpVariable %u32 Uniform +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u)); +} + +TEST_F(DecorationManagerTest, HaveTheSameDecorationsStringWithoutGroupsTrue) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Linkage +OpExtension "SPV_GOOGLE_hlsl_functionality1" +OpExtension "SPV_GOOGLE_decorate_string" +OpMemoryModel Logical GLSL450 +OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE "hello" +OpDecorateStringGOOGLE %2 HlslSemanticGOOGLE "world" +OpDecorateStringGOOGLE %2 HlslSemanticGOOGLE "hello" +OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE "world" +%u32 = OpTypeInt 32 0 +%1 = OpVariable %u32 Uniform +%2 = OpVariable %u32 Uniform +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_TRUE(decoManager->HaveTheSameDecorations(1u, 2u)); +} + +TEST_F(DecorationManagerTest, HaveTheSameDecorationsStringWithoutGroupsFalse) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Linkage +OpExtension "SPV_GOOGLE_hlsl_functionality1" +OpExtension "SPV_GOOGLE_decorate_string" +OpMemoryModel Logical GLSL450 +OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE "hello" +OpDecorateStringGOOGLE %2 HlslSemanticGOOGLE "world" +OpDecorateStringGOOGLE %2 HlslSemanticGOOGLE "hello" +%u32 = OpTypeInt 32 0 +%1 = OpVariable %u32 Uniform +%2 = OpVariable %u32 Uniform +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u)); +} + +TEST_F(DecorationManagerTest, HaveTheSameDecorationsWithGroupsTrue) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 Restrict +OpDecorate %2 Constant +OpDecorate %1 Constant +OpDecorate %3 Restrict +%3 = OpDecorationGroup +OpGroupDecorate %3 %2 +OpDecorate %4 Invariant +%4 = OpDecorationGroup +OpGroupDecorate %4 %1 %2 +%u32 = OpTypeInt 32 0 +%1 = OpVariable %u32 Uniform +%2 = OpVariable %u32 Uniform +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_TRUE(decoManager->HaveTheSameDecorations(1u, 2u)); +} + +TEST_F(DecorationManagerTest, HaveTheSameDecorationsWithGroupsFalse) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 Restrict +OpDecorate %2 Constant +OpDecorate %1 Constant +OpDecorate %4 Invariant +%4 = OpDecorationGroup +OpGroupDecorate %4 %1 %2 +%u32 = OpTypeInt 32 0 +%1 = OpVariable %u32 Uniform +%2 = OpVariable %u32 Uniform +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u)); +} + +TEST_F(DecorationManagerTest, HaveTheSameDecorationsDuplicateDecorations) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 Constant +OpDecorate %2 Constant +OpDecorate %2 Constant +%u32 = OpTypeInt 32 0 +%1 = OpVariable %u32 Uniform +%2 = OpVariable %u32 Uniform +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_TRUE(decoManager->HaveTheSameDecorations(1u, 2u)); +} + +TEST_F(DecorationManagerTest, HaveTheSameDecorationsDifferentVariations) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 Location 0 +OpDecorate %2 Location 1 +%u32 = OpTypeInt 32 0 +%1 = OpVariable %u32 Uniform +%2 = OpVariable %u32 Uniform +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u)); +} + +TEST_F(DecorationManagerTest, + HaveTheSameDecorationsDuplicateMemberDecorations) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpMemberDecorate %1 0 Location 0 +OpMemberDecorate %2 0 Location 0 +OpMemberDecorate %2 0 Location 0 +%u32 = OpTypeInt 32 0 +%1 = OpTypeStruct %u32 %u32 +%2 = OpTypeStruct %u32 %u32 +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_TRUE(decoManager->HaveTheSameDecorations(1u, 2u)); +} + +TEST_F(DecorationManagerTest, + HaveTheSameDecorationsDifferentMemberSameDecoration) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpMemberDecorate %1 0 Location 0 +OpMemberDecorate %2 1 Location 0 +%u32 = OpTypeInt 32 0 +%1 = OpTypeStruct %u32 %u32 +%2 = OpTypeStruct %u32 %u32 +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u)); +} + +TEST_F(DecorationManagerTest, HaveTheSameDecorationsDifferentMemberVariations) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpMemberDecorate %1 0 Location 0 +OpMemberDecorate %2 0 Location 1 +%u32 = OpTypeInt 32 0 +%1 = OpTypeStruct %u32 %u32 +%2 = OpTypeStruct %u32 %u32 +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u)); +} + +TEST_F(DecorationManagerTest, HaveTheSameDecorationsDuplicateIdDecorations) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorateId %1 AlignmentId %2 +OpDecorateId %3 AlignmentId %2 +OpDecorateId %3 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_TRUE(decoManager->HaveTheSameDecorations(1u, 3u)); +} + +TEST_F(DecorationManagerTest, + HaveTheSameDecorationsDuplicateStringDecorations) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpExtension "SPV_GOOGLE_hlsl_functionality1" +OpExtension "SPV_GOOGLE_decorate_string" +OpMemoryModel Logical GLSL450 +OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE "hello" +OpDecorateStringGOOGLE %2 HlslSemanticGOOGLE "hello" +OpDecorateStringGOOGLE %2 HlslSemanticGOOGLE "hello" +%u32 = OpTypeInt 32 0 +%1 = OpVariable %u32 Uniform +%2 = OpVariable %u32 Uniform +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_TRUE(decoManager->HaveTheSameDecorations(1u, 2u)); +} + +TEST_F(DecorationManagerTest, HaveTheSameDecorationsDifferentIdVariations) { + 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 0 +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u)); +} + +TEST_F(DecorationManagerTest, HaveTheSameDecorationsDifferentStringVariations) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpExtension "SPV_GOOGLE_hlsl_functionality1" +OpExtension "SPV_GOOGLE_decorate_string" +OpMemoryModel Logical GLSL450 +OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE "hello" +OpDecorateStringGOOGLE %2 HlslSemanticGOOGLE "world" +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u)); +} + +TEST_F(DecorationManagerTest, HaveTheSameDecorationsLeftSymmetry) { + // Left being a subset of right is not enough. + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 Constant +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->HaveTheSameDecorations(1u, 2u)); +} + +TEST_F(DecorationManagerTest, HaveTheSameDecorationsRightSymmetry) { + // Right being a subset of left is not enough. + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 Constant +OpDecorate %1 Restrict +OpDecorate %2 Constant +OpDecorate %2 Constant +%u32 = OpTypeInt 32 0 +%1 = OpVariable %u32 Uniform +%2 = OpVariable %u32 Uniform +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u)); +} + +TEST_F(DecorationManagerTest, HaveTheSameDecorationIdsLeftSymmetry) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorateId %1 AlignmentId %nine +OpDecorateId %1 AlignmentId %nine +OpDecorateId %2 AlignmentId %nine +OpDecorateId %2 MaxByteOffsetId %nine +%u32 = OpTypeInt 32 0 +%nine = OpConstant %u32 9 +%1 = OpVariable %u32 Uniform +%2 = OpVariable %u32 Uniform +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u)); +} + +TEST_F(DecorationManagerTest, HaveTheSameDecorationIdsRightSymmetry) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorateId %1 AlignmentId %nine +OpDecorateId %1 MaxByteOffsetId %nine +OpDecorateId %2 AlignmentId %nine +OpDecorateId %2 AlignmentId %nine +%u32 = OpTypeInt 32 0 +%nine = OpConstant %u32 9 +%1 = OpVariable %u32 Uniform +%2 = OpVariable %u32 Uniform +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u)); +} + +TEST_F(DecorationManagerTest, HaveTheSameDecorationStringsLeftSymmetry) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Linkage +OpExtension "SPV_GOOGLE_hlsl_functionality1" +OpExtension "SPV_GOOGLE_decorate_string" +OpMemoryModel Logical GLSL450 +OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE "hello" +OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE "hello" +OpDecorateStringGOOGLE %2 HlslSemanticGOOGLE "hello" +OpDecorateStringGOOGLE %2 HlslSemanticGOOGLE "world" +%u32 = OpTypeInt 32 0 +%1 = OpVariable %u32 Uniform +%2 = OpVariable %u32 Uniform +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u)); +} + +TEST_F(DecorationManagerTest, HaveTheSameDecorationStringsRightSymmetry) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Linkage +OpExtension "SPV_GOOGLE_hlsl_functionality1" +OpExtension "SPV_GOOGLE_decorate_string" +OpMemoryModel Logical GLSL450 +OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE "hello" +OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE "world" +OpDecorateStringGOOGLE %2 HlslSemanticGOOGLE "hello" +OpDecorateStringGOOGLE %2 HlslSemanticGOOGLE "hello" +%u32 = OpTypeInt 32 0 +%1 = OpVariable %u32 Uniform +%2 = OpVariable %u32 Uniform +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u)); +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/def_use_test.cpp b/3rdparty/spirv-tools/test/opt/def_use_test.cpp new file mode 100644 index 000000000..cdc829be1 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/def_use_test.cpp @@ -0,0 +1,1713 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include +#include + +#include "opt/build_module.h" +#include "opt/def_use_manager.h" +#include "opt/ir_context.h" +#include "opt/module.h" +#include "pass_fixture.h" +#include "pass_utils.h" +#include "spirv-tools/libspirv.hpp" + +namespace { + +using ::testing::Contains; +using ::testing::UnorderedElementsAre; +using ::testing::UnorderedElementsAreArray; + +using namespace spvtools; +using spvtools::opt::analysis::DefUseManager; + +// Returns the number of uses of |id|. +uint32_t NumUses(const std::unique_ptr& context, uint32_t id) { + uint32_t count = 0; + context->get_def_use_mgr()->ForEachUse( + id, [&count](ir::Instruction*, uint32_t) { ++count; }); + return count; +} + +// Returns the opcode of each use of |id|. +// +// If |id| is used multiple times in a single instruction, that instruction's +// opcode will appear a corresponding number of times. +std::vector GetUseOpcodes(const std::unique_ptr& context, + uint32_t id) { + std::vector opcodes; + context->get_def_use_mgr()->ForEachUse( + id, [&opcodes](ir::Instruction* user, uint32_t) { + opcodes.push_back(user->opcode()); + }); + return opcodes; +} + +// Disassembles the given |inst| and returns the disassembly. +std::string DisassembleInst(ir::Instruction* inst) { + SpirvTools tools(SPV_ENV_UNIVERSAL_1_1); + + std::vector binary; + // We need this to generate the necessary header in the binary. + tools.Assemble("", &binary); + inst->ToBinaryWithoutAttachedDebugInsts(&binary); + + std::string text; + // We'll need to check the underlying id numbers. + // So turn off friendly names for ids. + tools.Disassemble(binary, &text, SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + while (!text.empty() && text.back() == '\n') text.pop_back(); + return text; +} + +// A struct for holding expected id defs and uses. +struct InstDefUse { + using IdInstPair = std::pair; + using IdInstsPair = std::pair>; + + // Ids and their corresponding def instructions. + std::vector defs; + // Ids and their corresponding use instructions. + std::vector uses; +}; + +// Checks that the |actual_defs| and |actual_uses| are in accord with +// |expected_defs_uses|. +void CheckDef(const InstDefUse& expected_defs_uses, + const DefUseManager::IdToDefMap& actual_defs) { + // Check defs. + ASSERT_EQ(expected_defs_uses.defs.size(), actual_defs.size()); + for (uint32_t i = 0; i < expected_defs_uses.defs.size(); ++i) { + const auto id = expected_defs_uses.defs[i].first; + const auto expected_def = expected_defs_uses.defs[i].second; + ASSERT_EQ(1u, actual_defs.count(id)) << "expected to def id [" << id << "]"; + auto def = actual_defs.at(id); + if (def->opcode() != SpvOpConstant) { + // Constants don't disassemble properly without a full context. + EXPECT_EQ(expected_def, DisassembleInst(actual_defs.at(id))); + } + } +} + +using UserMap = std::unordered_map>; + +// Creates a mapping of all definitions to their users (except OpConstant). +// +// OpConstants are skipped because they cannot be disassembled in isolation. +UserMap BuildAllUsers(const DefUseManager* mgr, uint32_t idBound) { + UserMap userMap; + for (uint32_t id = 0; id != idBound; ++id) { + if (mgr->GetDef(id)) { + mgr->ForEachUser(id, [id, &userMap](ir::Instruction* user) { + if (user->opcode() != SpvOpConstant) { + userMap[id].push_back(user); + } + }); + } + } + return userMap; +} + +// Constants don't disassemble properly without a full context, so skip them as +// checks. +void CheckUse(const InstDefUse& expected_defs_uses, const DefUseManager* mgr, + uint32_t idBound) { + UserMap actual_uses = BuildAllUsers(mgr, idBound); + // Check uses. + ASSERT_EQ(expected_defs_uses.uses.size(), actual_uses.size()); + for (uint32_t i = 0; i < expected_defs_uses.uses.size(); ++i) { + const auto id = expected_defs_uses.uses[i].first; + const auto& expected_uses = expected_defs_uses.uses[i].second; + + ASSERT_EQ(1u, actual_uses.count(id)) << "expected to use id [" << id << "]"; + const auto& uses = actual_uses.at(id); + + ASSERT_EQ(expected_uses.size(), uses.size()) + << "id [" << id << "] # uses: expected: " << expected_uses.size() + << " actual: " << uses.size(); + + std::vector actual_uses_disassembled; + for (const auto actual_use : uses) { + actual_uses_disassembled.emplace_back(DisassembleInst(actual_use)); + } + EXPECT_THAT(actual_uses_disassembled, + UnorderedElementsAreArray(expected_uses)); + } +} + +// The following test case mimics how LLVM handles induction variables. +// But, yeah, it's not very readable. However, we only care about the id +// defs and uses. So, no need to make sure this is valid OpPhi construct. +const char kOpPhiTestFunction[] = + " %1 = OpTypeVoid " + " %6 = OpTypeInt 32 0 " + "%10 = OpTypeFloat 32 " + "%16 = OpTypeBool " + " %3 = OpTypeFunction %1 " + " %8 = OpConstant %6 0 " + "%18 = OpConstant %6 1 " + "%12 = OpConstant %10 1.0 " + " %2 = OpFunction %1 None %3 " + " %4 = OpLabel " + " OpBranch %5 " + + " %5 = OpLabel " + " %7 = OpPhi %6 %8 %4 %9 %5 " + "%11 = OpPhi %10 %12 %4 %13 %5 " + " %9 = OpIAdd %6 %7 %8 " + "%13 = OpFAdd %10 %11 %12 " + "%17 = OpSLessThan %16 %7 %18 " + " OpLoopMerge %19 %5 None " + " OpBranchConditional %17 %5 %19 " + + "%19 = OpLabel " + " OpReturn " + " OpFunctionEnd"; + +struct ParseDefUseCase { + const char* text; + InstDefUse du; +}; + +using ParseDefUseTest = ::testing::TestWithParam; + +TEST_P(ParseDefUseTest, Case) { + const auto& tc = GetParam(); + + // Build module. + const std::vector text = {tc.text}; + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, JoinAllInsts(text), + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ASSERT_NE(nullptr, context); + + // Analyze def and use. + opt::analysis::DefUseManager manager(context->module()); + + CheckDef(tc.du, manager.id_to_defs()); + CheckUse(tc.du, &manager, context->module()->IdBound()); +} + +// clang-format off +INSTANTIATE_TEST_CASE_P( + TestCase, ParseDefUseTest, + ::testing::ValuesIn(std::vector{ + {"", {{}, {}}}, // no instruction + {"OpMemoryModel Logical GLSL450", {{}, {}}}, // no def and use + { // single def, no use + "%1 = OpString \"wow\"", + { + {{1, "%1 = OpString \"wow\""}}, // defs + {} // uses + } + }, + { // multiple def, no use + "%1 = OpString \"hello\" " + "%2 = OpString \"world\" " + "%3 = OpTypeVoid", + { + { // defs + {1, "%1 = OpString \"hello\""}, + {2, "%2 = OpString \"world\""}, + {3, "%3 = OpTypeVoid"}, + }, + {} // uses + } + }, + { // multiple def, multiple use + "%1 = OpTypeBool " + "%2 = OpTypeVector %1 3 " + "%3 = OpTypeMatrix %2 3", + { + { // defs + {1, "%1 = OpTypeBool"}, + {2, "%2 = OpTypeVector %1 3"}, + {3, "%3 = OpTypeMatrix %2 3"}, + }, + { // uses + {1, {"%2 = OpTypeVector %1 3"}}, + {2, {"%3 = OpTypeMatrix %2 3"}}, + } + } + }, + { // multiple use of the same id + "%1 = OpTypeBool " + "%2 = OpTypeVector %1 2 " + "%3 = OpTypeVector %1 3 " + "%4 = OpTypeVector %1 4", + { + { // defs + {1, "%1 = OpTypeBool"}, + {2, "%2 = OpTypeVector %1 2"}, + {3, "%3 = OpTypeVector %1 3"}, + {4, "%4 = OpTypeVector %1 4"}, + }, + { // uses + {1, + { + "%2 = OpTypeVector %1 2", + "%3 = OpTypeVector %1 3", + "%4 = OpTypeVector %1 4", + } + }, + } + } + }, + { // labels + "%1 = OpTypeVoid " + "%2 = OpTypeBool " + "%3 = OpTypeFunction %1 " + "%4 = OpConstantTrue %2 " + "%5 = OpFunction %1 None %3 " + + "%6 = OpLabel " + "OpBranchConditional %4 %7 %8 " + + "%7 = OpLabel " + "OpBranch %7 " + + "%8 = OpLabel " + "OpReturn " + + "OpFunctionEnd", + { + { // defs + {1, "%1 = OpTypeVoid"}, + {2, "%2 = OpTypeBool"}, + {3, "%3 = OpTypeFunction %1"}, + {4, "%4 = OpConstantTrue %2"}, + {5, "%5 = OpFunction %1 None %3"}, + {6, "%6 = OpLabel"}, + {7, "%7 = OpLabel"}, + {8, "%8 = OpLabel"}, + }, + { // uses + {1, { + "%3 = OpTypeFunction %1", + "%5 = OpFunction %1 None %3", + } + }, + {2, {"%4 = OpConstantTrue %2"}}, + {3, {"%5 = OpFunction %1 None %3"}}, + {4, {"OpBranchConditional %4 %7 %8"}}, + {7, + { + "OpBranchConditional %4 %7 %8", + "OpBranch %7", + } + }, + {8, {"OpBranchConditional %4 %7 %8"}}, + } + } + }, + { // cross function + "%1 = OpTypeBool " + "%3 = OpTypeFunction %1 " + "%2 = OpFunction %1 None %3 " + + "%4 = OpLabel " + "%5 = OpVariable %1 Function " + "%6 = OpFunctionCall %1 %2 %5 " + "OpReturnValue %6 " + + "OpFunctionEnd", + { + { // defs + {1, "%1 = OpTypeBool"}, + {2, "%2 = OpFunction %1 None %3"}, + {3, "%3 = OpTypeFunction %1"}, + {4, "%4 = OpLabel"}, + {5, "%5 = OpVariable %1 Function"}, + {6, "%6 = OpFunctionCall %1 %2 %5"}, + }, + { // uses + {1, + { + "%2 = OpFunction %1 None %3", + "%3 = OpTypeFunction %1", + "%5 = OpVariable %1 Function", + "%6 = OpFunctionCall %1 %2 %5", + } + }, + {2, {"%6 = OpFunctionCall %1 %2 %5"}}, + {3, {"%2 = OpFunction %1 None %3"}}, + {5, {"%6 = OpFunctionCall %1 %2 %5"}}, + {6, {"OpReturnValue %6"}}, + } + } + }, + { // selection merge and loop merge + "%1 = OpTypeVoid " + "%3 = OpTypeFunction %1 " + "%10 = OpTypeBool " + "%8 = OpConstantTrue %10 " + "%2 = OpFunction %1 None %3 " + + "%4 = OpLabel " + "OpLoopMerge %5 %4 None " + "OpBranch %6 " + + "%5 = OpLabel " + "OpReturn " + + "%6 = OpLabel " + "OpSelectionMerge %7 None " + "OpBranchConditional %8 %9 %7 " + + "%7 = OpLabel " + "OpReturn " + + "%9 = OpLabel " + "OpReturn " + + "OpFunctionEnd", + { + { // defs + {1, "%1 = OpTypeVoid"}, + {2, "%2 = OpFunction %1 None %3"}, + {3, "%3 = OpTypeFunction %1"}, + {4, "%4 = OpLabel"}, + {5, "%5 = OpLabel"}, + {6, "%6 = OpLabel"}, + {7, "%7 = OpLabel"}, + {8, "%8 = OpConstantTrue %10"}, + {9, "%9 = OpLabel"}, + {10, "%10 = OpTypeBool"}, + }, + { // uses + {1, + { + "%2 = OpFunction %1 None %3", + "%3 = OpTypeFunction %1", + } + }, + {3, {"%2 = OpFunction %1 None %3"}}, + {4, {"OpLoopMerge %5 %4 None"}}, + {5, {"OpLoopMerge %5 %4 None"}}, + {6, {"OpBranch %6"}}, + {7, + { + "OpSelectionMerge %7 None", + "OpBranchConditional %8 %9 %7", + } + }, + {8, {"OpBranchConditional %8 %9 %7"}}, + {9, {"OpBranchConditional %8 %9 %7"}}, + {10, {"%8 = OpConstantTrue %10"}}, + } + } + }, + { // Forward reference + "OpDecorate %1 Block " + "OpTypeForwardPointer %2 Input " + "%3 = OpTypeInt 32 0 " + "%1 = OpTypeStruct %3 " + "%2 = OpTypePointer Input %3", + { + { // defs + {1, "%1 = OpTypeStruct %3"}, + {2, "%2 = OpTypePointer Input %3"}, + {3, "%3 = OpTypeInt 32 0"}, + }, + { // uses + {1, {"OpDecorate %1 Block"}}, + {2, {"OpTypeForwardPointer %2 Input"}}, + {3, + { + "%1 = OpTypeStruct %3", + "%2 = OpTypePointer Input %3", + } + } + }, + }, + }, + { // OpPhi + kOpPhiTestFunction, + { + { // defs + {1, "%1 = OpTypeVoid"}, + {2, "%2 = OpFunction %1 None %3"}, + {3, "%3 = OpTypeFunction %1"}, + {4, "%4 = OpLabel"}, + {5, "%5 = OpLabel"}, + {6, "%6 = OpTypeInt 32 0"}, + {7, "%7 = OpPhi %6 %8 %4 %9 %5"}, + {8, "%8 = OpConstant %6 0"}, + {9, "%9 = OpIAdd %6 %7 %8"}, + {10, "%10 = OpTypeFloat 32"}, + {11, "%11 = OpPhi %10 %12 %4 %13 %5"}, + {12, "%12 = OpConstant %10 1.0"}, + {13, "%13 = OpFAdd %10 %11 %12"}, + {16, "%16 = OpTypeBool"}, + {17, "%17 = OpSLessThan %16 %7 %18"}, + {18, "%18 = OpConstant %6 1"}, + {19, "%19 = OpLabel"}, + }, + { // uses + {1, + { + "%2 = OpFunction %1 None %3", + "%3 = OpTypeFunction %1", + } + }, + {3, {"%2 = OpFunction %1 None %3"}}, + {4, + { + "%7 = OpPhi %6 %8 %4 %9 %5", + "%11 = OpPhi %10 %12 %4 %13 %5", + } + }, + {5, + { + "OpBranch %5", + "%7 = OpPhi %6 %8 %4 %9 %5", + "%11 = OpPhi %10 %12 %4 %13 %5", + "OpLoopMerge %19 %5 None", + "OpBranchConditional %17 %5 %19", + } + }, + {6, + { + // Can't check constants properly + //"%8 = OpConstant %6 0", + //"%18 = OpConstant %6 1", + "%7 = OpPhi %6 %8 %4 %9 %5", + "%9 = OpIAdd %6 %7 %8", + } + }, + {7, + { + "%9 = OpIAdd %6 %7 %8", + "%17 = OpSLessThan %16 %7 %18", + } + }, + {8, + { + "%7 = OpPhi %6 %8 %4 %9 %5", + "%9 = OpIAdd %6 %7 %8", + } + }, + {9, {"%7 = OpPhi %6 %8 %4 %9 %5"}}, + {10, + { + //"%12 = OpConstant %10 1.0", + "%11 = OpPhi %10 %12 %4 %13 %5", + "%13 = OpFAdd %10 %11 %12", + } + }, + {11, {"%13 = OpFAdd %10 %11 %12"}}, + {12, + { + "%11 = OpPhi %10 %12 %4 %13 %5", + "%13 = OpFAdd %10 %11 %12", + } + }, + {13, {"%11 = OpPhi %10 %12 %4 %13 %5"}}, + {16, {"%17 = OpSLessThan %16 %7 %18"}}, + {17, {"OpBranchConditional %17 %5 %19"}}, + {18, {"%17 = OpSLessThan %16 %7 %18"}}, + {19, + { + "OpLoopMerge %19 %5 None", + "OpBranchConditional %17 %5 %19", + } + }, + }, + }, + }, + { // OpPhi defining and referencing the same id. + "%1 = OpTypeBool " + "%3 = OpTypeFunction %1 " + "%2 = OpConstantTrue %1 " + "%4 = OpFunction %1 None %3 " + "%6 = OpLabel " + " OpBranch %7 " + "%7 = OpLabel " + "%8 = OpPhi %1 %8 %7 %2 %6 " // both defines and uses %8 + " OpBranch %7 " + " OpFunctionEnd", + { + { // defs + {1, "%1 = OpTypeBool"}, + {2, "%2 = OpConstantTrue %1"}, + {3, "%3 = OpTypeFunction %1"}, + {4, "%4 = OpFunction %1 None %3"}, + {6, "%6 = OpLabel"}, + {7, "%7 = OpLabel"}, + {8, "%8 = OpPhi %1 %8 %7 %2 %6"}, + }, + { // uses + {1, + { + "%2 = OpConstantTrue %1", + "%3 = OpTypeFunction %1", + "%4 = OpFunction %1 None %3", + "%8 = OpPhi %1 %8 %7 %2 %6", + } + }, + {2, {"%8 = OpPhi %1 %8 %7 %2 %6"}}, + {3, {"%4 = OpFunction %1 None %3"}}, + {6, {"%8 = OpPhi %1 %8 %7 %2 %6"}}, + {7, + { + "OpBranch %7", + "%8 = OpPhi %1 %8 %7 %2 %6", + "OpBranch %7", + } + }, + {8, {"%8 = OpPhi %1 %8 %7 %2 %6"}}, + }, + }, + }, + }) +); +// clang-format on + +struct ReplaceUseCase { + const char* before; + std::vector> candidates; + const char* after; + InstDefUse du; +}; + +using ReplaceUseTest = ::testing::TestWithParam; + +// Disassembles the given |module| and returns the disassembly. +std::string DisassembleModule(ir::Module* module) { + SpirvTools tools(SPV_ENV_UNIVERSAL_1_1); + + std::vector binary; + module->ToBinary(&binary, /* skip_nop = */ false); + + std::string text; + // We'll need to check the underlying id numbers. + // So turn off friendly names for ids. + tools.Disassemble(binary, &text, SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + while (!text.empty() && text.back() == '\n') text.pop_back(); + return text; +} + +TEST_P(ReplaceUseTest, Case) { + const auto& tc = GetParam(); + + // Build module. + const std::vector text = {tc.before}; + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, JoinAllInsts(text), + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ASSERT_NE(nullptr, context); + + // Force a re-build of def-use manager. + context->InvalidateAnalyses(ir::IRContext::Analysis::kAnalysisDefUse); + (void)context->get_def_use_mgr(); + + // Do the substitution. + for (const auto& candidate : tc.candidates) { + context->ReplaceAllUsesWith(candidate.first, candidate.second); + } + + EXPECT_EQ(tc.after, DisassembleModule(context->module())); + CheckDef(tc.du, context->get_def_use_mgr()->id_to_defs()); + CheckUse(tc.du, context->get_def_use_mgr(), context->module()->IdBound()); +} + +// clang-format off +INSTANTIATE_TEST_CASE_P( + TestCase, ReplaceUseTest, + ::testing::ValuesIn(std::vector{ + { // no use, no replace request + "", {}, "", {}, + }, + { // replace one use + "%1 = OpTypeBool " + "%2 = OpTypeVector %1 3 " + "%3 = OpTypeInt 32 0 ", + {{1, 3}}, + "%1 = OpTypeBool\n" + "%2 = OpTypeVector %3 3\n" + "%3 = OpTypeInt 32 0", + { + { // defs + {1, "%1 = OpTypeBool"}, + {2, "%2 = OpTypeVector %3 3"}, + {3, "%3 = OpTypeInt 32 0"}, + }, + { // uses + {3, {"%2 = OpTypeVector %3 3"}}, + }, + }, + }, + { // replace and then replace back + "%1 = OpTypeBool " + "%2 = OpTypeVector %1 3 " + "%3 = OpTypeInt 32 0", + {{1, 3}, {3, 1}}, + "%1 = OpTypeBool\n" + "%2 = OpTypeVector %1 3\n" + "%3 = OpTypeInt 32 0", + { + { // defs + {1, "%1 = OpTypeBool"}, + {2, "%2 = OpTypeVector %1 3"}, + {3, "%3 = OpTypeInt 32 0"}, + }, + { // uses + {1, {"%2 = OpTypeVector %1 3"}}, + }, + }, + }, + { // replace with the same id + "%1 = OpTypeBool " + "%2 = OpTypeVector %1 3", + {{1, 1}, {2, 2}, {3, 3}}, + "%1 = OpTypeBool\n" + "%2 = OpTypeVector %1 3", + { + { // defs + {1, "%1 = OpTypeBool"}, + {2, "%2 = OpTypeVector %1 3"}, + }, + { // uses + {1, {"%2 = OpTypeVector %1 3"}}, + }, + }, + }, + { // replace in sequence + "%1 = OpTypeBool " + "%2 = OpTypeVector %1 3 " + "%3 = OpTypeInt 32 0 " + "%4 = OpTypeInt 32 1 ", + {{1, 3}, {3, 4}}, + "%1 = OpTypeBool\n" + "%2 = OpTypeVector %4 3\n" + "%3 = OpTypeInt 32 0\n" + "%4 = OpTypeInt 32 1", + { + { // defs + {1, "%1 = OpTypeBool"}, + {2, "%2 = OpTypeVector %4 3"}, + {3, "%3 = OpTypeInt 32 0"}, + {4, "%4 = OpTypeInt 32 1"}, + }, + { // uses + {4, {"%2 = OpTypeVector %4 3"}}, + }, + }, + }, + { // replace multiple uses + "%1 = OpTypeBool " + "%2 = OpTypeVector %1 2 " + "%3 = OpTypeVector %1 3 " + "%4 = OpTypeVector %1 4 " + "%5 = OpTypeMatrix %2 2 " + "%6 = OpTypeMatrix %3 3 " + "%7 = OpTypeMatrix %4 4 " + "%8 = OpTypeInt 32 0 " + "%9 = OpTypeInt 32 1 " + "%10 = OpTypeInt 64 0", + {{1, 8}, {2, 9}, {4, 10}}, + "%1 = OpTypeBool\n" + "%2 = OpTypeVector %8 2\n" + "%3 = OpTypeVector %8 3\n" + "%4 = OpTypeVector %8 4\n" + "%5 = OpTypeMatrix %9 2\n" + "%6 = OpTypeMatrix %3 3\n" + "%7 = OpTypeMatrix %10 4\n" + "%8 = OpTypeInt 32 0\n" + "%9 = OpTypeInt 32 1\n" + "%10 = OpTypeInt 64 0", + { + { // defs + {1, "%1 = OpTypeBool"}, + {2, "%2 = OpTypeVector %8 2"}, + {3, "%3 = OpTypeVector %8 3"}, + {4, "%4 = OpTypeVector %8 4"}, + {5, "%5 = OpTypeMatrix %9 2"}, + {6, "%6 = OpTypeMatrix %3 3"}, + {7, "%7 = OpTypeMatrix %10 4"}, + {8, "%8 = OpTypeInt 32 0"}, + {9, "%9 = OpTypeInt 32 1"}, + {10, "%10 = OpTypeInt 64 0"}, + }, + { // uses + {8, + { + "%2 = OpTypeVector %8 2", + "%3 = OpTypeVector %8 3", + "%4 = OpTypeVector %8 4", + } + }, + {9, {"%5 = OpTypeMatrix %9 2"}}, + {3, {"%6 = OpTypeMatrix %3 3"}}, + {10, {"%7 = OpTypeMatrix %10 4"}}, + }, + }, + }, + { // OpPhi. + kOpPhiTestFunction, + // replace one id used by OpPhi, replace one id generated by OpPhi + {{9, 13}, {11, 9}}, + "%1 = OpTypeVoid\n" + "%6 = OpTypeInt 32 0\n" + "%10 = OpTypeFloat 32\n" + "%16 = OpTypeBool\n" + "%3 = OpTypeFunction %1\n" + "%8 = OpConstant %6 0\n" + "%18 = OpConstant %6 1\n" + "%12 = OpConstant %10 1\n" + "%2 = OpFunction %1 None %3\n" + "%4 = OpLabel\n" + "OpBranch %5\n" + + "%5 = OpLabel\n" + "%7 = OpPhi %6 %8 %4 %13 %5\n" // %9 -> %13 + "%11 = OpPhi %10 %12 %4 %13 %5\n" + "%9 = OpIAdd %6 %7 %8\n" + "%13 = OpFAdd %10 %9 %12\n" // %11 -> %9 + "%17 = OpSLessThan %16 %7 %18\n" + "OpLoopMerge %19 %5 None\n" + "OpBranchConditional %17 %5 %19\n" + + "%19 = OpLabel\n" + "OpReturn\n" + "OpFunctionEnd", + { + { // defs. + {1, "%1 = OpTypeVoid"}, + {2, "%2 = OpFunction %1 None %3"}, + {3, "%3 = OpTypeFunction %1"}, + {4, "%4 = OpLabel"}, + {5, "%5 = OpLabel"}, + {6, "%6 = OpTypeInt 32 0"}, + {7, "%7 = OpPhi %6 %8 %4 %13 %5"}, + {8, "%8 = OpConstant %6 0"}, + {9, "%9 = OpIAdd %6 %7 %8"}, + {10, "%10 = OpTypeFloat 32"}, + {11, "%11 = OpPhi %10 %12 %4 %13 %5"}, + {12, "%12 = OpConstant %10 1.0"}, + {13, "%13 = OpFAdd %10 %9 %12"}, + {16, "%16 = OpTypeBool"}, + {17, "%17 = OpSLessThan %16 %7 %18"}, + {18, "%18 = OpConstant %6 1"}, + {19, "%19 = OpLabel"}, + }, + { // uses + {1, + { + "%2 = OpFunction %1 None %3", + "%3 = OpTypeFunction %1", + } + }, + {3, {"%2 = OpFunction %1 None %3"}}, + {4, + { + "%7 = OpPhi %6 %8 %4 %13 %5", + "%11 = OpPhi %10 %12 %4 %13 %5", + } + }, + {5, + { + "OpBranch %5", + "%7 = OpPhi %6 %8 %4 %13 %5", + "%11 = OpPhi %10 %12 %4 %13 %5", + "OpLoopMerge %19 %5 None", + "OpBranchConditional %17 %5 %19", + } + }, + {6, + { + // Can't properly check constants + //"%8 = OpConstant %6 0", + //"%18 = OpConstant %6 1", + "%7 = OpPhi %6 %8 %4 %13 %5", + "%9 = OpIAdd %6 %7 %8" + } + }, + {7, + { + "%9 = OpIAdd %6 %7 %8", + "%17 = OpSLessThan %16 %7 %18", + } + }, + {8, + { + "%7 = OpPhi %6 %8 %4 %13 %5", + "%9 = OpIAdd %6 %7 %8", + } + }, + {9, {"%13 = OpFAdd %10 %9 %12"}}, // uses of %9 changed from %7 to %13 + {10, + { + "%11 = OpPhi %10 %12 %4 %13 %5", + //"%12 = OpConstant %10 1", + "%13 = OpFAdd %10 %9 %12" + } + }, + // no more uses of %11 + {12, + { + "%11 = OpPhi %10 %12 %4 %13 %5", + "%13 = OpFAdd %10 %9 %12" + } + }, + {13, { + "%7 = OpPhi %6 %8 %4 %13 %5", + "%11 = OpPhi %10 %12 %4 %13 %5", + } + }, + {16, {"%17 = OpSLessThan %16 %7 %18"}}, + {17, {"OpBranchConditional %17 %5 %19"}}, + {18, {"%17 = OpSLessThan %16 %7 %18"}}, + {19, + { + "OpLoopMerge %19 %5 None", + "OpBranchConditional %17 %5 %19", + } + }, + }, + }, + }, + { // OpPhi defining and referencing the same id. + "%1 = OpTypeBool " + "%3 = OpTypeFunction %1 " + "%2 = OpConstantTrue %1 " + + "%4 = OpFunction %3 None %1 " + "%6 = OpLabel " + " OpBranch %7 " + "%7 = OpLabel " + "%8 = OpPhi %1 %8 %7 %2 %6 " // both defines and uses %8 + " OpBranch %7 " + " OpFunctionEnd", + {{8, 2}}, + "%1 = OpTypeBool\n" + "%3 = OpTypeFunction %1\n" + "%2 = OpConstantTrue %1\n" + + "%4 = OpFunction %3 None %1\n" + "%6 = OpLabel\n" + "OpBranch %7\n" + "%7 = OpLabel\n" + "%8 = OpPhi %1 %2 %7 %2 %6\n" // use of %8 changed to %2 + "OpBranch %7\n" + "OpFunctionEnd", + { + { // defs + {1, "%1 = OpTypeBool"}, + {2, "%2 = OpConstantTrue %1"}, + {3, "%3 = OpTypeFunction %1"}, + {4, "%4 = OpFunction %3 None %1"}, + {6, "%6 = OpLabel"}, + {7, "%7 = OpLabel"}, + {8, "%8 = OpPhi %1 %2 %7 %2 %6"}, + }, + { // uses + {1, + { + "%2 = OpConstantTrue %1", + "%3 = OpTypeFunction %1", + "%4 = OpFunction %3 None %1", + "%8 = OpPhi %1 %2 %7 %2 %6", + } + }, + {2, + { + // Only checking users + "%8 = OpPhi %1 %2 %7 %2 %6", + } + }, + {3, {"%4 = OpFunction %3 None %1"}}, + {6, {"%8 = OpPhi %1 %2 %7 %2 %6"}}, + {7, + { + "OpBranch %7", + "%8 = OpPhi %1 %2 %7 %2 %6", + "OpBranch %7", + } + }, + // {8, {"%8 = OpPhi %1 %8 %7 %2 %6"}}, + }, + }, + }, + }) +); +// clang-format on + +struct KillDefCase { + const char* before; + std::vector ids_to_kill; + const char* after; + InstDefUse du; +}; + +using KillDefTest = ::testing::TestWithParam; + +TEST_P(KillDefTest, Case) { + const auto& tc = GetParam(); + + // Build module. + const std::vector text = {tc.before}; + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, JoinAllInsts(text), + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ASSERT_NE(nullptr, context); + + // Analyze def and use. + opt::analysis::DefUseManager manager(context->module()); + + // Do the substitution. + for (const auto id : tc.ids_to_kill) context->KillDef(id); + + EXPECT_EQ(tc.after, DisassembleModule(context->module())); + CheckDef(tc.du, context->get_def_use_mgr()->id_to_defs()); + CheckUse(tc.du, context->get_def_use_mgr(), context->module()->IdBound()); +} + +// clang-format off +INSTANTIATE_TEST_CASE_P( + TestCase, KillDefTest, + ::testing::ValuesIn(std::vector{ + { // no def, no use, no kill + "", {}, "", {} + }, + { // kill nothing + "%1 = OpTypeBool " + "%2 = OpTypeVector %1 2 " + "%3 = OpTypeVector %1 3 ", + {}, + "%1 = OpTypeBool\n" + "%2 = OpTypeVector %1 2\n" + "%3 = OpTypeVector %1 3", + { + { // defs + {1, "%1 = OpTypeBool"}, + {2, "%2 = OpTypeVector %1 2"}, + {3, "%3 = OpTypeVector %1 3"}, + }, + { // uses + {1, + { + "%2 = OpTypeVector %1 2", + "%3 = OpTypeVector %1 3", + } + }, + }, + }, + }, + { // kill id used, kill id not used, kill id not defined + "%1 = OpTypeBool " + "%2 = OpTypeVector %1 2 " + "%3 = OpTypeVector %1 3 " + "%4 = OpTypeVector %1 4 " + "%5 = OpTypeMatrix %3 3 " + "%6 = OpTypeMatrix %2 3", + {1, 3, 5, 10}, // ids to kill + "%2 = OpTypeVector %1 2\n" + "%4 = OpTypeVector %1 4\n" + "%6 = OpTypeMatrix %2 3", + { + { // defs + {2, "%2 = OpTypeVector %1 2"}, + {4, "%4 = OpTypeVector %1 4"}, + {6, "%6 = OpTypeMatrix %2 3"}, + }, + { // uses. %1 and %3 are both killed, so no uses + // recorded for them anymore. + {2, {"%6 = OpTypeMatrix %2 3"}}, + } + }, + }, + { // OpPhi. + kOpPhiTestFunction, + {9, 11}, // kill one id used by OpPhi, kill one id generated by OpPhi + "%1 = OpTypeVoid\n" + "%6 = OpTypeInt 32 0\n" + "%10 = OpTypeFloat 32\n" + "%16 = OpTypeBool\n" + "%3 = OpTypeFunction %1\n" + "%8 = OpConstant %6 0\n" + "%18 = OpConstant %6 1\n" + "%12 = OpConstant %10 1\n" + "%2 = OpFunction %1 None %3\n" + "%4 = OpLabel\n" + "OpBranch %5\n" + + "%5 = OpLabel\n" + "%7 = OpPhi %6 %8 %4 %9 %5\n" + "%13 = OpFAdd %10 %11 %12\n" + "%17 = OpSLessThan %16 %7 %18\n" + "OpLoopMerge %19 %5 None\n" + "OpBranchConditional %17 %5 %19\n" + + "%19 = OpLabel\n" + "OpReturn\n" + "OpFunctionEnd", + { + { // defs. %9 & %11 are killed. + {1, "%1 = OpTypeVoid"}, + {2, "%2 = OpFunction %1 None %3"}, + {3, "%3 = OpTypeFunction %1"}, + {4, "%4 = OpLabel"}, + {5, "%5 = OpLabel"}, + {6, "%6 = OpTypeInt 32 0"}, + {7, "%7 = OpPhi %6 %8 %4 %9 %5"}, + {8, "%8 = OpConstant %6 0"}, + {10, "%10 = OpTypeFloat 32"}, + {12, "%12 = OpConstant %10 1.0"}, + {13, "%13 = OpFAdd %10 %11 %12"}, + {16, "%16 = OpTypeBool"}, + {17, "%17 = OpSLessThan %16 %7 %18"}, + {18, "%18 = OpConstant %6 1"}, + {19, "%19 = OpLabel"}, + }, + { // uses + {1, + { + "%2 = OpFunction %1 None %3", + "%3 = OpTypeFunction %1", + } + }, + {3, {"%2 = OpFunction %1 None %3"}}, + {4, + { + "%7 = OpPhi %6 %8 %4 %9 %5", + //"%11 = OpPhi %10 %12 %4 %13 %5", + } + }, + {5, + { + "OpBranch %5", + "%7 = OpPhi %6 %8 %4 %9 %5", + //"%11 = OpPhi %10 %12 %4 %13 %5", + "OpLoopMerge %19 %5 None", + "OpBranchConditional %17 %5 %19", + } + }, + {6, + { + // Can't properly check constants + //"%8 = OpConstant %6 0", + //"%18 = OpConstant %6 1", + "%7 = OpPhi %6 %8 %4 %9 %5", + //"%9 = OpIAdd %6 %7 %8" + } + }, + {7, {"%17 = OpSLessThan %16 %7 %18"}}, + {8, + { + "%7 = OpPhi %6 %8 %4 %9 %5", + //"%9 = OpIAdd %6 %7 %8", + } + }, + // {9, {"%7 = OpPhi %6 %8 %4 %13 %5"}}, + {10, + { + //"%11 = OpPhi %10 %12 %4 %13 %5", + //"%12 = OpConstant %10 1", + "%13 = OpFAdd %10 %11 %12" + } + }, + // {11, {"%13 = OpFAdd %10 %11 %12"}}, + {12, + { + //"%11 = OpPhi %10 %12 %4 %13 %5", + "%13 = OpFAdd %10 %11 %12" + } + }, + //{13, {"%11 = OpPhi %10 %12 %4 %13 %5"}}, + {16, {"%17 = OpSLessThan %16 %7 %18"}}, + {17, {"OpBranchConditional %17 %5 %19"}}, + {18, {"%17 = OpSLessThan %16 %7 %18"}}, + {19, + { + "OpLoopMerge %19 %5 None", + "OpBranchConditional %17 %5 %19", + } + }, + }, + }, + }, + { // OpPhi defining and referencing the same id. + "%1 = OpTypeBool " + "%3 = OpTypeFunction %1 " + "%2 = OpConstantTrue %1 " + "%4 = OpFunction %3 None %1 " + "%6 = OpLabel " + " OpBranch %7 " + "%7 = OpLabel " + "%8 = OpPhi %1 %8 %7 %2 %6 " // both defines and uses %8 + " OpBranch %7 " + " OpFunctionEnd", + {8}, + "%1 = OpTypeBool\n" + "%3 = OpTypeFunction %1\n" + "%2 = OpConstantTrue %1\n" + + "%4 = OpFunction %3 None %1\n" + "%6 = OpLabel\n" + "OpBranch %7\n" + "%7 = OpLabel\n" + "OpBranch %7\n" + "OpFunctionEnd", + { + { // defs + {1, "%1 = OpTypeBool"}, + {2, "%2 = OpConstantTrue %1"}, + {3, "%3 = OpTypeFunction %1"}, + {4, "%4 = OpFunction %3 None %1"}, + {6, "%6 = OpLabel"}, + {7, "%7 = OpLabel"}, + // {8, "%8 = OpPhi %1 %8 %7 %2 %6"}, + }, + { // uses + {1, + { + "%2 = OpConstantTrue %1", + "%3 = OpTypeFunction %1", + "%4 = OpFunction %3 None %1", + // "%8 = OpPhi %1 %8 %7 %2 %6", + } + }, + // {2, {"%8 = OpPhi %1 %8 %7 %2 %6"}}, + {3, {"%4 = OpFunction %3 None %1"}}, + // {6, {"%8 = OpPhi %1 %8 %7 %2 %6"}}, + {7, + { + "OpBranch %7", + // "%8 = OpPhi %1 %8 %7 %2 %6", + "OpBranch %7", + } + }, + // {8, {"%8 = OpPhi %1 %8 %7 %2 %6"}}, + }, + }, + }, + }) +); +// clang-format on + +TEST(DefUseTest, OpSwitch) { + // Because disassembler has basic type check for OpSwitch's selector, we + // cannot use the DisassembleInst() in the above. Thus, this special spotcheck + // test case. + + const char original_text[] = + // int64 f(int64 v) { + // switch (v) { + // case 1: break; + // case -4294967296: break; + // case 9223372036854775807: break; + // default: break; + // } + // return v; + // } + " %1 = OpTypeInt 64 1 " + " %3 = OpTypePointer Input %1 " + " %2 = OpFunction %1 None %3 " // %3 is int64(int64)* + " %4 = OpFunctionParameter %1 " + " %5 = OpLabel " + " %6 = OpLoad %1 %4 " // selector value + " OpSelectionMerge %7 None " + " OpSwitch %6 %8 " + " 1 %9 " // 1 + " -4294967296 %10 " // -2^32 + " 9223372036854775807 %11 " // 2^63-1 + " %8 = OpLabel " // default + " OpBranch %7 " + " %9 = OpLabel " + " OpBranch %7 " + "%10 = OpLabel " + " OpBranch %7 " + "%11 = OpLabel " + " OpBranch %7 " + " %7 = OpLabel " + " OpReturnValue %6 " + " OpFunctionEnd"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, original_text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ASSERT_NE(nullptr, context); + + // Force a re-build of def-use manager. + context->InvalidateAnalyses(ir::IRContext::Analysis::kAnalysisDefUse); + (void)context->get_def_use_mgr(); + + // Do a bunch replacements. + context->ReplaceAllUsesWith(11, 7); // to existing id + context->ReplaceAllUsesWith(10, 11); // to existing id + context->ReplaceAllUsesWith(9, 10); // to existing id + + // clang-format off + const char modified_text[] = + "%1 = OpTypeInt 64 1\n" + "%3 = OpTypePointer Input %1\n" + "%2 = OpFunction %1 None %3\n" // %3 is int64(int64)* + "%4 = OpFunctionParameter %1\n" + "%5 = OpLabel\n" + "%6 = OpLoad %1 %4\n" // selector value + "OpSelectionMerge %7 None\n" + "OpSwitch %6 %8 1 %10 -4294967296 %11 9223372036854775807 %7\n" // changed! + "%8 = OpLabel\n" // default + "OpBranch %7\n" + "%9 = OpLabel\n" + "OpBranch %7\n" + "%10 = OpLabel\n" + "OpBranch %7\n" + "%11 = OpLabel\n" + "OpBranch %7\n" + "%7 = OpLabel\n" + "OpReturnValue %6\n" + "OpFunctionEnd"; + // clang-format on + + EXPECT_EQ(modified_text, DisassembleModule(context->module())); + + InstDefUse def_uses = {}; + def_uses.defs = { + {1, "%1 = OpTypeInt 64 1"}, + {2, "%2 = OpFunction %1 None %3"}, + {3, "%3 = OpTypePointer Input %1"}, + {4, "%4 = OpFunctionParameter %1"}, + {5, "%5 = OpLabel"}, + {6, "%6 = OpLoad %1 %4"}, + {7, "%7 = OpLabel"}, + {8, "%8 = OpLabel"}, + {9, "%9 = OpLabel"}, + {10, "%10 = OpLabel"}, + {11, "%11 = OpLabel"}, + }; + CheckDef(def_uses, context->get_def_use_mgr()->id_to_defs()); + + { + EXPECT_EQ(2u, NumUses(context, 6)); + std::vector opcodes = GetUseOpcodes(context, 6u); + EXPECT_THAT(opcodes, UnorderedElementsAre(SpvOpSwitch, SpvOpReturnValue)); + } + { + EXPECT_EQ(6u, NumUses(context, 7)); + std::vector opcodes = GetUseOpcodes(context, 7u); + // OpSwitch is now a user of %7. + EXPECT_THAT(opcodes, UnorderedElementsAre(SpvOpSelectionMerge, SpvOpBranch, + SpvOpBranch, SpvOpBranch, + SpvOpBranch, SpvOpSwitch)); + } + // Check all ids only used by OpSwitch after replacement. + for (const auto id : {8u, 10u, 11u}) { + EXPECT_EQ(1u, NumUses(context, id)); + EXPECT_EQ(SpvOpSwitch, GetUseOpcodes(context, id).back()); + } +} + +// Test case for analyzing individual instructions. +struct AnalyzeInstDefUseTestCase { + const char* module_text; + InstDefUse expected_define_use; +}; + +using AnalyzeInstDefUseTest = + ::testing::TestWithParam; + +// Test the analyzing result for individual instructions. +TEST_P(AnalyzeInstDefUseTest, Case) { + auto tc = GetParam(); + + // Build module. + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.module_text); + ASSERT_NE(nullptr, context); + + // Analyze the instructions. + opt::analysis::DefUseManager manager(context->module()); + + CheckDef(tc.expected_define_use, manager.id_to_defs()); + CheckUse(tc.expected_define_use, &manager, context->module()->IdBound()); + // CheckUse(tc.expected_define_use, manager.id_to_uses()); +} + +// clang-format off +INSTANTIATE_TEST_CASE_P( + TestCase, AnalyzeInstDefUseTest, + ::testing::ValuesIn(std::vector{ + { // A type declaring instruction. + "%1 = OpTypeInt 32 1", + { + // defs + {{1, "%1 = OpTypeInt 32 1"}}, + {}, // no uses + }, + }, + { // A type declaring instruction and a constant value. + "%1 = OpTypeBool " + "%2 = OpConstantTrue %1", + { + { // defs + {1, "%1 = OpTypeBool"}, + {2, "%2 = OpConstantTrue %1"}, + }, + { // uses + {1, {"%2 = OpConstantTrue %1"}}, + }, + }, + }, + })); +// clang-format on + +using AnalyzeInstDefUse = ::testing::Test; + +TEST(AnalyzeInstDefUse, UseWithNoResultId) { + ir::IRContext context(SPV_ENV_UNIVERSAL_1_2, nullptr); + + // Analyze the instructions. + opt::analysis::DefUseManager manager(context.module()); + + ir::Instruction label(&context, SpvOpLabel, 0, 2, {}); + manager.AnalyzeInstDefUse(&label); + + ir::Instruction branch(&context, SpvOpBranch, 0, 0, + {{SPV_OPERAND_TYPE_ID, {2}}}); + manager.AnalyzeInstDefUse(&branch); + context.module()->SetIdBound(3); + + InstDefUse expected = { + // defs + { + {2, "%2 = OpLabel"}, + }, + // uses + {{2, {"OpBranch %2"}}}, + }; + + CheckDef(expected, manager.id_to_defs()); + CheckUse(expected, &manager, context.module()->IdBound()); +} + +TEST(AnalyzeInstDefUse, AddNewInstruction) { + const std::string input = "%1 = OpTypeBool"; + + // Build module. + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, input); + ASSERT_NE(nullptr, context); + + // Analyze the instructions. + opt::analysis::DefUseManager manager(context->module()); + + ir::Instruction newInst(context.get(), SpvOpConstantTrue, 1, 2, {}); + manager.AnalyzeInstDefUse(&newInst); + + InstDefUse expected = { + { + // defs + {1, "%1 = OpTypeBool"}, + {2, "%2 = OpConstantTrue %1"}, + }, + { + // uses + {1, {"%2 = OpConstantTrue %1"}}, + }, + }; + + CheckDef(expected, manager.id_to_defs()); + CheckUse(expected, &manager, context->module()->IdBound()); +} + +struct KillInstTestCase { + const char* before; + std::unordered_set indices_for_inst_to_kill; + const char* after; + InstDefUse expected_define_use; +}; + +using KillInstTest = ::testing::TestWithParam; + +TEST_P(KillInstTest, Case) { + auto tc = GetParam(); + + // Build module. + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.before, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ASSERT_NE(nullptr, context); + + // Force a re-build of the def-use manager. + context->InvalidateAnalyses(ir::IRContext::Analysis::kAnalysisDefUse); + (void)context->get_def_use_mgr(); + + // KillInst + context->module()->ForEachInst([&tc, &context](ir::Instruction* inst) { + if (tc.indices_for_inst_to_kill.count(inst->result_id())) { + context->KillInst(inst); + } + }); + + EXPECT_EQ(tc.after, DisassembleModule(context->module())); + CheckDef(tc.expected_define_use, context->get_def_use_mgr()->id_to_defs()); + CheckUse(tc.expected_define_use, context->get_def_use_mgr(), + context->module()->IdBound()); +} + +// clang-format off +INSTANTIATE_TEST_CASE_P( + TestCase, KillInstTest, + ::testing::ValuesIn(std::vector{ + // Kill id defining instructions. + { + "%3 = OpTypeVoid " + "%1 = OpTypeFunction %3 " + "%2 = OpFunction %1 None %3 " + "%4 = OpLabel " + " OpBranch %5 " + "%5 = OpLabel " + " OpBranch %6 " + "%6 = OpLabel " + " OpBranch %4 " + "%7 = OpLabel " + " OpReturn " + " OpFunctionEnd", + {3, 5, 7}, + "%1 = OpTypeFunction %3\n" + "%2 = OpFunction %1 None %3\n" + "%4 = OpLabel\n" + "OpBranch %5\n" + "OpNop\n" + "OpBranch %6\n" + "%6 = OpLabel\n" + "OpBranch %4\n" + "OpNop\n" + "OpReturn\n" + "OpFunctionEnd", + { + // defs + { + {1, "%1 = OpTypeFunction %3"}, + {2, "%2 = OpFunction %1 None %3"}, + {4, "%4 = OpLabel"}, + {6, "%6 = OpLabel"}, + }, + // uses + { + {1, {"%2 = OpFunction %1 None %3"}}, + {4, {"OpBranch %4"}}, + {6, {"OpBranch %6"}}, + } + } + }, + // Kill instructions that do not have result ids. + { + "%3 = OpTypeVoid " + "%1 = OpTypeFunction %3 " + "%2 = OpFunction %1 None %3 " + "%4 = OpLabel " + " OpBranch %5 " + "%5 = OpLabel " + " OpBranch %6 " + "%6 = OpLabel " + " OpBranch %4 " + "%7 = OpLabel " + " OpReturn " + " OpFunctionEnd", + {2, 4}, + "%3 = OpTypeVoid\n" + "%1 = OpTypeFunction %3\n" + "OpNop\n" + "OpNop\n" + "OpBranch %5\n" + "%5 = OpLabel\n" + "OpBranch %6\n" + "%6 = OpLabel\n" + "OpBranch %4\n" + "%7 = OpLabel\n" + "OpReturn\n" + "OpFunctionEnd", + { + // defs + { + {1, "%1 = OpTypeFunction %3"}, + {3, "%3 = OpTypeVoid"}, + {5, "%5 = OpLabel"}, + {6, "%6 = OpLabel"}, + {7, "%7 = OpLabel"}, + }, + // uses + { + {3, {"%1 = OpTypeFunction %3"}}, + {5, {"OpBranch %5"}}, + {6, {"OpBranch %6"}}, + } + } + }, + })); +// clang-format on + +struct GetAnnotationsTestCase { + const char* code; + uint32_t id; + std::vector annotations; +}; + +using GetAnnotationsTest = ::testing::TestWithParam; + +TEST_P(GetAnnotationsTest, Case) { + const GetAnnotationsTestCase& tc = GetParam(); + + // Build module. + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.code); + ASSERT_NE(nullptr, context); + + // Get annotations + opt::analysis::DefUseManager manager(context->module()); + auto insts = manager.GetAnnotations(tc.id); + + // Check + ASSERT_EQ(tc.annotations.size(), insts.size()) + << "wrong number of annotation instructions"; + auto inst_iter = insts.begin(); + for (const std::string& expected_anno_inst : tc.annotations) { + EXPECT_EQ(expected_anno_inst, DisassembleInst(*inst_iter)) + << "annotation instruction mismatch"; + inst_iter++; + } +} + +// clang-format off +INSTANTIATE_TEST_CASE_P( + TestCase, GetAnnotationsTest, + ::testing::ValuesIn(std::vector{ + // empty + {"", 0, {}}, + // basic + { + // code + "OpDecorate %1 Block " + "OpDecorate %1 RelaxedPrecision " + "%3 = OpTypeInt 32 0 " + "%1 = OpTypeStruct %3", + // id + 1, + // annotations + { + "OpDecorate %1 Block", + "OpDecorate %1 RelaxedPrecision", + }, + }, + // with debug instructions + { + // code + "OpName %1 \"struct_type\" " + "OpName %3 \"int_type\" " + "OpDecorate %1 Block " + "OpDecorate %1 RelaxedPrecision " + "%3 = OpTypeInt 32 0 " + "%1 = OpTypeStruct %3", + // id + 1, + // annotations + { + "OpDecorate %1 Block", + "OpDecorate %1 RelaxedPrecision", + }, + }, + // no annotations + { + // code + "OpName %1 \"struct_type\" " + "OpName %3 \"int_type\" " + "OpDecorate %1 Block " + "OpDecorate %1 RelaxedPrecision " + "%3 = OpTypeInt 32 0 " + "%1 = OpTypeStruct %3", + // id + 3, + // annotations + {}, + }, + // decoration group + { + // code + "OpDecorate %1 Block " + "OpDecorate %1 RelaxedPrecision " + "%1 = OpDecorationGroup " + "OpGroupDecorate %1 %2 %3 " + "%4 = OpTypeInt 32 0 " + "%2 = OpTypeStruct %4 " + "%3 = OpTypeStruct %4 %4", + // id + 3, + // annotations + { + "OpGroupDecorate %1 %2 %3", + }, + }, + // memeber decorate + { + // code + "OpMemberDecorate %1 0 RelaxedPrecision " + "%2 = OpTypeInt 32 0 " + "%1 = OpTypeStruct %2 %2", + // id + 1, + // annotations + { + "OpMemberDecorate %1 0 RelaxedPrecision", + }, + }, + })); + +using UpdateUsesTest = PassTest<::testing::Test>; + +TEST_F(UpdateUsesTest, KeepOldUses) { + const std::vector text = { + // clang-format off + "OpCapability Shader", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Vertex %main \"main\"", + "OpName %main \"main\"", + "%void = OpTypeVoid", + "%4 = OpTypeFunction %void", + "%uint = OpTypeInt 32 0", + "%uint_5 = OpConstant %uint 5", + "%25 = OpConstant %uint 25", + "%main = OpFunction %void None %4", + "%8 = OpLabel", + "%9 = OpIMul %uint %uint_5 %uint_5", + "%10 = OpIMul %uint %9 %uint_5", + "OpReturn", + "OpFunctionEnd" + // clang-format on + }; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, JoinAllInsts(text), + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ASSERT_NE(nullptr, context); + + DefUseManager* def_use_mgr = context->get_def_use_mgr(); + ir::Instruction* def = def_use_mgr->GetDef(9); + ir::Instruction* use = def_use_mgr->GetDef(10); + def->SetOpcode(SpvOpCopyObject); + def->SetInOperands({{SPV_OPERAND_TYPE_ID, {25}}}); + context->UpdateDefUse(def); + + auto users = def_use_mgr->id_to_users(); + opt::analysis::UserEntry entry = {def, use}; + EXPECT_THAT(users, Contains(entry)); +} +// clang-format on +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/dominator_tree/CMakeLists.txt b/3rdparty/spirv-tools/test/opt/dominator_tree/CMakeLists.txt new file mode 100644 index 000000000..31a5f322e --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/dominator_tree/CMakeLists.txt @@ -0,0 +1,79 @@ +# Copyright (c) 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +add_spvtools_unittest(TARGET dominator_analysis_simple + SRCS ../function_utils.h + simple.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET dominator_analysis_post + SRCS ../function_utils.h + post.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET dominator_analysis_nested_ifs + SRCS ../function_utils.h + nested_ifs.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET dominator_analysis_nested_ifs_post + SRCS ../function_utils.h + nested_ifs_post.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET dominator_analysis_nested_loops + SRCS ../function_utils.h + nested_loops.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET dominator_analysis_nested_loops_with_unreachables + SRCS ../function_utils.h + nested_loops_with_unreachables.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET dominator_analysis_switch_case_fallthrough + SRCS ../function_utils.h + switch_case_fallthrough.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET dominator_analysis_unreachable_for + SRCS ../function_utils.h + unreachable_for.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET dominator_analysis_unreachable_for_post + SRCS ../function_utils.h + unreachable_for_post.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET dominator_generated + SRCS ../function_utils.h + generated.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET dominator_common_dominators + SRCS common_dominators.cpp + LIBS SPIRV-Tools-opt +) diff --git a/3rdparty/spirv-tools/test/opt/dominator_tree/common_dominators.cpp b/3rdparty/spirv-tools/test/opt/dominator_tree/common_dominators.cpp new file mode 100644 index 000000000..612dc4543 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/dominator_tree/common_dominators.cpp @@ -0,0 +1,151 @@ +// Copyright (c) 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "opt/build_module.h" +#include "opt/ir_context.h" + +namespace { + +using namespace spvtools; +using CommonDominatorsTest = ::testing::Test; + +const std::string text = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %func "func" +%void = OpTypeVoid +%bool = OpTypeBool +%true = OpConstantTrue %bool +%functy = OpTypeFunction %void +%func = OpFunction %void None %functy +%1 = OpLabel +OpBranch %2 +%2 = OpLabel +OpLoopMerge %3 %4 None +OpBranch %5 +%5 = OpLabel +OpBranchConditional %true %3 %4 +%4 = OpLabel +OpBranch %2 +%3 = OpLabel +OpSelectionMerge %6 None +OpBranchConditional %true %7 %8 +%7 = OpLabel +OpBranch %6 +%8 = OpLabel +OpBranch %9 +%9 = OpLabel +OpBranch %6 +%6 = OpLabel +OpBranch %10 +%11 = OpLabel +OpBranch %10 +%10 = OpLabel +OpReturn +OpFunctionEnd +)"; + +ir::BasicBlock* GetBlock(uint32_t id, std::unique_ptr& context) { + return context->get_instr_block(context->get_def_use_mgr()->GetDef(id)); +} + +TEST(CommonDominatorsTest, SameBlock) { + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + EXPECT_NE(nullptr, context); + + ir::CFG cfg(context->module()); + opt::DominatorAnalysis* analysis = + context->GetDominatorAnalysis(&*context->module()->begin(), cfg); + + for (auto& block : *context->module()->begin()) { + EXPECT_EQ(&block, analysis->CommonDominator(&block, &block)); + } +} + +TEST(CommonDominatorsTest, ParentAndChild) { + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + EXPECT_NE(nullptr, context); + + ir::CFG cfg(context->module()); + opt::DominatorAnalysis* analysis = + context->GetDominatorAnalysis(&*context->module()->begin(), cfg); + + EXPECT_EQ( + GetBlock(1u, context), + analysis->CommonDominator(GetBlock(1u, context), GetBlock(2u, context))); + EXPECT_EQ( + GetBlock(2u, context), + analysis->CommonDominator(GetBlock(2u, context), GetBlock(5u, context))); + EXPECT_EQ( + GetBlock(1u, context), + analysis->CommonDominator(GetBlock(1u, context), GetBlock(5u, context))); +} + +TEST(CommonDominatorsTest, BranchSplit) { + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + EXPECT_NE(nullptr, context); + + ir::CFG cfg(context->module()); + opt::DominatorAnalysis* analysis = + context->GetDominatorAnalysis(&*context->module()->begin(), cfg); + + EXPECT_EQ( + GetBlock(3u, context), + analysis->CommonDominator(GetBlock(7u, context), GetBlock(8u, context))); + EXPECT_EQ( + GetBlock(3u, context), + analysis->CommonDominator(GetBlock(7u, context), GetBlock(9u, context))); +} + +TEST(CommonDominatorsTest, LoopContinueAndMerge) { + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + EXPECT_NE(nullptr, context); + + ir::CFG cfg(context->module()); + opt::DominatorAnalysis* analysis = + context->GetDominatorAnalysis(&*context->module()->begin(), cfg); + + EXPECT_EQ( + GetBlock(5u, context), + analysis->CommonDominator(GetBlock(3u, context), GetBlock(4u, context))); +} + +TEST(CommonDominatorsTest, NoCommonDominator) { + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + EXPECT_NE(nullptr, context); + + ir::CFG cfg(context->module()); + opt::DominatorAnalysis* analysis = + context->GetDominatorAnalysis(&*context->module()->begin(), cfg); + + EXPECT_EQ(nullptr, analysis->CommonDominator(GetBlock(10u, context), + GetBlock(11u, context))); + EXPECT_EQ(nullptr, analysis->CommonDominator(GetBlock(11u, context), + GetBlock(6u, context))); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/dominator_tree/generated.cpp b/3rdparty/spirv-tools/test/opt/dominator_tree/generated.cpp new file mode 100644 index 000000000..80fd86208 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/dominator_tree/generated.cpp @@ -0,0 +1,908 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include +#include +#include +#include + +#include "../assembly_builder.h" +#include "../function_utils.h" +#include "../pass_fixture.h" +#include "../pass_utils.h" +#include "opt/dominator_analysis.h" +#include "opt/iterator.h" +#include "opt/pass.h" + +namespace { + +using namespace spvtools; +using ::testing::UnorderedElementsAre; + +using PassClassTest = PassTest<::testing::Test>; + +// Check that x dominates y, and +// if x != y then +// x strictly dominates y and +// y does not dominate x and +// y does not strictly dominate x +// if x == x then +// x does not strictly dominate itself +void check_dominance(const opt::DominatorAnalysisBase& dom_tree, + const ir::Function* fn, uint32_t x, uint32_t y) { + SCOPED_TRACE("Check dominance properties for Basic Block " + + std::to_string(x) + " and " + std::to_string(y)); + EXPECT_TRUE(dom_tree.Dominates(spvtest::GetBasicBlock(fn, x), + spvtest::GetBasicBlock(fn, y))); + EXPECT_TRUE(dom_tree.Dominates(x, y)); + if (x == y) { + EXPECT_FALSE(dom_tree.StrictlyDominates(x, x)); + } else { + EXPECT_TRUE(dom_tree.StrictlyDominates(x, y)); + EXPECT_FALSE(dom_tree.Dominates(y, x)); + EXPECT_FALSE(dom_tree.StrictlyDominates(y, x)); + } +} + +// Check that x does not dominates y and vise versa +void check_no_dominance(const opt::DominatorAnalysisBase& dom_tree, + const ir::Function* fn, uint32_t x, uint32_t y) { + SCOPED_TRACE("Check no domination for Basic Block " + std::to_string(x) + + " and " + std::to_string(y)); + EXPECT_FALSE(dom_tree.Dominates(spvtest::GetBasicBlock(fn, x), + spvtest::GetBasicBlock(fn, y))); + EXPECT_FALSE(dom_tree.Dominates(x, y)); + EXPECT_FALSE(dom_tree.StrictlyDominates(spvtest::GetBasicBlock(fn, x), + spvtest::GetBasicBlock(fn, y))); + EXPECT_FALSE(dom_tree.StrictlyDominates(x, y)); + + EXPECT_FALSE(dom_tree.Dominates(spvtest::GetBasicBlock(fn, y), + spvtest::GetBasicBlock(fn, x))); + EXPECT_FALSE(dom_tree.Dominates(y, x)); + EXPECT_FALSE(dom_tree.StrictlyDominates(spvtest::GetBasicBlock(fn, y), + spvtest::GetBasicBlock(fn, x))); + EXPECT_FALSE(dom_tree.StrictlyDominates(y, x)); +} + +TEST_F(PassClassTest, DominatorSimpleCFG) { + const std::string text = R"( + OpCapability Addresses + OpCapability Kernel + OpMemoryModel Physical64 OpenCL + OpEntryPoint Kernel %1 "main" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %4 = OpTypeBool + %5 = OpTypeInt 32 0 + %6 = OpConstant %5 0 + %7 = OpConstantFalse %4 + %8 = OpConstantTrue %4 + %9 = OpConstant %5 1 + %1 = OpFunction %2 None %3 + %10 = OpLabel + OpBranch %11 + %11 = OpLabel + OpSwitch %6 %12 1 %13 + %12 = OpLabel + OpBranch %14 + %13 = OpLabel + OpBranch %14 + %14 = OpLabel + OpBranchConditional %8 %11 %15 + %15 = OpLabel + OpReturn + OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_0, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + const ir::Function* fn = spvtest::GetFunction(module, 1); + const ir::BasicBlock* entry = spvtest::GetBasicBlock(fn, 10); + EXPECT_EQ(entry, fn->entry().get()) + << "The entry node is not the expected one"; + + // Test normal dominator tree + { + opt::DominatorAnalysis dom_tree; + ir::CFG cfg(module); + dom_tree.InitializeTree(fn, cfg); + + // Inspect the actual tree + opt::DominatorTree& tree = dom_tree.GetDomTree(); + EXPECT_EQ(tree.GetRoot()->bb_, cfg.pseudo_entry_block()); + EXPECT_TRUE( + dom_tree.Dominates(cfg.pseudo_entry_block()->id(), entry->id())); + + // (strict) dominance checks + for (uint32_t id : {10, 11, 12, 13, 14, 15}) + check_dominance(dom_tree, fn, id, id); + + check_dominance(dom_tree, fn, 10, 11); + check_dominance(dom_tree, fn, 10, 12); + check_dominance(dom_tree, fn, 10, 13); + check_dominance(dom_tree, fn, 10, 14); + check_dominance(dom_tree, fn, 10, 15); + + check_dominance(dom_tree, fn, 11, 12); + check_dominance(dom_tree, fn, 11, 13); + check_dominance(dom_tree, fn, 11, 14); + check_dominance(dom_tree, fn, 11, 15); + + check_dominance(dom_tree, fn, 14, 15); + + check_no_dominance(dom_tree, fn, 12, 13); + check_no_dominance(dom_tree, fn, 12, 14); + check_no_dominance(dom_tree, fn, 13, 14); + + // check with some invalid inputs + EXPECT_FALSE(dom_tree.Dominates(nullptr, entry)); + EXPECT_FALSE(dom_tree.Dominates(entry, nullptr)); + EXPECT_FALSE(dom_tree.Dominates(static_cast(nullptr), + static_cast(nullptr))); + EXPECT_FALSE(dom_tree.Dominates(10, 1)); + EXPECT_FALSE(dom_tree.Dominates(1, 10)); + EXPECT_FALSE(dom_tree.Dominates(1, 1)); + + EXPECT_FALSE(dom_tree.StrictlyDominates(nullptr, entry)); + EXPECT_FALSE(dom_tree.StrictlyDominates(entry, nullptr)); + EXPECT_FALSE(dom_tree.StrictlyDominates(nullptr, nullptr)); + EXPECT_FALSE(dom_tree.StrictlyDominates(10, 1)); + EXPECT_FALSE(dom_tree.StrictlyDominates(1, 10)); + EXPECT_FALSE(dom_tree.StrictlyDominates(1, 1)); + + EXPECT_EQ(dom_tree.ImmediateDominator(cfg.pseudo_entry_block()), nullptr); + EXPECT_EQ(dom_tree.ImmediateDominator(entry), cfg.pseudo_entry_block()); + EXPECT_EQ(dom_tree.ImmediateDominator(nullptr), nullptr); + + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 11)), + spvtest::GetBasicBlock(fn, 10)); + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 12)), + spvtest::GetBasicBlock(fn, 11)); + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 13)), + spvtest::GetBasicBlock(fn, 11)); + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 14)), + spvtest::GetBasicBlock(fn, 11)); + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 15)), + spvtest::GetBasicBlock(fn, 14)); + } + + // Test post dominator tree + { + opt::PostDominatorAnalysis dom_tree; + ir::CFG cfg(module); + dom_tree.InitializeTree(fn, cfg); + + // Inspect the actual tree + opt::DominatorTree& tree = dom_tree.GetDomTree(); + EXPECT_EQ(tree.GetRoot()->bb_, cfg.pseudo_exit_block()); + EXPECT_TRUE(dom_tree.Dominates(cfg.pseudo_exit_block()->id(), 15)); + + // (strict) dominance checks + for (uint32_t id : {10, 11, 12, 13, 14, 15}) + check_dominance(dom_tree, fn, id, id); + + check_dominance(dom_tree, fn, 14, 10); + check_dominance(dom_tree, fn, 14, 11); + check_dominance(dom_tree, fn, 14, 12); + check_dominance(dom_tree, fn, 14, 13); + + check_dominance(dom_tree, fn, 15, 10); + check_dominance(dom_tree, fn, 15, 11); + check_dominance(dom_tree, fn, 15, 12); + check_dominance(dom_tree, fn, 15, 13); + check_dominance(dom_tree, fn, 15, 14); + + check_no_dominance(dom_tree, fn, 13, 12); + check_no_dominance(dom_tree, fn, 12, 11); + check_no_dominance(dom_tree, fn, 13, 11); + + // check with some invalid inputs + EXPECT_FALSE(dom_tree.Dominates(nullptr, entry)); + EXPECT_FALSE(dom_tree.Dominates(entry, nullptr)); + EXPECT_FALSE(dom_tree.Dominates(static_cast(nullptr), + static_cast(nullptr))); + EXPECT_FALSE(dom_tree.Dominates(10, 1)); + EXPECT_FALSE(dom_tree.Dominates(1, 10)); + EXPECT_FALSE(dom_tree.Dominates(1, 1)); + + EXPECT_FALSE(dom_tree.StrictlyDominates(nullptr, entry)); + EXPECT_FALSE(dom_tree.StrictlyDominates(entry, nullptr)); + EXPECT_FALSE(dom_tree.StrictlyDominates(nullptr, nullptr)); + EXPECT_FALSE(dom_tree.StrictlyDominates(10, 1)); + EXPECT_FALSE(dom_tree.StrictlyDominates(1, 10)); + EXPECT_FALSE(dom_tree.StrictlyDominates(1, 1)); + + EXPECT_EQ(dom_tree.ImmediateDominator(nullptr), nullptr); + + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 11)), + spvtest::GetBasicBlock(fn, 14)); + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 12)), + spvtest::GetBasicBlock(fn, 14)); + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 13)), + spvtest::GetBasicBlock(fn, 14)); + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 14)), + spvtest::GetBasicBlock(fn, 15)); + + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 15)), + cfg.pseudo_exit_block()); + + EXPECT_EQ(dom_tree.ImmediateDominator(cfg.pseudo_exit_block()), nullptr); + } +} + +TEST_F(PassClassTest, DominatorIrreducibleCFG) { + const std::string text = R"( + OpCapability Addresses + OpCapability Kernel + OpMemoryModel Physical64 OpenCL + OpEntryPoint Kernel %1 "main" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %4 = OpTypeBool + %5 = OpTypeInt 32 0 + %6 = OpConstantFalse %4 + %7 = OpConstantTrue %4 + %1 = OpFunction %2 None %3 + %8 = OpLabel + OpBranch %9 + %9 = OpLabel + OpBranchConditional %7 %10 %11 + %10 = OpLabel + OpBranch %11 + %11 = OpLabel + OpBranchConditional %7 %10 %12 + %12 = OpLabel + OpReturn + OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_0, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + const ir::Function* fn = spvtest::GetFunction(module, 1); + + const ir::BasicBlock* entry = spvtest::GetBasicBlock(fn, 8); + EXPECT_EQ(entry, fn->entry().get()) + << "The entry node is not the expected one"; + + // Check normal dominator tree + { + opt::DominatorAnalysis dom_tree; + ir::CFG cfg(module); + dom_tree.InitializeTree(fn, cfg); + + // Inspect the actual tree + opt::DominatorTree& tree = dom_tree.GetDomTree(); + EXPECT_EQ(tree.GetRoot()->bb_, cfg.pseudo_entry_block()); + EXPECT_TRUE( + dom_tree.Dominates(cfg.pseudo_entry_block()->id(), entry->id())); + + // (strict) dominance checks + for (uint32_t id : {8, 9, 10, 11, 12}) + check_dominance(dom_tree, fn, id, id); + + check_dominance(dom_tree, fn, 8, 9); + check_dominance(dom_tree, fn, 8, 10); + check_dominance(dom_tree, fn, 8, 11); + check_dominance(dom_tree, fn, 8, 12); + + check_dominance(dom_tree, fn, 9, 10); + check_dominance(dom_tree, fn, 9, 11); + check_dominance(dom_tree, fn, 9, 12); + + check_dominance(dom_tree, fn, 11, 12); + + check_no_dominance(dom_tree, fn, 10, 11); + + EXPECT_EQ(dom_tree.ImmediateDominator(cfg.pseudo_entry_block()), nullptr); + EXPECT_EQ(dom_tree.ImmediateDominator(entry), cfg.pseudo_entry_block()); + + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 9)), + spvtest::GetBasicBlock(fn, 8)); + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 10)), + spvtest::GetBasicBlock(fn, 9)); + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 11)), + spvtest::GetBasicBlock(fn, 9)); + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 12)), + spvtest::GetBasicBlock(fn, 11)); + } + + // Check post dominator tree + { + opt::PostDominatorAnalysis dom_tree; + ir::CFG cfg(module); + dom_tree.InitializeTree(fn, cfg); + + // Inspect the actual tree + opt::DominatorTree& tree = dom_tree.GetDomTree(); + EXPECT_EQ(tree.GetRoot()->bb_, cfg.pseudo_exit_block()); + EXPECT_TRUE(dom_tree.Dominates(cfg.pseudo_exit_block()->id(), 12)); + + // (strict) dominance checks + for (uint32_t id : {8, 9, 10, 11, 12}) + check_dominance(dom_tree, fn, id, id); + + check_dominance(dom_tree, fn, 12, 8); + check_dominance(dom_tree, fn, 12, 10); + check_dominance(dom_tree, fn, 12, 11); + check_dominance(dom_tree, fn, 12, 12); + + check_dominance(dom_tree, fn, 11, 8); + check_dominance(dom_tree, fn, 11, 9); + check_dominance(dom_tree, fn, 11, 10); + + check_dominance(dom_tree, fn, 9, 8); + + EXPECT_EQ(dom_tree.ImmediateDominator(entry), + spvtest::GetBasicBlock(fn, 9)); + + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 9)), + spvtest::GetBasicBlock(fn, 11)); + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 10)), + spvtest::GetBasicBlock(fn, 11)); + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 11)), + spvtest::GetBasicBlock(fn, 12)); + + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 12)), + cfg.pseudo_exit_block()); + + EXPECT_EQ(dom_tree.ImmediateDominator(cfg.pseudo_exit_block()), nullptr); + } +} + +TEST_F(PassClassTest, DominatorLoopToSelf) { + const std::string text = R"( + OpCapability Addresses + OpCapability Kernel + OpMemoryModel Physical64 OpenCL + OpEntryPoint Kernel %1 "main" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %4 = OpTypeBool + %5 = OpTypeInt 32 0 + %6 = OpConstant %5 0 + %7 = OpConstantFalse %4 + %8 = OpConstantTrue %4 + %9 = OpConstant %5 1 + %1 = OpFunction %2 None %3 + %10 = OpLabel + OpBranch %11 + %11 = OpLabel + OpSwitch %6 %12 1 %11 + %12 = OpLabel + OpReturn + OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_0, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + const ir::Function* fn = spvtest::GetFunction(module, 1); + + const ir::BasicBlock* entry = spvtest::GetBasicBlock(fn, 10); + EXPECT_EQ(entry, fn->entry().get()) + << "The entry node is not the expected one"; + + // Check normal dominator tree + { + opt::DominatorAnalysis dom_tree; + ir::CFG cfg(module); + dom_tree.InitializeTree(fn, cfg); + + // Inspect the actual tree + opt::DominatorTree& tree = dom_tree.GetDomTree(); + EXPECT_EQ(tree.GetRoot()->bb_, cfg.pseudo_entry_block()); + EXPECT_TRUE( + dom_tree.Dominates(cfg.pseudo_entry_block()->id(), entry->id())); + + // (strict) dominance checks + for (uint32_t id : {10, 11, 12}) check_dominance(dom_tree, fn, id, id); + + check_dominance(dom_tree, fn, 10, 11); + check_dominance(dom_tree, fn, 10, 12); + check_dominance(dom_tree, fn, 11, 12); + + EXPECT_EQ(dom_tree.ImmediateDominator(cfg.pseudo_entry_block()), nullptr); + EXPECT_EQ(dom_tree.ImmediateDominator(entry), cfg.pseudo_entry_block()); + + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 11)), + spvtest::GetBasicBlock(fn, 10)); + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 12)), + spvtest::GetBasicBlock(fn, 11)); + + std::array node_order = {{10, 11, 12}}; + { + // Test dominator tree iteration order. + opt::DominatorTree::iterator node_it = dom_tree.GetDomTree().begin(); + opt::DominatorTree::iterator node_end = dom_tree.GetDomTree().end(); + for (uint32_t id : node_order) { + EXPECT_NE(node_it, node_end); + EXPECT_EQ(node_it->id(), id); + node_it++; + } + EXPECT_EQ(node_it, node_end); + } + { + // Same as above, but with const iterators. + opt::DominatorTree::const_iterator node_it = + dom_tree.GetDomTree().cbegin(); + opt::DominatorTree::const_iterator node_end = + dom_tree.GetDomTree().cend(); + for (uint32_t id : node_order) { + EXPECT_NE(node_it, node_end); + EXPECT_EQ(node_it->id(), id); + node_it++; + } + EXPECT_EQ(node_it, node_end); + } + { + // Test dominator tree iteration order. + opt::DominatorTree::post_iterator node_it = + dom_tree.GetDomTree().post_begin(); + opt::DominatorTree::post_iterator node_end = + dom_tree.GetDomTree().post_end(); + for (uint32_t id : + ir::make_range(node_order.rbegin(), node_order.rend())) { + EXPECT_NE(node_it, node_end); + EXPECT_EQ(node_it->id(), id); + node_it++; + } + EXPECT_EQ(node_it, node_end); + } + { + // Same as above, but with const iterators. + opt::DominatorTree::const_post_iterator node_it = + dom_tree.GetDomTree().post_cbegin(); + opt::DominatorTree::const_post_iterator node_end = + dom_tree.GetDomTree().post_cend(); + for (uint32_t id : + ir::make_range(node_order.rbegin(), node_order.rend())) { + EXPECT_NE(node_it, node_end); + EXPECT_EQ(node_it->id(), id); + node_it++; + } + EXPECT_EQ(node_it, node_end); + } + } + + // Check post dominator tree + { + opt::PostDominatorAnalysis dom_tree; + ir::CFG cfg(module); + dom_tree.InitializeTree(fn, cfg); + + // Inspect the actual tree + opt::DominatorTree& tree = dom_tree.GetDomTree(); + EXPECT_EQ(tree.GetRoot()->bb_, cfg.pseudo_exit_block()); + EXPECT_TRUE(dom_tree.Dominates(cfg.pseudo_exit_block()->id(), 12)); + + // (strict) dominance checks + for (uint32_t id : {10, 11, 12}) check_dominance(dom_tree, fn, id, id); + + check_dominance(dom_tree, fn, 12, 10); + check_dominance(dom_tree, fn, 12, 11); + check_dominance(dom_tree, fn, 12, 12); + + EXPECT_EQ(dom_tree.ImmediateDominator(entry), + spvtest::GetBasicBlock(fn, 11)); + + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 11)), + spvtest::GetBasicBlock(fn, 12)); + + EXPECT_EQ(dom_tree.ImmediateDominator(cfg.pseudo_exit_block()), nullptr); + + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 12)), + cfg.pseudo_exit_block()); + + std::array node_order = {{12, 11, 10}}; + { + // Test dominator tree iteration order. + opt::DominatorTree::iterator node_it = tree.begin(); + opt::DominatorTree::iterator node_end = tree.end(); + for (uint32_t id : node_order) { + EXPECT_NE(node_it, node_end); + EXPECT_EQ(node_it->id(), id); + node_it++; + } + EXPECT_EQ(node_it, node_end); + } + { + // Same as above, but with const iterators. + opt::DominatorTree::const_iterator node_it = tree.cbegin(); + opt::DominatorTree::const_iterator node_end = tree.cend(); + for (uint32_t id : node_order) { + EXPECT_NE(node_it, node_end); + EXPECT_EQ(node_it->id(), id); + node_it++; + } + EXPECT_EQ(node_it, node_end); + } + { + // Test dominator tree iteration order. + opt::DominatorTree::post_iterator node_it = + dom_tree.GetDomTree().post_begin(); + opt::DominatorTree::post_iterator node_end = + dom_tree.GetDomTree().post_end(); + for (uint32_t id : + ir::make_range(node_order.rbegin(), node_order.rend())) { + EXPECT_NE(node_it, node_end); + EXPECT_EQ(node_it->id(), id); + node_it++; + } + EXPECT_EQ(node_it, node_end); + } + { + // Same as above, but with const iterators. + opt::DominatorTree::const_post_iterator node_it = + dom_tree.GetDomTree().post_cbegin(); + opt::DominatorTree::const_post_iterator node_end = + dom_tree.GetDomTree().post_cend(); + for (uint32_t id : + ir::make_range(node_order.rbegin(), node_order.rend())) { + EXPECT_NE(node_it, node_end); + EXPECT_EQ(node_it->id(), id); + node_it++; + } + EXPECT_EQ(node_it, node_end); + } + } +} + +TEST_F(PassClassTest, DominatorUnreachableInLoop) { + const std::string text = R"( + OpCapability Addresses + OpCapability Kernel + OpMemoryModel Physical64 OpenCL + OpEntryPoint Kernel %1 "main" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %4 = OpTypeBool + %5 = OpTypeInt 32 0 + %6 = OpConstant %5 0 + %7 = OpConstantFalse %4 + %8 = OpConstantTrue %4 + %9 = OpConstant %5 1 + %1 = OpFunction %2 None %3 + %10 = OpLabel + OpBranch %11 + %11 = OpLabel + OpSwitch %6 %12 1 %13 + %12 = OpLabel + OpBranch %14 + %13 = OpLabel + OpUnreachable + %14 = OpLabel + OpBranchConditional %8 %11 %15 + %15 = OpLabel + OpReturn + OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_0, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + const ir::Function* fn = spvtest::GetFunction(module, 1); + + const ir::BasicBlock* entry = spvtest::GetBasicBlock(fn, 10); + EXPECT_EQ(entry, fn->entry().get()) + << "The entry node is not the expected one"; + + // Check normal dominator tree + { + opt::DominatorAnalysis dom_tree; + ir::CFG cfg(module); + dom_tree.InitializeTree(fn, cfg); + + // Inspect the actual tree + opt::DominatorTree& tree = dom_tree.GetDomTree(); + EXPECT_EQ(tree.GetRoot()->bb_, cfg.pseudo_entry_block()); + EXPECT_TRUE( + dom_tree.Dominates(cfg.pseudo_entry_block()->id(), entry->id())); + + // (strict) dominance checks + for (uint32_t id : {10, 11, 12, 13, 14, 15}) + check_dominance(dom_tree, fn, id, id); + + check_dominance(dom_tree, fn, 10, 11); + check_dominance(dom_tree, fn, 10, 13); + check_dominance(dom_tree, fn, 10, 12); + check_dominance(dom_tree, fn, 10, 14); + check_dominance(dom_tree, fn, 10, 15); + + check_dominance(dom_tree, fn, 11, 12); + check_dominance(dom_tree, fn, 11, 13); + check_dominance(dom_tree, fn, 11, 14); + check_dominance(dom_tree, fn, 11, 15); + + check_dominance(dom_tree, fn, 12, 14); + check_dominance(dom_tree, fn, 12, 15); + + check_dominance(dom_tree, fn, 14, 15); + + check_no_dominance(dom_tree, fn, 13, 12); + check_no_dominance(dom_tree, fn, 13, 14); + check_no_dominance(dom_tree, fn, 13, 15); + + EXPECT_EQ(dom_tree.ImmediateDominator(cfg.pseudo_entry_block()), nullptr); + EXPECT_EQ(dom_tree.ImmediateDominator(entry), cfg.pseudo_entry_block()); + + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 11)), + spvtest::GetBasicBlock(fn, 10)); + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 12)), + spvtest::GetBasicBlock(fn, 11)); + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 13)), + spvtest::GetBasicBlock(fn, 11)); + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 14)), + spvtest::GetBasicBlock(fn, 12)); + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 15)), + spvtest::GetBasicBlock(fn, 14)); + } + + // Check post dominator tree. + { + opt::PostDominatorAnalysis dom_tree; + ir::CFG cfg(module); + dom_tree.InitializeTree(fn, cfg); + + // (strict) dominance checks. + for (uint32_t id : {10, 11, 12, 13, 14, 15}) + check_dominance(dom_tree, fn, id, id); + + check_no_dominance(dom_tree, fn, 15, 10); + check_no_dominance(dom_tree, fn, 15, 11); + check_no_dominance(dom_tree, fn, 15, 12); + check_no_dominance(dom_tree, fn, 15, 13); + check_no_dominance(dom_tree, fn, 15, 14); + + check_dominance(dom_tree, fn, 14, 12); + + check_no_dominance(dom_tree, fn, 13, 10); + check_no_dominance(dom_tree, fn, 13, 11); + check_no_dominance(dom_tree, fn, 13, 12); + check_no_dominance(dom_tree, fn, 13, 14); + check_no_dominance(dom_tree, fn, 13, 15); + + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 10)), + spvtest::GetBasicBlock(fn, 11)); + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 12)), + spvtest::GetBasicBlock(fn, 14)); + + EXPECT_EQ(dom_tree.ImmediateDominator(cfg.pseudo_exit_block()), nullptr); + + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 15)), + cfg.pseudo_exit_block()); + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 13)), + cfg.pseudo_exit_block()); + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 14)), + cfg.pseudo_exit_block()); + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 11)), + cfg.pseudo_exit_block()); + } +} + +TEST_F(PassClassTest, DominatorInfinitLoop) { + const std::string text = R"( + OpCapability Addresses + OpCapability Kernel + OpMemoryModel Physical64 OpenCL + OpEntryPoint Kernel %1 "main" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %4 = OpTypeBool + %5 = OpTypeInt 32 0 + %6 = OpConstant %5 0 + %7 = OpConstantFalse %4 + %8 = OpConstantTrue %4 + %9 = OpConstant %5 1 + %1 = OpFunction %2 None %3 + %10 = OpLabel + OpBranch %11 + %11 = OpLabel + OpSwitch %6 %12 1 %13 + %12 = OpLabel + OpReturn + %13 = OpLabel + OpBranch %13 + OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_0, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + const ir::Function* fn = spvtest::GetFunction(module, 1); + + const ir::BasicBlock* entry = spvtest::GetBasicBlock(fn, 10); + EXPECT_EQ(entry, fn->entry().get()) + << "The entry node is not the expected one"; + // Check normal dominator tree + { + opt::DominatorAnalysis dom_tree; + ir::CFG cfg(module); + dom_tree.InitializeTree(fn, cfg); + + // Inspect the actual tree + opt::DominatorTree& tree = dom_tree.GetDomTree(); + EXPECT_EQ(tree.GetRoot()->bb_, cfg.pseudo_entry_block()); + EXPECT_TRUE( + dom_tree.Dominates(cfg.pseudo_entry_block()->id(), entry->id())); + + // (strict) dominance checks + for (uint32_t id : {10, 11, 12, 13}) check_dominance(dom_tree, fn, id, id); + + check_dominance(dom_tree, fn, 10, 11); + check_dominance(dom_tree, fn, 10, 12); + check_dominance(dom_tree, fn, 10, 13); + + check_dominance(dom_tree, fn, 11, 12); + check_dominance(dom_tree, fn, 11, 13); + + check_no_dominance(dom_tree, fn, 13, 12); + + EXPECT_EQ(dom_tree.ImmediateDominator(cfg.pseudo_entry_block()), nullptr); + EXPECT_EQ(dom_tree.ImmediateDominator(entry), cfg.pseudo_entry_block()); + + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 11)), + spvtest::GetBasicBlock(fn, 10)); + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 12)), + spvtest::GetBasicBlock(fn, 11)); + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 13)), + spvtest::GetBasicBlock(fn, 11)); + } + + // Check post dominator tree + { + opt::PostDominatorAnalysis dom_tree; + ir::CFG cfg(module); + dom_tree.InitializeTree(fn, cfg); + + // Inspect the actual tree + opt::DominatorTree& tree = dom_tree.GetDomTree(); + EXPECT_EQ(tree.GetRoot()->bb_, cfg.pseudo_exit_block()); + EXPECT_TRUE(dom_tree.Dominates(cfg.pseudo_exit_block()->id(), 12)); + + // (strict) dominance checks + for (uint32_t id : {10, 11, 12}) check_dominance(dom_tree, fn, id, id); + + check_dominance(dom_tree, fn, 12, 11); + check_dominance(dom_tree, fn, 12, 10); + + // 13 should be completely out of tree as it's unreachable from exit nodes + check_no_dominance(dom_tree, fn, 12, 13); + check_no_dominance(dom_tree, fn, 11, 13); + check_no_dominance(dom_tree, fn, 10, 13); + + EXPECT_EQ(dom_tree.ImmediateDominator(cfg.pseudo_exit_block()), nullptr); + + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 12)), + cfg.pseudo_exit_block()); + + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 10)), + spvtest::GetBasicBlock(fn, 11)); + + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 11)), + spvtest::GetBasicBlock(fn, 12)); + } +} + +TEST_F(PassClassTest, DominatorUnreachableFromEntry) { + const std::string text = R"( + OpCapability Addresses + OpCapability Addresses + OpCapability Kernel + OpMemoryModel Physical64 OpenCL + OpEntryPoint Kernel %1 "main" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %4 = OpTypeBool + %5 = OpTypeInt 32 0 + %6 = OpConstantFalse %4 + %7 = OpConstantTrue %4 + %1 = OpFunction %2 None %3 + %8 = OpLabel + OpBranch %9 + %9 = OpLabel + OpReturn + %10 = OpLabel + OpBranch %9 + OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_0, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + const ir::Function* fn = spvtest::GetFunction(module, 1); + + const ir::BasicBlock* entry = spvtest::GetBasicBlock(fn, 8); + EXPECT_EQ(entry, fn->entry().get()) + << "The entry node is not the expected one"; + + // Check dominator tree + { + opt::DominatorAnalysis dom_tree; + ir::CFG cfg(module); + dom_tree.InitializeTree(fn, cfg); + // Inspect the actual tree + opt::DominatorTree& tree = dom_tree.GetDomTree(); + EXPECT_EQ(tree.GetRoot()->bb_, cfg.pseudo_entry_block()); + EXPECT_TRUE( + dom_tree.Dominates(cfg.pseudo_entry_block()->id(), entry->id())); + + // (strict) dominance checks + for (uint32_t id : {8, 9}) check_dominance(dom_tree, fn, id, id); + + check_dominance(dom_tree, fn, 8, 9); + + check_no_dominance(dom_tree, fn, 10, 8); + check_no_dominance(dom_tree, fn, 10, 9); + + EXPECT_EQ(dom_tree.ImmediateDominator(cfg.pseudo_entry_block()), nullptr); + EXPECT_EQ(dom_tree.ImmediateDominator(entry), cfg.pseudo_entry_block()); + + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 9)), + spvtest::GetBasicBlock(fn, 8)); + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 10)), + nullptr); + } + + // Check post dominator tree + { + opt::PostDominatorAnalysis dom_tree; + ir::CFG cfg(module); + dom_tree.InitializeTree(fn, cfg); + + // Inspect the actual tree + opt::DominatorTree& tree = dom_tree.GetDomTree(); + EXPECT_EQ(tree.GetRoot()->bb_, cfg.pseudo_exit_block()); + EXPECT_TRUE(dom_tree.Dominates(cfg.pseudo_exit_block()->id(), 9)); + + // (strict) dominance checks + for (uint32_t id : {8, 9, 10}) check_dominance(dom_tree, fn, id, id); + + check_dominance(dom_tree, fn, 9, 8); + check_dominance(dom_tree, fn, 9, 10); + + EXPECT_EQ(dom_tree.ImmediateDominator(entry), + spvtest::GetBasicBlock(fn, 9)); + + EXPECT_EQ(dom_tree.ImmediateDominator(cfg.pseudo_exit_block()), nullptr); + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 9)), + cfg.pseudo_exit_block()); + EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 10)), + spvtest::GetBasicBlock(fn, 9)); + } +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/dominator_tree/nested_ifs.cpp b/3rdparty/spirv-tools/test/opt/dominator_tree/nested_ifs.cpp new file mode 100644 index 000000000..fbb2d12cc --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/dominator_tree/nested_ifs.cpp @@ -0,0 +1,152 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include +#include + +#include "../assembly_builder.h" +#include "../function_utils.h" +#include "../pass_fixture.h" +#include "../pass_utils.h" +#include "opt/dominator_analysis.h" +#include "opt/pass.h" + +namespace { + +using namespace spvtools; +using ::testing::UnorderedElementsAre; + +using PassClassTest = PassTest<::testing::Test>; + +/* + Generated from the following GLSL +#version 330 core +layout(location = 0) out vec4 v; +void main(){ + if (true) { + if (true) { + v = vec4(1,1,1,1); + } else { + v = vec4(2,2,2,2); + } + } else { + if (true) { + v = vec4(3,3,3,3); + } else { + v = vec4(4,4,4,4); + } + } +} +*/ +TEST_F(PassClassTest, UnreachableNestedIfs) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %15 + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 330 + OpName %4 "main" + OpName %15 "v" + OpDecorate %15 Location 0 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeBool + %7 = OpConstantTrue %6 + %12 = OpTypeFloat 32 + %13 = OpTypeVector %12 4 + %14 = OpTypePointer Output %13 + %15 = OpVariable %14 Output + %16 = OpConstant %12 1 + %17 = OpConstantComposite %13 %16 %16 %16 %16 + %19 = OpConstant %12 2 + %20 = OpConstantComposite %13 %19 %19 %19 %19 + %24 = OpConstant %12 3 + %25 = OpConstantComposite %13 %24 %24 %24 %24 + %27 = OpConstant %12 4 + %28 = OpConstantComposite %13 %27 %27 %27 %27 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpSelectionMerge %9 None + OpBranchConditional %7 %8 %21 + %8 = OpLabel + OpSelectionMerge %11 None + OpBranchConditional %7 %10 %18 + %10 = OpLabel + OpStore %15 %17 + OpBranch %11 + %18 = OpLabel + OpStore %15 %20 + OpBranch %11 + %11 = OpLabel + OpBranch %9 + %21 = OpLabel + OpSelectionMerge %23 None + OpBranchConditional %7 %22 %26 + %22 = OpLabel + OpStore %15 %25 + OpBranch %23 + %26 = OpLabel + OpStore %15 %28 + OpBranch %23 + %23 = OpLabel + OpBranch %9 + %9 = OpLabel + OpReturn + OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + + const ir::Function* f = spvtest::GetFunction(module, 4); + + ir::CFG cfg(module); + opt::DominatorAnalysis* analysis = context->GetDominatorAnalysis(f, cfg); + + EXPECT_TRUE(analysis->Dominates(5, 8)); + EXPECT_TRUE(analysis->Dominates(5, 9)); + EXPECT_TRUE(analysis->Dominates(5, 21)); + EXPECT_TRUE(analysis->Dominates(5, 18)); + EXPECT_TRUE(analysis->Dominates(5, 10)); + EXPECT_TRUE(analysis->Dominates(5, 11)); + EXPECT_TRUE(analysis->Dominates(5, 23)); + EXPECT_TRUE(analysis->Dominates(5, 22)); + EXPECT_TRUE(analysis->Dominates(5, 26)); + EXPECT_TRUE(analysis->Dominates(8, 18)); + EXPECT_TRUE(analysis->Dominates(8, 10)); + EXPECT_TRUE(analysis->Dominates(8, 11)); + EXPECT_TRUE(analysis->Dominates(21, 23)); + EXPECT_TRUE(analysis->Dominates(21, 22)); + EXPECT_TRUE(analysis->Dominates(21, 26)); + + EXPECT_TRUE(analysis->StrictlyDominates(5, 8)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 9)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 21)); + EXPECT_TRUE(analysis->StrictlyDominates(8, 18)); + EXPECT_TRUE(analysis->StrictlyDominates(8, 10)); + EXPECT_TRUE(analysis->StrictlyDominates(8, 11)); + EXPECT_TRUE(analysis->StrictlyDominates(21, 23)); + EXPECT_TRUE(analysis->StrictlyDominates(21, 22)); + EXPECT_TRUE(analysis->StrictlyDominates(21, 26)); +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/dominator_tree/nested_ifs_post.cpp b/3rdparty/spirv-tools/test/opt/dominator_tree/nested_ifs_post.cpp new file mode 100644 index 000000000..a112af99c --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/dominator_tree/nested_ifs_post.cpp @@ -0,0 +1,157 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include +#include + +#include "../assembly_builder.h" +#include "../function_utils.h" +#include "../pass_fixture.h" +#include "../pass_utils.h" +#include "opt/dominator_analysis.h" +#include "opt/pass.h" + +namespace { + +using namespace spvtools; +using ::testing::UnorderedElementsAre; + +using PassClassTest = PassTest<::testing::Test>; + +/* + Generated from the following GLSL +#version 330 core +layout(location = 0) out vec4 v; +void main(){ + if (true) { + if (true) { + v = vec4(1,1,1,1); + } else { + v = vec4(2,2,2,2); + } + } else { + if (true) { + v = vec4(3,3,3,3); + } else { + v = vec4(4,4,4,4); + } + } +} +*/ +TEST_F(PassClassTest, UnreachableNestedIfs) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %15 + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 330 + OpName %4 "main" + OpName %15 "v" + OpDecorate %15 Location 0 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeBool + %7 = OpConstantTrue %6 + %12 = OpTypeFloat 32 + %13 = OpTypeVector %12 4 + %14 = OpTypePointer Output %13 + %15 = OpVariable %14 Output + %16 = OpConstant %12 1 + %17 = OpConstantComposite %13 %16 %16 %16 %16 + %19 = OpConstant %12 2 + %20 = OpConstantComposite %13 %19 %19 %19 %19 + %24 = OpConstant %12 3 + %25 = OpConstantComposite %13 %24 %24 %24 %24 + %27 = OpConstant %12 4 + %28 = OpConstantComposite %13 %27 %27 %27 %27 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpSelectionMerge %9 None + OpBranchConditional %7 %8 %21 + %8 = OpLabel + OpSelectionMerge %11 None + OpBranchConditional %7 %10 %18 + %10 = OpLabel + OpStore %15 %17 + OpBranch %11 + %18 = OpLabel + OpStore %15 %20 + OpBranch %11 + %11 = OpLabel + OpBranch %9 + %21 = OpLabel + OpSelectionMerge %23 None + OpBranchConditional %7 %22 %26 + %22 = OpLabel + OpStore %15 %25 + OpBranch %23 + %26 = OpLabel + OpStore %15 %28 + OpBranch %23 + %23 = OpLabel + OpBranch %9 + %9 = OpLabel + OpReturn + OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + + const ir::Function* f = spvtest::GetFunction(module, 4); + + ir::CFG cfg(module); + opt::PostDominatorAnalysis* analysis = + context->GetPostDominatorAnalysis(f, cfg); + + EXPECT_TRUE(analysis->Dominates(5, 5)); + EXPECT_TRUE(analysis->Dominates(8, 8)); + EXPECT_TRUE(analysis->Dominates(9, 9)); + EXPECT_TRUE(analysis->Dominates(10, 10)); + EXPECT_TRUE(analysis->Dominates(11, 11)); + EXPECT_TRUE(analysis->Dominates(18, 18)); + EXPECT_TRUE(analysis->Dominates(21, 21)); + EXPECT_TRUE(analysis->Dominates(22, 22)); + EXPECT_TRUE(analysis->Dominates(23, 23)); + EXPECT_TRUE(analysis->Dominates(26, 26)); + EXPECT_TRUE(analysis->Dominates(9, 5)); + EXPECT_TRUE(analysis->Dominates(9, 11)); + EXPECT_TRUE(analysis->Dominates(9, 23)); + EXPECT_TRUE(analysis->Dominates(11, 10)); + EXPECT_TRUE(analysis->Dominates(11, 18)); + EXPECT_TRUE(analysis->Dominates(11, 8)); + EXPECT_TRUE(analysis->Dominates(23, 22)); + EXPECT_TRUE(analysis->Dominates(23, 26)); + EXPECT_TRUE(analysis->Dominates(23, 21)); + + EXPECT_TRUE(analysis->StrictlyDominates(9, 5)); + EXPECT_TRUE(analysis->StrictlyDominates(9, 11)); + EXPECT_TRUE(analysis->StrictlyDominates(9, 23)); + EXPECT_TRUE(analysis->StrictlyDominates(11, 10)); + EXPECT_TRUE(analysis->StrictlyDominates(11, 18)); + EXPECT_TRUE(analysis->StrictlyDominates(11, 8)); + EXPECT_TRUE(analysis->StrictlyDominates(23, 22)); + EXPECT_TRUE(analysis->StrictlyDominates(23, 26)); + EXPECT_TRUE(analysis->StrictlyDominates(23, 21)); +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/dominator_tree/nested_loops.cpp b/3rdparty/spirv-tools/test/opt/dominator_tree/nested_loops.cpp new file mode 100644 index 000000000..4e5c072c8 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/dominator_tree/nested_loops.cpp @@ -0,0 +1,433 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include +#include + +#include "../assembly_builder.h" +#include "../function_utils.h" +#include "../pass_fixture.h" +#include "../pass_utils.h" +#include "opt/dominator_analysis.h" +#include "opt/pass.h" + +namespace { + +using namespace spvtools; +using ::testing::UnorderedElementsAre; + +using PassClassTest = PassTest<::testing::Test>; + +/* + Generated from the following GLSL +#version 440 core +layout(location = 0) out vec4 v; +layout(location = 1) in vec4 in_val; +void main() { + for (int i = 0; i < in_val.x; ++i) { + for (int j = 0; j < in_val.y; j++) { + } + } + for (int i = 0; i < in_val.x; ++i) { + for (int j = 0; j < in_val.y; j++) { + } + if (in_val.z == in_val.w) { + break; + } + } + int i = 0; + while (i < in_val.x) { + ++i; + for (int j = 0; j < 1; j++) { + for (int k = 0; k < 1; k++) { + } + } + } + i = 0; + while (i < in_val.x) { + ++i; + if (in_val.z == in_val.w) { + continue; + } + for (int j = 0; j < 1; j++) { + for (int k = 0; k < 1; k++) { + } + if (in_val.z == in_val.w) { + break; + } + } + } + v = vec4(1,1,1,1); +} +*/ +TEST_F(PassClassTest, BasicVisitFromEntryPoint) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %20 %163 + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %20 "in_val" + OpName %28 "j" + OpName %45 "i" + OpName %56 "j" + OpName %81 "i" + OpName %94 "j" + OpName %102 "k" + OpName %134 "j" + OpName %142 "k" + OpName %163 "v" + OpDecorate %20 Location 1 + OpDecorate %163 Location 0 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpTypeFloat 32 + %18 = OpTypeVector %16 4 + %19 = OpTypePointer Input %18 + %20 = OpVariable %19 Input + %21 = OpTypeInt 32 0 + %22 = OpConstant %21 0 + %23 = OpTypePointer Input %16 + %26 = OpTypeBool + %36 = OpConstant %21 1 + %41 = OpConstant %6 1 + %69 = OpConstant %21 2 + %72 = OpConstant %21 3 + %162 = OpTypePointer Output %18 + %163 = OpVariable %162 Output + %164 = OpConstant %16 1 + %165 = OpConstantComposite %18 %164 %164 %164 %164 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %28 = OpVariable %7 Function + %45 = OpVariable %7 Function + %56 = OpVariable %7 Function + %81 = OpVariable %7 Function + %94 = OpVariable %7 Function + %102 = OpVariable %7 Function + %134 = OpVariable %7 Function + %142 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %15 = OpLoad %6 %8 + %17 = OpConvertSToF %16 %15 + %24 = OpAccessChain %23 %20 %22 + %25 = OpLoad %16 %24 + %27 = OpFOrdLessThan %26 %17 %25 + OpBranchConditional %27 %11 %12 + %11 = OpLabel + OpStore %28 %9 + OpBranch %29 + %29 = OpLabel + OpLoopMerge %31 %32 None + OpBranch %33 + %33 = OpLabel + %34 = OpLoad %6 %28 + %35 = OpConvertSToF %16 %34 + %37 = OpAccessChain %23 %20 %36 + %38 = OpLoad %16 %37 + %39 = OpFOrdLessThan %26 %35 %38 + OpBranchConditional %39 %30 %31 + %30 = OpLabel + OpBranch %32 + %32 = OpLabel + %40 = OpLoad %6 %28 + %42 = OpIAdd %6 %40 %41 + OpStore %28 %42 + OpBranch %29 + %31 = OpLabel + OpBranch %13 + %13 = OpLabel + %43 = OpLoad %6 %8 + %44 = OpIAdd %6 %43 %41 + OpStore %8 %44 + OpBranch %10 + %12 = OpLabel + OpStore %45 %9 + OpBranch %46 + %46 = OpLabel + OpLoopMerge %48 %49 None + OpBranch %50 + %50 = OpLabel + %51 = OpLoad %6 %45 + %52 = OpConvertSToF %16 %51 + %53 = OpAccessChain %23 %20 %22 + %54 = OpLoad %16 %53 + %55 = OpFOrdLessThan %26 %52 %54 + OpBranchConditional %55 %47 %48 + %47 = OpLabel + OpStore %56 %9 + OpBranch %57 + %57 = OpLabel + OpLoopMerge %59 %60 None + OpBranch %61 + %61 = OpLabel + %62 = OpLoad %6 %56 + %63 = OpConvertSToF %16 %62 + %64 = OpAccessChain %23 %20 %36 + %65 = OpLoad %16 %64 + %66 = OpFOrdLessThan %26 %63 %65 + OpBranchConditional %66 %58 %59 + %58 = OpLabel + OpBranch %60 + %60 = OpLabel + %67 = OpLoad %6 %56 + %68 = OpIAdd %6 %67 %41 + OpStore %56 %68 + OpBranch %57 + %59 = OpLabel + %70 = OpAccessChain %23 %20 %69 + %71 = OpLoad %16 %70 + %73 = OpAccessChain %23 %20 %72 + %74 = OpLoad %16 %73 + %75 = OpFOrdEqual %26 %71 %74 + OpSelectionMerge %77 None + OpBranchConditional %75 %76 %77 + %76 = OpLabel + OpBranch %48 + %77 = OpLabel + OpBranch %49 + %49 = OpLabel + %79 = OpLoad %6 %45 + %80 = OpIAdd %6 %79 %41 + OpStore %45 %80 + OpBranch %46 + %48 = OpLabel + OpStore %81 %9 + OpBranch %82 + %82 = OpLabel + OpLoopMerge %84 %85 None + OpBranch %86 + %86 = OpLabel + %87 = OpLoad %6 %81 + %88 = OpConvertSToF %16 %87 + %89 = OpAccessChain %23 %20 %22 + %90 = OpLoad %16 %89 + %91 = OpFOrdLessThan %26 %88 %90 + OpBranchConditional %91 %83 %84 + %83 = OpLabel + %92 = OpLoad %6 %81 + %93 = OpIAdd %6 %92 %41 + OpStore %81 %93 + OpStore %94 %9 + OpBranch %95 + %95 = OpLabel + OpLoopMerge %97 %98 None + OpBranch %99 + %99 = OpLabel + %100 = OpLoad %6 %94 + %101 = OpSLessThan %26 %100 %41 + OpBranchConditional %101 %96 %97 + %96 = OpLabel + OpStore %102 %9 + OpBranch %103 + %103 = OpLabel + OpLoopMerge %105 %106 None + OpBranch %107 + %107 = OpLabel + %108 = OpLoad %6 %102 + %109 = OpSLessThan %26 %108 %41 + OpBranchConditional %109 %104 %105 + %104 = OpLabel + OpBranch %106 + %106 = OpLabel + %110 = OpLoad %6 %102 + %111 = OpIAdd %6 %110 %41 + OpStore %102 %111 + OpBranch %103 + %105 = OpLabel + OpBranch %98 + %98 = OpLabel + %112 = OpLoad %6 %94 + %113 = OpIAdd %6 %112 %41 + OpStore %94 %113 + OpBranch %95 + %97 = OpLabel + OpBranch %85 + %85 = OpLabel + OpBranch %82 + %84 = OpLabel + OpStore %81 %9 + OpBranch %114 + %114 = OpLabel + OpLoopMerge %116 %117 None + OpBranch %118 + %118 = OpLabel + %119 = OpLoad %6 %81 + %120 = OpConvertSToF %16 %119 + %121 = OpAccessChain %23 %20 %22 + %122 = OpLoad %16 %121 + %123 = OpFOrdLessThan %26 %120 %122 + OpBranchConditional %123 %115 %116 + %115 = OpLabel + %124 = OpLoad %6 %81 + %125 = OpIAdd %6 %124 %41 + OpStore %81 %125 + %126 = OpAccessChain %23 %20 %69 + %127 = OpLoad %16 %126 + %128 = OpAccessChain %23 %20 %72 + %129 = OpLoad %16 %128 + %130 = OpFOrdEqual %26 %127 %129 + OpSelectionMerge %132 None + OpBranchConditional %130 %131 %132 + %131 = OpLabel + OpBranch %117 + %132 = OpLabel + OpStore %134 %9 + OpBranch %135 + %135 = OpLabel + OpLoopMerge %137 %138 None + OpBranch %139 + %139 = OpLabel + %140 = OpLoad %6 %134 + %141 = OpSLessThan %26 %140 %41 + OpBranchConditional %141 %136 %137 + %136 = OpLabel + OpStore %142 %9 + OpBranch %143 + %143 = OpLabel + OpLoopMerge %145 %146 None + OpBranch %147 + %147 = OpLabel + %148 = OpLoad %6 %142 + %149 = OpSLessThan %26 %148 %41 + OpBranchConditional %149 %144 %145 + %144 = OpLabel + OpBranch %146 + %146 = OpLabel + %150 = OpLoad %6 %142 + %151 = OpIAdd %6 %150 %41 + OpStore %142 %151 + OpBranch %143 + %145 = OpLabel + %152 = OpAccessChain %23 %20 %69 + %153 = OpLoad %16 %152 + %154 = OpAccessChain %23 %20 %72 + %155 = OpLoad %16 %154 + %156 = OpFOrdEqual %26 %153 %155 + OpSelectionMerge %158 None + OpBranchConditional %156 %157 %158 + %157 = OpLabel + OpBranch %137 + %158 = OpLabel + OpBranch %138 + %138 = OpLabel + %160 = OpLoad %6 %134 + %161 = OpIAdd %6 %160 %41 + OpStore %134 %161 + OpBranch %135 + %137 = OpLabel + OpBranch %117 + %117 = OpLabel + OpBranch %114 + %116 = OpLabel + OpStore %163 %165 + OpReturn + OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + + const ir::Function* f = spvtest::GetFunction(module, 4); + ir::CFG cfg(module); + opt::DominatorAnalysis* analysis = context->GetDominatorAnalysis(f, cfg); + + EXPECT_TRUE(analysis->Dominates(5, 10)); + EXPECT_TRUE(analysis->Dominates(5, 46)); + EXPECT_TRUE(analysis->Dominates(5, 82)); + EXPECT_TRUE(analysis->Dominates(5, 114)); + EXPECT_TRUE(analysis->Dominates(5, 116)); + + EXPECT_TRUE(analysis->Dominates(10, 14)); + EXPECT_TRUE(analysis->Dominates(10, 11)); + EXPECT_TRUE(analysis->Dominates(10, 29)); + EXPECT_TRUE(analysis->Dominates(10, 33)); + EXPECT_TRUE(analysis->Dominates(10, 30)); + EXPECT_TRUE(analysis->Dominates(10, 32)); + EXPECT_TRUE(analysis->Dominates(10, 31)); + EXPECT_TRUE(analysis->Dominates(10, 13)); + EXPECT_TRUE(analysis->Dominates(10, 12)); + + EXPECT_TRUE(analysis->Dominates(12, 46)); + + EXPECT_TRUE(analysis->Dominates(46, 50)); + EXPECT_TRUE(analysis->Dominates(46, 47)); + EXPECT_TRUE(analysis->Dominates(46, 57)); + EXPECT_TRUE(analysis->Dominates(46, 61)); + EXPECT_TRUE(analysis->Dominates(46, 58)); + EXPECT_TRUE(analysis->Dominates(46, 60)); + EXPECT_TRUE(analysis->Dominates(46, 59)); + EXPECT_TRUE(analysis->Dominates(46, 77)); + EXPECT_TRUE(analysis->Dominates(46, 49)); + EXPECT_TRUE(analysis->Dominates(46, 76)); + EXPECT_TRUE(analysis->Dominates(46, 48)); + + EXPECT_TRUE(analysis->Dominates(48, 82)); + + EXPECT_TRUE(analysis->Dominates(82, 86)); + EXPECT_TRUE(analysis->Dominates(82, 83)); + EXPECT_TRUE(analysis->Dominates(82, 95)); + EXPECT_TRUE(analysis->Dominates(82, 99)); + EXPECT_TRUE(analysis->Dominates(82, 96)); + EXPECT_TRUE(analysis->Dominates(82, 103)); + EXPECT_TRUE(analysis->Dominates(82, 107)); + EXPECT_TRUE(analysis->Dominates(82, 104)); + EXPECT_TRUE(analysis->Dominates(82, 106)); + EXPECT_TRUE(analysis->Dominates(82, 105)); + EXPECT_TRUE(analysis->Dominates(82, 98)); + EXPECT_TRUE(analysis->Dominates(82, 97)); + EXPECT_TRUE(analysis->Dominates(82, 85)); + EXPECT_TRUE(analysis->Dominates(82, 84)); + + EXPECT_TRUE(analysis->Dominates(84, 114)); + + EXPECT_TRUE(analysis->Dominates(114, 118)); + EXPECT_TRUE(analysis->Dominates(114, 116)); + EXPECT_TRUE(analysis->Dominates(114, 115)); + EXPECT_TRUE(analysis->Dominates(114, 132)); + EXPECT_TRUE(analysis->Dominates(114, 135)); + EXPECT_TRUE(analysis->Dominates(114, 139)); + EXPECT_TRUE(analysis->Dominates(114, 136)); + EXPECT_TRUE(analysis->Dominates(114, 143)); + EXPECT_TRUE(analysis->Dominates(114, 147)); + EXPECT_TRUE(analysis->Dominates(114, 144)); + EXPECT_TRUE(analysis->Dominates(114, 146)); + EXPECT_TRUE(analysis->Dominates(114, 145)); + EXPECT_TRUE(analysis->Dominates(114, 158)); + EXPECT_TRUE(analysis->Dominates(114, 138)); + EXPECT_TRUE(analysis->Dominates(114, 137)); + EXPECT_TRUE(analysis->Dominates(114, 131)); + EXPECT_TRUE(analysis->Dominates(114, 117)); +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/dominator_tree/nested_loops_with_unreachables.cpp b/3rdparty/spirv-tools/test/opt/dominator_tree/nested_loops_with_unreachables.cpp new file mode 100644 index 000000000..ab1b6a796 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/dominator_tree/nested_loops_with_unreachables.cpp @@ -0,0 +1,846 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include + +#include "../assembly_builder.h" +#include "../function_utils.h" +#include "../pass_fixture.h" +#include "../pass_utils.h" +#include "opt/dominator_analysis.h" +#include "opt/pass.h" + +namespace { + +using namespace spvtools; +using ::testing::UnorderedElementsAre; + +using PassClassTest = PassTest<::testing::Test>; + +/* +Generated from the following GLSL +#version 440 core +layout(location = 0) out vec4 v; +layout(location = 1) in vec4 in_val; +void main() { + for (int i = 0; i < in_val.x; ++i) { + for (int j = 0; j < in_val.y; j++) { + } + } + for (int i = 0; i < in_val.x; ++i) { + for (int j = 0; j < in_val.y; j++) { + } + break; + } + int i = 0; + while (i < in_val.x) { + ++i; + for (int j = 0; j < 1; j++) { + for (int k = 0; k < 1; k++) { + } + break; + } + } + i = 0; + while (i < in_val.x) { + ++i; + continue; + for (int j = 0; j < 1; j++) { + for (int k = 0; k < 1; k++) { + } + break; + } + } + v = vec4(1,1,1,1); +} +*/ +TEST_F(PassClassTest, BasicVisitFromEntryPoint) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %20 %141 + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %20 "in_val" + OpName %28 "j" + OpName %45 "i" + OpName %56 "j" + OpName %72 "i" + OpName %85 "j" + OpName %93 "k" + OpName %119 "j" + OpName %127 "k" + OpName %141 "v" + OpDecorate %20 Location 1 + OpDecorate %141 Location 0 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpTypeFloat 32 + %18 = OpTypeVector %16 4 + %19 = OpTypePointer Input %18 + %20 = OpVariable %19 Input + %21 = OpTypeInt 32 0 + %22 = OpConstant %21 0 + %23 = OpTypePointer Input %16 + %26 = OpTypeBool + %36 = OpConstant %21 1 + %41 = OpConstant %6 1 + %140 = OpTypePointer Output %18 + %141 = OpVariable %140 Output + %142 = OpConstant %16 1 + %143 = OpConstantComposite %18 %142 %142 %142 %142 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %28 = OpVariable %7 Function + %45 = OpVariable %7 Function + %56 = OpVariable %7 Function + %72 = OpVariable %7 Function + %85 = OpVariable %7 Function + %93 = OpVariable %7 Function + %119 = OpVariable %7 Function + %127 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %15 = OpLoad %6 %8 + %17 = OpConvertSToF %16 %15 + %24 = OpAccessChain %23 %20 %22 + %25 = OpLoad %16 %24 + %27 = OpFOrdLessThan %26 %17 %25 + OpBranchConditional %27 %11 %12 + %11 = OpLabel + OpStore %28 %9 + OpBranch %29 + %29 = OpLabel + OpLoopMerge %31 %32 None + OpBranch %33 + %33 = OpLabel + %34 = OpLoad %6 %28 + %35 = OpConvertSToF %16 %34 + %37 = OpAccessChain %23 %20 %36 + %38 = OpLoad %16 %37 + %39 = OpFOrdLessThan %26 %35 %38 + OpBranchConditional %39 %30 %31 + %30 = OpLabel + OpBranch %32 + %32 = OpLabel + %40 = OpLoad %6 %28 + %42 = OpIAdd %6 %40 %41 + OpStore %28 %42 + OpBranch %29 + %31 = OpLabel + OpBranch %13 + %13 = OpLabel + %43 = OpLoad %6 %8 + %44 = OpIAdd %6 %43 %41 + OpStore %8 %44 + OpBranch %10 + %12 = OpLabel + OpStore %45 %9 + OpBranch %46 + %46 = OpLabel + OpLoopMerge %48 %49 None + OpBranch %50 + %50 = OpLabel + %51 = OpLoad %6 %45 + %52 = OpConvertSToF %16 %51 + %53 = OpAccessChain %23 %20 %22 + %54 = OpLoad %16 %53 + %55 = OpFOrdLessThan %26 %52 %54 + OpBranchConditional %55 %47 %48 + %47 = OpLabel + OpStore %56 %9 + OpBranch %57 + %57 = OpLabel + OpLoopMerge %59 %60 None + OpBranch %61 + %61 = OpLabel + %62 = OpLoad %6 %56 + %63 = OpConvertSToF %16 %62 + %64 = OpAccessChain %23 %20 %36 + %65 = OpLoad %16 %64 + %66 = OpFOrdLessThan %26 %63 %65 + OpBranchConditional %66 %58 %59 + %58 = OpLabel + OpBranch %60 + %60 = OpLabel + %67 = OpLoad %6 %56 + %68 = OpIAdd %6 %67 %41 + OpStore %56 %68 + OpBranch %57 + %59 = OpLabel + OpBranch %48 + %49 = OpLabel + %70 = OpLoad %6 %45 + %71 = OpIAdd %6 %70 %41 + OpStore %45 %71 + OpBranch %46 + %48 = OpLabel + OpStore %72 %9 + OpBranch %73 + %73 = OpLabel + OpLoopMerge %75 %76 None + OpBranch %77 + %77 = OpLabel + %78 = OpLoad %6 %72 + %79 = OpConvertSToF %16 %78 + %80 = OpAccessChain %23 %20 %22 + %81 = OpLoad %16 %80 + %82 = OpFOrdLessThan %26 %79 %81 + OpBranchConditional %82 %74 %75 + %74 = OpLabel + %83 = OpLoad %6 %72 + %84 = OpIAdd %6 %83 %41 + OpStore %72 %84 + OpStore %85 %9 + OpBranch %86 + %86 = OpLabel + OpLoopMerge %88 %89 None + OpBranch %90 + %90 = OpLabel + %91 = OpLoad %6 %85 + %92 = OpSLessThan %26 %91 %41 + OpBranchConditional %92 %87 %88 + %87 = OpLabel + OpStore %93 %9 + OpBranch %94 + %94 = OpLabel + OpLoopMerge %96 %97 None + OpBranch %98 + %98 = OpLabel + %99 = OpLoad %6 %93 + %100 = OpSLessThan %26 %99 %41 + OpBranchConditional %100 %95 %96 + %95 = OpLabel + OpBranch %97 + %97 = OpLabel + %101 = OpLoad %6 %93 + %102 = OpIAdd %6 %101 %41 + OpStore %93 %102 + OpBranch %94 + %96 = OpLabel + OpBranch %88 + %89 = OpLabel + %104 = OpLoad %6 %85 + %105 = OpIAdd %6 %104 %41 + OpStore %85 %105 + OpBranch %86 + %88 = OpLabel + OpBranch %76 + %76 = OpLabel + OpBranch %73 + %75 = OpLabel + OpStore %72 %9 + OpBranch %106 + %106 = OpLabel + OpLoopMerge %108 %109 None + OpBranch %110 + %110 = OpLabel + %111 = OpLoad %6 %72 + %112 = OpConvertSToF %16 %111 + %113 = OpAccessChain %23 %20 %22 + %114 = OpLoad %16 %113 + %115 = OpFOrdLessThan %26 %112 %114 + OpBranchConditional %115 %107 %108 + %107 = OpLabel + %116 = OpLoad %6 %72 + %117 = OpIAdd %6 %116 %41 + OpStore %72 %117 + OpBranch %109 + %109 = OpLabel + OpBranch %106 + %108 = OpLabel + OpStore %141 %143 + OpReturn + OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + + const ir::Function* f = spvtest::GetFunction(module, 4); + ir::CFG cfg(module); + opt::DominatorAnalysis* analysis = context->GetDominatorAnalysis(f, cfg); + + EXPECT_TRUE(analysis->Dominates(5, 10)); + EXPECT_TRUE(analysis->Dominates(5, 14)); + EXPECT_TRUE(analysis->Dominates(5, 11)); + EXPECT_TRUE(analysis->Dominates(5, 29)); + EXPECT_TRUE(analysis->Dominates(5, 33)); + EXPECT_TRUE(analysis->Dominates(5, 30)); + EXPECT_TRUE(analysis->Dominates(5, 32)); + EXPECT_TRUE(analysis->Dominates(5, 31)); + EXPECT_TRUE(analysis->Dominates(5, 13)); + EXPECT_TRUE(analysis->Dominates(5, 12)); + EXPECT_TRUE(analysis->Dominates(5, 46)); + EXPECT_TRUE(analysis->Dominates(5, 50)); + EXPECT_TRUE(analysis->Dominates(5, 47)); + EXPECT_TRUE(analysis->Dominates(5, 57)); + EXPECT_TRUE(analysis->Dominates(5, 61)); + EXPECT_TRUE(analysis->Dominates(5, 59)); + EXPECT_TRUE(analysis->Dominates(5, 58)); + EXPECT_TRUE(analysis->Dominates(5, 60)); + EXPECT_TRUE(analysis->Dominates(5, 48)); + EXPECT_TRUE(analysis->Dominates(5, 73)); + EXPECT_TRUE(analysis->Dominates(5, 77)); + EXPECT_TRUE(analysis->Dominates(5, 75)); + EXPECT_TRUE(analysis->Dominates(5, 106)); + EXPECT_TRUE(analysis->Dominates(5, 110)); + EXPECT_TRUE(analysis->Dominates(5, 107)); + EXPECT_TRUE(analysis->Dominates(5, 108)); + EXPECT_TRUE(analysis->Dominates(5, 109)); + EXPECT_TRUE(analysis->Dominates(5, 74)); + EXPECT_TRUE(analysis->Dominates(5, 86)); + EXPECT_TRUE(analysis->Dominates(5, 90)); + EXPECT_TRUE(analysis->Dominates(5, 87)); + EXPECT_TRUE(analysis->Dominates(5, 94)); + EXPECT_TRUE(analysis->Dominates(5, 98)); + EXPECT_TRUE(analysis->Dominates(5, 95)); + EXPECT_TRUE(analysis->Dominates(5, 97)); + EXPECT_TRUE(analysis->Dominates(5, 96)); + EXPECT_TRUE(analysis->Dominates(5, 88)); + EXPECT_TRUE(analysis->Dominates(5, 76)); + + EXPECT_TRUE(analysis->Dominates(10, 14)); + EXPECT_TRUE(analysis->Dominates(10, 11)); + EXPECT_TRUE(analysis->Dominates(10, 29)); + EXPECT_TRUE(analysis->Dominates(10, 33)); + EXPECT_TRUE(analysis->Dominates(10, 30)); + EXPECT_TRUE(analysis->Dominates(10, 32)); + EXPECT_TRUE(analysis->Dominates(10, 31)); + EXPECT_TRUE(analysis->Dominates(10, 13)); + EXPECT_TRUE(analysis->Dominates(10, 12)); + EXPECT_TRUE(analysis->Dominates(10, 46)); + EXPECT_TRUE(analysis->Dominates(10, 50)); + EXPECT_TRUE(analysis->Dominates(10, 47)); + EXPECT_TRUE(analysis->Dominates(10, 57)); + EXPECT_TRUE(analysis->Dominates(10, 61)); + EXPECT_TRUE(analysis->Dominates(10, 59)); + EXPECT_TRUE(analysis->Dominates(10, 58)); + EXPECT_TRUE(analysis->Dominates(10, 60)); + EXPECT_TRUE(analysis->Dominates(10, 48)); + EXPECT_TRUE(analysis->Dominates(10, 73)); + EXPECT_TRUE(analysis->Dominates(10, 77)); + EXPECT_TRUE(analysis->Dominates(10, 75)); + EXPECT_TRUE(analysis->Dominates(10, 106)); + EXPECT_TRUE(analysis->Dominates(10, 110)); + EXPECT_TRUE(analysis->Dominates(10, 107)); + EXPECT_TRUE(analysis->Dominates(10, 108)); + EXPECT_TRUE(analysis->Dominates(10, 109)); + EXPECT_TRUE(analysis->Dominates(10, 74)); + EXPECT_TRUE(analysis->Dominates(10, 86)); + EXPECT_TRUE(analysis->Dominates(10, 90)); + EXPECT_TRUE(analysis->Dominates(10, 87)); + EXPECT_TRUE(analysis->Dominates(10, 94)); + EXPECT_TRUE(analysis->Dominates(10, 98)); + EXPECT_TRUE(analysis->Dominates(10, 95)); + EXPECT_TRUE(analysis->Dominates(10, 97)); + EXPECT_TRUE(analysis->Dominates(10, 96)); + EXPECT_TRUE(analysis->Dominates(10, 88)); + EXPECT_TRUE(analysis->Dominates(10, 76)); + + EXPECT_TRUE(analysis->Dominates(14, 11)); + EXPECT_TRUE(analysis->Dominates(14, 29)); + EXPECT_TRUE(analysis->Dominates(14, 33)); + EXPECT_TRUE(analysis->Dominates(14, 30)); + EXPECT_TRUE(analysis->Dominates(14, 32)); + EXPECT_TRUE(analysis->Dominates(14, 31)); + + EXPECT_TRUE(analysis->Dominates(11, 29)); + EXPECT_TRUE(analysis->Dominates(11, 33)); + EXPECT_TRUE(analysis->Dominates(11, 30)); + EXPECT_TRUE(analysis->Dominates(11, 32)); + EXPECT_TRUE(analysis->Dominates(11, 31)); + + EXPECT_TRUE(analysis->Dominates(29, 33)); + EXPECT_TRUE(analysis->Dominates(29, 30)); + EXPECT_TRUE(analysis->Dominates(29, 32)); + EXPECT_TRUE(analysis->Dominates(29, 31)); + + EXPECT_TRUE(analysis->Dominates(33, 30)); + + EXPECT_TRUE(analysis->Dominates(12, 46)); + EXPECT_TRUE(analysis->Dominates(12, 50)); + EXPECT_TRUE(analysis->Dominates(12, 47)); + EXPECT_TRUE(analysis->Dominates(12, 57)); + EXPECT_TRUE(analysis->Dominates(12, 61)); + EXPECT_TRUE(analysis->Dominates(12, 59)); + EXPECT_TRUE(analysis->Dominates(12, 58)); + EXPECT_TRUE(analysis->Dominates(12, 60)); + EXPECT_TRUE(analysis->Dominates(12, 48)); + EXPECT_TRUE(analysis->Dominates(12, 73)); + EXPECT_TRUE(analysis->Dominates(12, 77)); + EXPECT_TRUE(analysis->Dominates(12, 75)); + EXPECT_TRUE(analysis->Dominates(12, 106)); + EXPECT_TRUE(analysis->Dominates(12, 110)); + EXPECT_TRUE(analysis->Dominates(12, 107)); + EXPECT_TRUE(analysis->Dominates(12, 108)); + EXPECT_TRUE(analysis->Dominates(12, 109)); + EXPECT_TRUE(analysis->Dominates(12, 74)); + EXPECT_TRUE(analysis->Dominates(12, 86)); + EXPECT_TRUE(analysis->Dominates(12, 90)); + EXPECT_TRUE(analysis->Dominates(12, 87)); + EXPECT_TRUE(analysis->Dominates(12, 94)); + EXPECT_TRUE(analysis->Dominates(12, 98)); + EXPECT_TRUE(analysis->Dominates(12, 95)); + EXPECT_TRUE(analysis->Dominates(12, 97)); + EXPECT_TRUE(analysis->Dominates(12, 96)); + EXPECT_TRUE(analysis->Dominates(12, 88)); + EXPECT_TRUE(analysis->Dominates(12, 76)); + + EXPECT_TRUE(analysis->Dominates(46, 50)); + EXPECT_TRUE(analysis->Dominates(46, 47)); + EXPECT_TRUE(analysis->Dominates(46, 57)); + EXPECT_TRUE(analysis->Dominates(46, 61)); + EXPECT_TRUE(analysis->Dominates(46, 59)); + EXPECT_TRUE(analysis->Dominates(46, 58)); + EXPECT_TRUE(analysis->Dominates(46, 60)); + EXPECT_TRUE(analysis->Dominates(46, 48)); + EXPECT_TRUE(analysis->Dominates(46, 73)); + EXPECT_TRUE(analysis->Dominates(46, 77)); + EXPECT_TRUE(analysis->Dominates(46, 75)); + EXPECT_TRUE(analysis->Dominates(46, 106)); + EXPECT_TRUE(analysis->Dominates(46, 110)); + EXPECT_TRUE(analysis->Dominates(46, 107)); + EXPECT_TRUE(analysis->Dominates(46, 108)); + EXPECT_TRUE(analysis->Dominates(46, 109)); + EXPECT_TRUE(analysis->Dominates(46, 74)); + EXPECT_TRUE(analysis->Dominates(46, 86)); + EXPECT_TRUE(analysis->Dominates(46, 90)); + EXPECT_TRUE(analysis->Dominates(46, 87)); + EXPECT_TRUE(analysis->Dominates(46, 94)); + EXPECT_TRUE(analysis->Dominates(46, 98)); + EXPECT_TRUE(analysis->Dominates(46, 95)); + EXPECT_TRUE(analysis->Dominates(46, 97)); + EXPECT_TRUE(analysis->Dominates(46, 96)); + EXPECT_TRUE(analysis->Dominates(46, 88)); + EXPECT_TRUE(analysis->Dominates(46, 76)); + + EXPECT_TRUE(analysis->Dominates(50, 47)); + EXPECT_TRUE(analysis->Dominates(50, 57)); + EXPECT_TRUE(analysis->Dominates(50, 61)); + EXPECT_TRUE(analysis->Dominates(50, 59)); + EXPECT_TRUE(analysis->Dominates(50, 58)); + EXPECT_TRUE(analysis->Dominates(50, 60)); + + EXPECT_TRUE(analysis->Dominates(47, 57)); + EXPECT_TRUE(analysis->Dominates(47, 61)); + EXPECT_TRUE(analysis->Dominates(47, 59)); + EXPECT_TRUE(analysis->Dominates(47, 58)); + EXPECT_TRUE(analysis->Dominates(47, 60)); + + EXPECT_TRUE(analysis->Dominates(57, 61)); + EXPECT_TRUE(analysis->Dominates(57, 59)); + EXPECT_TRUE(analysis->Dominates(57, 58)); + EXPECT_TRUE(analysis->Dominates(57, 60)); + + EXPECT_TRUE(analysis->Dominates(61, 59)); + + EXPECT_TRUE(analysis->Dominates(48, 73)); + EXPECT_TRUE(analysis->Dominates(48, 77)); + EXPECT_TRUE(analysis->Dominates(48, 75)); + EXPECT_TRUE(analysis->Dominates(48, 106)); + EXPECT_TRUE(analysis->Dominates(48, 110)); + EXPECT_TRUE(analysis->Dominates(48, 107)); + EXPECT_TRUE(analysis->Dominates(48, 108)); + EXPECT_TRUE(analysis->Dominates(48, 109)); + EXPECT_TRUE(analysis->Dominates(48, 74)); + EXPECT_TRUE(analysis->Dominates(48, 86)); + EXPECT_TRUE(analysis->Dominates(48, 90)); + EXPECT_TRUE(analysis->Dominates(48, 87)); + EXPECT_TRUE(analysis->Dominates(48, 94)); + EXPECT_TRUE(analysis->Dominates(48, 98)); + EXPECT_TRUE(analysis->Dominates(48, 95)); + EXPECT_TRUE(analysis->Dominates(48, 97)); + EXPECT_TRUE(analysis->Dominates(48, 96)); + EXPECT_TRUE(analysis->Dominates(48, 88)); + EXPECT_TRUE(analysis->Dominates(48, 76)); + + EXPECT_TRUE(analysis->Dominates(73, 77)); + EXPECT_TRUE(analysis->Dominates(73, 75)); + EXPECT_TRUE(analysis->Dominates(73, 106)); + EXPECT_TRUE(analysis->Dominates(73, 110)); + EXPECT_TRUE(analysis->Dominates(73, 107)); + EXPECT_TRUE(analysis->Dominates(73, 108)); + EXPECT_TRUE(analysis->Dominates(73, 109)); + EXPECT_TRUE(analysis->Dominates(73, 74)); + EXPECT_TRUE(analysis->Dominates(73, 86)); + EXPECT_TRUE(analysis->Dominates(73, 90)); + EXPECT_TRUE(analysis->Dominates(73, 87)); + EXPECT_TRUE(analysis->Dominates(73, 94)); + EXPECT_TRUE(analysis->Dominates(73, 98)); + EXPECT_TRUE(analysis->Dominates(73, 95)); + EXPECT_TRUE(analysis->Dominates(73, 97)); + EXPECT_TRUE(analysis->Dominates(73, 96)); + EXPECT_TRUE(analysis->Dominates(73, 88)); + EXPECT_TRUE(analysis->Dominates(73, 76)); + + EXPECT_TRUE(analysis->Dominates(75, 106)); + EXPECT_TRUE(analysis->Dominates(75, 110)); + EXPECT_TRUE(analysis->Dominates(75, 107)); + EXPECT_TRUE(analysis->Dominates(75, 108)); + EXPECT_TRUE(analysis->Dominates(75, 109)); + + EXPECT_TRUE(analysis->Dominates(106, 110)); + EXPECT_TRUE(analysis->Dominates(106, 107)); + EXPECT_TRUE(analysis->Dominates(106, 108)); + EXPECT_TRUE(analysis->Dominates(106, 109)); + + EXPECT_TRUE(analysis->Dominates(110, 107)); + + EXPECT_TRUE(analysis->Dominates(77, 74)); + EXPECT_TRUE(analysis->Dominates(77, 86)); + EXPECT_TRUE(analysis->Dominates(77, 90)); + EXPECT_TRUE(analysis->Dominates(77, 87)); + EXPECT_TRUE(analysis->Dominates(77, 94)); + EXPECT_TRUE(analysis->Dominates(77, 98)); + EXPECT_TRUE(analysis->Dominates(77, 95)); + EXPECT_TRUE(analysis->Dominates(77, 97)); + EXPECT_TRUE(analysis->Dominates(77, 96)); + EXPECT_TRUE(analysis->Dominates(77, 88)); + + EXPECT_TRUE(analysis->Dominates(74, 86)); + EXPECT_TRUE(analysis->Dominates(74, 90)); + EXPECT_TRUE(analysis->Dominates(74, 87)); + EXPECT_TRUE(analysis->Dominates(74, 94)); + EXPECT_TRUE(analysis->Dominates(74, 98)); + EXPECT_TRUE(analysis->Dominates(74, 95)); + EXPECT_TRUE(analysis->Dominates(74, 97)); + EXPECT_TRUE(analysis->Dominates(74, 96)); + EXPECT_TRUE(analysis->Dominates(74, 88)); + + EXPECT_TRUE(analysis->Dominates(86, 90)); + EXPECT_TRUE(analysis->Dominates(86, 87)); + EXPECT_TRUE(analysis->Dominates(86, 94)); + EXPECT_TRUE(analysis->Dominates(86, 98)); + EXPECT_TRUE(analysis->Dominates(86, 95)); + EXPECT_TRUE(analysis->Dominates(86, 97)); + EXPECT_TRUE(analysis->Dominates(86, 96)); + EXPECT_TRUE(analysis->Dominates(86, 88)); + + EXPECT_TRUE(analysis->Dominates(90, 87)); + EXPECT_TRUE(analysis->Dominates(90, 94)); + EXPECT_TRUE(analysis->Dominates(90, 98)); + EXPECT_TRUE(analysis->Dominates(90, 95)); + EXPECT_TRUE(analysis->Dominates(90, 97)); + EXPECT_TRUE(analysis->Dominates(90, 96)); + + EXPECT_TRUE(analysis->Dominates(87, 94)); + EXPECT_TRUE(analysis->Dominates(87, 98)); + EXPECT_TRUE(analysis->Dominates(87, 95)); + EXPECT_TRUE(analysis->Dominates(87, 97)); + EXPECT_TRUE(analysis->Dominates(87, 96)); + + EXPECT_TRUE(analysis->Dominates(94, 98)); + EXPECT_TRUE(analysis->Dominates(94, 95)); + EXPECT_TRUE(analysis->Dominates(94, 97)); + EXPECT_TRUE(analysis->Dominates(94, 96)); + + EXPECT_TRUE(analysis->Dominates(98, 95)); + + EXPECT_TRUE(analysis->StrictlyDominates(5, 10)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 14)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 11)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 29)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 33)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 30)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 32)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 31)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 13)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 12)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 46)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 50)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 47)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 57)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 61)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 59)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 58)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 60)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 48)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 73)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 77)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 75)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 106)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 110)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 107)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 108)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 109)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 74)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 86)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 90)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 87)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 94)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 98)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 95)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 97)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 96)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 88)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 76)); + + EXPECT_TRUE(analysis->StrictlyDominates(10, 14)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 11)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 29)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 33)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 30)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 32)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 31)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 13)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 12)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 46)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 50)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 47)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 57)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 61)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 59)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 58)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 60)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 48)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 73)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 77)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 75)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 106)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 110)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 107)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 108)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 109)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 74)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 86)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 90)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 87)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 94)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 98)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 95)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 97)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 96)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 88)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 76)); + + EXPECT_TRUE(analysis->StrictlyDominates(14, 11)); + EXPECT_TRUE(analysis->StrictlyDominates(14, 29)); + EXPECT_TRUE(analysis->StrictlyDominates(14, 33)); + EXPECT_TRUE(analysis->StrictlyDominates(14, 30)); + EXPECT_TRUE(analysis->StrictlyDominates(14, 32)); + EXPECT_TRUE(analysis->StrictlyDominates(14, 31)); + + EXPECT_TRUE(analysis->StrictlyDominates(11, 29)); + EXPECT_TRUE(analysis->StrictlyDominates(11, 33)); + EXPECT_TRUE(analysis->StrictlyDominates(11, 30)); + EXPECT_TRUE(analysis->StrictlyDominates(11, 32)); + EXPECT_TRUE(analysis->StrictlyDominates(11, 31)); + + EXPECT_TRUE(analysis->StrictlyDominates(29, 33)); + EXPECT_TRUE(analysis->StrictlyDominates(29, 30)); + EXPECT_TRUE(analysis->StrictlyDominates(29, 32)); + EXPECT_TRUE(analysis->StrictlyDominates(29, 31)); + + EXPECT_TRUE(analysis->StrictlyDominates(33, 30)); + + EXPECT_TRUE(analysis->StrictlyDominates(12, 46)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 50)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 47)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 57)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 61)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 59)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 58)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 60)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 48)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 73)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 77)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 75)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 106)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 110)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 107)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 108)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 109)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 74)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 86)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 90)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 87)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 94)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 98)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 95)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 97)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 96)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 88)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 76)); + + EXPECT_TRUE(analysis->StrictlyDominates(46, 50)); + EXPECT_TRUE(analysis->StrictlyDominates(46, 47)); + EXPECT_TRUE(analysis->StrictlyDominates(46, 57)); + EXPECT_TRUE(analysis->StrictlyDominates(46, 61)); + EXPECT_TRUE(analysis->StrictlyDominates(46, 59)); + EXPECT_TRUE(analysis->StrictlyDominates(46, 58)); + EXPECT_TRUE(analysis->StrictlyDominates(46, 60)); + EXPECT_TRUE(analysis->StrictlyDominates(46, 48)); + EXPECT_TRUE(analysis->StrictlyDominates(46, 73)); + EXPECT_TRUE(analysis->StrictlyDominates(46, 77)); + EXPECT_TRUE(analysis->StrictlyDominates(46, 75)); + EXPECT_TRUE(analysis->StrictlyDominates(46, 106)); + EXPECT_TRUE(analysis->StrictlyDominates(46, 110)); + EXPECT_TRUE(analysis->StrictlyDominates(46, 107)); + EXPECT_TRUE(analysis->StrictlyDominates(46, 108)); + EXPECT_TRUE(analysis->StrictlyDominates(46, 109)); + EXPECT_TRUE(analysis->StrictlyDominates(46, 74)); + EXPECT_TRUE(analysis->StrictlyDominates(46, 86)); + EXPECT_TRUE(analysis->StrictlyDominates(46, 90)); + EXPECT_TRUE(analysis->StrictlyDominates(46, 87)); + EXPECT_TRUE(analysis->StrictlyDominates(46, 94)); + EXPECT_TRUE(analysis->StrictlyDominates(46, 98)); + EXPECT_TRUE(analysis->StrictlyDominates(46, 95)); + EXPECT_TRUE(analysis->StrictlyDominates(46, 97)); + EXPECT_TRUE(analysis->StrictlyDominates(46, 96)); + EXPECT_TRUE(analysis->StrictlyDominates(46, 88)); + EXPECT_TRUE(analysis->StrictlyDominates(46, 76)); + + EXPECT_TRUE(analysis->StrictlyDominates(50, 47)); + EXPECT_TRUE(analysis->StrictlyDominates(50, 57)); + EXPECT_TRUE(analysis->StrictlyDominates(50, 61)); + EXPECT_TRUE(analysis->StrictlyDominates(50, 59)); + EXPECT_TRUE(analysis->StrictlyDominates(50, 58)); + EXPECT_TRUE(analysis->StrictlyDominates(50, 60)); + + EXPECT_TRUE(analysis->StrictlyDominates(47, 57)); + EXPECT_TRUE(analysis->StrictlyDominates(47, 61)); + EXPECT_TRUE(analysis->StrictlyDominates(47, 59)); + EXPECT_TRUE(analysis->StrictlyDominates(47, 58)); + EXPECT_TRUE(analysis->StrictlyDominates(47, 60)); + + EXPECT_TRUE(analysis->StrictlyDominates(57, 61)); + EXPECT_TRUE(analysis->StrictlyDominates(57, 59)); + EXPECT_TRUE(analysis->StrictlyDominates(57, 58)); + EXPECT_TRUE(analysis->StrictlyDominates(57, 60)); + + EXPECT_TRUE(analysis->StrictlyDominates(61, 59)); + + EXPECT_TRUE(analysis->StrictlyDominates(48, 73)); + EXPECT_TRUE(analysis->StrictlyDominates(48, 77)); + EXPECT_TRUE(analysis->StrictlyDominates(48, 75)); + EXPECT_TRUE(analysis->StrictlyDominates(48, 106)); + EXPECT_TRUE(analysis->StrictlyDominates(48, 110)); + EXPECT_TRUE(analysis->StrictlyDominates(48, 107)); + EXPECT_TRUE(analysis->StrictlyDominates(48, 108)); + EXPECT_TRUE(analysis->StrictlyDominates(48, 109)); + EXPECT_TRUE(analysis->StrictlyDominates(48, 74)); + EXPECT_TRUE(analysis->StrictlyDominates(48, 86)); + EXPECT_TRUE(analysis->StrictlyDominates(48, 90)); + EXPECT_TRUE(analysis->StrictlyDominates(48, 87)); + EXPECT_TRUE(analysis->StrictlyDominates(48, 94)); + EXPECT_TRUE(analysis->StrictlyDominates(48, 98)); + EXPECT_TRUE(analysis->StrictlyDominates(48, 95)); + EXPECT_TRUE(analysis->StrictlyDominates(48, 97)); + EXPECT_TRUE(analysis->StrictlyDominates(48, 96)); + EXPECT_TRUE(analysis->StrictlyDominates(48, 88)); + EXPECT_TRUE(analysis->StrictlyDominates(48, 76)); + + EXPECT_TRUE(analysis->StrictlyDominates(73, 77)); + EXPECT_TRUE(analysis->StrictlyDominates(73, 75)); + EXPECT_TRUE(analysis->StrictlyDominates(73, 106)); + EXPECT_TRUE(analysis->StrictlyDominates(73, 110)); + EXPECT_TRUE(analysis->StrictlyDominates(73, 107)); + EXPECT_TRUE(analysis->StrictlyDominates(73, 108)); + EXPECT_TRUE(analysis->StrictlyDominates(73, 109)); + EXPECT_TRUE(analysis->StrictlyDominates(73, 74)); + EXPECT_TRUE(analysis->StrictlyDominates(73, 86)); + EXPECT_TRUE(analysis->StrictlyDominates(73, 90)); + EXPECT_TRUE(analysis->StrictlyDominates(73, 87)); + EXPECT_TRUE(analysis->StrictlyDominates(73, 94)); + EXPECT_TRUE(analysis->StrictlyDominates(73, 98)); + EXPECT_TRUE(analysis->StrictlyDominates(73, 95)); + EXPECT_TRUE(analysis->StrictlyDominates(73, 97)); + EXPECT_TRUE(analysis->StrictlyDominates(73, 96)); + EXPECT_TRUE(analysis->StrictlyDominates(73, 88)); + EXPECT_TRUE(analysis->StrictlyDominates(73, 76)); + + EXPECT_TRUE(analysis->StrictlyDominates(75, 106)); + EXPECT_TRUE(analysis->StrictlyDominates(75, 110)); + EXPECT_TRUE(analysis->StrictlyDominates(75, 107)); + EXPECT_TRUE(analysis->StrictlyDominates(75, 108)); + EXPECT_TRUE(analysis->StrictlyDominates(75, 109)); + + EXPECT_TRUE(analysis->StrictlyDominates(106, 110)); + EXPECT_TRUE(analysis->StrictlyDominates(106, 107)); + EXPECT_TRUE(analysis->StrictlyDominates(106, 108)); + EXPECT_TRUE(analysis->StrictlyDominates(106, 109)); + + EXPECT_TRUE(analysis->StrictlyDominates(110, 107)); + + EXPECT_TRUE(analysis->StrictlyDominates(77, 74)); + EXPECT_TRUE(analysis->StrictlyDominates(77, 86)); + EXPECT_TRUE(analysis->StrictlyDominates(77, 90)); + EXPECT_TRUE(analysis->StrictlyDominates(77, 87)); + EXPECT_TRUE(analysis->StrictlyDominates(77, 94)); + EXPECT_TRUE(analysis->StrictlyDominates(77, 98)); + EXPECT_TRUE(analysis->StrictlyDominates(77, 95)); + EXPECT_TRUE(analysis->StrictlyDominates(77, 97)); + EXPECT_TRUE(analysis->StrictlyDominates(77, 96)); + EXPECT_TRUE(analysis->StrictlyDominates(77, 88)); + + EXPECT_TRUE(analysis->StrictlyDominates(74, 86)); + EXPECT_TRUE(analysis->StrictlyDominates(74, 90)); + EXPECT_TRUE(analysis->StrictlyDominates(74, 87)); + EXPECT_TRUE(analysis->StrictlyDominates(74, 94)); + EXPECT_TRUE(analysis->StrictlyDominates(74, 98)); + EXPECT_TRUE(analysis->StrictlyDominates(74, 95)); + EXPECT_TRUE(analysis->StrictlyDominates(74, 97)); + EXPECT_TRUE(analysis->StrictlyDominates(74, 96)); + EXPECT_TRUE(analysis->StrictlyDominates(74, 88)); + + EXPECT_TRUE(analysis->StrictlyDominates(86, 90)); + EXPECT_TRUE(analysis->StrictlyDominates(86, 87)); + EXPECT_TRUE(analysis->StrictlyDominates(86, 94)); + EXPECT_TRUE(analysis->StrictlyDominates(86, 98)); + EXPECT_TRUE(analysis->StrictlyDominates(86, 95)); + EXPECT_TRUE(analysis->StrictlyDominates(86, 97)); + EXPECT_TRUE(analysis->StrictlyDominates(86, 96)); + EXPECT_TRUE(analysis->StrictlyDominates(86, 88)); + + EXPECT_TRUE(analysis->StrictlyDominates(90, 87)); + EXPECT_TRUE(analysis->StrictlyDominates(90, 94)); + EXPECT_TRUE(analysis->StrictlyDominates(90, 98)); + EXPECT_TRUE(analysis->StrictlyDominates(90, 95)); + EXPECT_TRUE(analysis->StrictlyDominates(90, 97)); + EXPECT_TRUE(analysis->StrictlyDominates(90, 96)); + + EXPECT_TRUE(analysis->StrictlyDominates(87, 94)); + EXPECT_TRUE(analysis->StrictlyDominates(87, 98)); + EXPECT_TRUE(analysis->StrictlyDominates(87, 95)); + EXPECT_TRUE(analysis->StrictlyDominates(87, 97)); + EXPECT_TRUE(analysis->StrictlyDominates(87, 96)); + + EXPECT_TRUE(analysis->StrictlyDominates(94, 98)); + EXPECT_TRUE(analysis->StrictlyDominates(94, 95)); + EXPECT_TRUE(analysis->StrictlyDominates(94, 97)); + EXPECT_TRUE(analysis->StrictlyDominates(94, 96)); + + EXPECT_TRUE(analysis->StrictlyDominates(98, 95)); +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/dominator_tree/post.cpp b/3rdparty/spirv-tools/test/opt/dominator_tree/post.cpp new file mode 100644 index 000000000..2c786aee7 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/dominator_tree/post.cpp @@ -0,0 +1,206 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include + +#include "../assembly_builder.h" +#include "../function_utils.h" +#include "../pass_fixture.h" +#include "../pass_utils.h" +#include "opt/dominator_analysis.h" +#include "opt/pass.h" + +namespace { + +using namespace spvtools; +using ::testing::UnorderedElementsAre; + +using PassClassTest = PassTest<::testing::Test>; + +/* + Generated from the following GLSL +#version 440 core +layout(location = 0) out vec4 c; +layout(location = 1)in vec4 in_val; +void main(){ + if ( in_val.x < 10) { + int z = 0; + int i = 0; + for (i = 0; i < in_val.y; ++i) { + z += i; + } + c = vec4(i,i,i,i); + } else { + c = vec4(1,1,1,1); + } +} +*/ +TEST_F(PassClassTest, BasicVisitFromEntryPoint) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %9 %43 + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %9 "in_val" + OpName %22 "z" + OpName %24 "i" + OpName %43 "c" + OpDecorate %9 Location 1 + OpDecorate %43 Location 0 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypeVector %6 4 + %8 = OpTypePointer Input %7 + %9 = OpVariable %8 Input + %10 = OpTypeInt 32 0 + %11 = OpConstant %10 0 + %12 = OpTypePointer Input %6 + %15 = OpConstant %6 10 + %16 = OpTypeBool + %20 = OpTypeInt 32 1 + %21 = OpTypePointer Function %20 + %23 = OpConstant %20 0 + %32 = OpConstant %10 1 + %40 = OpConstant %20 1 + %42 = OpTypePointer Output %7 + %43 = OpVariable %42 Output + %54 = OpConstant %6 1 + %55 = OpConstantComposite %7 %54 %54 %54 %54 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %22 = OpVariable %21 Function + %24 = OpVariable %21 Function + %13 = OpAccessChain %12 %9 %11 + %14 = OpLoad %6 %13 + %17 = OpFOrdLessThan %16 %14 %15 + OpSelectionMerge %19 None + OpBranchConditional %17 %18 %53 + %18 = OpLabel + OpStore %22 %23 + OpStore %24 %23 + OpStore %24 %23 + OpBranch %25 + %25 = OpLabel + OpLoopMerge %27 %28 None + OpBranch %29 + %29 = OpLabel + %30 = OpLoad %20 %24 + %31 = OpConvertSToF %6 %30 + %33 = OpAccessChain %12 %9 %32 + %34 = OpLoad %6 %33 + %35 = OpFOrdLessThan %16 %31 %34 + OpBranchConditional %35 %26 %27 + %26 = OpLabel + %36 = OpLoad %20 %24 + %37 = OpLoad %20 %22 + %38 = OpIAdd %20 %37 %36 + OpStore %22 %38 + OpBranch %28 + %28 = OpLabel + %39 = OpLoad %20 %24 + %41 = OpIAdd %20 %39 %40 + OpStore %24 %41 + OpBranch %25 + %27 = OpLabel + %44 = OpLoad %20 %24 + %45 = OpConvertSToF %6 %44 + %46 = OpLoad %20 %24 + %47 = OpConvertSToF %6 %46 + %48 = OpLoad %20 %24 + %49 = OpConvertSToF %6 %48 + %50 = OpLoad %20 %24 + %51 = OpConvertSToF %6 %50 + %52 = OpCompositeConstruct %7 %45 %47 %49 %51 + OpStore %43 %52 + OpBranch %19 + %53 = OpLabel + OpStore %43 %55 + OpBranch %19 + %19 = OpLabel + OpReturn + OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + + const ir::Function* f = spvtest::GetFunction(module, 4); + ir::CFG cfg(module); + opt::PostDominatorAnalysis* analysis = + context->GetPostDominatorAnalysis(f, cfg); + + EXPECT_TRUE(analysis->Dominates(19, 18)); + EXPECT_TRUE(analysis->Dominates(19, 5)); + EXPECT_TRUE(analysis->Dominates(19, 53)); + EXPECT_TRUE(analysis->Dominates(19, 19)); + EXPECT_TRUE(analysis->Dominates(19, 25)); + EXPECT_TRUE(analysis->Dominates(19, 29)); + EXPECT_TRUE(analysis->Dominates(19, 27)); + EXPECT_TRUE(analysis->Dominates(19, 26)); + EXPECT_TRUE(analysis->Dominates(19, 28)); + + EXPECT_TRUE(analysis->Dominates(27, 18)); + EXPECT_TRUE(analysis->Dominates(27, 25)); + EXPECT_TRUE(analysis->Dominates(27, 29)); + EXPECT_TRUE(analysis->Dominates(27, 27)); + EXPECT_TRUE(analysis->Dominates(27, 26)); + EXPECT_TRUE(analysis->Dominates(27, 28)); + + EXPECT_FALSE(analysis->Dominates(27, 19)); + EXPECT_FALSE(analysis->Dominates(27, 5)); + EXPECT_FALSE(analysis->Dominates(27, 53)); + + EXPECT_FALSE(analysis->StrictlyDominates(19, 19)); + + EXPECT_TRUE(analysis->StrictlyDominates(19, 18)); + EXPECT_TRUE(analysis->StrictlyDominates(19, 5)); + EXPECT_TRUE(analysis->StrictlyDominates(19, 53)); + EXPECT_TRUE(analysis->StrictlyDominates(19, 25)); + EXPECT_TRUE(analysis->StrictlyDominates(19, 29)); + EXPECT_TRUE(analysis->StrictlyDominates(19, 27)); + EXPECT_TRUE(analysis->StrictlyDominates(19, 26)); + EXPECT_TRUE(analysis->StrictlyDominates(19, 28)); + + // These would be expected true for a normal, non post, dominator tree + EXPECT_FALSE(analysis->Dominates(5, 18)); + EXPECT_FALSE(analysis->Dominates(5, 53)); + EXPECT_FALSE(analysis->Dominates(5, 19)); + EXPECT_FALSE(analysis->Dominates(5, 25)); + EXPECT_FALSE(analysis->Dominates(5, 29)); + EXPECT_FALSE(analysis->Dominates(5, 27)); + EXPECT_FALSE(analysis->Dominates(5, 26)); + EXPECT_FALSE(analysis->Dominates(5, 28)); + + EXPECT_FALSE(analysis->StrictlyDominates(5, 18)); + EXPECT_FALSE(analysis->StrictlyDominates(5, 53)); + EXPECT_FALSE(analysis->StrictlyDominates(5, 19)); + EXPECT_FALSE(analysis->StrictlyDominates(5, 25)); + EXPECT_FALSE(analysis->StrictlyDominates(5, 29)); + EXPECT_FALSE(analysis->StrictlyDominates(5, 27)); + EXPECT_FALSE(analysis->StrictlyDominates(5, 26)); + EXPECT_FALSE(analysis->StrictlyDominates(5, 28)); +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/dominator_tree/simple.cpp b/3rdparty/spirv-tools/test/opt/dominator_tree/simple.cpp new file mode 100644 index 000000000..4cba7dc8a --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/dominator_tree/simple.cpp @@ -0,0 +1,175 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include + +#include "../assembly_builder.h" +#include "../function_utils.h" +#include "../pass_fixture.h" +#include "../pass_utils.h" +#include "opt/dominator_analysis.h" +#include "opt/pass.h" + +namespace { + +using namespace spvtools; +using ::testing::UnorderedElementsAre; + +using PassClassTest = PassTest<::testing::Test>; + +/* +Generated from the following GLSL +#version 440 core +layout(location = 0) out vec4 c; +layout(location = 1)in vec4 in_val; +void main(){ + if ( in_val.x < 10) { + int z = 0; + int i = 0; + for (i = 0; i < in_val.y; ++i) { + z += i; + } + c = vec4(i,i,i,i); + } else { + c = vec4(1,1,1,1); + } +} +*/ +TEST_F(PassClassTest, BasicVisitFromEntryPoint) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %9 %43 + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %9 "in_val" + OpName %22 "z" + OpName %24 "i" + OpName %43 "c" + OpDecorate %9 Location 1 + OpDecorate %43 Location 0 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypeVector %6 4 + %8 = OpTypePointer Input %7 + %9 = OpVariable %8 Input + %10 = OpTypeInt 32 0 + %11 = OpConstant %10 0 + %12 = OpTypePointer Input %6 + %15 = OpConstant %6 10 + %16 = OpTypeBool + %20 = OpTypeInt 32 1 + %21 = OpTypePointer Function %20 + %23 = OpConstant %20 0 + %32 = OpConstant %10 1 + %40 = OpConstant %20 1 + %42 = OpTypePointer Output %7 + %43 = OpVariable %42 Output + %54 = OpConstant %6 1 + %55 = OpConstantComposite %7 %54 %54 %54 %54 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %22 = OpVariable %21 Function + %24 = OpVariable %21 Function + %13 = OpAccessChain %12 %9 %11 + %14 = OpLoad %6 %13 + %17 = OpFOrdLessThan %16 %14 %15 + OpSelectionMerge %19 None + OpBranchConditional %17 %18 %53 + %18 = OpLabel + OpStore %22 %23 + OpStore %24 %23 + OpStore %24 %23 + OpBranch %25 + %25 = OpLabel + OpLoopMerge %27 %28 None + OpBranch %29 + %29 = OpLabel + %30 = OpLoad %20 %24 + %31 = OpConvertSToF %6 %30 + %33 = OpAccessChain %12 %9 %32 + %34 = OpLoad %6 %33 + %35 = OpFOrdLessThan %16 %31 %34 + OpBranchConditional %35 %26 %27 + %26 = OpLabel + %36 = OpLoad %20 %24 + %37 = OpLoad %20 %22 + %38 = OpIAdd %20 %37 %36 + OpStore %22 %38 + OpBranch %28 + %28 = OpLabel + %39 = OpLoad %20 %24 + %41 = OpIAdd %20 %39 %40 + OpStore %24 %41 + OpBranch %25 + %27 = OpLabel + %44 = OpLoad %20 %24 + %45 = OpConvertSToF %6 %44 + %46 = OpLoad %20 %24 + %47 = OpConvertSToF %6 %46 + %48 = OpLoad %20 %24 + %49 = OpConvertSToF %6 %48 + %50 = OpLoad %20 %24 + %51 = OpConvertSToF %6 %50 + %52 = OpCompositeConstruct %7 %45 %47 %49 %51 + OpStore %43 %52 + OpBranch %19 + %53 = OpLabel + OpStore %43 %55 + OpBranch %19 + %19 = OpLabel + OpReturn + OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + const ir::Function* f = spvtest::GetFunction(module, 4); + + ir::CFG cfg(module); + opt::DominatorAnalysis* analysis = context->GetDominatorAnalysis(f, cfg); + + opt::DominatorTree& tree = analysis->GetDomTree(); + + EXPECT_EQ(tree.GetRoot()->bb_, cfg.pseudo_entry_block()); + EXPECT_TRUE(analysis->Dominates(5, 18)); + EXPECT_TRUE(analysis->Dominates(5, 53)); + EXPECT_TRUE(analysis->Dominates(5, 19)); + EXPECT_TRUE(analysis->Dominates(5, 25)); + EXPECT_TRUE(analysis->Dominates(5, 29)); + EXPECT_TRUE(analysis->Dominates(5, 27)); + EXPECT_TRUE(analysis->Dominates(5, 26)); + EXPECT_TRUE(analysis->Dominates(5, 28)); + + EXPECT_TRUE(analysis->StrictlyDominates(5, 18)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 53)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 19)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 25)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 29)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 27)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 26)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 28)); +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/dominator_tree/switch_case_fallthrough.cpp b/3rdparty/spirv-tools/test/opt/dominator_tree/switch_case_fallthrough.cpp new file mode 100644 index 000000000..2b2e27ba4 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/dominator_tree/switch_case_fallthrough.cpp @@ -0,0 +1,162 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include + +#include "../assembly_builder.h" +#include "../function_utils.h" +#include "../pass_fixture.h" +#include "../pass_utils.h" +#include "opt/dominator_analysis.h" +#include "opt/pass.h" + +namespace { + +using namespace spvtools; +using ::testing::UnorderedElementsAre; + +using PassClassTest = PassTest<::testing::Test>; + +/* + Generated from the following GLSL +#version 440 core +layout(location = 0) out vec4 v; +layout(location = 1) in vec4 in_val; +void main() { + int i; + switch (int(in_val.x)) { + case 0: + i = 0; + case 1: + i = 1; + break; + case 2: + i = 2; + case 3: + i = 3; + case 4: + i = 4; + break; + default: + i = 0; + } + v = vec4(i, i, i, i); +} +*/ +TEST_F(PassClassTest, UnreachableNestedIfs) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %9 %35 + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %9 "in_val" + OpName %25 "i" + OpName %35 "v" + OpDecorate %9 Location 1 + OpDecorate %35 Location 0 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypeVector %6 4 + %8 = OpTypePointer Input %7 + %9 = OpVariable %8 Input + %10 = OpTypeInt 32 0 + %11 = OpConstant %10 0 + %12 = OpTypePointer Input %6 + %15 = OpTypeInt 32 1 + %24 = OpTypePointer Function %15 + %26 = OpConstant %15 0 + %27 = OpConstant %15 1 + %29 = OpConstant %15 2 + %30 = OpConstant %15 3 + %31 = OpConstant %15 4 + %34 = OpTypePointer Output %7 + %35 = OpVariable %34 Output + %4 = OpFunction %2 None %3 + %5 = OpLabel + %25 = OpVariable %24 Function + %13 = OpAccessChain %12 %9 %11 + %14 = OpLoad %6 %13 + %16 = OpConvertFToS %15 %14 + OpSelectionMerge %23 None + OpSwitch %16 %22 0 %17 1 %18 2 %19 3 %20 4 %21 + %22 = OpLabel + OpStore %25 %26 + OpBranch %23 + %17 = OpLabel + OpStore %25 %26 + OpBranch %18 + %18 = OpLabel + OpStore %25 %27 + OpBranch %23 + %19 = OpLabel + OpStore %25 %29 + OpBranch %20 + %20 = OpLabel + OpStore %25 %30 + OpBranch %21 + %21 = OpLabel + OpStore %25 %31 + OpBranch %23 + %23 = OpLabel + %36 = OpLoad %15 %25 + %37 = OpConvertSToF %6 %36 + %38 = OpLoad %15 %25 + %39 = OpConvertSToF %6 %38 + %40 = OpLoad %15 %25 + %41 = OpConvertSToF %6 %40 + %42 = OpLoad %15 %25 + %43 = OpConvertSToF %6 %42 + %44 = OpCompositeConstruct %7 %37 %39 %41 %43 + OpStore %35 %44 + OpReturn + OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + + const ir::Function* f = spvtest::GetFunction(module, 4); + ir::CFG cfg(module); + opt::DominatorAnalysis* analysis = context->GetDominatorAnalysis(f, cfg); + + EXPECT_TRUE(analysis->Dominates(5, 5)); + EXPECT_TRUE(analysis->Dominates(5, 17)); + EXPECT_TRUE(analysis->Dominates(5, 18)); + EXPECT_TRUE(analysis->Dominates(5, 19)); + EXPECT_TRUE(analysis->Dominates(5, 20)); + EXPECT_TRUE(analysis->Dominates(5, 21)); + EXPECT_TRUE(analysis->Dominates(5, 22)); + EXPECT_TRUE(analysis->Dominates(5, 23)); + + EXPECT_TRUE(analysis->StrictlyDominates(5, 17)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 18)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 19)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 20)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 21)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 22)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 23)); +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/dominator_tree/unreachable_for.cpp b/3rdparty/spirv-tools/test/opt/dominator_tree/unreachable_for.cpp new file mode 100644 index 000000000..a720cf4ea --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/dominator_tree/unreachable_for.cpp @@ -0,0 +1,120 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include + +#include "../assembly_builder.h" +#include "../function_utils.h" +#include "../pass_fixture.h" +#include "../pass_utils.h" +#include "opt/dominator_analysis.h" +#include "opt/pass.h" + +namespace { + +using namespace spvtools; +using ::testing::UnorderedElementsAre; + +using PassClassTest = PassTest<::testing::Test>; + +/* + Generated from the following GLSL +#version 440 core +void main() { + for (int i = 0; i < 1; i++) { + break; + } +} +*/ +TEST_F(PassClassTest, UnreachableNestedIfs) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 1 + %17 = OpTypeBool + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %15 = OpLoad %6 %8 + %18 = OpSLessThan %17 %15 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpBranch %12 + %13 = OpLabel + %20 = OpLoad %6 %8 + %21 = OpIAdd %6 %20 %16 + OpStore %8 %21 + OpBranch %10 + %12 = OpLabel + OpReturn + OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + + const ir::Function* f = spvtest::GetFunction(module, 4); + ir::CFG cfg(module); + opt::DominatorAnalysis* analysis = context->GetDominatorAnalysis(f, cfg); + EXPECT_TRUE(analysis->Dominates(5, 5)); + EXPECT_TRUE(analysis->Dominates(5, 10)); + EXPECT_TRUE(analysis->Dominates(5, 14)); + EXPECT_TRUE(analysis->Dominates(5, 11)); + EXPECT_TRUE(analysis->Dominates(5, 12)); + EXPECT_TRUE(analysis->Dominates(10, 10)); + EXPECT_TRUE(analysis->Dominates(10, 14)); + EXPECT_TRUE(analysis->Dominates(10, 11)); + EXPECT_TRUE(analysis->Dominates(10, 12)); + EXPECT_TRUE(analysis->Dominates(14, 14)); + EXPECT_TRUE(analysis->Dominates(14, 11)); + EXPECT_TRUE(analysis->Dominates(14, 12)); + EXPECT_TRUE(analysis->Dominates(11, 11)); + EXPECT_TRUE(analysis->Dominates(12, 12)); + + EXPECT_TRUE(analysis->StrictlyDominates(5, 10)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 14)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 11)); + EXPECT_TRUE(analysis->StrictlyDominates(5, 12)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 14)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 11)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 12)); + EXPECT_TRUE(analysis->StrictlyDominates(14, 11)); + EXPECT_TRUE(analysis->StrictlyDominates(14, 12)); +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/dominator_tree/unreachable_for_post.cpp b/3rdparty/spirv-tools/test/opt/dominator_tree/unreachable_for_post.cpp new file mode 100644 index 000000000..18a112ee2 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/dominator_tree/unreachable_for_post.cpp @@ -0,0 +1,118 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include + +#include "../assembly_builder.h" +#include "../function_utils.h" +#include "../pass_fixture.h" +#include "../pass_utils.h" +#include "opt/dominator_analysis.h" +#include "opt/pass.h" + +namespace { + +using namespace spvtools; +using ::testing::UnorderedElementsAre; + +using PassClassTest = PassTest<::testing::Test>; + +/* + Generated from the following GLSL +#version 440 core +void main() { + for (int i = 0; i < 1; i++) { + break; + } +} +*/ +TEST_F(PassClassTest, UnreachableNestedIfs) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 1 + %17 = OpTypeBool + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %15 = OpLoad %6 %8 + %18 = OpSLessThan %17 %15 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpBranch %12 + %13 = OpLabel + %20 = OpLoad %6 %8 + %21 = OpIAdd %6 %20 %16 + OpStore %8 %21 + OpBranch %10 + %12 = OpLabel + OpReturn + OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + + const ir::Function* f = spvtest::GetFunction(module, 4); + + ir::CFG cfg(module); + opt::PostDominatorAnalysis* analysis = + context->GetPostDominatorAnalysis(f, cfg); + + EXPECT_TRUE(analysis->Dominates(12, 12)); + EXPECT_TRUE(analysis->Dominates(12, 14)); + EXPECT_TRUE(analysis->Dominates(12, 11)); + EXPECT_TRUE(analysis->Dominates(12, 10)); + EXPECT_TRUE(analysis->Dominates(12, 5)); + EXPECT_TRUE(analysis->Dominates(14, 14)); + EXPECT_TRUE(analysis->Dominates(14, 10)); + EXPECT_TRUE(analysis->Dominates(14, 5)); + EXPECT_TRUE(analysis->Dominates(10, 10)); + EXPECT_TRUE(analysis->Dominates(10, 5)); + EXPECT_TRUE(analysis->Dominates(5, 5)); + + EXPECT_TRUE(analysis->StrictlyDominates(12, 14)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 11)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 10)); + EXPECT_TRUE(analysis->StrictlyDominates(12, 5)); + EXPECT_TRUE(analysis->StrictlyDominates(14, 10)); + EXPECT_TRUE(analysis->StrictlyDominates(14, 5)); + EXPECT_TRUE(analysis->StrictlyDominates(10, 5)); +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/eliminate_dead_const_test.cpp b/3rdparty/spirv-tools/test/opt/eliminate_dead_const_test.cpp new file mode 100644 index 000000000..89613384e --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/eliminate_dead_const_test.cpp @@ -0,0 +1,842 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "assembly_builder.h" +#include "pass_fixture.h" +#include "pass_utils.h" + +#include +#include +#include +#include +#include + +namespace { + +using namespace spvtools; + +using EliminateDeadConstantBasicTest = PassTest<::testing::Test>; + +TEST_F(EliminateDeadConstantBasicTest, BasicAllDeadConstants) { + const std::vector text = { + // clang-format off + "OpCapability Shader", + "OpCapability Float64", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Vertex %main \"main\"", + "OpName %main \"main\"", + "%void = OpTypeVoid", + "%4 = OpTypeFunction %void", + "%bool = OpTypeBool", + "%true = OpConstantTrue %bool", + "%false = OpConstantFalse %bool", + "%int = OpTypeInt 32 1", + "%9 = OpConstant %int 1", + "%uint = OpTypeInt 32 0", + "%11 = OpConstant %uint 2", + "%float = OpTypeFloat 32", + "%13 = OpConstant %float 3.1415", + "%double = OpTypeFloat 64", + "%15 = OpConstant %double 3.14159265358979", + "%main = OpFunction %void None %4", + "%16 = OpLabel", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + // None of the above constants is ever used, so all of them should be + // eliminated. + const char* const_decl_opcodes[] = { + " OpConstantTrue ", + " OpConstantFalse ", + " OpConstant ", + }; + // Skip lines that have any one of const_decl_opcodes. + const std::string expected_disassembly = + SelectiveJoin(text, [&const_decl_opcodes](const char* line) { + return std::any_of( + std::begin(const_decl_opcodes), std::end(const_decl_opcodes), + [&line](const char* const_decl_op) { + return std::string(line).find(const_decl_op) != std::string::npos; + }); + }); + + SinglePassRunAndCheck( + JoinAllInsts(text), expected_disassembly, /* skip_nop = */ true); +} + +TEST_F(EliminateDeadConstantBasicTest, BasicNoneDeadConstants) { + const std::vector text = { + // clang-format off + "OpCapability Shader", + "OpCapability Float64", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Vertex %main \"main\"", + "OpName %main \"main\"", + "OpName %btv \"btv\"", + "OpName %bfv \"bfv\"", + "OpName %iv \"iv\"", + "OpName %uv \"uv\"", + "OpName %fv \"fv\"", + "OpName %dv \"dv\"", + "%void = OpTypeVoid", + "%10 = OpTypeFunction %void", + "%bool = OpTypeBool", + "%_ptr_Function_bool = OpTypePointer Function %bool", + "%true = OpConstantTrue %bool", + "%false = OpConstantFalse %bool", + "%int = OpTypeInt 32 1", + "%_ptr_Function_int = OpTypePointer Function %int", + "%int_1 = OpConstant %int 1", + "%uint = OpTypeInt 32 0", + "%_ptr_Function_uint = OpTypePointer Function %uint", + "%uint_2 = OpConstant %uint 2", + "%float = OpTypeFloat 32", + "%_ptr_Function_float = OpTypePointer Function %float", + "%float_3_1415 = OpConstant %float 3.1415", + "%double = OpTypeFloat 64", + "%_ptr_Function_double = OpTypePointer Function %double", + "%double_3_14159265358979 = OpConstant %double 3.14159265358979", + "%main = OpFunction %void None %10", + "%27 = OpLabel", + "%btv = OpVariable %_ptr_Function_bool Function", + "%bfv = OpVariable %_ptr_Function_bool Function", + "%iv = OpVariable %_ptr_Function_int Function", + "%uv = OpVariable %_ptr_Function_uint Function", + "%fv = OpVariable %_ptr_Function_float Function", + "%dv = OpVariable %_ptr_Function_double Function", + "OpStore %btv %true", + "OpStore %bfv %false", + "OpStore %iv %int_1", + "OpStore %uv %uint_2", + "OpStore %fv %float_3_1415", + "OpStore %dv %double_3_14159265358979", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + // All constants are used, so none of them should be eliminated. + SinglePassRunAndCheck( + JoinAllInsts(text), JoinAllInsts(text), /* skip_nop = */ true); +} + +struct EliminateDeadConstantTestCase { + // Type declarations and constants that should be kept. + std::vector used_consts; + // Instructions that refer to constants, this is added to create uses for + // some constants so they won't be treated as dead constants. + std::vector main_insts; + // Dead constants that should be removed. + std::vector dead_consts; +}; + +// All types that are potentially required in EliminateDeadConstantTest. +const std::vector CommonTypes = { + // clang-format off + // scalar types + "%bool = OpTypeBool", + "%uint = OpTypeInt 32 0", + "%int = OpTypeInt 32 1", + "%float = OpTypeFloat 32", + "%double = OpTypeFloat 64", + // vector types + "%v2bool = OpTypeVector %bool 2", + "%v2uint = OpTypeVector %uint 2", + "%v2int = OpTypeVector %int 2", + "%v3int = OpTypeVector %int 3", + "%v4int = OpTypeVector %int 4", + "%v2float = OpTypeVector %float 2", + "%v3float = OpTypeVector %float 3", + "%v2double = OpTypeVector %double 2", + // variable pointer types + "%_pf_bool = OpTypePointer Function %bool", + "%_pf_uint = OpTypePointer Function %uint", + "%_pf_int = OpTypePointer Function %int", + "%_pf_float = OpTypePointer Function %float", + "%_pf_double = OpTypePointer Function %double", + "%_pf_v2int = OpTypePointer Function %v2int", + "%_pf_v3int = OpTypePointer Function %v3int", + "%_pf_v2float = OpTypePointer Function %v2float", + "%_pf_v3float = OpTypePointer Function %v3float", + "%_pf_v2double = OpTypePointer Function %v2double", + // struct types + "%inner_struct = OpTypeStruct %bool %int %float %double", + "%outer_struct = OpTypeStruct %inner_struct %int %double", + "%flat_struct = OpTypeStruct %bool %int %float %double", + // clang-format on +}; + +using EliminateDeadConstantTest = + PassTest<::testing::TestWithParam>; + +TEST_P(EliminateDeadConstantTest, Custom) { + auto& tc = GetParam(); + AssemblyBuilder builder; + builder.AppendTypesConstantsGlobals(CommonTypes) + .AppendTypesConstantsGlobals(tc.used_consts) + .AppendInMain(tc.main_insts); + const std::string expected = builder.GetCode(); + builder.AppendTypesConstantsGlobals(tc.dead_consts); + const std::string assembly_with_dead_const = builder.GetCode(); + SinglePassRunAndCheck( + assembly_with_dead_const, expected, /* skip_nop = */ true); +} + +INSTANTIATE_TEST_CASE_P( + ScalarTypeConstants, EliminateDeadConstantTest, + ::testing::ValuesIn(std::vector({ + // clang-format off + // Scalar type constants, one dead constant and one used constant. + { + /* .used_consts = */ + { + "%used_const_int = OpConstant %int 1", + }, + /* .main_insts = */ + { + "%int_var = OpVariable %_pf_int Function", + "OpStore %int_var %used_const_int", + }, + /* .dead_consts = */ + { + "%dead_const_int = OpConstant %int 1", + }, + }, + { + /* .used_consts = */ + { + "%used_const_uint = OpConstant %uint 1", + }, + /* .main_insts = */ + { + "%uint_var = OpVariable %_pf_uint Function", + "OpStore %uint_var %used_const_uint", + }, + /* .dead_consts = */ + { + "%dead_const_uint = OpConstant %uint 1", + }, + }, + { + /* .used_consts = */ + { + "%used_const_float = OpConstant %float 3.1415", + }, + /* .main_insts = */ + { + "%float_var = OpVariable %_pf_float Function", + "OpStore %float_var %used_const_float", + }, + /* .dead_consts = */ + { + "%dead_const_float = OpConstant %float 3.1415", + }, + }, + { + /* .used_consts = */ + { + "%used_const_double = OpConstant %double 3.141592653", + }, + /* .main_insts = */ + { + "%double_var = OpVariable %_pf_double Function", + "OpStore %double_var %used_const_double", + }, + /* .dead_consts = */ + { + "%dead_const_double = OpConstant %double 3.141592653", + }, + }, + // clang-format on + }))); + +INSTANTIATE_TEST_CASE_P( + VectorTypeConstants, EliminateDeadConstantTest, + ::testing::ValuesIn(std::vector({ + // clang-format off + // Tests eliminating dead constant type ivec2. One dead constant vector + // and one used constant vector, each built from its own group of + // scalar constants. + { + /* .used_consts = */ + { + "%used_int_x = OpConstant %int 1", + "%used_int_y = OpConstant %int 2", + "%used_v2int = OpConstantComposite %v2int %used_int_x %used_int_y", + }, + /* .main_insts = */ + { + "%v2int_var = OpVariable %_pf_v2int Function", + "OpStore %v2int_var %used_v2int", + }, + /* .dead_consts = */ + { + "%dead_int_x = OpConstant %int 1", + "%dead_int_y = OpConstant %int 2", + "%dead_v2int = OpConstantComposite %v2int %dead_int_x %dead_int_y", + }, + }, + // Tests eliminating dead constant ivec2. One dead constant vector and + // one used constant vector. But both built from a same group of + // scalar constants. + { + /* .used_consts = */ + { + "%used_int_x = OpConstant %int 1", + "%used_int_y = OpConstant %int 2", + "%used_int_z = OpConstant %int 3", + "%used_v3int = OpConstantComposite %v3int %used_int_x %used_int_y %used_int_z", + }, + /* .main_insts = */ + { + "%v3int_var = OpVariable %_pf_v3int Function", + "OpStore %v3int_var %used_v3int", + }, + /* .dead_consts = */ + { + "%dead_v3int = OpConstantComposite %v3int %used_int_x %used_int_y %used_int_z", + }, + }, + // Tests eliminating dead cosntant vec2. One dead constant vector and + // one used constant vector. Each built from its own group of scalar + // constants. + { + /* .used_consts = */ + { + "%used_float_x = OpConstant %float 3.1415", + "%used_float_y = OpConstant %float 4.25", + "%used_v2float = OpConstantComposite %v2float %used_float_x %used_float_y", + }, + /* .main_insts = */ + { + "%v2float_var = OpVariable %_pf_v2float Function", + "OpStore %v2float_var %used_v2float", + }, + /* .dead_consts = */ + { + "%dead_float_x = OpConstant %float 3.1415", + "%dead_float_y = OpConstant %float 4.25", + "%dead_v2float = OpConstantComposite %v2float %dead_float_x %dead_float_y", + }, + }, + // Tests eliminating dead cosntant vec2. One dead constant vector and + // one used constant vector. Both built from a same group of scalar + // constants. + { + /* .used_consts = */ + { + "%used_float_x = OpConstant %float 3.1415", + "%used_float_y = OpConstant %float 4.25", + "%used_float_z = OpConstant %float 4.75", + "%used_v3float = OpConstantComposite %v3float %used_float_x %used_float_y %used_float_z", + }, + /* .main_insts = */ + { + "%v3float_var = OpVariable %_pf_v3float Function", + "OpStore %v3float_var %used_v3float", + }, + /* .dead_consts = */ + { + "%dead_v3float = OpConstantComposite %v3float %used_float_x %used_float_y %used_float_z", + }, + }, + // clang-format on + }))); + +INSTANTIATE_TEST_CASE_P( + StructTypeConstants, EliminateDeadConstantTest, + ::testing::ValuesIn(std::vector({ + // clang-format off + // A plain struct type dead constants. All of its components are dead + // constants too. + { + /* .used_consts = */ {}, + /* .main_insts = */ {}, + /* .dead_consts = */ + { + "%dead_bool = OpConstantTrue %bool", + "%dead_int = OpConstant %int 1", + "%dead_float = OpConstant %float 2.5", + "%dead_double = OpConstant %double 3.14159265358979", + "%dead_struct = OpConstantComposite %flat_struct %dead_bool %dead_int %dead_float %dead_double", + }, + }, + // A plain struct type dead constants. Some of its components are dead + // constants while others are not. + { + /* .used_consts = */ + { + "%used_int = OpConstant %int 1", + "%used_double = OpConstant %double 3.14159265358979", + }, + /* .main_insts = */ + { + "%int_var = OpVariable %_pf_int Function", + "OpStore %int_var %used_int", + "%double_var = OpVariable %_pf_double Function", + "OpStore %double_var %used_double", + }, + /* .dead_consts = */ + { + "%dead_bool = OpConstantTrue %bool", + "%dead_float = OpConstant %float 2.5", + "%dead_struct = OpConstantComposite %flat_struct %dead_bool %used_int %dead_float %used_double", + }, + }, + // A nesting struct type dead constants. All components of both outer + // and inner structs are dead and should be removed after dead constant + // elimination. + { + /* .used_consts = */ {}, + /* .main_insts = */ {}, + /* .dead_consts = */ + { + "%dead_bool = OpConstantTrue %bool", + "%dead_int = OpConstant %int 1", + "%dead_float = OpConstant %float 2.5", + "%dead_double = OpConstant %double 3.1415926535", + "%dead_inner_struct = OpConstantComposite %inner_struct %dead_bool %dead_int %dead_float %dead_double", + "%dead_int2 = OpConstant %int 2", + "%dead_double2 = OpConstant %double 1.428571428514", + "%dead_outer_struct = OpConstantComposite %outer_struct %dead_inner_struct %dead_int2 %dead_double2", + }, + }, + // A nesting struct type dead constants. Some of its components are + // dead constants while others are not. + { + /* .used_consts = */ + { + "%used_int = OpConstant %int 1", + "%used_double = OpConstant %double 3.14159265358979", + }, + /* .main_insts = */ + { + "%int_var = OpVariable %_pf_int Function", + "OpStore %int_var %used_int", + "%double_var = OpVariable %_pf_double Function", + "OpStore %double_var %used_double", + }, + /* .dead_consts = */ + { + "%dead_bool = OpConstantTrue %bool", + "%dead_float = OpConstant %float 2.5", + "%dead_inner_struct = OpConstantComposite %inner_struct %dead_bool %used_int %dead_float %used_double", + "%dead_int = OpConstant %int 2", + "%dead_outer_struct = OpConstantComposite %outer_struct %dead_inner_struct %dead_int %used_double", + }, + }, + // A nesting struct case. The inner struct is used while the outer struct is not + { + /* .used_const = */ + { + "%used_bool = OpConstantTrue %bool", + "%used_int = OpConstant %int 1", + "%used_float = OpConstant %float 1.25", + "%used_double = OpConstant %double 1.23456789012345", + "%used_inner_struct = OpConstantComposite %inner_struct %used_bool %used_int %used_float %used_double", + }, + /* .main_insts = */ + { + "%bool_var = OpVariable %_pf_bool Function", + "%bool_from_inner_struct = OpCompositeExtract %bool %used_inner_struct 0", + "OpStore %bool_var %bool_from_inner_struct", + }, + /* .dead_consts = */ + { + "%dead_int = OpConstant %int 2", + "%dead_outer_struct = OpConstantComposite %outer_struct %used_inner_struct %dead_int %used_double" + }, + }, + // A nesting struct case. The outer struct is used, so the inner struct should not + // be removed even though it is not used anywhere. + { + /* .used_const = */ + { + "%used_bool = OpConstantTrue %bool", + "%used_int = OpConstant %int 1", + "%used_float = OpConstant %float 1.25", + "%used_double = OpConstant %double 1.23456789012345", + "%used_inner_struct = OpConstantComposite %inner_struct %used_bool %used_int %used_float %used_double", + "%used_outer_struct = OpConstantComposite %outer_struct %used_inner_struct %used_int %used_double" + }, + /* .main_insts = */ + { + "%int_var = OpVariable %_pf_int Function", + "%int_from_outer_struct = OpCompositeExtract %int %used_outer_struct 1", + "OpStore %int_var %int_from_outer_struct", + }, + /* .dead_consts = */ {}, + }, + // clang-format on + }))); + +INSTANTIATE_TEST_CASE_P( + ScalarTypeSpecConstants, EliminateDeadConstantTest, + ::testing::ValuesIn(std::vector({ + // clang-format off + // All scalar type spec constants. + { + /* .used_consts = */ + { + "%used_bool = OpSpecConstantTrue %bool", + "%used_uint = OpSpecConstant %uint 2", + "%used_int = OpSpecConstant %int 2", + "%used_float = OpSpecConstant %float 2.5", + "%used_double = OpSpecConstant %double 1.42857142851", + }, + /* .main_insts = */ + { + "%bool_var = OpVariable %_pf_bool Function", + "%uint_var = OpVariable %_pf_uint Function", + "%int_var = OpVariable %_pf_int Function", + "%float_var = OpVariable %_pf_float Function", + "%double_var = OpVariable %_pf_double Function", + "OpStore %bool_var %used_bool", "OpStore %uint_var %used_uint", + "OpStore %int_var %used_int", "OpStore %float_var %used_float", + "OpStore %double_var %used_double", + }, + /* .dead_consts = */ + { + "%dead_bool = OpSpecConstantTrue %bool", + "%dead_uint = OpSpecConstant %uint 2", + "%dead_int = OpSpecConstant %int 2", + "%dead_float = OpSpecConstant %float 2.5", + "%dead_double = OpSpecConstant %double 1.42857142851", + }, + }, + // clang-format on + }))); + +INSTANTIATE_TEST_CASE_P( + VectorTypeSpecConstants, EliminateDeadConstantTest, + ::testing::ValuesIn(std::vector({ + // clang-format off + // Bool vector type spec constants. One vector has all component dead, + // another vector has one dead boolean and one used boolean. + { + /* .used_consts = */ + { + "%used_bool = OpSpecConstantTrue %bool", + }, + /* .main_insts = */ + { + "%bool_var = OpVariable %_pf_bool Function", + "OpStore %bool_var %used_bool", + }, + /* .dead_consts = */ + { + "%dead_bool = OpSpecConstantFalse %bool", + "%dead_bool_vec1 = OpSpecConstantComposite %v2bool %dead_bool %dead_bool", + "%dead_bool_vec2 = OpSpecConstantComposite %v2bool %dead_bool %used_bool", + }, + }, + + // Uint vector type spec constants. One vector has all component dead, + // another vector has one dead unsigend integer and one used unsigned + // integer. + { + /* .used_consts = */ + { + "%used_uint = OpSpecConstant %uint 3", + }, + /* .main_insts = */ + { + "%uint_var = OpVariable %_pf_uint Function", + "OpStore %uint_var %used_uint", + }, + /* .dead_consts = */ + { + "%dead_uint = OpSpecConstant %uint 1", + "%dead_uint_vec1 = OpSpecConstantComposite %v2uint %dead_uint %dead_uint", + "%dead_uint_vec2 = OpSpecConstantComposite %v2uint %dead_uint %used_uint", + }, + }, + + // Int vector type spec constants. One vector has all component dead, + // another vector has one dead integer and one used integer. + { + /* .used_consts = */ + { + "%used_int = OpSpecConstant %int 3", + }, + /* .main_insts = */ + { + "%int_var = OpVariable %_pf_int Function", + "OpStore %int_var %used_int", + }, + /* .dead_consts = */ + { + "%dead_int = OpSpecConstant %int 1", + "%dead_int_vec1 = OpSpecConstantComposite %v2int %dead_int %dead_int", + "%dead_int_vec2 = OpSpecConstantComposite %v2int %dead_int %used_int", + }, + }, + + // Int vector type spec constants built with both spec constants and + // front-end constants. + { + /* .used_consts = */ + { + "%used_spec_int = OpSpecConstant %int 3", + "%used_front_end_int = OpConstant %int 3", + }, + /* .main_insts = */ + { + "%int_var1 = OpVariable %_pf_int Function", + "OpStore %int_var1 %used_spec_int", + "%int_var2 = OpVariable %_pf_int Function", + "OpStore %int_var2 %used_front_end_int", + }, + /* .dead_consts = */ + { + "%dead_spec_int = OpSpecConstant %int 1", + "%dead_front_end_int = OpConstant %int 1", + // Dead front-end and dead spec constants + "%dead_int_vec1 = OpSpecConstantComposite %v2int %dead_spec_int %dead_front_end_int", + // Used front-end and dead spec constants + "%dead_int_vec2 = OpSpecConstantComposite %v2int %dead_spec_int %used_front_end_int", + // Dead front-end and used spec constants + "%dead_int_vec3 = OpSpecConstantComposite %v2int %dead_front_end_int %used_spec_int", + }, + }, + // clang-format on + }))); + +INSTANTIATE_TEST_CASE_P( + SpecConstantOp, EliminateDeadConstantTest, + ::testing::ValuesIn(std::vector({ + // clang-format off + // Cast operations: uint <-> int <-> bool + { + /* .used_consts = */ {}, + /* .main_insts = */ {}, + /* .dead_consts = */ + { + // Assistant constants, only used in dead spec constant + // operations. + "%signed_zero = OpConstant %int 0", + "%signed_zero_vec = OpConstantComposite %v2int %signed_zero %signed_zero", + "%unsigned_zero = OpConstant %uint 0", + "%unsigned_zero_vec = OpConstantComposite %v2uint %unsigned_zero %unsigned_zero", + "%signed_one = OpConstant %int 1", + "%signed_one_vec = OpConstantComposite %v2int %signed_one %signed_one", + "%unsigned_one = OpConstant %uint 1", + "%unsigned_one_vec = OpConstantComposite %v2uint %unsigned_one %unsigned_one", + + // Spec constants that support casting to each other. + "%dead_bool = OpSpecConstantTrue %bool", + "%dead_uint = OpSpecConstant %uint 1", + "%dead_int = OpSpecConstant %int 2", + "%dead_bool_vec = OpSpecConstantComposite %v2bool %dead_bool %dead_bool", + "%dead_uint_vec = OpSpecConstantComposite %v2uint %dead_uint %dead_uint", + "%dead_int_vec = OpSpecConstantComposite %v2int %dead_int %dead_int", + + // Scalar cast to boolean spec constant. + "%int_to_bool = OpSpecConstantOp %bool INotEqual %dead_int %signed_zero", + "%uint_to_bool = OpSpecConstantOp %bool INotEqual %dead_uint %unsigned_zero", + + // Vector cast to boolean spec constant. + "%int_to_bool_vec = OpSpecConstantOp %v2bool INotEqual %dead_int_vec %signed_zero_vec", + "%uint_to_bool_vec = OpSpecConstantOp %v2bool INotEqual %dead_uint_vec %unsigned_zero_vec", + + // Scalar cast to int spec constant. + "%bool_to_int = OpSpecConstantOp %int Select %dead_bool %signed_one %signed_zero", + "%uint_to_int = OpSpecConstantOp %uint IAdd %dead_uint %unsigned_zero", + + // Vector cast to int spec constant. + "%bool_to_int_vec = OpSpecConstantOp %v2int Select %dead_bool_vec %signed_one_vec %signed_zero_vec", + "%uint_to_int_vec = OpSpecConstantOp %v2uint IAdd %dead_uint_vec %unsigned_zero_vec", + + // Scalar cast to uint spec constant. + "%bool_to_uint = OpSpecConstantOp %uint Select %dead_bool %unsigned_one %unsigned_zero", + "%int_to_uint_vec = OpSpecConstantOp %uint IAdd %dead_int %signed_zero", + + // Vector cast to uint spec constant. + "%bool_to_uint_vec = OpSpecConstantOp %v2uint Select %dead_bool_vec %unsigned_one_vec %unsigned_zero_vec", + "%int_to_uint = OpSpecConstantOp %v2uint IAdd %dead_int_vec %signed_zero_vec", + }, + }, + + // Add, sub, mul, div, rem. + { + /* .used_consts = */ {}, + /* .main_insts = */ {}, + /* .dead_consts = */ + { + "%dead_spec_int_a = OpSpecConstant %int 1", + "%dead_spec_int_a_vec = OpSpecConstantComposite %v2int %dead_spec_int_a %dead_spec_int_a", + + "%dead_spec_int_b = OpSpecConstant %int 2", + "%dead_spec_int_b_vec = OpSpecConstantComposite %v2int %dead_spec_int_b %dead_spec_int_b", + + "%dead_const_int_c = OpConstant %int 3", + "%dead_const_int_c_vec = OpConstantComposite %v2int %dead_const_int_c %dead_const_int_c", + + // Add + "%add_a_b = OpSpecConstantOp %int IAdd %dead_spec_int_a %dead_spec_int_b", + "%add_a_b_vec = OpSpecConstantOp %v2int IAdd %dead_spec_int_a_vec %dead_spec_int_b_vec", + + // Sub + "%sub_a_b = OpSpecConstantOp %int ISub %dead_spec_int_a %dead_spec_int_b", + "%sub_a_b_vec = OpSpecConstantOp %v2int ISub %dead_spec_int_a_vec %dead_spec_int_b_vec", + + // Mul + "%mul_a_b = OpSpecConstantOp %int IMul %dead_spec_int_a %dead_spec_int_b", + "%mul_a_b_vec = OpSpecConstantOp %v2int IMul %dead_spec_int_a_vec %dead_spec_int_b_vec", + + // Div + "%div_a_b = OpSpecConstantOp %int SDiv %dead_spec_int_a %dead_spec_int_b", + "%div_a_b_vec = OpSpecConstantOp %v2int SDiv %dead_spec_int_a_vec %dead_spec_int_b_vec", + + // Bitwise Xor + "%xor_a_b = OpSpecConstantOp %int BitwiseXor %dead_spec_int_a %dead_spec_int_b", + "%xor_a_b_vec = OpSpecConstantOp %v2int BitwiseXor %dead_spec_int_a_vec %dead_spec_int_b_vec", + + // Scalar Comparison + "%less_a_b = OpSpecConstantOp %bool SLessThan %dead_spec_int_a %dead_spec_int_b", + }, + }, + + // Vectors without used swizzles should be removed. + { + /* .used_consts = */ + { + "%used_int = OpConstant %int 3", + }, + /* .main_insts = */ + { + "%int_var = OpVariable %_pf_int Function", + "OpStore %int_var %used_int", + }, + /* .dead_consts = */ + { + "%dead_int = OpConstant %int 3", + + "%dead_spec_int_a = OpSpecConstant %int 1", + "%vec_a = OpSpecConstantComposite %v4int %dead_spec_int_a %dead_spec_int_a %dead_int %dead_int", + + "%dead_spec_int_b = OpSpecConstant %int 2", + "%vec_b = OpSpecConstantComposite %v4int %dead_spec_int_b %dead_spec_int_b %used_int %used_int", + + // Extract scalar + "%a_x = OpSpecConstantOp %int CompositeExtract %vec_a 0", + "%b_x = OpSpecConstantOp %int CompositeExtract %vec_b 0", + + // Extract vector + "%a_xy = OpSpecConstantOp %v2int VectorShuffle %vec_a %vec_a 0 1", + "%b_xy = OpSpecConstantOp %v2int VectorShuffle %vec_b %vec_b 0 1", + }, + }, + // Vectors with used swizzles should not be removed. + { + /* .used_consts = */ + { + "%used_int = OpConstant %int 3", + "%used_spec_int_a = OpSpecConstant %int 1", + "%used_spec_int_b = OpSpecConstant %int 2", + // Create vectors + "%vec_a = OpSpecConstantComposite %v4int %used_spec_int_a %used_spec_int_a %used_int %used_int", + "%vec_b = OpSpecConstantComposite %v4int %used_spec_int_b %used_spec_int_b %used_int %used_int", + // Extract vector + "%a_xy = OpSpecConstantOp %v2int VectorShuffle %vec_a %vec_a 0 1", + "%b_xy = OpSpecConstantOp %v2int VectorShuffle %vec_b %vec_b 0 1", + }, + /* .main_insts = */ + { + "%v2int_var_a = OpVariable %_pf_v2int Function", + "%v2int_var_b = OpVariable %_pf_v2int Function", + "OpStore %v2int_var_a %a_xy", + "OpStore %v2int_var_b %b_xy", + }, + /* .dead_consts = */ {}, + }, + // clang-format on + }))); + +INSTANTIATE_TEST_CASE_P( + LongDefUseChain, EliminateDeadConstantTest, + ::testing::ValuesIn(std::vector({ + // clang-format off + // Long Def-Use chain with binary operations. + { + /* .used_consts = */ + { + "%array_size = OpConstant %int 4", + "%type_arr_int_4 = OpTypeArray %int %array_size", + "%used_int_0 = OpConstant %int 100", + "%used_int_1 = OpConstant %int 1", + "%used_int_2 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_1", + "%used_int_3 = OpSpecConstantOp %int ISub %used_int_0 %used_int_2", + "%used_int_4 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_3", + "%used_int_5 = OpSpecConstantOp %int ISub %used_int_0 %used_int_4", + "%used_int_6 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_5", + "%used_int_7 = OpSpecConstantOp %int ISub %used_int_0 %used_int_6", + "%used_int_8 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_7", + "%used_int_9 = OpSpecConstantOp %int ISub %used_int_0 %used_int_8", + "%used_int_10 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_9", + "%used_int_11 = OpSpecConstantOp %int ISub %used_int_0 %used_int_10", + "%used_int_12 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_11", + "%used_int_13 = OpSpecConstantOp %int ISub %used_int_0 %used_int_12", + "%used_int_14 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_13", + "%used_int_15 = OpSpecConstantOp %int ISub %used_int_0 %used_int_14", + "%used_int_16 = OpSpecConstantOp %int ISub %used_int_0 %used_int_15", + "%used_int_17 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_16", + "%used_int_18 = OpSpecConstantOp %int ISub %used_int_0 %used_int_17", + "%used_int_19 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_18", + "%used_int_20 = OpSpecConstantOp %int ISub %used_int_0 %used_int_19", + "%used_vec_a = OpSpecConstantComposite %v2int %used_int_18 %used_int_19", + "%used_vec_b = OpSpecConstantOp %v2int IMul %used_vec_a %used_vec_a", + "%used_int_21 = OpSpecConstantOp %int CompositeExtract %used_vec_b 0", + "%used_array = OpConstantComposite %type_arr_int_4 %used_int_20 %used_int_20 %used_int_21 %used_int_21", + }, + /* .main_insts = */ + { + "%int_var = OpVariable %_pf_int Function", + "%used_array_2 = OpCompositeExtract %int %used_array 2", + "OpStore %int_var %used_array_2", + }, + /* .dead_consts = */ + { + "%dead_int_1 = OpConstant %int 2", + "%dead_int_2 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_1", + "%dead_int_3 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_2", + "%dead_int_4 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_3", + "%dead_int_5 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_4", + "%dead_int_6 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_5", + "%dead_int_7 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_6", + "%dead_int_8 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_7", + "%dead_int_9 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_8", + "%dead_int_10 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_9", + "%dead_int_11 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_10", + "%dead_int_12 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_11", + "%dead_int_13 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_12", + "%dead_int_14 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_13", + "%dead_int_15 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_14", + "%dead_int_16 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_15", + "%dead_int_17 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_16", + "%dead_int_18 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_17", + "%dead_int_19 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_18", + "%dead_int_20 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_19", + "%dead_vec_a = OpSpecConstantComposite %v2int %dead_int_18 %dead_int_19", + "%dead_vec_b = OpSpecConstantOp %v2int IMul %dead_vec_a %dead_vec_a", + "%dead_int_21 = OpSpecConstantOp %int CompositeExtract %dead_vec_b 0", + "%dead_array = OpConstantComposite %type_arr_int_4 %dead_int_20 %used_int_20 %dead_int_19 %used_int_19", + }, + }, + // Long Def-Use chain with swizzle + // clang-format on + }))); +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/eliminate_dead_functions_test.cpp b/3rdparty/spirv-tools/test/opt/eliminate_dead_functions_test.cpp new file mode 100644 index 000000000..c780717d8 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/eliminate_dead_functions_test.cpp @@ -0,0 +1,206 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include + +#include "assembly_builder.h" +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; +using ::testing::HasSubstr; + +using EliminateDeadFunctionsBasicTest = PassTest<::testing::Test>; + +TEST_F(EliminateDeadFunctionsBasicTest, BasicDeleteDeadFunction) { + // The function Dead should be removed because it is never called. + const std::vector common_code = { + // clang-format off + "OpCapability Shader", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Fragment %main \"main\"", + "OpName %main \"main\"", + "OpName %Live \"Live\"", + "%void = OpTypeVoid", + "%7 = OpTypeFunction %void", + "%main = OpFunction %void None %7", + "%15 = OpLabel", + "%16 = OpFunctionCall %void %Live", + "%17 = OpFunctionCall %void %Live", + "OpReturn", + "OpFunctionEnd", + "%Live = OpFunction %void None %7", + "%20 = OpLabel", + "OpReturn", + "OpFunctionEnd" + // clang-format on + }; + + const std::vector dead_function = { + // clang-format off + "%Dead = OpFunction %void None %7", + "%19 = OpLabel", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck( + JoinAllInsts(Concat(common_code, dead_function)), + JoinAllInsts(common_code), /* skip_nop = */ true); +} + +TEST_F(EliminateDeadFunctionsBasicTest, BasicKeepLiveFunction) { + // Everything is reachable from an entry point, so no functions should be + // deleted. + const std::vector text = { + // clang-format off + "OpCapability Shader", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Fragment %main \"main\"", + "OpName %main \"main\"", + "OpName %Live1 \"Live1\"", + "OpName %Live2 \"Live2\"", + "%void = OpTypeVoid", + "%7 = OpTypeFunction %void", + "%main = OpFunction %void None %7", + "%15 = OpLabel", + "%16 = OpFunctionCall %void %Live2", + "%17 = OpFunctionCall %void %Live1", + "OpReturn", + "OpFunctionEnd", + "%Live1 = OpFunction %void None %7", + "%19 = OpLabel", + "OpReturn", + "OpFunctionEnd", + "%Live2 = OpFunction %void None %7", + "%20 = OpLabel", + "OpReturn", + "OpFunctionEnd" + // clang-format on + }; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + std::string assembly = JoinAllInsts(text); + auto result = SinglePassRunAndDisassemble( + assembly, /* skip_nop = */ true, /* do_validation = */ false); + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); + EXPECT_EQ(assembly, std::get<0>(result)); +} + +TEST_F(EliminateDeadFunctionsBasicTest, BasicKeepExportFunctions) { + // All functions are reachable. In particular, ExportedFunc and Constant are + // reachable because ExportedFunc is exported. Nothing should be removed. + const std::vector text = { + // clang-format off + "OpCapability Shader", + "OpCapability Linkage", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Fragment %main \"main\"", + "OpName %main \"main\"", + "OpName %ExportedFunc \"ExportedFunc\"", + "OpName %Live \"Live\"", + "OpDecorate %ExportedFunc LinkageAttributes \"ExportedFunc\" Export", + "%void = OpTypeVoid", + "%7 = OpTypeFunction %void", + "%main = OpFunction %void None %7", + "%15 = OpLabel", + "OpReturn", + "OpFunctionEnd", +"%ExportedFunc = OpFunction %void None %7", + "%19 = OpLabel", + "%16 = OpFunctionCall %void %Live", + "OpReturn", + "OpFunctionEnd", + "%Live = OpFunction %void None %7", + "%20 = OpLabel", + "OpReturn", + "OpFunctionEnd" + // clang-format on + }; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + std::string assembly = JoinAllInsts(text); + auto result = SinglePassRunAndDisassemble( + assembly, /* skip_nop = */ true, /* do_validation = */ false); + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); + EXPECT_EQ(assembly, std::get<0>(result)); +} + +TEST_F(EliminateDeadFunctionsBasicTest, BasicRemoveDecorationsAndNames) { + // We want to remove the names and decorations associated with results that + // are removed. This test will check for that. + const std::string text = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" + OpName %main "main" + OpName %Dead "Dead" + OpName %x "x" + OpName %y "y" + OpName %z "z" + OpDecorate %x RelaxedPrecision + OpDecorate %y RelaxedPrecision + OpDecorate %z RelaxedPrecision + OpDecorate %6 RelaxedPrecision + OpDecorate %7 RelaxedPrecision + OpDecorate %8 RelaxedPrecision + %void = OpTypeVoid + %10 = OpTypeFunction %void + %float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float + %float_1 = OpConstant %float 1 + %main = OpFunction %void None %10 + %14 = OpLabel + OpReturn + OpFunctionEnd + %Dead = OpFunction %void None %10 + %15 = OpLabel + %x = OpVariable %_ptr_Function_float Function + %y = OpVariable %_ptr_Function_float Function + %z = OpVariable %_ptr_Function_float Function + OpStore %x %float_1 + OpStore %y %float_1 + %6 = OpLoad %float %x + %7 = OpLoad %float %y + %8 = OpFAdd %float %6 %7 + OpStore %z %8 + OpReturn + OpFunctionEnd)"; + + const std::string expected_output = R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %main "main" +OpName %main "main" +%void = OpTypeVoid +%10 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float +%float_1 = OpConstant %float 1 +%main = OpFunction %void None %10 +%14 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck(text, expected_output, + /* skip_nop = */ true); +} +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/feature_manager_test.cpp b/3rdparty/spirv-tools/test/opt/feature_manager_test.cpp new file mode 100644 index 000000000..f67388a8c --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/feature_manager_test.cpp @@ -0,0 +1,134 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "opt/build_module.h" +#include "opt/ir_context.h" + +using namespace spvtools; + +using FeatureManagerTest = ::testing::Test; + +TEST_F(FeatureManagerTest, MissingExtension) { + const std::string text = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + ASSERT_NE(context, nullptr); + + EXPECT_FALSE(context->get_feature_mgr()->HasExtension( + libspirv::Extension::kSPV_KHR_variable_pointers)); +} + +TEST_F(FeatureManagerTest, OneExtension) { + const std::string text = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpExtension "SPV_KHR_variable_pointers" + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + ASSERT_NE(context, nullptr); + + EXPECT_TRUE(context->get_feature_mgr()->HasExtension( + libspirv::Extension::kSPV_KHR_variable_pointers)); +} + +TEST_F(FeatureManagerTest, NotADifferentExtension) { + const std::string text = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpExtension "SPV_KHR_variable_pointers" + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + ASSERT_NE(context, nullptr); + + EXPECT_FALSE(context->get_feature_mgr()->HasExtension( + libspirv::Extension::kSPV_KHR_storage_buffer_storage_class)); +} + +TEST_F(FeatureManagerTest, TwoExtensions) { + const std::string text = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpExtension "SPV_KHR_variable_pointers" +OpExtension "SPV_KHR_storage_buffer_storage_class" + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + ASSERT_NE(context, nullptr); + + EXPECT_TRUE(context->get_feature_mgr()->HasExtension( + libspirv::Extension::kSPV_KHR_variable_pointers)); + EXPECT_TRUE(context->get_feature_mgr()->HasExtension( + libspirv::Extension::kSPV_KHR_storage_buffer_storage_class)); +} + +// Test capability checks. +TEST_F(FeatureManagerTest, ExplicitlyPresent1) { + const std::string text = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + ASSERT_NE(context, nullptr); + + EXPECT_TRUE(context->get_feature_mgr()->HasCapability(SpvCapabilityShader)); + EXPECT_FALSE(context->get_feature_mgr()->HasCapability(SpvCapabilityKernel)); +} + +TEST_F(FeatureManagerTest, ExplicitlyPresent2) { + const std::string text = R"( +OpCapability Kernel +OpMemoryModel Logical GLSL450 + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + ASSERT_NE(context, nullptr); + + EXPECT_FALSE(context->get_feature_mgr()->HasCapability(SpvCapabilityShader)); + EXPECT_TRUE(context->get_feature_mgr()->HasCapability(SpvCapabilityKernel)); +} + +TEST_F(FeatureManagerTest, ImplicitlyPresent) { + const std::string text = R"( +OpCapability Tessellation +OpMemoryModel Logical GLSL450 + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + ASSERT_NE(context, nullptr); + + // Check multiple levels of indirection. Tessellation implies Shader, which + // implies Matrix. + EXPECT_TRUE( + context->get_feature_mgr()->HasCapability(SpvCapabilityTessellation)); + EXPECT_TRUE(context->get_feature_mgr()->HasCapability(SpvCapabilityShader)); + EXPECT_TRUE(context->get_feature_mgr()->HasCapability(SpvCapabilityMatrix)); + EXPECT_FALSE(context->get_feature_mgr()->HasCapability(SpvCapabilityKernel)); +} diff --git a/3rdparty/spirv-tools/test/opt/flatten_decoration_test.cpp b/3rdparty/spirv-tools/test/opt/flatten_decoration_test.cpp new file mode 100644 index 000000000..8e6d979ad --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/flatten_decoration_test.cpp @@ -0,0 +1,234 @@ +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; + +// Returns the initial part of the assembly text for a valid +// SPIR-V module, including instructions prior to decorations. +std::string PreambleAssembly() { + return + R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %hue %saturation %value +OpName %main "main" +OpName %void_fn "void_fn" +OpName %hue "hue" +OpName %saturation "saturation" +OpName %value "value" +OpName %entry "entry" +OpName %Point "Point" +OpName %Camera "Camera" +)"; +} + +// Retuns types +std::string TypesAndFunctionsAssembly() { + return + R"(%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%float = OpTypeFloat 32 +%Point = OpTypeStruct %float %float %float +%Camera = OpTypeStruct %float %float +%_ptr_Input_float = OpTypePointer Input %float +%hue = OpVariable %_ptr_Input_float Input +%saturation = OpVariable %_ptr_Input_float Input +%value = OpVariable %_ptr_Input_float Input +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; +} + +struct FlattenDecorationCase { + // Names and decorations before the pass. + std::string input; + // Names and decorations after the pass. + std::string expected; +}; + +using FlattenDecorationTest = + PassTest<::testing::TestWithParam>; + +TEST_P(FlattenDecorationTest, TransformsDecorations) { + const auto before = + PreambleAssembly() + GetParam().input + TypesAndFunctionsAssembly(); + const auto after = + PreambleAssembly() + GetParam().expected + TypesAndFunctionsAssembly(); + + SinglePassRunAndCheck(before, after, false, true); +} + +INSTANTIATE_TEST_CASE_P(NoUses, FlattenDecorationTest, + ::testing::ValuesIn(std::vector{ + // No OpDecorationGroup + {"", ""}, + + // OpDecorationGroup without any uses, and + // no OpName. + {"%group = OpDecorationGroup\n", ""}, + + // OpDecorationGroup without any uses, and + // with OpName targeting it. Proves you must + // remove the names as well. + {"OpName %group \"group\"\n" + "%group = OpDecorationGroup\n", + ""}, + + // OpDecorationGroup with decorations that + // target it, but no uses in OpGroupDecorate + // or OpGroupMemberDecorate instructions. + {"OpDecorate %group Flat\n" + "OpDecorate %group NoPerspective\n" + "%group = OpDecorationGroup\n", + ""}, + }), ); + +INSTANTIATE_TEST_CASE_P(OpGroupDecorate, FlattenDecorationTest, + ::testing::ValuesIn(std::vector{ + // One OpGroupDecorate + {"OpName %group \"group\"\n" + "OpDecorate %group Flat\n" + "OpDecorate %group NoPerspective\n" + "%group = OpDecorationGroup\n" + "OpGroupDecorate %group %hue %saturation\n", + "OpDecorate %hue Flat\n" + "OpDecorate %saturation Flat\n" + "OpDecorate %hue NoPerspective\n" + "OpDecorate %saturation NoPerspective\n"}, + // Multiple OpGroupDecorate + {"OpName %group \"group\"\n" + "OpDecorate %group Flat\n" + "OpDecorate %group NoPerspective\n" + "%group = OpDecorationGroup\n" + "OpGroupDecorate %group %hue %value\n" + "OpGroupDecorate %group %saturation\n", + "OpDecorate %hue Flat\n" + "OpDecorate %value Flat\n" + "OpDecorate %saturation Flat\n" + "OpDecorate %hue NoPerspective\n" + "OpDecorate %value NoPerspective\n" + "OpDecorate %saturation NoPerspective\n"}, + // Two group decorations, interleaved + {"OpName %group0 \"group0\"\n" + "OpName %group1 \"group1\"\n" + "OpDecorate %group0 Flat\n" + "OpDecorate %group1 NoPerspective\n" + "%group0 = OpDecorationGroup\n" + "%group1 = OpDecorationGroup\n" + "OpGroupDecorate %group0 %hue %value\n" + "OpGroupDecorate %group1 %saturation\n", + "OpDecorate %hue Flat\n" + "OpDecorate %value Flat\n" + "OpDecorate %saturation NoPerspective\n"}, + // Decoration with operands + {"OpName %group \"group\"\n" + "OpDecorate %group Location 42\n" + "%group = OpDecorationGroup\n" + "OpGroupDecorate %group %hue %saturation\n", + "OpDecorate %hue Location 42\n" + "OpDecorate %saturation Location 42\n"}, + }), ); + +INSTANTIATE_TEST_CASE_P(OpGroupMemberDecorate, FlattenDecorationTest, + ::testing::ValuesIn(std::vector{ + // One OpGroupMemberDecorate + {"OpName %group \"group\"\n" + "OpDecorate %group Flat\n" + "OpDecorate %group Offset 16\n" + "%group = OpDecorationGroup\n" + "OpGroupMemberDecorate %group %Point 1\n", + "OpMemberDecorate %Point 1 Flat\n" + "OpMemberDecorate %Point 1 Offset 16\n"}, + // Multiple OpGroupMemberDecorate using the same + // decoration group. + {"OpName %group \"group\"\n" + "OpDecorate %group Flat\n" + "OpDecorate %group NoPerspective\n" + "OpDecorate %group Offset 8\n" + "%group = OpDecorationGroup\n" + "OpGroupMemberDecorate %group %Point 2\n" + "OpGroupMemberDecorate %group %Camera 1\n", + "OpMemberDecorate %Point 2 Flat\n" + "OpMemberDecorate %Camera 1 Flat\n" + "OpMemberDecorate %Point 2 NoPerspective\n" + "OpMemberDecorate %Camera 1 NoPerspective\n" + "OpMemberDecorate %Point 2 Offset 8\n" + "OpMemberDecorate %Camera 1 Offset 8\n"}, + // Two groups of member decorations, interleaved. + // Decoration is with and without operands. + {"OpName %group0 \"group0\"\n" + "OpName %group1 \"group1\"\n" + "OpDecorate %group0 Flat\n" + "OpDecorate %group0 Offset 8\n" + "OpDecorate %group1 NoPerspective\n" + "OpDecorate %group1 Offset 16\n" + "%group0 = OpDecorationGroup\n" + "%group1 = OpDecorationGroup\n" + "OpGroupMemberDecorate %group0 %Point 0\n" + "OpGroupMemberDecorate %group1 %Point 2\n", + "OpMemberDecorate %Point 0 Flat\n" + "OpMemberDecorate %Point 0 Offset 8\n" + "OpMemberDecorate %Point 2 NoPerspective\n" + "OpMemberDecorate %Point 2 Offset 16\n"}, + }), ); + +INSTANTIATE_TEST_CASE_P(UnrelatedDecorations, FlattenDecorationTest, + ::testing::ValuesIn(std::vector{ + // A non-group non-member decoration is untouched. + {"OpDecorate %hue Centroid\n" + "OpDecorate %saturation Flat\n", + "OpDecorate %hue Centroid\n" + "OpDecorate %saturation Flat\n"}, + // A non-group member decoration is untouched. + {"OpMemberDecorate %Point 0 Offset 0\n" + "OpMemberDecorate %Point 1 Offset 4\n" + "OpMemberDecorate %Point 1 Flat\n", + "OpMemberDecorate %Point 0 Offset 0\n" + "OpMemberDecorate %Point 1 Offset 4\n" + "OpMemberDecorate %Point 1 Flat\n"}, + // A non-group non-member decoration survives any + // replacement of group decorations. + {"OpName %group \"group\"\n" + "OpDecorate %group Flat\n" + "OpDecorate %hue Centroid\n" + "OpDecorate %group NoPerspective\n" + "%group = OpDecorationGroup\n" + "OpGroupDecorate %group %hue %saturation\n", + "OpDecorate %hue Flat\n" + "OpDecorate %saturation Flat\n" + "OpDecorate %hue Centroid\n" + "OpDecorate %hue NoPerspective\n" + "OpDecorate %saturation NoPerspective\n"}, + // A non-group member decoration survives any + // replacement of group decorations. + {"OpDecorate %group Offset 0\n" + "OpDecorate %group Flat\n" + "OpMemberDecorate %Point 1 Offset 4\n" + "%group = OpDecorationGroup\n" + "OpGroupMemberDecorate %group %Point 0\n", + "OpMemberDecorate %Point 0 Offset 0\n" + "OpMemberDecorate %Point 0 Flat\n" + "OpMemberDecorate %Point 1 Offset 4\n"}, + }), ); + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/fold_spec_const_op_composite_test.cpp b/3rdparty/spirv-tools/test/opt/fold_spec_const_op_composite_test.cpp new file mode 100644 index 000000000..a8debdf76 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/fold_spec_const_op_composite_test.cpp @@ -0,0 +1,1389 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "assembly_builder.h" + +#include + +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { +using namespace spvtools; + +using FoldSpecConstantOpAndCompositePassBasicTest = PassTest<::testing::Test>; + +TEST_F(FoldSpecConstantOpAndCompositePassBasicTest, Empty) { + SinglePassRunAndCheck( + "", "", /* skip_nop = */ true); +} + +// A test of the basic functionality of FoldSpecConstantOpAndCompositePass. +// A spec constant defined with an integer addition operation should be folded +// to a normal constant with fixed value. +TEST_F(FoldSpecConstantOpAndCompositePassBasicTest, Basic) { + AssemblyBuilder builder; + builder.AppendTypesConstantsGlobals({ + // clang-format off + "%int = OpTypeInt 32 1", + "%frozen_spec_const_int = OpConstant %int 1", + "%const_int = OpConstant %int 2", + // Folding target: + "%spec_add = OpSpecConstantOp %int IAdd %frozen_spec_const_int %const_int", + // clang-format on + }); + + std::vector expected = { + // clang-format off + "OpCapability Shader", + "OpCapability Float64", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Vertex %main \"main\"", + "OpName %void \"void\"", + "OpName %main_func_type \"main_func_type\"", + "OpName %main \"main\"", + "OpName %main_func_entry_block \"main_func_entry_block\"", + "OpName %int \"int\"", + "OpName %frozen_spec_const_int \"frozen_spec_const_int\"", + "OpName %const_int \"const_int\"", + "OpName %spec_add \"spec_add\"", + "%void = OpTypeVoid", + "%main_func_type = OpTypeFunction %void", + "%int = OpTypeInt 32 1", +"%frozen_spec_const_int = OpConstant %int 1", + "%const_int = OpConstant %int 2", + // The SpecConstantOp IAdd instruction should be replace by OpConstant + // instruction: + "%spec_add = OpConstant %int 3", + "%main = OpFunction %void None %main_func_type", +"%main_func_entry_block = OpLabel", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + SinglePassRunAndCheck( + builder.GetCode(), JoinAllInsts(expected), /* skip_nop = */ true); +} + +// A test of skipping folding an instruction when the instruction result type +// has decorations. +TEST_F(FoldSpecConstantOpAndCompositePassBasicTest, + SkipWhenTypeHasDecorations) { + AssemblyBuilder builder; + builder + .AppendAnnotations({ + // clang-format off + "OpDecorate %int RelaxedPrecision", + // clang-format on + }) + .AppendTypesConstantsGlobals({ + // clang-format off + "%int = OpTypeInt 32 1", + "%frozen_spec_const_int = OpConstant %int 1", + "%const_int = OpConstant %int 2", + // The following spec constant should not be folded as the result type + // has relaxed precision decoration. + "%spec_add = OpSpecConstantOp %int IAdd %frozen_spec_const_int %const_int", + // clang-format on + }); + + SinglePassRunAndCheck( + builder.GetCode(), builder.GetCode(), /* skip_nop = */ true); +} + +// All types and some common constants that are potentially required in +// FoldSpecConstantOpAndCompositeTest. +std::vector CommonTypesAndConstants() { + return std::vector{ + // clang-format off + // scalar types + "%bool = OpTypeBool", + "%uint = OpTypeInt 32 0", + "%int = OpTypeInt 32 1", + "%float = OpTypeFloat 32", + "%double = OpTypeFloat 64", + // vector types + "%v2bool = OpTypeVector %bool 2", + "%v2uint = OpTypeVector %uint 2", + "%v2int = OpTypeVector %int 2", + "%v3int = OpTypeVector %int 3", + "%v4int = OpTypeVector %int 4", + "%v2float = OpTypeVector %float 2", + "%v2double = OpTypeVector %double 2", + // variable pointer types + "%_pf_bool = OpTypePointer Function %bool", + "%_pf_uint = OpTypePointer Function %uint", + "%_pf_int = OpTypePointer Function %int", + "%_pf_float = OpTypePointer Function %float", + "%_pf_double = OpTypePointer Function %double", + "%_pf_v2int = OpTypePointer Function %v2int", + "%_pf_v2float = OpTypePointer Function %v2float", + "%_pf_v2double = OpTypePointer Function %v2double", + // struct types + "%inner_struct = OpTypeStruct %bool %int %float", + "%outer_struct = OpTypeStruct %inner_struct %int", + "%flat_struct = OpTypeStruct %bool %int %float", + + // common constants + // scalar constants: + "%bool_true = OpConstantTrue %bool", + "%bool_false = OpConstantFalse %bool", + "%bool_null = OpConstantNull %bool", + "%signed_zero = OpConstant %int 0", + "%unsigned_zero = OpConstant %uint 0", + "%signed_one = OpConstant %int 1", + "%unsigned_one = OpConstant %uint 1", + "%signed_two = OpConstant %int 2", + "%unsigned_two = OpConstant %uint 2", + "%signed_three = OpConstant %int 3", + "%unsigned_three = OpConstant %uint 3", + "%signed_null = OpConstantNull %int", + "%unsigned_null = OpConstantNull %uint", + // vector constants: + "%bool_true_vec = OpConstantComposite %v2bool %bool_true %bool_true", + "%bool_false_vec = OpConstantComposite %v2bool %bool_false %bool_false", + "%bool_null_vec = OpConstantNull %v2bool", + "%signed_zero_vec = OpConstantComposite %v2int %signed_zero %signed_zero", + "%unsigned_zero_vec = OpConstantComposite %v2uint %unsigned_zero %unsigned_zero", + "%signed_one_vec = OpConstantComposite %v2int %signed_one %signed_one", + "%unsigned_one_vec = OpConstantComposite %v2uint %unsigned_one %unsigned_one", + "%signed_two_vec = OpConstantComposite %v2int %signed_two %signed_two", + "%unsigned_two_vec = OpConstantComposite %v2uint %unsigned_two %unsigned_two", + "%signed_three_vec = OpConstantComposite %v2int %signed_three %signed_three", + "%unsigned_three_vec = OpConstantComposite %v2uint %unsigned_three %unsigned_three", + "%signed_null_vec = OpConstantNull %v2int", + "%unsigned_null_vec = OpConstantNull %v2uint", + "%v4int_0_1_2_3 = OpConstantComposite %v4int %signed_zero %signed_one %signed_two %signed_three", + // clang-format on + }; +} + +// A helper function to strip OpName instructions from the given string of +// disassembly code. Returns the string with all OpName instruction stripped. +std::string StripOpNameInstructions(const std::string& str) { + std::stringstream ss(str); + std::ostringstream oss; + std::string inst_str; + while (std::getline(ss, inst_str, '\n')) { + if (inst_str.find("OpName %") == std::string::npos) { + oss << inst_str << '\n'; + } + } + return oss.str(); +} + +struct FoldSpecConstantOpAndCompositePassTestCase { + // Original constants with unfolded spec constants. + std::vector original; + // Expected cosntants after folding. + std::vector expected; +}; + +using FoldSpecConstantOpAndCompositePassTest = PassTest< + ::testing::TestWithParam>; + +TEST_P(FoldSpecConstantOpAndCompositePassTest, ParamTestCase) { + AssemblyBuilder test_code_builder, expected_code_builder; + const auto& tc = GetParam(); + test_code_builder.AppendTypesConstantsGlobals(CommonTypesAndConstants()); + test_code_builder.AppendTypesConstantsGlobals(tc.original); + expected_code_builder.AppendTypesConstantsGlobals(CommonTypesAndConstants()); + expected_code_builder.AppendTypesConstantsGlobals(tc.expected); + const std::string original = test_code_builder.GetCode(); + const std::string expected = expected_code_builder.GetCode(); + + // Run the optimization and get the result code in disassembly. + std::string optimized; + auto status = opt::Pass::Status::SuccessWithoutChange; + std::tie(optimized, status) = + SinglePassRunAndDisassemble( + original, /* skip_nop = */ true, /* do_validation = */ false); + + // Check the optimized code, but ignore the OpName instructions. + EXPECT_NE(opt::Pass::Status::Failure, status); + EXPECT_EQ( + StripOpNameInstructions(expected) == StripOpNameInstructions(original), + status == opt::Pass::Status::SuccessWithoutChange); + EXPECT_EQ(StripOpNameInstructions(expected), + StripOpNameInstructions(optimized)); +} + +// Tests that OpSpecConstantComposite opcodes are replace with +// OpConstantComposite correctly. +INSTANTIATE_TEST_CASE_P( + Composite, FoldSpecConstantOpAndCompositePassTest, + ::testing::ValuesIn(std::vector< + FoldSpecConstantOpAndCompositePassTestCase>({ + // clang-format off + // normal vector + { + // original + { + "%spec_v2bool = OpSpecConstantComposite %v2bool %bool_true %bool_false", + "%spec_v2uint = OpSpecConstantComposite %v2uint %unsigned_one %unsigned_one", + "%spec_v2int_a = OpSpecConstantComposite %v2int %signed_one %signed_two", + // Spec constants whose value can not be fully resolved should + // not be processed. + "%spec_int = OpSpecConstant %int 99", + "%spec_v2int_b = OpSpecConstantComposite %v2int %signed_one %spec_int", + }, + // expected + { + "%spec_v2bool = OpConstantComposite %v2bool %bool_true %bool_false", + "%spec_v2uint = OpConstantComposite %v2uint %unsigned_one %unsigned_one", + "%spec_v2int_a = OpConstantComposite %v2int %signed_one %signed_two", + "%spec_int = OpSpecConstant %int 99", + "%spec_v2int_b = OpSpecConstantComposite %v2int %signed_one %spec_int", + }, + }, + // vector with null constants + { + // original + { + "%null_bool = OpConstantNull %bool", + "%null_int = OpConstantNull %int", + "%spec_v2bool = OpSpecConstantComposite %v2bool %null_bool %null_bool", + "%spec_v3int = OpSpecConstantComposite %v3int %null_int %null_int %null_int", + "%spec_v4int = OpSpecConstantComposite %v4int %null_int %null_int %null_int %null_int", + }, + // expected + { + "%null_bool = OpConstantNull %bool", + "%null_int = OpConstantNull %int", + "%spec_v2bool = OpConstantComposite %v2bool %null_bool %null_bool", + "%spec_v3int = OpConstantComposite %v3int %null_int %null_int %null_int", + "%spec_v4int = OpConstantComposite %v4int %null_int %null_int %null_int %null_int", + }, + }, + // flat struct + { + // original + { + "%float_1 = OpConstant %float 1", + "%flat_1 = OpSpecConstantComposite %flat_struct %bool_true %signed_null %float_1", + // following struct should not be folded as the value of + // %spec_float is not determined. + "%spec_float = OpSpecConstant %float 1", + "%flat_2 = OpSpecConstantComposite %flat_struct %bool_true %signed_one %spec_float", + }, + // expected + { + "%float_1 = OpConstant %float 1", + "%flat_1 = OpConstantComposite %flat_struct %bool_true %signed_null %float_1", + "%spec_float = OpSpecConstant %float 1", + "%flat_2 = OpSpecConstantComposite %flat_struct %bool_true %signed_one %spec_float", + } + }, + // nested struct + { + // original + { + "%float_1 = OpConstant %float 1", + "%inner_1 = OpSpecConstantComposite %inner_struct %bool_true %signed_null %float_1", + "%outer_1 = OpSpecConstantComposite %outer_struct %inner_1 %signed_one", + // following structs should not be folded as the value of + // %spec_float is not determined. + "%spec_float = OpSpecConstant %float 1", + "%inner_2 = OpSpecConstantComposite %inner_struct %bool_true %signed_null %spec_float", + "%outer_2 = OpSpecConstantComposite %outer_struct %inner_2 %signed_one", + }, + // expected + { + "%float_1 = OpConstant %float 1", + "%inner_1 = OpConstantComposite %inner_struct %bool_true %signed_null %float_1", + "%outer_1 = OpConstantComposite %outer_struct %inner_1 %signed_one", + "%spec_float = OpSpecConstant %float 1", + "%inner_2 = OpSpecConstantComposite %inner_struct %bool_true %signed_null %spec_float", + "%outer_2 = OpSpecConstantComposite %outer_struct %inner_2 %signed_one", + } + }, + // composite constants touched by OpUndef should be skipped + { + // original + { + "%undef = OpUndef %float", + "%inner = OpConstantComposite %inner_struct %bool_true %signed_one %undef", + "%outer = OpSpecConstantComposite %outer_struct %inner %signed_one", + }, + // expected + { + "%undef = OpUndef %float", + "%inner = OpConstantComposite %inner_struct %bool_true %signed_one %undef", + "%outer = OpSpecConstantComposite %outer_struct %inner %signed_one", + }, + } + // clang-format on + }))); + +// Tests for operations that resulting in different types. +INSTANTIATE_TEST_CASE_P( + Cast, FoldSpecConstantOpAndCompositePassTest, + ::testing::ValuesIn( + std::vector({ + // clang-format off + // int -> bool scalar + { + // original + { + "%spec_bool_t = OpSpecConstantOp %bool INotEqual %signed_three %signed_zero", + "%spec_bool_f = OpSpecConstantOp %bool INotEqual %signed_zero %signed_zero", + "%spec_bool_from_null = OpSpecConstantOp %bool INotEqual %signed_null %signed_zero", + }, + // expected + { + "%spec_bool_t = OpConstantTrue %bool", + "%spec_bool_f = OpConstantFalse %bool", + "%spec_bool_from_null = OpConstantFalse %bool", + }, + }, + + // uint -> bool scalar + { + // original + { + "%spec_bool_t = OpSpecConstantOp %bool INotEqual %unsigned_three %unsigned_zero", + "%spec_bool_f = OpSpecConstantOp %bool INotEqual %unsigned_zero %unsigned_zero", + "%spec_bool_from_null = OpSpecConstantOp %bool INotEqual %unsigned_null %unsigned_zero", + }, + // expected + { + "%spec_bool_t = OpConstantTrue %bool", + "%spec_bool_f = OpConstantFalse %bool", + "%spec_bool_from_null = OpConstantFalse %bool", + }, + }, + + // bool -> int scalar + { + // original + { + "%spec_int_one = OpSpecConstantOp %int Select %bool_true %signed_one %signed_zero", + "%spec_int_zero = OpSpecConstantOp %int Select %bool_false %signed_one %signed_zero", + "%spec_int_from_null = OpSpecConstantOp %int Select %bool_null %signed_one %signed_zero", + }, + // expected + { + "%spec_int_one = OpConstant %int 1", + "%spec_int_zero = OpConstant %int 0", + "%spec_int_from_null = OpConstant %int 0", + }, + }, + + // uint -> int scalar + { + // original + { + "%spec_int_one = OpSpecConstantOp %int IAdd %unsigned_one %signed_zero", + "%spec_int_zero = OpSpecConstantOp %int IAdd %unsigned_zero %signed_zero", + "%spec_int_from_null = OpSpecConstantOp %int IAdd %unsigned_null %unsigned_zero", + }, + // expected + { + "%spec_int_one = OpConstant %int 1", + "%spec_int_zero = OpConstant %int 0", + "%spec_int_from_null = OpConstant %int 0", + }, + }, + + // bool -> uint scalar + { + // original + { + "%spec_uint_one = OpSpecConstantOp %uint Select %bool_true %unsigned_one %unsigned_zero", + "%spec_uint_zero = OpSpecConstantOp %uint Select %bool_false %unsigned_one %unsigned_zero", + "%spec_uint_from_null = OpSpecConstantOp %uint Select %bool_null %unsigned_one %unsigned_zero", + }, + // expected + { + "%spec_uint_one = OpConstant %uint 1", + "%spec_uint_zero = OpConstant %uint 0", + "%spec_uint_from_null = OpConstant %uint 0", + }, + }, + + // int -> uint scalar + { + // original + { + "%spec_uint_one = OpSpecConstantOp %uint IAdd %signed_one %unsigned_zero", + "%spec_uint_zero = OpSpecConstantOp %uint IAdd %signed_zero %unsigned_zero", + "%spec_uint_from_null = OpSpecConstantOp %uint IAdd %signed_null %unsigned_zero", + }, + // expected + { + "%spec_uint_one = OpConstant %uint 1", + "%spec_uint_zero = OpConstant %uint 0", + "%spec_uint_from_null = OpConstant %uint 0", + }, + }, + + // int -> bool vector + { + // original + { + "%spec_bool_t_vec = OpSpecConstantOp %v2bool INotEqual %signed_three_vec %signed_zero_vec", + "%spec_bool_f_vec = OpSpecConstantOp %v2bool INotEqual %signed_zero_vec %signed_zero_vec", + "%spec_bool_from_null = OpSpecConstantOp %v2bool INotEqual %signed_null_vec %signed_zero_vec", + }, + // expected + { + "%true = OpConstantTrue %bool", + "%true_0 = OpConstantTrue %bool", + "%spec_bool_t_vec = OpConstantComposite %v2bool %true_0 %true_0", + "%false = OpConstantFalse %bool", + "%false_0 = OpConstantFalse %bool", + "%spec_bool_f_vec = OpConstantComposite %v2bool %false_0 %false_0", + "%false_1 = OpConstantFalse %bool", + "%false_2 = OpConstantFalse %bool", + "%spec_bool_from_null = OpConstantComposite %v2bool %false_2 %false_2", + }, + }, + + // uint -> bool vector + { + // original + { + "%spec_bool_t_vec = OpSpecConstantOp %v2bool INotEqual %unsigned_three_vec %unsigned_zero_vec", + "%spec_bool_f_vec = OpSpecConstantOp %v2bool INotEqual %unsigned_zero_vec %unsigned_zero_vec", + "%spec_bool_from_null = OpSpecConstantOp %v2bool INotEqual %unsigned_null_vec %unsigned_zero_vec", + }, + // expected + { + "%true = OpConstantTrue %bool", + "%true_0 = OpConstantTrue %bool", + "%spec_bool_t_vec = OpConstantComposite %v2bool %true_0 %true_0", + "%false = OpConstantFalse %bool", + "%false_0 = OpConstantFalse %bool", + "%spec_bool_f_vec = OpConstantComposite %v2bool %false_0 %false_0", + "%false_1 = OpConstantFalse %bool", + "%false_2 = OpConstantFalse %bool", + "%spec_bool_from_null = OpConstantComposite %v2bool %false_2 %false_2", + }, + }, + + // bool -> int vector + { + // original + { + "%spec_int_one_vec = OpSpecConstantOp %v2int Select %bool_true_vec %signed_one_vec %signed_zero_vec", + "%spec_int_zero_vec = OpSpecConstantOp %v2int Select %bool_false_vec %signed_one_vec %signed_zero_vec", + "%spec_int_from_null = OpSpecConstantOp %v2int Select %bool_null_vec %signed_one_vec %signed_zero_vec", + }, + // expected + { + "%int_1 = OpConstant %int 1", + "%int_1_0 = OpConstant %int 1", + "%spec_int_one_vec = OpConstantComposite %v2int %int_1_0 %int_1_0", + "%int_0 = OpConstant %int 0", + "%int_0_0 = OpConstant %int 0", + "%spec_int_zero_vec = OpConstantComposite %v2int %int_0_0 %int_0_0", + "%int_0_1 = OpConstant %int 0", + "%int_0_2 = OpConstant %int 0", + "%spec_int_from_null = OpConstantComposite %v2int %int_0_2 %int_0_2", + }, + }, + + // uint -> int vector + { + // original + { + "%spec_int_one_vec = OpSpecConstantOp %v2int IAdd %unsigned_one_vec %signed_zero_vec", + "%spec_int_zero_vec = OpSpecConstantOp %v2int IAdd %unsigned_zero_vec %signed_zero_vec", + "%spec_int_from_null = OpSpecConstantOp %v2int IAdd %unsigned_null_vec %signed_zero_vec", + }, + // expected + { + "%int_1 = OpConstant %int 1", + "%int_1_0 = OpConstant %int 1", + "%spec_int_one_vec = OpConstantComposite %v2int %int_1_0 %int_1_0", + "%int_0 = OpConstant %int 0", + "%int_0_0 = OpConstant %int 0", + "%spec_int_zero_vec = OpConstantComposite %v2int %int_0_0 %int_0_0", + "%int_0_1 = OpConstant %int 0", + "%int_0_2 = OpConstant %int 0", + "%spec_int_from_null = OpConstantComposite %v2int %int_0_2 %int_0_2", + }, + }, + + // bool -> uint vector + { + // original + { + "%spec_uint_one_vec = OpSpecConstantOp %v2uint Select %bool_true_vec %unsigned_one_vec %unsigned_zero_vec", + "%spec_uint_zero_vec = OpSpecConstantOp %v2uint Select %bool_false_vec %unsigned_one_vec %unsigned_zero_vec", + "%spec_uint_from_null = OpSpecConstantOp %v2uint Select %bool_null_vec %unsigned_one_vec %unsigned_zero_vec", + }, + // expected + { + "%uint_1 = OpConstant %uint 1", + "%uint_1_0 = OpConstant %uint 1", + "%spec_uint_one_vec = OpConstantComposite %v2uint %uint_1_0 %uint_1_0", + "%uint_0 = OpConstant %uint 0", + "%uint_0_0 = OpConstant %uint 0", + "%spec_uint_zero_vec = OpConstantComposite %v2uint %uint_0_0 %uint_0_0", + "%uint_0_1 = OpConstant %uint 0", + "%uint_0_2 = OpConstant %uint 0", + "%spec_uint_from_null = OpConstantComposite %v2uint %uint_0_2 %uint_0_2", + }, + }, + + // int -> uint vector + { + // original + { + "%spec_uint_one_vec = OpSpecConstantOp %v2uint IAdd %signed_one_vec %unsigned_zero_vec", + "%spec_uint_zero_vec = OpSpecConstantOp %v2uint IAdd %signed_zero_vec %unsigned_zero_vec", + "%spec_uint_from_null = OpSpecConstantOp %v2uint IAdd %signed_null_vec %unsigned_zero_vec", + }, + // expected + { + "%uint_1 = OpConstant %uint 1", + "%uint_1_0 = OpConstant %uint 1", + "%spec_uint_one_vec = OpConstantComposite %v2uint %uint_1_0 %uint_1_0", + "%uint_0 = OpConstant %uint 0", + "%uint_0_0 = OpConstant %uint 0", + "%spec_uint_zero_vec = OpConstantComposite %v2uint %uint_0_0 %uint_0_0", + "%uint_0_1 = OpConstant %uint 0", + "%uint_0_2 = OpConstant %uint 0", + "%spec_uint_from_null = OpConstantComposite %v2uint %uint_0_2 %uint_0_2", + }, + }, + // clang-format on + }))); + +// Tests about boolean scalar logical operations and comparison operations with +// scalar int/uint type. +INSTANTIATE_TEST_CASE_P( + Logical, FoldSpecConstantOpAndCompositePassTest, + ::testing::ValuesIn(std::vector< + FoldSpecConstantOpAndCompositePassTestCase>({ + // clang-format off + // scalar integer comparison + { + // original + { + "%int_minus_1 = OpConstant %int -1", + + "%slt_0_1 = OpSpecConstantOp %bool SLessThan %signed_zero %signed_one", + "%sgt_0_1 = OpSpecConstantOp %bool SGreaterThan %signed_zero %signed_one", + "%sle_2_2 = OpSpecConstantOp %bool SLessThanEqual %signed_two %signed_two", + "%sge_2_1 = OpSpecConstantOp %bool SGreaterThanEqual %signed_two %signed_one", + "%sge_2_null = OpSpecConstantOp %bool SGreaterThanEqual %signed_two %signed_null", + "%sge_minus_1_null = OpSpecConstantOp %bool SGreaterThanEqual %int_minus_1 %signed_null", + + "%ult_0_1 = OpSpecConstantOp %bool ULessThan %unsigned_zero %unsigned_one", + "%ugt_0_1 = OpSpecConstantOp %bool UGreaterThan %unsigned_zero %unsigned_one", + "%ule_2_3 = OpSpecConstantOp %bool ULessThanEqual %unsigned_two %unsigned_three", + "%uge_1_1 = OpSpecConstantOp %bool UGreaterThanEqual %unsigned_one %unsigned_one", + "%uge_2_null = OpSpecConstantOp %bool UGreaterThanEqual %unsigned_two %unsigned_null", + "%uge_minus_1_null = OpSpecConstantOp %bool UGreaterThanEqual %int_minus_1 %unsigned_null", + }, + // expected + { + "%int_minus_1 = OpConstant %int -1", + + "%slt_0_1 = OpConstantTrue %bool", + "%sgt_0_1 = OpConstantFalse %bool", + "%sle_2_2 = OpConstantTrue %bool", + "%sge_2_1 = OpConstantTrue %bool", + "%sge_2_null = OpConstantTrue %bool", + "%sge_minus_1_null = OpConstantFalse %bool", + + "%ult_0_1 = OpConstantTrue %bool", + "%ugt_0_1 = OpConstantFalse %bool", + "%ule_2_3 = OpConstantTrue %bool", + "%uge_1_1 = OpConstantTrue %bool", + "%uge_2_null = OpConstantTrue %bool", + "%uge_minus_1_null = OpConstantTrue %bool", + }, + }, + // Logical and, or, xor. + { + // original + { + "%logical_or = OpSpecConstantOp %bool LogicalOr %bool_true %bool_false", + "%logical_and = OpSpecConstantOp %bool LogicalAnd %bool_true %bool_false", + "%logical_not = OpSpecConstantOp %bool LogicalNot %bool_true", + "%logical_eq = OpSpecConstantOp %bool LogicalEqual %bool_true %bool_true", + "%logical_neq = OpSpecConstantOp %bool LogicalNotEqual %bool_true %bool_true", + "%logical_and_null = OpSpecConstantOp %bool LogicalAnd %bool_true %bool_null", + }, + // expected + { + "%logical_or = OpConstantTrue %bool", + "%logical_and = OpConstantFalse %bool", + "%logical_not = OpConstantFalse %bool", + "%logical_eq = OpConstantTrue %bool", + "%logical_neq = OpConstantFalse %bool", + "%logical_and_null = OpConstantFalse %bool", + }, + }, + // clang-format on + }))); + +// Tests about arithmetic operations for scalar int and uint types. +INSTANTIATE_TEST_CASE_P( + ScalarArithmetic, FoldSpecConstantOpAndCompositePassTest, + ::testing::ValuesIn(std::vector< + FoldSpecConstantOpAndCompositePassTestCase>({ + // clang-format off + // scalar integer negate + { + // original + { + "%int_minus_1 = OpSpecConstantOp %int SNegate %signed_one", + "%int_minus_2 = OpSpecConstantOp %int SNegate %signed_two", + "%int_neg_null = OpSpecConstantOp %int SNegate %signed_null", + "%int_max = OpConstant %int 2147483647", + "%int_neg_max = OpSpecConstantOp %int SNegate %int_max", + }, + // expected + { + "%int_minus_1 = OpConstant %int -1", + "%int_minus_2 = OpConstant %int -2", + "%int_neg_null = OpConstant %int 0", + "%int_max = OpConstant %int 2147483647", + "%int_neg_max = OpConstant %int -2147483647", + }, + }, + // scalar integer not + { + // original + { + "%uint_4294967294 = OpSpecConstantOp %uint Not %unsigned_one", + "%uint_4294967293 = OpSpecConstantOp %uint Not %unsigned_two", + "%uint_neg_null = OpSpecConstantOp %uint Not %unsigned_null", + }, + // expected + { + "%uint_4294967294 = OpConstant %uint 4294967294", + "%uint_4294967293 = OpConstant %uint 4294967293", + "%uint_neg_null = OpConstant %uint 4294967295", + }, + }, + // scalar integer add, sub, mul, div + { + // original + { + "%signed_max = OpConstant %int 2147483647", + "%signed_min = OpConstant %int -2147483648", + + "%spec_int_iadd = OpSpecConstantOp %int IAdd %signed_three %signed_two", + "%spec_int_isub = OpSpecConstantOp %int ISub %signed_one %spec_int_iadd", + "%spec_int_sdiv = OpSpecConstantOp %int SDiv %spec_int_isub %signed_two", + "%spec_int_imul = OpSpecConstantOp %int IMul %spec_int_sdiv %signed_three", + "%spec_int_iadd_null = OpSpecConstantOp %int IAdd %spec_int_imul %signed_null", + "%spec_int_imul_null = OpSpecConstantOp %int IMul %spec_int_iadd_null %signed_null", + "%spec_int_iadd_overflow = OpSpecConstantOp %int IAdd %signed_max %signed_three", + "%spec_int_isub_overflow = OpSpecConstantOp %int ISub %signed_min %signed_three", + + "%spec_uint_iadd = OpSpecConstantOp %uint IAdd %unsigned_three %unsigned_two", + "%spec_uint_isub = OpSpecConstantOp %uint ISub %unsigned_one %spec_uint_iadd", + "%spec_uint_udiv = OpSpecConstantOp %uint UDiv %spec_uint_isub %unsigned_three", + "%spec_uint_imul = OpSpecConstantOp %uint IMul %spec_uint_udiv %unsigned_two", + "%spec_uint_isub_null = OpSpecConstantOp %uint ISub %spec_uint_imul %signed_null", + }, + // expected + { + "%signed_max = OpConstant %int 2147483647", + "%signed_min = OpConstant %int -2147483648", + + "%spec_int_iadd = OpConstant %int 5", + "%spec_int_isub = OpConstant %int -4", + "%spec_int_sdiv = OpConstant %int -2", + "%spec_int_imul = OpConstant %int -6", + "%spec_int_iadd_null = OpConstant %int -6", + "%spec_int_imul_null = OpConstant %int 0", + "%spec_int_iadd_overflow = OpConstant %int -2147483646", + "%spec_int_isub_overflow = OpConstant %int 2147483645", + + "%spec_uint_iadd = OpConstant %uint 5", + "%spec_uint_isub = OpConstant %uint 4294967292", + "%spec_uint_udiv = OpConstant %uint 1431655764", + "%spec_uint_imul = OpConstant %uint 2863311528", + "%spec_uint_isub_null = OpConstant %uint 2863311528", + }, + }, + // scalar integer rem, mod + { + // original + { + // common constants + "%int_7 = OpConstant %int 7", + "%uint_7 = OpConstant %uint 7", + "%int_minus_7 = OpConstant %int -7", + "%int_minus_3 = OpConstant %int -3", + + // srem + "%7_srem_3 = OpSpecConstantOp %int SRem %int_7 %signed_three", + "%minus_7_srem_3 = OpSpecConstantOp %int SRem %int_minus_7 %signed_three", + "%7_srem_minus_3 = OpSpecConstantOp %int SRem %int_7 %int_minus_3", + "%minus_7_srem_minus_3 = OpSpecConstantOp %int SRem %int_minus_7 %int_minus_3", + // smod + "%7_smod_3 = OpSpecConstantOp %int SMod %int_7 %signed_three", + "%minus_7_smod_3 = OpSpecConstantOp %int SMod %int_minus_7 %signed_three", + "%7_smod_minus_3 = OpSpecConstantOp %int SMod %int_7 %int_minus_3", + "%minus_7_smod_minus_3 = OpSpecConstantOp %int SMod %int_minus_7 %int_minus_3", + // umod + "%7_umod_3 = OpSpecConstantOp %uint UMod %uint_7 %unsigned_three", + // null constant + "%null_srem_3 = OpSpecConstantOp %int SRem %signed_null %signed_three", + "%null_smod_3 = OpSpecConstantOp %int SMod %signed_null %signed_three", + "%null_umod_3 = OpSpecConstantOp %uint UMod %unsigned_null %unsigned_three", + }, + // expected + { + // common constants + "%int_7 = OpConstant %int 7", + "%uint_7 = OpConstant %uint 7", + "%int_minus_7 = OpConstant %int -7", + "%int_minus_3 = OpConstant %int -3", + + // srem + "%7_srem_3 = OpConstant %int 1", + "%minus_7_srem_3 = OpConstant %int -1", + "%7_srem_minus_3 = OpConstant %int 1", + "%minus_7_srem_minus_3 = OpConstant %int -1", + // smod + "%7_smod_3 = OpConstant %int 1", + "%minus_7_smod_3 = OpConstant %int 2", + "%7_smod_minus_3 = OpConstant %int -2", + "%minus_7_smod_minus_3 = OpConstant %int -1", + // umod + "%7_umod_3 = OpConstant %uint 1", + // null constant + "%null_srem_3 = OpConstant %int 0", + "%null_smod_3 = OpConstant %int 0", + "%null_umod_3 = OpConstant %uint 0", + }, + }, + // scalar integer bitwise and shift + { + // original + { + // bitwise + "%xor_1_3 = OpSpecConstantOp %int BitwiseXor %signed_one %signed_three", + "%and_1_2 = OpSpecConstantOp %int BitwiseAnd %signed_one %xor_1_3", + "%or_1_2 = OpSpecConstantOp %int BitwiseOr %signed_one %xor_1_3", + "%xor_3_null = OpSpecConstantOp %int BitwiseXor %or_1_2 %signed_null", + + // shift + "%unsigned_31 = OpConstant %uint 31", + "%unsigned_left_shift_max = OpSpecConstantOp %uint ShiftLeftLogical %unsigned_one %unsigned_31", + "%unsigned_right_shift_logical = OpSpecConstantOp %uint ShiftRightLogical %unsigned_left_shift_max %unsigned_31", + "%signed_right_shift_arithmetic = OpSpecConstantOp %int ShiftRightArithmetic %unsigned_left_shift_max %unsigned_31", + "%left_shift_null_31 = OpSpecConstantOp %uint ShiftLeftLogical %unsigned_null %unsigned_31", + "%right_shift_31_null = OpSpecConstantOp %uint ShiftRightLogical %unsigned_31 %unsigned_null", + }, + // expected + { + "%xor_1_3 = OpConstant %int 2", + "%and_1_2 = OpConstant %int 0", + "%or_1_2 = OpConstant %int 3", + "%xor_3_null = OpConstant %int 3", + + "%unsigned_31 = OpConstant %uint 31", + "%unsigned_left_shift_max = OpConstant %uint 2147483648", + "%unsigned_right_shift_logical = OpConstant %uint 1", + "%signed_right_shift_arithmetic = OpConstant %int -1", + "%left_shift_null_31 = OpConstant %uint 0", + "%right_shift_31_null = OpConstant %uint 31", + }, + }, + // Skip folding if any operands have undetermined value. + { + // original + { + "%spec_int = OpSpecConstant %int 1", + "%spec_iadd = OpSpecConstantOp %int IAdd %signed_three %spec_int", + }, + // expected + { + "%spec_int = OpSpecConstant %int 1", + "%spec_iadd = OpSpecConstantOp %int IAdd %signed_three %spec_int", + }, + }, + // clang-format on + }))); + +// Tests about arithmetic operations for vector int and uint types. +INSTANTIATE_TEST_CASE_P( + VectorArithmetic, FoldSpecConstantOpAndCompositePassTest, + ::testing::ValuesIn(std::vector< + FoldSpecConstantOpAndCompositePassTestCase>({ + // clang-format off + // vector integer negate + { + // original + { + "%v2int_minus_1 = OpSpecConstantOp %v2int SNegate %signed_one_vec", + "%v2int_minus_2 = OpSpecConstantOp %v2int SNegate %signed_two_vec", + "%v2int_neg_null = OpSpecConstantOp %v2int SNegate %signed_null_vec", + }, + // expected + { + "%int_n1 = OpConstant %int -1", + "%int_n1_0 = OpConstant %int -1", + "%v2int_minus_1 = OpConstantComposite %v2int %int_n1_0 %int_n1_0", + "%int_n2 = OpConstant %int -2", + "%int_n2_0 = OpConstant %int -2", + "%v2int_minus_2 = OpConstantComposite %v2int %int_n2_0 %int_n2_0", + "%int_0 = OpConstant %int 0", + "%int_0_0 = OpConstant %int 0", + "%v2int_neg_null = OpConstantComposite %v2int %int_0_0 %int_0_0", + }, + }, + // vector integer (including null vetors) add, sub, div, mul + { + // original + { + "%spec_v2int_iadd = OpSpecConstantOp %v2int IAdd %signed_three_vec %signed_two_vec", + "%spec_v2int_isub = OpSpecConstantOp %v2int ISub %signed_one_vec %spec_v2int_iadd", + "%spec_v2int_sdiv = OpSpecConstantOp %v2int SDiv %spec_v2int_isub %signed_two_vec", + "%spec_v2int_imul = OpSpecConstantOp %v2int IMul %spec_v2int_sdiv %signed_three_vec", + "%spec_v2int_iadd_null = OpSpecConstantOp %v2int IAdd %spec_v2int_imul %signed_null_vec", + + "%spec_v2uint_iadd = OpSpecConstantOp %v2uint IAdd %unsigned_three_vec %unsigned_two_vec", + "%spec_v2uint_isub = OpSpecConstantOp %v2uint ISub %unsigned_one_vec %spec_v2uint_iadd", + "%spec_v2uint_udiv = OpSpecConstantOp %v2uint UDiv %spec_v2uint_isub %unsigned_three_vec", + "%spec_v2uint_imul = OpSpecConstantOp %v2uint IMul %spec_v2uint_udiv %unsigned_two_vec", + "%spec_v2uint_isub_null = OpSpecConstantOp %v2uint ISub %spec_v2uint_imul %signed_null_vec", + }, + // expected + { + "%int_5 = OpConstant %int 5", + "%int_5_0 = OpConstant %int 5", + "%spec_v2int_iadd = OpConstantComposite %v2int %int_5_0 %int_5_0", + "%int_n4 = OpConstant %int -4", + "%int_n4_0 = OpConstant %int -4", + "%spec_v2int_isub = OpConstantComposite %v2int %int_n4_0 %int_n4_0", + "%int_n2 = OpConstant %int -2", + "%int_n2_0 = OpConstant %int -2", + "%spec_v2int_sdiv = OpConstantComposite %v2int %int_n2_0 %int_n2_0", + "%int_n6 = OpConstant %int -6", + "%int_n6_0 = OpConstant %int -6", + "%spec_v2int_imul = OpConstantComposite %v2int %int_n6_0 %int_n6_0", + "%int_n6_1 = OpConstant %int -6", + "%int_n6_2 = OpConstant %int -6", + "%spec_v2int_iadd_null = OpConstantComposite %v2int %int_n6_2 %int_n6_2", + + "%uint_5 = OpConstant %uint 5", + "%uint_5_0 = OpConstant %uint 5", + "%spec_v2uint_iadd = OpConstantComposite %v2uint %uint_5_0 %uint_5_0", + "%uint_4294967292 = OpConstant %uint 4294967292", + "%uint_4294967292_0 = OpConstant %uint 4294967292", + "%spec_v2uint_isub = OpConstantComposite %v2uint %uint_4294967292_0 %uint_4294967292_0", + "%uint_1431655764 = OpConstant %uint 1431655764", + "%uint_1431655764_0 = OpConstant %uint 1431655764", + "%spec_v2uint_udiv = OpConstantComposite %v2uint %uint_1431655764_0 %uint_1431655764_0", + "%uint_2863311528 = OpConstant %uint 2863311528", + "%uint_2863311528_0 = OpConstant %uint 2863311528", + "%spec_v2uint_imul = OpConstantComposite %v2uint %uint_2863311528_0 %uint_2863311528_0", + "%uint_2863311528_1 = OpConstant %uint 2863311528", + "%uint_2863311528_2 = OpConstant %uint 2863311528", + "%spec_v2uint_isub_null = OpConstantComposite %v2uint %uint_2863311528_2 %uint_2863311528_2", + }, + }, + // vector integer rem, mod + { + // original + { + // common constants + "%int_7 = OpConstant %int 7", + "%v2int_7 = OpConstantComposite %v2int %int_7 %int_7", + "%uint_7 = OpConstant %uint 7", + "%v2uint_7 = OpConstantComposite %v2uint %uint_7 %uint_7", + "%int_minus_7 = OpConstant %int -7", + "%v2int_minus_7 = OpConstantComposite %v2int %int_minus_7 %int_minus_7", + "%int_minus_3 = OpConstant %int -3", + "%v2int_minus_3 = OpConstantComposite %v2int %int_minus_3 %int_minus_3", + + // srem + "%7_srem_3 = OpSpecConstantOp %v2int SRem %v2int_7 %signed_three_vec", + "%minus_7_srem_3 = OpSpecConstantOp %v2int SRem %v2int_minus_7 %signed_three_vec", + "%7_srem_minus_3 = OpSpecConstantOp %v2int SRem %v2int_7 %v2int_minus_3", + "%minus_7_srem_minus_3 = OpSpecConstantOp %v2int SRem %v2int_minus_7 %v2int_minus_3", + // smod + "%7_smod_3 = OpSpecConstantOp %v2int SMod %v2int_7 %signed_three_vec", + "%minus_7_smod_3 = OpSpecConstantOp %v2int SMod %v2int_minus_7 %signed_three_vec", + "%7_smod_minus_3 = OpSpecConstantOp %v2int SMod %v2int_7 %v2int_minus_3", + "%minus_7_smod_minus_3 = OpSpecConstantOp %v2int SMod %v2int_minus_7 %v2int_minus_3", + // umod + "%7_umod_3 = OpSpecConstantOp %v2uint UMod %v2uint_7 %unsigned_three_vec", + }, + // expected + { + // common constants + "%int_7 = OpConstant %int 7", + "%v2int_7 = OpConstantComposite %v2int %int_7 %int_7", + "%uint_7 = OpConstant %uint 7", + "%v2uint_7 = OpConstantComposite %v2uint %uint_7 %uint_7", + "%int_minus_7 = OpConstant %int -7", + "%v2int_minus_7 = OpConstantComposite %v2int %int_minus_7 %int_minus_7", + "%int_minus_3 = OpConstant %int -3", + "%v2int_minus_3 = OpConstantComposite %v2int %int_minus_3 %int_minus_3", + + // srem + "%int_1 = OpConstant %int 1", + "%int_1_0 = OpConstant %int 1", + "%7_srem_3 = OpConstantComposite %v2int %int_1_0 %int_1_0", + "%int_n1 = OpConstant %int -1", + "%int_n1_0 = OpConstant %int -1", + "%minus_7_srem_3 = OpConstantComposite %v2int %int_n1_0 %int_n1_0", + "%int_1_1 = OpConstant %int 1", + "%int_1_2 = OpConstant %int 1", + "%7_srem_minus_3 = OpConstantComposite %v2int %int_1_2 %int_1_2", + "%int_n1_1 = OpConstant %int -1", + "%int_n1_2 = OpConstant %int -1", + "%minus_7_srem_minus_3 = OpConstantComposite %v2int %int_n1_2 %int_n1_2", + // smod + "%int_1_3 = OpConstant %int 1", + "%int_1_4 = OpConstant %int 1", + "%7_smod_3 = OpConstantComposite %v2int %int_1_4 %int_1_4", + "%int_2 = OpConstant %int 2", + "%int_2_0 = OpConstant %int 2", + "%minus_7_smod_3 = OpConstantComposite %v2int %int_2_0 %int_2_0", + "%int_n2 = OpConstant %int -2", + "%int_n2_0 = OpConstant %int -2", + "%7_smod_minus_3 = OpConstantComposite %v2int %int_n2_0 %int_n2_0", + "%int_n1_3 = OpConstant %int -1", + "%int_n1_4 = OpConstant %int -1", + "%minus_7_smod_minus_3 = OpConstantComposite %v2int %int_n1_4 %int_n1_4", + // umod + "%uint_1 = OpConstant %uint 1", + "%uint_1_0 = OpConstant %uint 1", + "%7_umod_3 = OpConstantComposite %v2uint %uint_1_0 %uint_1_0", + }, + }, + // vector integer bitwise, shift + { + // original + { + "%xor_1_3 = OpSpecConstantOp %v2int BitwiseXor %signed_one_vec %signed_three_vec", + "%and_1_2 = OpSpecConstantOp %v2int BitwiseAnd %signed_one_vec %xor_1_3", + "%or_1_2 = OpSpecConstantOp %v2int BitwiseOr %signed_one_vec %xor_1_3", + + "%unsigned_31 = OpConstant %uint 31", + "%v2unsigned_31 = OpConstantComposite %v2uint %unsigned_31 %unsigned_31", + "%unsigned_left_shift_max = OpSpecConstantOp %v2uint ShiftLeftLogical %unsigned_one_vec %v2unsigned_31", + "%unsigned_right_shift_logical = OpSpecConstantOp %v2uint ShiftRightLogical %unsigned_left_shift_max %v2unsigned_31", + "%signed_right_shift_arithmetic = OpSpecConstantOp %v2int ShiftRightArithmetic %unsigned_left_shift_max %v2unsigned_31", + }, + // expected + { + "%int_2 = OpConstant %int 2", + "%int_2_0 = OpConstant %int 2", + "%xor_1_3 = OpConstantComposite %v2int %int_2_0 %int_2_0", + "%int_0 = OpConstant %int 0", + "%int_0_0 = OpConstant %int 0", + "%and_1_2 = OpConstantComposite %v2int %int_0_0 %int_0_0", + "%int_3 = OpConstant %int 3", + "%int_3_0 = OpConstant %int 3", + "%or_1_2 = OpConstantComposite %v2int %int_3_0 %int_3_0", + + "%unsigned_31 = OpConstant %uint 31", + "%v2unsigned_31 = OpConstantComposite %v2uint %unsigned_31 %unsigned_31", + "%uint_2147483648 = OpConstant %uint 2147483648", + "%uint_2147483648_0 = OpConstant %uint 2147483648", + "%unsigned_left_shift_max = OpConstantComposite %v2uint %uint_2147483648_0 %uint_2147483648_0", + "%uint_1 = OpConstant %uint 1", + "%uint_1_0 = OpConstant %uint 1", + "%unsigned_right_shift_logical = OpConstantComposite %v2uint %uint_1_0 %uint_1_0", + "%int_n1 = OpConstant %int -1", + "%int_n1_0 = OpConstant %int -1", + "%signed_right_shift_arithmetic = OpConstantComposite %v2int %int_n1_0 %int_n1_0", + }, + }, + // Skip folding if any vector operands or components of the operands + // have undetermined value. + { + // original + { + "%spec_int = OpSpecConstant %int 1", + "%spec_vec = OpSpecConstantComposite %v2int %signed_zero %spec_int", + "%spec_iadd = OpSpecConstantOp %v2int IAdd %signed_three_vec %spec_vec", + }, + // expected + { + "%spec_int = OpSpecConstant %int 1", + "%spec_vec = OpSpecConstantComposite %v2int %signed_zero %spec_int", + "%spec_iadd = OpSpecConstantOp %v2int IAdd %signed_three_vec %spec_vec", + }, + }, + // Skip folding if any vector operands are defined by OpUndef + { + // original + { + "%undef = OpUndef %int", + "%vec = OpConstantComposite %v2int %undef %signed_one", + "%spec_iadd = OpSpecConstantOp %v2int IAdd %signed_three_vec %vec", + }, + // expected + { + "%undef = OpUndef %int", + "%vec = OpConstantComposite %v2int %undef %signed_one", + "%spec_iadd = OpSpecConstantOp %v2int IAdd %signed_three_vec %vec", + }, + }, + // clang-format on + }))); + +// Tests for SpecConstantOp CompositeExtract instruction +INSTANTIATE_TEST_CASE_P( + CompositeExtract, FoldSpecConstantOpAndCompositePassTest, + ::testing::ValuesIn(std::vector< + FoldSpecConstantOpAndCompositePassTestCase>({ + // clang-format off + // normal vector + { + // original + { + "%r = OpSpecConstantOp %int CompositeExtract %signed_three_vec 0", + "%x = OpSpecConstantOp %int CompositeExtract %v4int_0_1_2_3 0", + "%y = OpSpecConstantOp %int CompositeExtract %v4int_0_1_2_3 1", + "%z = OpSpecConstantOp %int CompositeExtract %v4int_0_1_2_3 2", + "%w = OpSpecConstantOp %int CompositeExtract %v4int_0_1_2_3 3", + }, + // expected + { + "%r = OpConstant %int 3", + "%x = OpConstant %int 0", + "%y = OpConstant %int 1", + "%z = OpConstant %int 2", + "%w = OpConstant %int 3", + }, + }, + // null vector + { + // original + { + "%x = OpSpecConstantOp %int CompositeExtract %signed_null_vec 0", + "%y = OpSpecConstantOp %int CompositeExtract %signed_null_vec 1", + "%null_v4int = OpConstantNull %v4int", + "%z = OpSpecConstantOp %int CompositeExtract %signed_null_vec 2", + }, + // expected + { + "%x = OpConstantNull %int", + "%y = OpConstantNull %int", + "%null_v4int = OpConstantNull %v4int", + "%z = OpConstantNull %int", + } + }, + // normal flat struct + { + // original + { + "%float_1 = OpConstant %float 1", + "%flat_1 = OpConstantComposite %flat_struct %bool_true %signed_null %float_1", + "%extract_bool = OpSpecConstantOp %bool CompositeExtract %flat_1 0", + "%extract_int = OpSpecConstantOp %int CompositeExtract %flat_1 1", + "%extract_float_1 = OpSpecConstantOp %float CompositeExtract %flat_1 2", + // foldable composite constants built with OpSpecConstantComposite + // should also be processed. + "%flat_2 = OpSpecConstantComposite %flat_struct %bool_true %signed_null %float_1", + "%extract_float_2 = OpSpecConstantOp %float CompositeExtract %flat_2 2", + }, + // expected + { + "%float_1 = OpConstant %float 1", + "%flat_1 = OpConstantComposite %flat_struct %bool_true %signed_null %float_1", + "%extract_bool = OpConstantTrue %bool", + "%extract_int = OpConstantNull %int", + "%extract_float_1 = OpConstant %float 1", + "%flat_2 = OpConstantComposite %flat_struct %bool_true %signed_null %float_1", + "%extract_float_2 = OpConstant %float 1", + }, + }, + // null flat struct + { + // original + { + "%flat = OpConstantNull %flat_struct", + "%extract_bool = OpSpecConstantOp %bool CompositeExtract %flat 0", + "%extract_int = OpSpecConstantOp %int CompositeExtract %flat 1", + "%extract_float = OpSpecConstantOp %float CompositeExtract %flat 2", + }, + // expected + { + "%flat = OpConstantNull %flat_struct", + "%extract_bool = OpConstantNull %bool", + "%extract_int = OpConstantNull %int", + "%extract_float = OpConstantNull %float", + }, + }, + // normal nested struct + { + // original + { + "%float_1 = OpConstant %float 1", + "%inner = OpConstantComposite %inner_struct %bool_true %signed_null %float_1", + "%outer = OpConstantComposite %outer_struct %inner %signed_one", + "%extract_inner = OpSpecConstantOp %inner_struct CompositeExtract %outer 0", + "%extract_int = OpSpecConstantOp %int CompositeExtract %outer 1", + "%extract_inner_float = OpSpecConstantOp %int CompositeExtract %outer 0 2", + }, + // expected + { + "%float_1 = OpConstant %float 1", + "%inner = OpConstantComposite %inner_struct %bool_true %signed_null %float_1", + "%outer = OpConstantComposite %outer_struct %inner %signed_one", + "%extract_inner = OpConstantComposite %flat_struct %bool_true %signed_null %float_1", + "%extract_int = OpConstant %int 1", + "%extract_inner_float = OpConstant %float 1", + }, + }, + // null nested struct + { + // original + { + "%outer = OpConstantNull %outer_struct", + "%extract_inner = OpSpecConstantOp %inner_struct CompositeExtract %outer 0", + "%extract_int = OpSpecConstantOp %int CompositeExtract %outer 1", + "%extract_inner_float = OpSpecConstantOp %float CompositeExtract %outer 0 2", + }, + // expected + { + "%outer = OpConstantNull %outer_struct", + "%extract_inner = OpConstantNull %inner_struct", + "%extract_int = OpConstantNull %int", + "%extract_inner_float = OpConstantNull %float", + }, + }, + // skip folding if the any composite constant's value are not fully + // determined, even though the extracting target might have + // determined value. + { + // original + { + "%float_1 = OpConstant %float 1", + "%spec_float = OpSpecConstant %float 1", + "%spec_inner = OpSpecConstantComposite %inner_struct %bool_true %signed_null %spec_float", + "%spec_outer = OpSpecConstantComposite %outer_struct %spec_inner %signed_one", + "%spec_vec = OpSpecConstantComposite %v2float %spec_float %float_1", + "%extract_inner = OpSpecConstantOp %int CompositeExtract %spec_inner 1", + "%extract_outer = OpSpecConstantOp %int CompositeExtract %spec_outer 1", + "%extract_vec = OpSpecConstantOp %float CompositeExtract %spec_vec 1", + }, + // expected + { + "%float_1 = OpConstant %float 1", + "%spec_float = OpSpecConstant %float 1", + "%spec_inner = OpSpecConstantComposite %inner_struct %bool_true %signed_null %spec_float", + "%spec_outer = OpSpecConstantComposite %outer_struct %spec_inner %signed_one", + "%spec_vec = OpSpecConstantComposite %v2float %spec_float %float_1", + "%extract_inner = OpSpecConstantOp %int CompositeExtract %spec_inner 1", + "%extract_outer = OpSpecConstantOp %int CompositeExtract %spec_outer 1", + "%extract_vec = OpSpecConstantOp %float CompositeExtract %spec_vec 1", + }, + }, + // skip if the composite constant depends on the result of OpUndef, + // even though the composite extract target element does not depends + // on the OpUndef. + { + // original + { + "%undef = OpUndef %float", + "%inner = OpConstantComposite %inner_struct %bool_true %signed_one %undef", + "%outer = OpConstantComposite %outer_struct %inner %signed_one", + "%extract_inner = OpSpecConstantOp %int CompositeExtract %inner 1", + "%extract_outer = OpSpecConstantOp %int CompositeExtract %outer 1", + }, + // expected + { + "%undef = OpUndef %float", + "%inner = OpConstantComposite %inner_struct %bool_true %signed_one %undef", + "%outer = OpConstantComposite %outer_struct %inner %signed_one", + "%extract_inner = OpSpecConstantOp %int CompositeExtract %inner 1", + "%extract_outer = OpSpecConstantOp %int CompositeExtract %outer 1", + }, + }, + // TODO(qining): Add tests for Array and other composite type constants. + // clang-format on + }))); + +// Tests the swizzle operations for spec const vectors. +INSTANTIATE_TEST_CASE_P( + VectorShuffle, FoldSpecConstantOpAndCompositePassTest, + ::testing::ValuesIn(std::vector< + FoldSpecConstantOpAndCompositePassTestCase>({ + // clang-format off + // normal vector + { + // original + { + "%xy = OpSpecConstantOp %v2int VectorShuffle %v4int_0_1_2_3 %v4int_0_1_2_3 0 1", + "%yz = OpSpecConstantOp %v2int VectorShuffle %v4int_0_1_2_3 %v4int_0_1_2_3 1 2", + "%zw = OpSpecConstantOp %v2int VectorShuffle %v4int_0_1_2_3 %v4int_0_1_2_3 2 3", + "%wx = OpSpecConstantOp %v2int VectorShuffle %v4int_0_1_2_3 %v4int_0_1_2_3 3 0", + "%xx = OpSpecConstantOp %v2int VectorShuffle %v4int_0_1_2_3 %v4int_0_1_2_3 0 0", + "%yyy = OpSpecConstantOp %v3int VectorShuffle %v4int_0_1_2_3 %v4int_0_1_2_3 1 1 1", + "%wwww = OpSpecConstantOp %v4int VectorShuffle %v4int_0_1_2_3 %v4int_0_1_2_3 2 2 2 2", + }, + // expected + { + "%xy = OpConstantComposite %v2int %signed_zero %signed_one", + "%yz = OpConstantComposite %v2int %signed_one %signed_two", + "%zw = OpConstantComposite %v2int %signed_two %signed_three", + "%wx = OpConstantComposite %v2int %signed_three %signed_zero", + "%xx = OpConstantComposite %v2int %signed_zero %signed_zero", + "%yyy = OpConstantComposite %v3int %signed_one %signed_one %signed_one", + "%wwww = OpConstantComposite %v4int %signed_two %signed_two %signed_two %signed_two", + }, + }, + // null vector + { + // original + { + "%a = OpSpecConstantOp %v2int VectorShuffle %signed_null_vec %v4int_0_1_2_3 0 1", + "%b = OpSpecConstantOp %v2int VectorShuffle %signed_null_vec %v4int_0_1_2_3 2 3", + "%c = OpSpecConstantOp %v2int VectorShuffle %v4int_0_1_2_3 %signed_null_vec 3 4", + "%d = OpSpecConstantOp %v2int VectorShuffle %signed_null_vec %signed_null_vec 1 2", + }, + // expected + { + "%60 = OpConstantNull %int", + "%a = OpConstantComposite %v2int %60 %60", + "%62 = OpConstantNull %int", + "%b = OpConstantComposite %v2int %signed_zero %signed_one", + "%64 = OpConstantNull %int", + "%c = OpConstantComposite %v2int %signed_three %64", + "%66 = OpConstantNull %int", + "%d = OpConstantComposite %v2int %66 %66", + } + }, + // skip if any of the components of the vector operands do not have + // determined value, even though the result vector might not be + // built with those undermined values. + { + // original + { + "%spec_int = OpSpecConstant %int 1", + "%spec_ivec = OpSpecConstantComposite %v2int %signed_null %spec_int", + "%a = OpSpecConstantOp %v2int VectorShuffle %v4int_0_1_2_3 %spec_ivec 0 1", + "%b = OpSpecConstantOp %v2int VectorShuffle %v4int_0_1_2_3 %spec_ivec 3 4", + }, + // expected + { + "%spec_int = OpSpecConstant %int 1", + "%spec_ivec = OpSpecConstantComposite %v2int %signed_null %spec_int", + "%a = OpSpecConstantOp %v2int VectorShuffle %v4int_0_1_2_3 %spec_ivec 0 1", + "%b = OpSpecConstantOp %v2int VectorShuffle %v4int_0_1_2_3 %spec_ivec 3 4", + }, + }, + // Skip if any components of the two vector operands depend on + // the result of OpUndef. Even though the selected components do + // not depend on the OpUndef result. + { + // original + { + "%undef = OpUndef %int", + "%vec_1 = OpConstantComposite %v2int %undef %signed_one", + "%dep = OpSpecConstantOp %v2int VectorShuffle %vec_1 %signed_three_vec 0 3", + "%not_dep_element = OpSpecConstantOp %v2int VectorShuffle %vec_1 %signed_three_vec 1 3", + "%no_dep_vector = OpSpecConstantOp %v2int VectorShuffle %vec_1 %signed_three_vec 2 3", + }, + // expected + { + "%undef = OpUndef %int", + "%vec_1 = OpConstantComposite %v2int %undef %signed_one", + "%dep = OpSpecConstantOp %v2int VectorShuffle %vec_1 %signed_three_vec 0 3", + "%not_dep_element = OpSpecConstantOp %v2int VectorShuffle %vec_1 %signed_three_vec 1 3", + "%no_dep_vector = OpSpecConstantOp %v2int VectorShuffle %vec_1 %signed_three_vec 2 3", + }, + }, + // clang-format on + }))); + +// Test with long use-def chain. +INSTANTIATE_TEST_CASE_P( + LongDefUseChain, FoldSpecConstantOpAndCompositePassTest, + ::testing::ValuesIn(std::vector< + FoldSpecConstantOpAndCompositePassTestCase>({ + // clang-format off + // Long Def-Use chain with binary operations. + { + // original + { + "%array_size = OpConstant %int 4", + "%type_arr_int_4 = OpTypeArray %int %array_size", + "%spec_int_0 = OpConstant %int 100", + "%spec_int_1 = OpConstant %int 1", + "%spec_int_2 = OpSpecConstantOp %int IAdd %spec_int_0 %spec_int_1", + "%spec_int_3 = OpSpecConstantOp %int ISub %spec_int_0 %spec_int_2", + "%spec_int_4 = OpSpecConstantOp %int IAdd %spec_int_0 %spec_int_3", + "%spec_int_5 = OpSpecConstantOp %int ISub %spec_int_0 %spec_int_4", + "%spec_int_6 = OpSpecConstantOp %int IAdd %spec_int_0 %spec_int_5", + "%spec_int_7 = OpSpecConstantOp %int ISub %spec_int_0 %spec_int_6", + "%spec_int_8 = OpSpecConstantOp %int IAdd %spec_int_0 %spec_int_7", + "%spec_int_9 = OpSpecConstantOp %int ISub %spec_int_0 %spec_int_8", + "%spec_int_10 = OpSpecConstantOp %int IAdd %spec_int_0 %spec_int_9", + "%spec_int_11 = OpSpecConstantOp %int ISub %spec_int_0 %spec_int_10", + "%spec_int_12 = OpSpecConstantOp %int IAdd %spec_int_0 %spec_int_11", + "%spec_int_13 = OpSpecConstantOp %int ISub %spec_int_0 %spec_int_12", + "%spec_int_14 = OpSpecConstantOp %int IAdd %spec_int_0 %spec_int_13", + "%spec_int_15 = OpSpecConstantOp %int ISub %spec_int_0 %spec_int_14", + "%spec_int_16 = OpSpecConstantOp %int ISub %spec_int_0 %spec_int_15", + "%spec_int_17 = OpSpecConstantOp %int IAdd %spec_int_0 %spec_int_16", + "%spec_int_18 = OpSpecConstantOp %int ISub %spec_int_0 %spec_int_17", + "%spec_int_19 = OpSpecConstantOp %int IAdd %spec_int_0 %spec_int_18", + "%spec_int_20 = OpSpecConstantOp %int ISub %spec_int_0 %spec_int_19", + "%used_vec_a = OpSpecConstantComposite %v2int %spec_int_18 %spec_int_19", + "%used_vec_b = OpSpecConstantOp %v2int IMul %used_vec_a %used_vec_a", + "%spec_int_21 = OpSpecConstantOp %int CompositeExtract %used_vec_b 0", + "%array = OpConstantComposite %type_arr_int_4 %spec_int_20 %spec_int_20 %spec_int_21 %spec_int_21", + // Spec constants whose values can not be fully resolved should + // not be processed. + "%spec_int_22 = OpSpecConstant %int 123", + "%spec_int_23 = OpSpecConstantOp %int IAdd %spec_int_22 %signed_one", + }, + // expected + { + "%array_size = OpConstant %int 4", + "%type_arr_int_4 = OpTypeArray %int %array_size", + "%spec_int_0 = OpConstant %int 100", + "%spec_int_1 = OpConstant %int 1", + "%spec_int_2 = OpConstant %int 101", + "%spec_int_3 = OpConstant %int -1", + "%spec_int_4 = OpConstant %int 99", + "%spec_int_5 = OpConstant %int 1", + "%spec_int_6 = OpConstant %int 101", + "%spec_int_7 = OpConstant %int -1", + "%spec_int_8 = OpConstant %int 99", + "%spec_int_9 = OpConstant %int 1", + "%spec_int_10 = OpConstant %int 101", + "%spec_int_11 = OpConstant %int -1", + "%spec_int_12 = OpConstant %int 99", + "%spec_int_13 = OpConstant %int 1", + "%spec_int_14 = OpConstant %int 101", + "%spec_int_15 = OpConstant %int -1", + "%spec_int_16 = OpConstant %int 101", + "%spec_int_17 = OpConstant %int 201", + "%spec_int_18 = OpConstant %int -101", + "%spec_int_19 = OpConstant %int -1", + "%spec_int_20 = OpConstant %int 101", + "%used_vec_a = OpConstantComposite %v2int %spec_int_18 %spec_int_19", + "%int_10201 = OpConstant %int 10201", + "%int_1 = OpConstant %int 1", + "%used_vec_b = OpConstantComposite %v2int %int_10201 %int_1", + "%spec_int_21 = OpConstant %int 10201", + "%array = OpConstantComposite %type_arr_int_4 %spec_int_20 %spec_int_20 %spec_int_21 %spec_int_21", + "%spec_int_22 = OpSpecConstant %int 123", + "%spec_int_23 = OpSpecConstantOp %int IAdd %spec_int_22 %signed_one", + }, + }, + // Long Def-Use chain with swizzle + }))); +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/fold_test.cpp b/3rdparty/spirv-tools/test/opt/fold_test.cpp new file mode 100644 index 000000000..c33bbd99c --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/fold_test.cpp @@ -0,0 +1,4470 @@ +// Copyright (c) 2016 Google Inc. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include +#include +#include + +#ifdef SPIRV_EFFCEE +#include "effcee/effcee.h" +#endif + +#include "opt/build_module.h" +#include "opt/def_use_manager.h" +#include "opt/ir_context.h" +#include "opt/module.h" +#include "pass_utils.h" +#include "spirv-tools/libspirv.hpp" + +namespace { + +using ::testing::Contains; + +using namespace spvtools; +using spvtools::opt::analysis::DefUseManager; + +#ifdef SPIRV_EFFCEE +std::string Disassemble(const std::string& original, ir::IRContext* context, + uint32_t disassemble_options = 0) { + std::vector optimized_bin; + context->module()->ToBinary(&optimized_bin, true); + spv_target_env target_env = SPV_ENV_UNIVERSAL_1_2; + SpirvTools tools(target_env); + std::string optimized_asm; + EXPECT_TRUE( + tools.Disassemble(optimized_bin, &optimized_asm, disassemble_options)) + << "Disassembling failed for shader:\n" + << original << std::endl; + return optimized_asm; +} + +void Match(const std::string& original, ir::IRContext* context, + uint32_t disassemble_options = 0) { + std::string disassembly = Disassemble(original, context, disassemble_options); + auto match_result = effcee::Match(disassembly, original); + EXPECT_EQ(effcee::Result::Status::Ok, match_result.status()) + << match_result.message() << "\nChecking result:\n" + << disassembly; +} +#endif + +template +struct InstructionFoldingCase { + InstructionFoldingCase(const std::string& tb, uint32_t id, ResultType result) + : test_body(tb), id_to_fold(id), expected_result(result) {} + + std::string test_body; + uint32_t id_to_fold; + ResultType expected_result; +}; + +using IntegerInstructionFoldingTest = + ::testing::TestWithParam>; + +TEST_P(IntegerInstructionFoldingTest, Case) { + const auto& tc = GetParam(); + + // Build module. + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.test_body, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ASSERT_NE(nullptr, context); + + // Fold the instruction to test. + opt::analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); + ir::Instruction* inst = def_use_mgr->GetDef(tc.id_to_fold); + bool succeeded = opt::FoldInstruction(inst); + + // Make sure the instruction folded as expected. + EXPECT_TRUE(succeeded); + if (inst != nullptr) { + EXPECT_EQ(inst->opcode(), SpvOpCopyObject); + inst = def_use_mgr->GetDef(inst->GetSingleWordInOperand(0)); + EXPECT_EQ(inst->opcode(), SpvOpConstant); + opt::analysis::ConstantManager* const_mrg = context->get_constant_mgr(); + const opt::analysis::IntConstant* result = + const_mrg->GetConstantFromInst(inst)->AsIntConstant(); + EXPECT_NE(result, nullptr); + if (result != nullptr) { + EXPECT_EQ(result->GetU32BitValue(), tc.expected_result); + } + } +} + +// Returns a common SPIR-V header for all of the test that follow. +#define INT_0_ID 100 +#define TRUE_ID 101 +#define VEC2_0_ID 102 +#define INT_7_ID 103 +#define FLOAT_0_ID 104 +#define DOUBLE_0_ID 105 +#define VEC4_0_ID 106 +#define DVEC4_0_ID 106 +#define HALF_0_ID 108 +const std::string& Header() { + static const std::string header = R"(OpCapability Shader +OpCapability Float16 +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +%void = OpTypeVoid +%void_func = OpTypeFunction %void +%bool = OpTypeBool +%float16 = OpTypeFloat 16 +%float = OpTypeFloat 32 +%double = OpTypeFloat 64 +%half = OpTypeFloat 16 +%101 = OpConstantTrue %bool ; Need a def with an numerical id to define id maps. +%true = OpConstantTrue %bool +%false = OpConstantFalse %bool +%bool_null = OpConstantNull %bool +%short = OpTypeInt 16 1 +%int = OpTypeInt 32 1 +%long = OpTypeInt 64 1 +%uint = OpTypeInt 32 0 +%v2int = OpTypeVector %int 2 +%v4int = OpTypeVector %int 4 +%v4float = OpTypeVector %float 4 +%v4double = OpTypeVector %double 4 +%v2float = OpTypeVector %float 2 +%v2bool = OpTypeVector %bool 2 +%struct_v2int_int_int = OpTypeStruct %v2int %int %int +%_ptr_int = OpTypePointer Function %int +%_ptr_uint = OpTypePointer Function %uint +%_ptr_bool = OpTypePointer Function %bool +%_ptr_float = OpTypePointer Function %float +%_ptr_double = OpTypePointer Function %double +%_ptr_half = OpTypePointer Function %half +%_ptr_long = OpTypePointer Function %long +%_ptr_v2int = OpTypePointer Function %v2int +%_ptr_v4int = OpTypePointer Function %v4int +%_ptr_v4float = OpTypePointer Function %v4float +%_ptr_v4double = OpTypePointer Function %v4double +%_ptr_struct_v2int_int_int = OpTypePointer Function %struct_v2int_int_int +%_ptr_v2float = OpTypePointer Function %v2float +%short_0 = OpConstant %short 0 +%short_3 = OpConstant %short 3 +%100 = OpConstant %int 0 ; Need a def with an numerical id to define id maps. +%103 = OpConstant %int 7 ; Need a def with an numerical id to define id maps. +%int_0 = OpConstant %int 0 +%int_1 = OpConstant %int 1 +%int_2 = OpConstant %int 2 +%int_3 = OpConstant %int 3 +%int_4 = OpConstant %int 4 +%int_min = OpConstant %int -2147483648 +%int_max = OpConstant %int 2147483647 +%long_0 = OpConstant %long 0 +%long_2 = OpConstant %long 2 +%long_3 = OpConstant %long 3 +%uint_0 = OpConstant %uint 0 +%uint_2 = OpConstant %uint 2 +%uint_3 = OpConstant %uint 3 +%uint_4 = OpConstant %uint 4 +%uint_32 = OpConstant %uint 32 +%uint_max = OpConstant %uint 4294967295 +%v2int_undef = OpUndef %v2int +%v2int_2_2 = OpConstantComposite %v2int %int_2 %int_2 +%v2int_2_3 = OpConstantComposite %v2int %int_2 %int_3 +%v2int_3_2 = OpConstantComposite %v2int %int_3 %int_2 +%v2int_4_4 = OpConstantComposite %v2int %int_4 %int_4 +%v2bool_null = OpConstantNull %v2bool +%v2bool_true_false = OpConstantComposite %v2bool %true %false +%v2bool_false_true = OpConstantComposite %v2bool %false %true +%struct_v2int_int_int_null = OpConstantNull %struct_v2int_int_int +%v2int_null = OpConstantNull %v2int +%102 = OpConstantComposite %v2int %103 %103 +%v4int_0_0_0_0 = OpConstantComposite %v4int %int_0 %int_0 %int_0 %int_0 +%struct_undef_0_0 = OpConstantComposite %struct_v2int_int_int %v2int_undef %int_0 %int_0 +%float16_0 = OpConstant %float16 0 +%float16_1 = OpConstant %float16 1 +%float16_2 = OpConstant %float16 2 +%float_n1 = OpConstant %float -1 +%104 = OpConstant %float 0 ; Need a def with an numerical id to define id maps. +%float_0 = OpConstant %float 0 +%float_half = OpConstant %float 0.5 +%float_1 = OpConstant %float 1 +%float_2 = OpConstant %float 2 +%float_3 = OpConstant %float 3 +%float_4 = OpConstant %float 4 +%float_0p5 = OpConstant %float 0.5 +%v2float_2_2 = OpConstantComposite %v2float %float_2 %float_2 +%v2float_2_3 = OpConstantComposite %v2float %float_2 %float_3 +%v2float_3_2 = OpConstantComposite %v2float %float_3 %float_2 +%v2float_4_4 = OpConstantComposite %v2float %float_4 %float_4 +%v2float_2_0p5 = OpConstantComposite %v2float %float_2 %float_0p5 +%v2float_null = OpConstantNull %v2float +%double_n1 = OpConstant %double -1 +%105 = OpConstant %double 0 ; Need a def with an numerical id to define id maps. +%double_0 = OpConstant %double 0 +%double_1 = OpConstant %double 1 +%double_2 = OpConstant %double 2 +%double_3 = OpConstant %double 3 +%float_nan = OpConstant %float -0x1.8p+128 +%double_nan = OpConstant %double -0x1.8p+1024 +%108 = OpConstant %half 0 +%half_1 = OpConstant %half 1 +%106 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 +%v4float_0_0_0_0 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 +%v4float_0_0_0_1 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_1 +%v4float_1_1_1_1 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +%107 = OpConstantComposite %v4double %double_0 %double_0 %double_0 %double_0 +%v4double_0_0_0_0 = OpConstantComposite %v4double %double_0 %double_0 %double_0 %double_0 +%v4double_0_0_0_1 = OpConstantComposite %v4double %double_0 %double_0 %double_0 %double_1 +%v4double_1_1_1_1 = OpConstantComposite %v4double %double_1 %double_1 %double_1 %double_1 +)"; + + return header; +} + +// clang-format off +INSTANTIATE_TEST_CASE_P(TestCase, IntegerInstructionFoldingTest, + ::testing::Values( + // Test case 0: fold 0*n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load = OpLoad %int %n\n" + + "%2 = OpIMul %int %int_0 %load\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 1: fold n*0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load = OpLoad %int %n\n" + + "%2 = OpIMul %int %load %int_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 2: fold 0/n (signed) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load = OpLoad %int %n\n" + + "%2 = OpSDiv %int %int_0 %load\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 3: fold n/0 (signed) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load = OpLoad %int %n\n" + + "%2 = OpSDiv %int %load %int_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 4: fold 0/n (unsigned) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%load = OpLoad %uint %n\n" + + "%2 = OpUDiv %uint %uint_0 %load\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 5: fold n/0 (unsigned) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load = OpLoad %int %n\n" + + "%2 = OpSDiv %int %load %int_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 6: fold 0 remainder n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load = OpLoad %int %n\n" + + "%2 = OpSRem %int %int_0 %load\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 7: fold n remainder 0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load = OpLoad %int %n\n" + + "%2 = OpSRem %int %load %int_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 8: fold 0%n (signed) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load = OpLoad %int %n\n" + + "%2 = OpSMod %int %int_0 %load\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 9: fold n%0 (signed) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load = OpLoad %int %n\n" + + "%2 = OpSMod %int %load %int_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 10: fold 0%n (unsigned) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%load = OpLoad %uint %n\n" + + "%2 = OpUMod %uint %uint_0 %load\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 11: fold n%0 (unsigned) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%load = OpLoad %uint %n\n" + + "%2 = OpUMod %uint %load %uint_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 12: fold n << 32 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%load = OpLoad %uint %n\n" + + "%2 = OpShiftLeftLogical %uint %load %uint_32\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 13: fold n >> 32 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%load = OpLoad %uint %n\n" + + "%2 = OpShiftRightLogical %uint %load %uint_32\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 14: fold n | 0xFFFFFFFF + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%load = OpLoad %uint %n\n" + + "%2 = OpBitwiseOr %uint %load %uint_max\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0xFFFFFFFF), + // Test case 15: fold 0xFFFFFFFF | n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%load = OpLoad %uint %n\n" + + "%2 = OpBitwiseOr %uint %uint_max %load\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0xFFFFFFFF), + // Test case 16: fold n & 0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%load = OpLoad %uint %n\n" + + "%2 = OpBitwiseAnd %uint %load %uint_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0) +)); +// clang-format on + +using IntVectorInstructionFoldingTest = + ::testing::TestWithParam>>; + +TEST_P(IntVectorInstructionFoldingTest, Case) { + const auto& tc = GetParam(); + + // Build module. + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.test_body, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ASSERT_NE(nullptr, context); + + // Fold the instruction to test. + opt::analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); + ir::Instruction* inst = def_use_mgr->GetDef(tc.id_to_fold); + bool succeeded = opt::FoldInstruction(inst); + + // Make sure the instruction folded as expected. + EXPECT_TRUE(succeeded); + if (inst != nullptr) { + EXPECT_EQ(inst->opcode(), SpvOpCopyObject); + inst = def_use_mgr->GetDef(inst->GetSingleWordInOperand(0)); + std::vector opcodes = {SpvOpConstantComposite}; + EXPECT_THAT(opcodes, Contains(inst->opcode())); + opt::analysis::ConstantManager* const_mrg = context->get_constant_mgr(); + const opt::analysis::Constant* result = + const_mrg->GetConstantFromInst(inst); + EXPECT_NE(result, nullptr); + if (result != nullptr) { + const std::vector& componenets = + result->AsVectorConstant()->GetComponents(); + EXPECT_EQ(componenets.size(), tc.expected_result.size()); + for (size_t i = 0; i < componenets.size(); i++) { + EXPECT_EQ(tc.expected_result[i], componenets[i]->GetU32()); + } + } + } +} + +// clang-format off +INSTANTIATE_TEST_CASE_P(TestCase, IntVectorInstructionFoldingTest, +::testing::Values( + // Test case 0: fold 0*n + InstructionFoldingCase>( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load = OpLoad %int %n\n" + + "%2 = OpVectorShuffle %v2int %v2int_2_2 %v2int_2_3 0 3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, {2,3}), + InstructionFoldingCase>( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load = OpLoad %int %n\n" + + "%2 = OpVectorShuffle %v2int %v2int_null %v2int_2_3 0 3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, {0,3}) +)); +// clang-format on + +using BooleanInstructionFoldingTest = + ::testing::TestWithParam>; + +TEST_P(BooleanInstructionFoldingTest, Case) { + const auto& tc = GetParam(); + + // Build module. + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.test_body, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ASSERT_NE(nullptr, context); + + // Fold the instruction to test. + opt::analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); + ir::Instruction* inst = def_use_mgr->GetDef(tc.id_to_fold); + bool succeeded = opt::FoldInstruction(inst); + + // Make sure the instruction folded as expected. + EXPECT_TRUE(succeeded); + if (inst != nullptr) { + EXPECT_EQ(inst->opcode(), SpvOpCopyObject); + inst = def_use_mgr->GetDef(inst->GetSingleWordInOperand(0)); + std::vector bool_opcodes = {SpvOpConstantTrue, SpvOpConstantFalse}; + EXPECT_THAT(bool_opcodes, Contains(inst->opcode())); + opt::analysis::ConstantManager* const_mrg = context->get_constant_mgr(); + const opt::analysis::BoolConstant* result = + const_mrg->GetConstantFromInst(inst)->AsBoolConstant(); + EXPECT_NE(result, nullptr); + if (result != nullptr) { + EXPECT_EQ(result->value(), tc.expected_result); + } + } +} + +// clang-format off +INSTANTIATE_TEST_CASE_P(TestCase, BooleanInstructionFoldingTest, + ::testing::Values( + // Test case 0: fold true || n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_bool Function\n" + + "%load = OpLoad %bool %n\n" + + "%2 = OpLogicalOr %bool %true %load\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 1: fold n || true + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_bool Function\n" + + "%load = OpLoad %bool %n\n" + + "%2 = OpLogicalOr %bool %load %true\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 2: fold false && n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_bool Function\n" + + "%load = OpLoad %bool %n\n" + + "%2 = OpLogicalAnd %bool %false %load\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 3: fold n && false + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_bool Function\n" + + "%load = OpLoad %bool %n\n" + + "%2 = OpLogicalAnd %bool %load %false\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 4: fold n < 0 (unsigned) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%load = OpLoad %uint %n\n" + + "%2 = OpULessThan %bool %load %uint_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 5: fold UINT_MAX < n (unsigned) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%load = OpLoad %uint %n\n" + + "%2 = OpULessThan %bool %uint_max %load\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 6: fold INT_MAX < n (signed) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load = OpLoad %int %n\n" + + "%2 = OpSLessThan %bool %int_max %load\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 7: fold n < INT_MIN (signed) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load = OpLoad %int %n\n" + + "%2 = OpSLessThan %bool %load %int_min\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 8: fold 0 > n (unsigned) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%load = OpLoad %uint %n\n" + + "%2 = OpUGreaterThan %bool %uint_0 %load\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 9: fold n > UINT_MAX (unsigned) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%load = OpLoad %uint %n\n" + + "%2 = OpUGreaterThan %bool %load %uint_max\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 10: fold n > INT_MAX (signed) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load = OpLoad %int %n\n" + + "%2 = OpSGreaterThan %bool %load %int_max\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 11: fold INT_MIN > n (signed) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%load = OpLoad %uint %n\n" + + "%2 = OpSGreaterThan %bool %int_min %load\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 12: fold 0 <= n (unsigned) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%load = OpLoad %uint %n\n" + + "%2 = OpULessThanEqual %bool %uint_0 %load\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 13: fold n <= UINT_MAX (unsigned) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%load = OpLoad %uint %n\n" + + "%2 = OpULessThanEqual %bool %load %uint_max\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 14: fold INT_MIN <= n (signed) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load = OpLoad %int %n\n" + + "%2 = OpSLessThanEqual %bool %int_min %load\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 15: fold n <= INT_MAX (signed) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load = OpLoad %int %n\n" + + "%2 = OpSLessThanEqual %bool %load %int_max\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 16: fold n >= 0 (unsigned) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%load = OpLoad %uint %n\n" + + "%2 = OpUGreaterThanEqual %bool %load %uint_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 17: fold UINT_MAX >= n (unsigned) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%load = OpLoad %uint %n\n" + + "%2 = OpUGreaterThanEqual %bool %uint_max %load\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 18: fold n >= INT_MIN (signed) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load = OpLoad %int %n\n" + + "%2 = OpSGreaterThanEqual %bool %load %int_min\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 19: fold INT_MAX >= n (signed) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load = OpLoad %int %n\n" + + "%2 = OpSGreaterThanEqual %bool %int_max %load\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true) +)); +// clang-format on + +using FloatInstructionFoldingTest = + ::testing::TestWithParam>; + +TEST_P(FloatInstructionFoldingTest, Case) { + const auto& tc = GetParam(); + + // Build module. + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.test_body, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ASSERT_NE(nullptr, context); + + // Fold the instruction to test. + opt::analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); + ir::Instruction* inst = def_use_mgr->GetDef(tc.id_to_fold); + bool succeeded = opt::FoldInstruction(inst); + + // Make sure the instruction folded as expected. + EXPECT_TRUE(succeeded); + if (inst != nullptr) { + EXPECT_EQ(inst->opcode(), SpvOpCopyObject); + inst = def_use_mgr->GetDef(inst->GetSingleWordInOperand(0)); + EXPECT_EQ(inst->opcode(), SpvOpConstant); + opt::analysis::ConstantManager* const_mrg = context->get_constant_mgr(); + const opt::analysis::FloatConstant* result = + const_mrg->GetConstantFromInst(inst)->AsFloatConstant(); + EXPECT_NE(result, nullptr); + if (result != nullptr) { + EXPECT_EQ(result->GetFloatValue(), tc.expected_result); + } + } +} + +// Not testing NaNs because there are no expectations concerning NaNs according +// to the "Precision and Operation of SPIR-V Instructions" section of the Vulkan +// specification. + +// clang-format off +INSTANTIATE_TEST_CASE_P(FloatConstantFoldingTest, FloatInstructionFoldingTest, +::testing::Values( + // Test case 0: Fold 2.0 - 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFSub %float %float_2 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 1.0), + // Test case 1: Fold 2.0 + 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFAdd %float %float_2 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3.0), + // Test case 2: Fold 3.0 * 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFMul %float %float_3 %float_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 6.0), + // Test case 3: Fold 1.0 / 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFDiv %float %float_1 %float_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0.5), + // Test case 4: Fold 1.0 / 0.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFDiv %float %float_1 %float_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, std::numeric_limits::infinity()), + // Test case 5: Fold -1.0 / 0.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFDiv %float %float_n1 %float_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, -std::numeric_limits::infinity()) +)); +// clang-format on + +using DoubleInstructionFoldingTest = + ::testing::TestWithParam>; + +TEST_P(DoubleInstructionFoldingTest, Case) { + const auto& tc = GetParam(); + + // Build module. + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.test_body, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ASSERT_NE(nullptr, context); + + // Fold the instruction to test. + opt::analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); + ir::Instruction* inst = def_use_mgr->GetDef(tc.id_to_fold); + bool succeeded = opt::FoldInstruction(inst); + + // Make sure the instruction folded as expected. + EXPECT_TRUE(succeeded); + if (inst != nullptr) { + EXPECT_EQ(inst->opcode(), SpvOpCopyObject); + inst = def_use_mgr->GetDef(inst->GetSingleWordInOperand(0)); + EXPECT_EQ(inst->opcode(), SpvOpConstant); + opt::analysis::ConstantManager* const_mrg = context->get_constant_mgr(); + const opt::analysis::FloatConstant* result = + const_mrg->GetConstantFromInst(inst)->AsFloatConstant(); + EXPECT_NE(result, nullptr); + if (result != nullptr) { + EXPECT_EQ(result->GetDoubleValue(), tc.expected_result); + } + } +} + +// clang-format off +INSTANTIATE_TEST_CASE_P(DoubleConstantFoldingTest, DoubleInstructionFoldingTest, +::testing::Values( + // Test case 0: Fold 2.0 - 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFSub %double %double_2 %double_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 1.0), + // Test case 1: Fold 2.0 + 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFAdd %double %double_2 %double_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3.0), + // Test case 2: Fold 3.0 * 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFMul %double %double_3 %double_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 6.0), + // Test case 3: Fold 1.0 / 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFDiv %double %double_1 %double_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0.5), + // Test case 4: Fold 1.0 / 0.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFDiv %double %double_1 %double_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, std::numeric_limits::infinity()), + // Test case 4: Fold -1.0 / 0.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFDiv %double %double_n1 %double_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, -std::numeric_limits::infinity()) +)); +// clang-format on + +// clang-format off +INSTANTIATE_TEST_CASE_P(DoubleOrderedCompareConstantFoldingTest, BooleanInstructionFoldingTest, + ::testing::Values( + // Test case 0: fold 1.0 == 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdEqual %bool %double_1 %double_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 1: fold 1.0 != 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdNotEqual %bool %double_1 %double_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 2: fold 1.0 < 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdLessThan %bool %double_1 %double_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 3: fold 1.0 > 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdGreaterThan %bool %double_1 %double_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 4: fold 1.0 <= 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdLessThanEqual %bool %double_1 %double_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 5: fold 1.0 >= 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdGreaterThanEqual %bool %double_1 %double_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 6: fold 1.0 == 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdEqual %bool %double_1 %double_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 7: fold 1.0 != 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdNotEqual %bool %double_1 %double_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 8: fold 1.0 < 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdLessThan %bool %double_1 %double_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 9: fold 1.0 > 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdGreaterThan %bool %double_1 %double_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 10: fold 1.0 <= 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdLessThanEqual %bool %double_1 %double_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 11: fold 1.0 >= 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdGreaterThanEqual %bool %double_1 %double_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 12: fold 2.0 < 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdLessThan %bool %double_2 %double_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 13: fold 2.0 > 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdGreaterThan %bool %double_2 %double_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 14: fold 2.0 <= 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdLessThanEqual %bool %double_2 %double_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 15: fold 2.0 >= 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdGreaterThanEqual %bool %double_2 %double_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true) +)); + +INSTANTIATE_TEST_CASE_P(DoubleUnorderedCompareConstantFoldingTest, BooleanInstructionFoldingTest, + ::testing::Values( + // Test case 0: fold 1.0 == 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordEqual %bool %double_1 %double_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 1: fold 1.0 != 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordNotEqual %bool %double_1 %double_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 2: fold 1.0 < 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordLessThan %bool %double_1 %double_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 3: fold 1.0 > 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordGreaterThan %bool %double_1 %double_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 4: fold 1.0 <= 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordLessThanEqual %bool %double_1 %double_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 5: fold 1.0 >= 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordGreaterThanEqual %bool %double_1 %double_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 6: fold 1.0 == 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordEqual %bool %double_1 %double_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 7: fold 1.0 != 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordNotEqual %bool %double_1 %double_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 8: fold 1.0 < 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordLessThan %bool %double_1 %double_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 9: fold 1.0 > 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordGreaterThan %bool %double_1 %double_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 10: fold 1.0 <= 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordLessThanEqual %bool %double_1 %double_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 11: fold 1.0 >= 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordGreaterThanEqual %bool %double_1 %double_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 12: fold 2.0 < 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordLessThan %bool %double_2 %double_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 13: fold 2.0 > 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordGreaterThan %bool %double_2 %double_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 14: fold 2.0 <= 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordLessThanEqual %bool %double_2 %double_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 15: fold 2.0 >= 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordGreaterThanEqual %bool %double_2 %double_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true) +)); + +INSTANTIATE_TEST_CASE_P(FloatOrderedCompareConstantFoldingTest, BooleanInstructionFoldingTest, + ::testing::Values( + // Test case 0: fold 1.0 == 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdEqual %bool %float_1 %float_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 1: fold 1.0 != 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdNotEqual %bool %float_1 %float_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 2: fold 1.0 < 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdLessThan %bool %float_1 %float_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 3: fold 1.0 > 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdGreaterThan %bool %float_1 %float_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 4: fold 1.0 <= 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdLessThanEqual %bool %float_1 %float_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 5: fold 1.0 >= 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdGreaterThanEqual %bool %float_1 %float_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 6: fold 1.0 == 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdEqual %bool %float_1 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 7: fold 1.0 != 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdNotEqual %bool %float_1 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 8: fold 1.0 < 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdLessThan %bool %float_1 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 9: fold 1.0 > 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdGreaterThan %bool %float_1 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 10: fold 1.0 <= 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdLessThanEqual %bool %float_1 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 11: fold 1.0 >= 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdGreaterThanEqual %bool %float_1 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 12: fold 2.0 < 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdLessThan %bool %float_2 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 13: fold 2.0 > 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdGreaterThan %bool %float_2 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 14: fold 2.0 <= 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdLessThanEqual %bool %float_2 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 15: fold 2.0 >= 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdGreaterThanEqual %bool %float_2 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true) +)); + +INSTANTIATE_TEST_CASE_P(FloatUnorderedCompareConstantFoldingTest, BooleanInstructionFoldingTest, + ::testing::Values( + // Test case 0: fold 1.0 == 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordEqual %bool %float_1 %float_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 1: fold 1.0 != 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordNotEqual %bool %float_1 %float_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 2: fold 1.0 < 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordLessThan %bool %float_1 %float_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 3: fold 1.0 > 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordGreaterThan %bool %float_1 %float_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 4: fold 1.0 <= 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordLessThanEqual %bool %float_1 %float_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 5: fold 1.0 >= 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordGreaterThanEqual %bool %float_1 %float_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 6: fold 1.0 == 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordEqual %bool %float_1 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 7: fold 1.0 != 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordNotEqual %bool %float_1 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 8: fold 1.0 < 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordLessThan %bool %float_1 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 9: fold 1.0 > 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordGreaterThan %bool %float_1 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 10: fold 1.0 <= 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordLessThanEqual %bool %float_1 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 11: fold 1.0 >= 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordGreaterThanEqual %bool %float_1 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 12: fold 2.0 < 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordLessThan %bool %float_2 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 13: fold 2.0 > 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordGreaterThan %bool %float_2 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 14: fold 2.0 <= 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordLessThanEqual %bool %float_2 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 15: fold 2.0 >= 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordGreaterThanEqual %bool %float_2 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true) +)); + +INSTANTIATE_TEST_CASE_P(DoubleNaNCompareConstantFoldingTest, BooleanInstructionFoldingTest, + ::testing::Values( + // Test case 0: fold NaN == 0 (ord) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdEqual %bool %double_nan %double_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 1: fold NaN == NaN (unord) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordEqual %bool %double_nan %double_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 2: fold NaN != NaN (ord) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdNotEqual %bool %double_nan %double_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 3: fold NaN != NaN (unord) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordNotEqual %bool %double_nan %double_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true) +)); + +INSTANTIATE_TEST_CASE_P(FloatNaNCompareConstantFoldingTest, BooleanInstructionFoldingTest, + ::testing::Values( + // Test case 0: fold NaN == 0 (ord) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdEqual %bool %float_nan %float_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 1: fold NaN == NaN (unord) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordEqual %bool %float_nan %float_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 2: fold NaN != NaN (ord) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFOrdNotEqual %bool %float_nan %float_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, false), + // Test case 3: fold NaN != NaN (unord) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFUnordNotEqual %bool %float_nan %float_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true) +)); +// clang-format on + +template +struct InstructionFoldingCaseWithMap { + InstructionFoldingCaseWithMap(const std::string& tb, uint32_t id, + ResultType result, + std::function map) + : test_body(tb), id_to_fold(id), expected_result(result), id_map(map) {} + + std::string test_body; + uint32_t id_to_fold; + ResultType expected_result; + std::function id_map; +}; + +using IntegerInstructionFoldingTestWithMap = + ::testing::TestWithParam>; + +TEST_P(IntegerInstructionFoldingTestWithMap, Case) { + const auto& tc = GetParam(); + + // Build module. + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.test_body, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ASSERT_NE(nullptr, context); + + // Fold the instruction to test. + opt::analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); + ir::Instruction* inst = def_use_mgr->GetDef(tc.id_to_fold); + inst = opt::FoldInstructionToConstant(inst, tc.id_map); + + // Make sure the instruction folded as expected. + EXPECT_NE(inst, nullptr); + if (inst != nullptr) { + EXPECT_EQ(inst->opcode(), SpvOpConstant); + opt::analysis::ConstantManager* const_mrg = context->get_constant_mgr(); + const opt::analysis::IntConstant* result = + const_mrg->GetConstantFromInst(inst)->AsIntConstant(); + EXPECT_NE(result, nullptr); + if (result != nullptr) { + EXPECT_EQ(result->GetU32BitValue(), tc.expected_result); + } + } +} +// clang-format off +INSTANTIATE_TEST_CASE_P(TestCase, IntegerInstructionFoldingTestWithMap, + ::testing::Values( + // Test case 0: fold %3 = 0; %3 * n + InstructionFoldingCaseWithMap( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load = OpLoad %int %n\n" + + "%3 = OpCopyObject %int %int_0\n" + "%2 = OpIMul %int %3 %load\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0, [](uint32_t id) {return (id == 3 ? INT_0_ID : id);}) + )); +// clang-format on + +using BooleanInstructionFoldingTestWithMap = + ::testing::TestWithParam>; + +TEST_P(BooleanInstructionFoldingTestWithMap, Case) { + const auto& tc = GetParam(); + + // Build module. + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.test_body, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ASSERT_NE(nullptr, context); + + // Fold the instruction to test. + opt::analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); + ir::Instruction* inst = def_use_mgr->GetDef(tc.id_to_fold); + inst = opt::FoldInstructionToConstant(inst, tc.id_map); + + // Make sure the instruction folded as expected. + EXPECT_NE(inst, nullptr); + if (inst != nullptr) { + std::vector bool_opcodes = {SpvOpConstantTrue, SpvOpConstantFalse}; + EXPECT_THAT(bool_opcodes, Contains(inst->opcode())); + opt::analysis::ConstantManager* const_mrg = context->get_constant_mgr(); + const opt::analysis::BoolConstant* result = + const_mrg->GetConstantFromInst(inst)->AsBoolConstant(); + EXPECT_NE(result, nullptr); + if (result != nullptr) { + EXPECT_EQ(result->value(), tc.expected_result); + } + } +} + +// clang-format off +INSTANTIATE_TEST_CASE_P(TestCase, BooleanInstructionFoldingTestWithMap, + ::testing::Values( + // Test case 0: fold %3 = true; %3 || n + InstructionFoldingCaseWithMap( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_bool Function\n" + + "%load = OpLoad %bool %n\n" + + "%3 = OpCopyObject %bool %true\n" + + "%2 = OpLogicalOr %bool %3 %load\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true, [](uint32_t id) {return (id == 3 ? TRUE_ID : id);}) + )); +// clang-format on + +using GeneralInstructionFoldingTest = + ::testing::TestWithParam>; + +TEST_P(GeneralInstructionFoldingTest, Case) { + const auto& tc = GetParam(); + + // Build module. + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.test_body, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ASSERT_NE(nullptr, context); + + // Fold the instruction to test. + opt::analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); + ir::Instruction* inst = def_use_mgr->GetDef(tc.id_to_fold); + std::unique_ptr original_inst(inst->Clone(context.get())); + bool succeeded = opt::FoldInstruction(inst); + + // Make sure the instruction folded as expected. + EXPECT_EQ(inst->result_id(), original_inst->result_id()); + EXPECT_EQ(inst->type_id(), original_inst->type_id()); + EXPECT_TRUE((!succeeded) == (tc.expected_result == 0)); + if (succeeded) { + EXPECT_EQ(inst->opcode(), SpvOpCopyObject); + EXPECT_EQ(inst->GetSingleWordInOperand(0), tc.expected_result); + } else { + EXPECT_EQ(inst->NumInOperands(), original_inst->NumInOperands()); + for (uint32_t i = 0; i < inst->NumInOperands(); ++i) { + EXPECT_EQ(inst->GetOperand(i), original_inst->GetOperand(i)); + } + } +} + +// clang-format off +INSTANTIATE_TEST_CASE_P(IntegerArithmeticTestCases, GeneralInstructionFoldingTest, + ::testing::Values( + // Test case 0: Don't fold n * m + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%m = OpVariable %_ptr_int Function\n" + + "%load_n = OpLoad %int %n\n" + + "%load_m = OpLoad %int %m\n" + + "%2 = OpIMul %int %load_n %load_m\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 1: Don't fold n / m (unsigned) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%m = OpVariable %_ptr_uint Function\n" + + "%load_n = OpLoad %uint %n\n" + + "%load_m = OpLoad %uint %m\n" + + "%2 = OpUDiv %uint %load_n %load_m\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 2: Don't fold n / m (signed) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%m = OpVariable %_ptr_int Function\n" + + "%load_n = OpLoad %int %n\n" + + "%load_m = OpLoad %int %m\n" + + "%2 = OpSDiv %int %load_n %load_m\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 3: Don't fold n remainder m + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%m = OpVariable %_ptr_int Function\n" + + "%load_n = OpLoad %int %n\n" + + "%load_m = OpLoad %int %m\n" + + "%2 = OpSRem %int %load_n %load_m\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 4: Don't fold n % m (signed) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%m = OpVariable %_ptr_int Function\n" + + "%load_n = OpLoad %int %n\n" + + "%load_m = OpLoad %int %m\n" + + "%2 = OpSMod %int %load_n %load_m\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 5: Don't fold n % m (unsigned) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%m = OpVariable %_ptr_uint Function\n" + + "%load_n = OpLoad %uint %n\n" + + "%load_m = OpLoad %uint %m\n" + + "%2 = OpUMod %int %load_n %load_m\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 6: Don't fold n << m + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%m = OpVariable %_ptr_uint Function\n" + + "%load_n = OpLoad %uint %n\n" + + "%load_m = OpLoad %uint %m\n" + + "%2 = OpShiftRightLogical %int %load_n %load_m\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 7: Don't fold n >> m + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%m = OpVariable %_ptr_uint Function\n" + + "%load_n = OpLoad %uint %n\n" + + "%load_m = OpLoad %uint %m\n" + + "%2 = OpShiftLeftLogical %int %load_n %load_m\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 8: Don't fold n | m + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%m = OpVariable %_ptr_uint Function\n" + + "%load_n = OpLoad %uint %n\n" + + "%load_m = OpLoad %uint %m\n" + + "%2 = OpBitwiseOr %int %load_n %load_m\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 9: Don't fold n & m + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%m = OpVariable %_ptr_uint Function\n" + + "%load_n = OpLoad %uint %n\n" + + "%load_m = OpLoad %uint %m\n" + + "%2 = OpBitwiseAnd %int %load_n %load_m\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 10: Don't fold n < m (unsigned) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%m = OpVariable %_ptr_uint Function\n" + + "%load_n = OpLoad %uint %n\n" + + "%load_m = OpLoad %uint %m\n" + + "%2 = OpULessThan %bool %load_n %load_m\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 11: Don't fold n > m (unsigned) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%m = OpVariable %_ptr_uint Function\n" + + "%load_n = OpLoad %uint %n\n" + + "%load_m = OpLoad %uint %m\n" + + "%2 = OpUGreaterThan %bool %load_n %load_m\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 12: Don't fold n <= m (unsigned) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%m = OpVariable %_ptr_uint Function\n" + + "%load_n = OpLoad %uint %n\n" + + "%load_m = OpLoad %uint %m\n" + + "%2 = OpULessThanEqual %bool %load_n %load_m\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 13: Don't fold n >= m (unsigned) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%m = OpVariable %_ptr_uint Function\n" + + "%load_n = OpLoad %uint %n\n" + + "%load_m = OpLoad %uint %m\n" + + "%2 = OpUGreaterThanEqual %bool %load_n %load_m\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 14: Don't fold n < m (signed) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%m = OpVariable %_ptr_int Function\n" + + "%load_n = OpLoad %int %n\n" + + "%load_m = OpLoad %int %m\n" + + "%2 = OpULessThan %bool %load_n %load_m\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 15: Don't fold n > m (signed) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%m = OpVariable %_ptr_int Function\n" + + "%load_n = OpLoad %int %n\n" + + "%load_m = OpLoad %int %m\n" + + "%2 = OpUGreaterThan %bool %load_n %load_m\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 16: Don't fold n <= m (signed) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%m = OpVariable %_ptr_int Function\n" + + "%load_n = OpLoad %int %n\n" + + "%load_m = OpLoad %int %m\n" + + "%2 = OpULessThanEqual %bool %load_n %load_m\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 17: Don't fold n >= m (signed) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%m = OpVariable %_ptr_int Function\n" + + "%load_n = OpLoad %int %n\n" + + "%load_m = OpLoad %int %m\n" + + "%2 = OpUGreaterThanEqual %bool %load_n %load_m\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 18: Don't fold n || m + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_bool Function\n" + + "%m = OpVariable %_ptr_bool Function\n" + + "%load_n = OpLoad %bool %n\n" + + "%load_m = OpLoad %bool %m\n" + + "%2 = OpLogicalOr %bool %load_n %load_m\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 19: Don't fold n && m + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_bool Function\n" + + "%m = OpVariable %_ptr_bool Function\n" + + "%load_n = OpLoad %bool %n\n" + + "%load_m = OpLoad %bool %m\n" + + "%2 = OpLogicalAnd %bool %load_n %load_m\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 20: Don't fold n * 3 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load_n = OpLoad %int %n\n" + + "%2 = OpIMul %int %load_n %int_3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 21: Don't fold n / 3 (unsigned) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%load_n = OpLoad %uint %n\n" + + "%2 = OpUDiv %uint %load_n %uint_3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 22: Don't fold n / 3 (signed) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load_n = OpLoad %int %n\n" + + "%2 = OpSDiv %int %load_n %int_3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 23: Don't fold n remainder 3 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load_n = OpLoad %int %n\n" + + "%2 = OpSRem %int %load_n %int_3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 24: Don't fold n % 3 (signed) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load_n = OpLoad %int %n\n" + + "%2 = OpSMod %int %load_n %int_3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 25: Don't fold n % 3 (unsigned) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%load_n = OpLoad %uint %n\n" + + "%2 = OpUMod %int %load_n %int_3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 26: Don't fold n << 3 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%load_n = OpLoad %uint %n\n" + + "%2 = OpShiftRightLogical %int %load_n %int_3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 27: Don't fold n >> 3 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%load_n = OpLoad %uint %n\n" + + "%2 = OpShiftLeftLogical %int %load_n %int_3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 28: Don't fold n | 3 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%load_n = OpLoad %uint %n\n" + + "%2 = OpBitwiseOr %int %load_n %int_3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 29: Don't fold n & 3 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%load_n = OpLoad %uint %n\n" + + "%2 = OpBitwiseAnd %uint %load_n %uint_3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 30: Don't fold n < 3 (unsigned) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%load_n = OpLoad %uint %n\n" + + "%2 = OpULessThan %bool %load_n %uint_3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 31: Don't fold n > 3 (unsigned) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%load_n = OpLoad %uint %n\n" + + "%2 = OpUGreaterThan %bool %load_n %uint_3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 32: Don't fold n <= 3 (unsigned) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%load_n = OpLoad %uint %n\n" + + "%2 = OpULessThanEqual %bool %load_n %uint_3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 33: Don't fold n >= 3 (unsigned) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%load_n = OpLoad %uint %n\n" + + "%2 = OpUGreaterThanEqual %bool %load_n %uint_3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 34: Don't fold n < 3 (signed) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load_n = OpLoad %int %n\n" + + "%2 = OpULessThan %bool %load_n %int_3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 35: Don't fold n > 3 (signed) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load_n = OpLoad %int %n\n" + + "%2 = OpUGreaterThan %bool %load_n %int_3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 36: Don't fold n <= 3 (signed) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load_n = OpLoad %int %n\n" + + "%2 = OpULessThanEqual %bool %load_n %int_3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 37: Don't fold n >= 3 (signed) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load_n = OpLoad %int %n\n" + + "%2 = OpUGreaterThanEqual %bool %load_n %int_3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 38: Don't fold 0 + 3 (long), bad length + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpIAdd %long %long_0 %long_3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 39: Don't fold 0 + 3 (short), bad length + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpIAdd %short %short_0 %short_3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 40: fold 1*n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%3 = OpLoad %int %n\n" + + "%2 = OpIMul %int %int_1 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 41: fold n*1 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%3 = OpLoad %int %n\n" + + "%2 = OpIMul %int %3 %int_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3) +)); + +INSTANTIATE_TEST_CASE_P(CompositeExtractFoldingTest, GeneralInstructionFoldingTest, +::testing::Values( + // Test case 0: fold Insert feeding extract + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%2 = OpLoad %int %n\n" + + "%3 = OpCompositeInsert %v4int %2 %v4int_0_0_0_0 0\n" + + "%4 = OpCompositeInsert %v4int %int_1 %3 1\n" + + "%5 = OpCompositeInsert %v4int %int_1 %4 2\n" + + "%6 = OpCompositeInsert %v4int %int_1 %5 3\n" + + "%7 = OpCompositeExtract %int %6 0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 7, 2), + // Test case 1: fold Composite construct feeding extract (position 0) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%2 = OpLoad %int %n\n" + + "%3 = OpCompositeConstruct %v4int %2 %int_0 %int_0 %int_0\n" + + "%4 = OpCompositeExtract %int %3 0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, 2), + // Test case 2: fold Composite construct feeding extract (position 3) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%2 = OpLoad %int %n\n" + + "%3 = OpCompositeConstruct %v4int %2 %int_0 %int_0 %100\n" + + "%4 = OpCompositeExtract %int %3 3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, INT_0_ID), + // Test case 3: fold Composite construct with vectors feeding extract (scalar element) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%2 = OpLoad %int %n\n" + + "%3 = OpCompositeConstruct %v2int %2 %int_0\n" + + "%4 = OpCompositeConstruct %v4int %3 %int_0 %100\n" + + "%5 = OpCompositeExtract %int %4 3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 5, INT_0_ID), + // Test case 4: fold Composite construct with vectors feeding extract (start of vector element) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%2 = OpLoad %int %n\n" + + "%3 = OpCompositeConstruct %v2int %2 %int_0\n" + + "%4 = OpCompositeConstruct %v4int %3 %int_0 %100\n" + + "%5 = OpCompositeExtract %int %4 0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 5, 2), + // Test case 5: fold Composite construct with vectors feeding extract (middle of vector element) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%2 = OpLoad %int %n\n" + + "%3 = OpCompositeConstruct %v2int %int_0 %2\n" + + "%4 = OpCompositeConstruct %v4int %3 %int_0 %100\n" + + "%5 = OpCompositeExtract %int %4 1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 5, 2), + // Test case 6: fold Composite construct with multiple indices. + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%2 = OpLoad %int %n\n" + + "%3 = OpCompositeConstruct %v2int %int_0 %2\n" + + "%4 = OpCompositeConstruct %struct_v2int_int_int %3 %int_0 %100\n" + + "%5 = OpCompositeExtract %int %4 0 1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 5, 2), + // Test case 7: fold constant extract. + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpCompositeExtract %int %102 1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, INT_7_ID), + // Test case 8: constant struct has OpUndef + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpCompositeExtract %int %struct_undef_0_0 0 1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 9: Extracting a member of element inserted via Insert + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_struct_v2int_int_int Function\n" + + "%2 = OpLoad %struct_v2int_int_int %n\n" + + "%3 = OpCompositeInsert %struct_v2int_int_int %102 %2 0\n" + + "%4 = OpCompositeExtract %int %3 0 1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, 103), + // Test case 10: Extracting a element that is partially changed by Insert. (Don't fold) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_struct_v2int_int_int Function\n" + + "%2 = OpLoad %struct_v2int_int_int %n\n" + + "%3 = OpCompositeInsert %struct_v2int_int_int %int_0 %2 0 1\n" + + "%4 = OpCompositeExtract %v2int %3 0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, 0), + // Test case 11: Extracting from result of vector shuffle (first input) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_v2int Function\n" + + "%2 = OpLoad %v2int %n\n" + + "%3 = OpVectorShuffle %v2int %102 %2 3 0\n" + + "%4 = OpCompositeExtract %int %3 1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, INT_7_ID), + // Test case 12: Extracting from result of vector shuffle (second input) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_v2int Function\n" + + "%2 = OpLoad %v2int %n\n" + + "%3 = OpVectorShuffle %v2int %2 %102 2 0\n" + + "%4 = OpCompositeExtract %int %3 0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, INT_7_ID) +)); + +INSTANTIATE_TEST_CASE_P(CompositeConstructFoldingTest, GeneralInstructionFoldingTest, +::testing::Values( + // Test case 0: fold Extracts feeding construct + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpCopyObject %v4int %v4int_0_0_0_0\n" + + "%3 = OpCompositeExtract %int %2 0\n" + + "%4 = OpCompositeExtract %int %2 1\n" + + "%5 = OpCompositeExtract %int %2 2\n" + + "%6 = OpCompositeExtract %int %2 3\n" + + "%7 = OpCompositeConstruct %v4int %3 %4 %5 %6\n" + + "OpReturn\n" + + "OpFunctionEnd", + 7, 2), + // Test case 1: Don't fold Extracts feeding construct (Different source) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpCopyObject %v4int %v4int_0_0_0_0\n" + + "%3 = OpCompositeExtract %int %2 0\n" + + "%4 = OpCompositeExtract %int %2 1\n" + + "%5 = OpCompositeExtract %int %2 2\n" + + "%6 = OpCompositeExtract %int %v4int_0_0_0_0 3\n" + + "%7 = OpCompositeConstruct %v4int %3 %4 %5 %6\n" + + "OpReturn\n" + + "OpFunctionEnd", + 7, 0), + // Test case 2: Don't fold Extracts feeding construct (bad indices) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpCopyObject %v4int %v4int_0_0_0_0\n" + + "%3 = OpCompositeExtract %int %2 0\n" + + "%4 = OpCompositeExtract %int %2 0\n" + + "%5 = OpCompositeExtract %int %2 2\n" + + "%6 = OpCompositeExtract %int %2 3\n" + + "%7 = OpCompositeConstruct %v4int %3 %4 %5 %6\n" + + "OpReturn\n" + + "OpFunctionEnd", + 7, 0), + // Test case 3: Don't fold Extracts feeding construct (different type) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpCopyObject %struct_v2int_int_int %struct_v2int_int_int_null\n" + + "%3 = OpCompositeExtract %v2int %2 0\n" + + "%4 = OpCompositeExtract %int %2 1\n" + + "%5 = OpCompositeExtract %int %2 2\n" + + "%7 = OpCompositeConstruct %v4int %3 %4 %5\n" + + "OpReturn\n" + + "OpFunctionEnd", + 7, 0), + // Test case 4: Fold construct with constants to constant. + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpCompositeConstruct %v2int %103 %103\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, VEC2_0_ID) +)); + +INSTANTIATE_TEST_CASE_P(PhiFoldingTest, GeneralInstructionFoldingTest, +::testing::Values( + // Test case 0: Fold phi with the same values for all edges. + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + " OpBranchConditional %true %l1 %l2\n" + + "%l1 = OpLabel\n" + + " OpBranch %merge_lab\n" + + "%l2 = OpLabel\n" + + " OpBranch %merge_lab\n" + + "%merge_lab = OpLabel\n" + + "%2 = OpPhi %int %100 %l1 %100 %l2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, INT_0_ID), + // Test case 1: Fold phi in pass through loop. + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + " OpBranch %l1\n" + + "%l1 = OpLabel\n" + + "%2 = OpPhi %int %100 %main_lab %2 %l1\n" + + " OpBranchConditional %true %l1 %merge_lab\n" + + "%merge_lab = OpLabel\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, INT_0_ID), + // Test case 2: Don't Fold phi because of different values. + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + " OpBranch %l1\n" + + "%l1 = OpLabel\n" + + "%2 = OpPhi %int %int_0 %main_lab %int_3 %l1\n" + + " OpBranchConditional %true %l1 %merge_lab\n" + + "%merge_lab = OpLabel\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0) +)); + +INSTANTIATE_TEST_CASE_P(FloatRedundantFoldingTest, GeneralInstructionFoldingTest, + ::testing::Values( + // Test case 0: Don't fold n + 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_float Function\n" + + "%3 = OpLoad %float %n\n" + + "%2 = OpFAdd %float %3 %float_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 1: Don't fold n - 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_float Function\n" + + "%3 = OpLoad %float %n\n" + + "%2 = OpFSub %float %3 %float_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 2: Don't fold n * 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_float Function\n" + + "%3 = OpLoad %float %n\n" + + "%2 = OpFMul %float %3 %float_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 3: Fold n + 0.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_float Function\n" + + "%3 = OpLoad %float %n\n" + + "%2 = OpFAdd %float %3 %float_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 4: Fold 0.0 + n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_float Function\n" + + "%3 = OpLoad %float %n\n" + + "%2 = OpFAdd %float %float_0 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 5: Fold n - 0.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_float Function\n" + + "%3 = OpLoad %float %n\n" + + "%2 = OpFSub %float %3 %float_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 6: Fold n * 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_float Function\n" + + "%3 = OpLoad %float %n\n" + + "%2 = OpFMul %float %3 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 7: Fold 1.0 * n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_float Function\n" + + "%3 = OpLoad %float %n\n" + + "%2 = OpFMul %float %float_1 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 8: Fold n / 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_float Function\n" + + "%3 = OpLoad %float %n\n" + + "%2 = OpFDiv %float %3 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 9: Fold n * 0.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_float Function\n" + + "%3 = OpLoad %float %n\n" + + "%2 = OpFMul %float %3 %104\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, FLOAT_0_ID), + // Test case 10: Fold 0.0 * n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_float Function\n" + + "%3 = OpLoad %float %n\n" + + "%2 = OpFMul %float %104 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, FLOAT_0_ID), + // Test case 11: Fold 0.0 / n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_float Function\n" + + "%3 = OpLoad %float %n\n" + + "%2 = OpFDiv %float %104 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, FLOAT_0_ID), + // Test case 12: Don't fold mix(a, b, 2.0) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%a = OpVariable %_ptr_float Function\n" + + "%b = OpVariable %_ptr_float Function\n" + + "%3 = OpLoad %float %a\n" + + "%4 = OpLoad %float %b\n" + + "%2 = OpExtInst %float %1 FMix %3 %4 %float_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 13: Fold mix(a, b, 0.0) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%a = OpVariable %_ptr_float Function\n" + + "%b = OpVariable %_ptr_float Function\n" + + "%3 = OpLoad %float %a\n" + + "%4 = OpLoad %float %b\n" + + "%2 = OpExtInst %float %1 FMix %3 %4 %float_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 14: Fold mix(a, b, 1.0) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%a = OpVariable %_ptr_float Function\n" + + "%b = OpVariable %_ptr_float Function\n" + + "%3 = OpLoad %float %a\n" + + "%4 = OpLoad %float %b\n" + + "%2 = OpExtInst %float %1 FMix %3 %4 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 4), + // Test case 15: Fold vector fadd with null + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%a = OpVariable %_ptr_v2float Function\n" + + "%2 = OpLoad %v2float %a\n" + + "%3 = OpFAdd %v2float %2 %v2float_null\n" + + "OpReturn\n" + + "OpFunctionEnd", + 3, 2), + // Test case 16: Fold vector fadd with null + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%a = OpVariable %_ptr_v2float Function\n" + + "%2 = OpLoad %v2float %a\n" + + "%3 = OpFAdd %v2float %v2float_null %2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 3, 2), + // Test case 15: Fold vector fsub with null + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%a = OpVariable %_ptr_v2float Function\n" + + "%2 = OpLoad %v2float %a\n" + + "%3 = OpFSub %v2float %2 %v2float_null\n" + + "OpReturn\n" + + "OpFunctionEnd", + 3, 2), + // Test case 16: Fold 0.0(half) * n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_half Function\n" + + "%3 = OpLoad %half %n\n" + + "%2 = OpFMul %half %108 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, HALF_0_ID), + // Test case 17: Don't fold 1.0(half) * n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_half Function\n" + + "%3 = OpLoad %half %n\n" + + "%2 = OpFMul %half %half_1 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 18: Don't fold 1.0 * 1.0 (half) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpFMul %half %half_1 %half_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0) +)); + +INSTANTIATE_TEST_CASE_P(DoubleRedundantFoldingTest, GeneralInstructionFoldingTest, + ::testing::Values( + // Test case 0: Don't fold n + 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_double Function\n" + + "%3 = OpLoad %double %n\n" + + "%2 = OpFAdd %double %3 %double_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 1: Don't fold n - 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_double Function\n" + + "%3 = OpLoad %double %n\n" + + "%2 = OpFSub %double %3 %double_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 2: Don't fold n * 2.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_double Function\n" + + "%3 = OpLoad %double %n\n" + + "%2 = OpFMul %double %3 %double_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 3: Fold n + 0.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_double Function\n" + + "%3 = OpLoad %double %n\n" + + "%2 = OpFAdd %double %3 %double_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 4: Fold 0.0 + n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_double Function\n" + + "%3 = OpLoad %double %n\n" + + "%2 = OpFAdd %double %double_0 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 5: Fold n - 0.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_double Function\n" + + "%3 = OpLoad %double %n\n" + + "%2 = OpFSub %double %3 %double_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 6: Fold n * 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_double Function\n" + + "%3 = OpLoad %double %n\n" + + "%2 = OpFMul %double %3 %double_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 7: Fold 1.0 * n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_double Function\n" + + "%3 = OpLoad %double %n\n" + + "%2 = OpFMul %double %double_1 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 8: Fold n / 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_double Function\n" + + "%3 = OpLoad %double %n\n" + + "%2 = OpFDiv %double %3 %double_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 9: Fold n * 0.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_double Function\n" + + "%3 = OpLoad %double %n\n" + + "%2 = OpFMul %double %3 %105\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, DOUBLE_0_ID), + // Test case 10: Fold 0.0 * n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_double Function\n" + + "%3 = OpLoad %double %n\n" + + "%2 = OpFMul %double %105 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, DOUBLE_0_ID), + // Test case 11: Fold 0.0 / n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_double Function\n" + + "%3 = OpLoad %double %n\n" + + "%2 = OpFDiv %double %105 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, DOUBLE_0_ID), + // Test case 12: Don't fold mix(a, b, 2.0) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%a = OpVariable %_ptr_double Function\n" + + "%b = OpVariable %_ptr_double Function\n" + + "%3 = OpLoad %double %a\n" + + "%4 = OpLoad %double %b\n" + + "%2 = OpExtInst %double %1 FMix %3 %4 %double_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 13: Fold mix(a, b, 0.0) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%a = OpVariable %_ptr_double Function\n" + + "%b = OpVariable %_ptr_double Function\n" + + "%3 = OpLoad %double %a\n" + + "%4 = OpLoad %double %b\n" + + "%2 = OpExtInst %double %1 FMix %3 %4 %double_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 14: Fold mix(a, b, 1.0) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%a = OpVariable %_ptr_double Function\n" + + "%b = OpVariable %_ptr_double Function\n" + + "%3 = OpLoad %double %a\n" + + "%4 = OpLoad %double %b\n" + + "%2 = OpExtInst %double %1 FMix %3 %4 %double_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 4) +)); + +INSTANTIATE_TEST_CASE_P(FloatVectorRedundantFoldingTest, GeneralInstructionFoldingTest, + ::testing::Values( + // Test case 0: Don't fold a * vec4(0.0, 0.0, 0.0, 1.0) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_v4float Function\n" + + "%3 = OpLoad %v4float %n\n" + + "%2 = OpFMul %v4float %3 %v4float_0_0_0_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 1: Fold a * vec4(0.0, 0.0, 0.0, 0.0) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_v4float Function\n" + + "%3 = OpLoad %v4float %n\n" + + "%2 = OpFMul %v4float %3 %106\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, VEC4_0_ID), + // Test case 2: Fold a * vec4(1.0, 1.0, 1.0, 1.0) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_v4float Function\n" + + "%3 = OpLoad %v4float %n\n" + + "%2 = OpFMul %v4float %3 %v4float_1_1_1_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3) +)); + +INSTANTIATE_TEST_CASE_P(DoubleVectorRedundantFoldingTest, GeneralInstructionFoldingTest, + ::testing::Values( + // Test case 0: Don't fold a * vec4(0.0, 0.0, 0.0, 1.0) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_v4double Function\n" + + "%3 = OpLoad %v4double %n\n" + + "%2 = OpFMul %v4double %3 %v4double_0_0_0_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 1: Fold a * vec4(0.0, 0.0, 0.0, 0.0) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_v4double Function\n" + + "%3 = OpLoad %v4double %n\n" + + "%2 = OpFMul %v4double %3 %106\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, DVEC4_0_ID), + // Test case 2: Fold a * vec4(1.0, 1.0, 1.0, 1.0) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_v4double Function\n" + + "%3 = OpLoad %v4double %n\n" + + "%2 = OpFMul %v4double %3 %v4double_1_1_1_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3) +)); + +INSTANTIATE_TEST_CASE_P(FToIConstantFoldingTest, IntegerInstructionFoldingTest, + ::testing::Values( + // Test case 0: Fold int(3.0) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpConvertFToS %int %float_3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 1: Fold uint(3.0) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpConvertFToU %int %float_3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3) +)); + +INSTANTIATE_TEST_CASE_P(IToFConstantFoldingTest, FloatInstructionFoldingTest, + ::testing::Values( + // Test case 0: Fold float(3) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpConvertSToF %float %int_3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3.0), + // Test case 1: Fold float(3u) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpConvertUToF %float %uint_3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3.0) +)); +// clang-format on + +using ToNegateFoldingTest = + ::testing::TestWithParam>; + +TEST_P(ToNegateFoldingTest, Case) { + const auto& tc = GetParam(); + + // Build module. + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.test_body, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ASSERT_NE(nullptr, context); + + // Fold the instruction to test. + opt::analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); + ir::Instruction* inst = def_use_mgr->GetDef(tc.id_to_fold); + std::unique_ptr original_inst(inst->Clone(context.get())); + bool succeeded = opt::FoldInstruction(inst); + + // Make sure the instruction folded as expected. + EXPECT_EQ(inst->result_id(), original_inst->result_id()); + EXPECT_EQ(inst->type_id(), original_inst->type_id()); + EXPECT_TRUE((!succeeded) == (tc.expected_result == 0)); + if (succeeded) { + EXPECT_EQ(inst->opcode(), SpvOpFNegate); + EXPECT_EQ(inst->GetSingleWordInOperand(0), tc.expected_result); + } else { + EXPECT_EQ(inst->NumInOperands(), original_inst->NumInOperands()); + for (uint32_t i = 0; i < inst->NumInOperands(); ++i) { + EXPECT_EQ(inst->GetOperand(i), original_inst->GetOperand(i)); + } + } +} + +// clang-format off +INSTANTIATE_TEST_CASE_P(FloatRedundantSubFoldingTest, ToNegateFoldingTest, + ::testing::Values( + // Test case 0: Don't fold 1.0 - n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_float Function\n" + + "%3 = OpLoad %float %n\n" + + "%2 = OpFSub %float %float_1 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 1: Fold 0.0 - n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_float Function\n" + + "%3 = OpLoad %float %n\n" + + "%2 = OpFSub %float %float_0 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 2: Don't fold (0,0,0,1) - n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_v4float Function\n" + + "%3 = OpLoad %v4float %n\n" + + "%2 = OpFSub %v4float %v4float_0_0_0_1 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 3: Fold (0,0,0,0) - n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_v4float Function\n" + + "%3 = OpLoad %v4float %n\n" + + "%2 = OpFSub %v4float %v4float_0_0_0_0 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3) +)); + +INSTANTIATE_TEST_CASE_P(DoubleRedundantSubFoldingTest, ToNegateFoldingTest, + ::testing::Values( + // Test case 0: Don't fold 1.0 - n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_double Function\n" + + "%3 = OpLoad %double %n\n" + + "%2 = OpFSub %double %double_1 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 1: Fold 0.0 - n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_double Function\n" + + "%3 = OpLoad %double %n\n" + + "%2 = OpFSub %double %double_0 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 2: Don't fold (0,0,0,1) - n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_v4double Function\n" + + "%3 = OpLoad %v4double %n\n" + + "%2 = OpFSub %v4double %v4double_0_0_0_1 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 0), + // Test case 3: Fold (0,0,0,0) - n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_v4double Function\n" + + "%3 = OpLoad %v4double %n\n" + + "%2 = OpFSub %v4double %v4double_0_0_0_0 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3) +)); + +#ifdef SPIRV_EFFCEE +using MatchingInstructionFoldingTest = + ::testing::TestWithParam>; + +TEST_P(MatchingInstructionFoldingTest, Case) { + const auto& tc = GetParam(); + + // Build module. + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.test_body, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ASSERT_NE(nullptr, context); + + // Fold the instruction to test. + opt::analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); + ir::Instruction* inst = def_use_mgr->GetDef(tc.id_to_fold); + std::unique_ptr original_inst(inst->Clone(context.get())); + bool succeeded = opt::FoldInstruction(inst); + EXPECT_EQ(succeeded, tc.expected_result); + if (succeeded) { + Match(tc.test_body, context.get()); + } +} + +INSTANTIATE_TEST_CASE_P(MergeNegateTest, MatchingInstructionFoldingTest, +::testing::Values( + // Test case 0: fold consecutive fnegate + // -(-x) = x + InstructionFoldingCase( + Header() + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float:%\\w+]]\n" + + "; CHECK: %4 = OpCopyObject [[float]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFNegate %float %2\n" + + "%4 = OpFNegate %float %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, true), + // Test case 1: fold fnegate(fmul with const). + // -(x * 2.0) = x * -2.0 + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_n2:%\\w+]] = OpConstant [[float]] -2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFMul [[float]] [[ld]] [[float_n2]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFMul %float %2 %float_2\n" + + "%4 = OpFNegate %float %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, true), + // Test case 2: fold fnegate(fmul with const). + // -(2.0 * x) = x * 2.0 + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_n2:%\\w+]] = OpConstant [[float]] -2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFMul [[float]] [[ld]] [[float_n2]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFMul %float %float_2 %2\n" + + "%4 = OpFNegate %float %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, true), + // Test case 3: fold fnegate(fdiv with const). + // -(x / 2.0) = x * -0.5 + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_n0p5:%\\w+]] = OpConstant [[float]] -0.5\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFMul [[float]] [[ld]] [[float_n0p5]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFDiv %float %2 %float_2\n" + + "%4 = OpFNegate %float %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, true), + // Test case 4: fold fnegate(fdiv with const). + // -(2.0 / x) = -2.0 / x + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_n2:%\\w+]] = OpConstant [[float]] -2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFDiv [[float]] [[float_n2]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFDiv %float %float_2 %2\n" + + "%4 = OpFNegate %float %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, true), + // Test case 5: fold fnegate(fadd with const). + // -(2.0 + x) = -2.0 - x + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_n2:%\\w+]] = OpConstant [[float]] -2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFSub [[float]] [[float_n2]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFAdd %float %float_2 %2\n" + + "%4 = OpFNegate %float %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, true), + // Test case 6: fold fnegate(fadd with const). + // -(x + 2.0) = -2.0 - x + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_n2:%\\w+]] = OpConstant [[float]] -2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFSub [[float]] [[float_n2]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFAdd %float %2 %float_2\n" + + "%4 = OpFNegate %float %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, true), + // Test case 7: fold fnegate(fsub with const). + // -(2.0 - x) = x - 2.0 + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_2:%\\w+]] = OpConstant [[float]] 2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFSub [[float]] [[ld]] [[float_2]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFSub %float %float_2 %2\n" + + "%4 = OpFNegate %float %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, true), + // Test case 8: fold fnegate(fsub with const). + // -(x - 2.0) = 2.0 - x + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_2:%\\w+]] = OpConstant [[float]] 2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFSub [[float]] [[float_2]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFSub %float %2 %float_2\n" + + "%4 = OpFNegate %float %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, true), + // Test case 9: fold consecutive snegate + // -(-x) = x + InstructionFoldingCase( + Header() + + "; CHECK: [[ld:%\\w+]] = OpLoad [[int:%\\w+]]\n" + + "; CHECK: %4 = OpCopyObject [[int]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_int Function\n" + + "%2 = OpLoad %int %var\n" + + "%3 = OpSNegate %int %2\n" + + "%4 = OpSNegate %int %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, true), + // Test case 10: fold consecutive vector negate + // -(-x) = x + InstructionFoldingCase( + Header() + + "; CHECK: [[ld:%\\w+]] = OpLoad [[v2float:%\\w+]]\n" + + "; CHECK: %4 = OpCopyObject [[v2float]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_v2float Function\n" + + "%2 = OpLoad %v2float %var\n" + + "%3 = OpFNegate %v2float %2\n" + + "%4 = OpFNegate %v2float %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, true), + // Test case 11: fold snegate(iadd with const). + // -(2 + x) = -2 - x + InstructionFoldingCase( + Header() + + "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" + + "; CHECK: OpConstant [[int]] -2147483648\n" + + "; CHECK: [[int_n2:%\\w+]] = OpConstant [[int]] -2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[int]]\n" + + "; CHECK: %4 = OpISub [[int]] [[int_n2]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_int Function\n" + + "%2 = OpLoad %int %var\n" + + "%3 = OpIAdd %int %int_2 %2\n" + + "%4 = OpSNegate %int %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, true), + // Test case 12: fold snegate(iadd with const). + // -(x + 2) = -2 - x + InstructionFoldingCase( + Header() + + "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" + + "; CHECK: OpConstant [[int]] -2147483648\n" + + "; CHECK: [[int_n2:%\\w+]] = OpConstant [[int]] -2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[int]]\n" + + "; CHECK: %4 = OpISub [[int]] [[int_n2]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_int Function\n" + + "%2 = OpLoad %int %var\n" + + "%3 = OpIAdd %int %2 %int_2\n" + + "%4 = OpSNegate %int %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, true), + // Test case 13: fold snegate(isub with const). + // -(2 - x) = x - 2 + InstructionFoldingCase( + Header() + + "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" + + "; CHECK: [[int_2:%\\w+]] = OpConstant [[int]] 2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[int]]\n" + + "; CHECK: %4 = OpISub [[int]] [[ld]] [[int_2]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_int Function\n" + + "%2 = OpLoad %int %var\n" + + "%3 = OpISub %int %int_2 %2\n" + + "%4 = OpSNegate %int %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, true), + // Test case 14: fold snegate(isub with const). + // -(x - 2) = 2 - x + InstructionFoldingCase( + Header() + + "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" + + "; CHECK: [[int_2:%\\w+]] = OpConstant [[int]] 2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[int]]\n" + + "; CHECK: %4 = OpISub [[int]] [[int_2]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_int Function\n" + + "%2 = OpLoad %int %var\n" + + "%3 = OpISub %int %2 %int_2\n" + + "%4 = OpSNegate %int %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, true), + // Test case 15: fold snegate(iadd with const). + // -(x + 2) = -2 - x + InstructionFoldingCase( + Header() + + "; CHECK: [[long:%\\w+]] = OpTypeInt 64 1\n" + + "; CHECK: [[long_n2:%\\w+]] = OpConstant [[long]] -2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[long]]\n" + + "; CHECK: %4 = OpISub [[long]] [[long_n2]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_long Function\n" + + "%2 = OpLoad %long %var\n" + + "%3 = OpIAdd %long %2 %long_2\n" + + "%4 = OpSNegate %long %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, true), + // Test case 16: fold snegate(isub with const). + // -(2 - x) = x - 2 + InstructionFoldingCase( + Header() + + "; CHECK: [[long:%\\w+]] = OpTypeInt 64 1\n" + + "; CHECK: [[long_2:%\\w+]] = OpConstant [[long]] 2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[long]]\n" + + "; CHECK: %4 = OpISub [[long]] [[ld]] [[long_2]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_long Function\n" + + "%2 = OpLoad %long %var\n" + + "%3 = OpISub %long %long_2 %2\n" + + "%4 = OpSNegate %long %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, true), + // Test case 17: fold snegate(isub with const). + // -(x - 2) = 2 - x + InstructionFoldingCase( + Header() + + "; CHECK: [[long:%\\w+]] = OpTypeInt 64 1\n" + + "; CHECK: [[long_2:%\\w+]] = OpConstant [[long]] 2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[long]]\n" + + "; CHECK: %4 = OpISub [[long]] [[long_2]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_long Function\n" + + "%2 = OpLoad %long %var\n" + + "%3 = OpISub %long %2 %long_2\n" + + "%4 = OpSNegate %long %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, true) +)); + +INSTANTIATE_TEST_CASE_P(ReciprocalFDivTest, MatchingInstructionFoldingTest, +::testing::Values( + // Test case 0: scalar reicprocal + // x / 0.5 = x * 2.0 + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_2:%\\w+]] = OpConstant [[float]] 2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %3 = OpFMul [[float]] [[ld]] [[float_2]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFDiv %float %2 %float_0p5\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 3, true), + // Test case 1: Unfoldable + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_0:%\\w+]] = OpConstant [[float]] 0\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %3 = OpFDiv [[float]] [[ld]] [[float_0]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFDiv %float %2 %104\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 3, false), + // Test case 2: Vector reciprocal + // x / {2.0, 0.5} = x * {0.5, 2.0} + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[v2float:%\\w+]] = OpTypeVector [[float]] 2\n" + + "; CHECK: [[float_2:%\\w+]] = OpConstant [[float]] 2\n" + + "; CHECK: [[float_0p5:%\\w+]] = OpConstant [[float]] 0.5\n" + + "; CHECK: [[v2float_0p5_2:%\\w+]] = OpConstantComposite [[v2float]] [[float_0p5]] [[float_2]]\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[v2float]]\n" + + "; CHECK: %3 = OpFMul [[v2float]] [[ld]] [[v2float_0p5_2]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_v2float Function\n" + + "%2 = OpLoad %v2float %var\n" + + "%3 = OpFDiv %v2float %2 %v2float_2_0p5\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 3, true), + // Test case 3: double reciprocal + // x / 2.0 = x * 0.5 + InstructionFoldingCase( + Header() + + "; CHECK: [[double:%\\w+]] = OpTypeFloat 64\n" + + "; CHECK: [[double_0p5:%\\w+]] = OpConstant [[double]] 0.5\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[double]]\n" + + "; CHECK: %3 = OpFMul [[double]] [[ld]] [[double_0p5]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_double Function\n" + + "%2 = OpLoad %double %var\n" + + "%3 = OpFDiv %double %2 %double_2\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 3, true), + // Test case 4: don't fold x / 0. + InstructionFoldingCase( + Header() + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_v2float Function\n" + + "%2 = OpLoad %v2float %var\n" + + "%3 = OpFDiv %v2float %2 %v2float_null\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 3, false) +)); + +INSTANTIATE_TEST_CASE_P(MergeMulTest, MatchingInstructionFoldingTest, +::testing::Values( + // Test case 0: fold consecutive fmuls + // (x * 3.0) * 2.0 = x * 6.0 + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_6:%\\w+]] = OpConstant [[float]] 6\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFMul [[float]] [[ld]] [[float_6]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFMul %float %2 %float_3\n" + + "%4 = OpFMul %float %3 %float_2\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 1: fold consecutive fmuls + // 2.0 * (x * 3.0) = x * 6.0 + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_6:%\\w+]] = OpConstant [[float]] 6\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFMul [[float]] [[ld]] [[float_6]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFMul %float %2 %float_3\n" + + "%4 = OpFMul %float %float_2 %3\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 2: fold consecutive fmuls + // (3.0 * x) * 2.0 = x * 6.0 + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_6:%\\w+]] = OpConstant [[float]] 6\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFMul [[float]] [[ld]] [[float_6]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFMul %float %float_3 %2\n" + + "%4 = OpFMul %float %float_2 %3\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 3: fold vector fmul + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[v2float:%\\w+]] = OpTypeVector [[float]] 2\n" + + "; CHECK: [[float_6:%\\w+]] = OpConstant [[float]] 6\n" + + "; CHECK: [[v2float_6_6:%\\w+]] = OpConstantComposite [[v2float]] [[float_6]] [[float_6]]\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[v2float]]\n" + + "; CHECK: %4 = OpFMul [[v2float]] [[ld]] [[v2float_6_6]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_v2float Function\n" + + "%2 = OpLoad %v2float %var\n" + + "%3 = OpFMul %v2float %2 %v2float_2_3\n" + + "%4 = OpFMul %v2float %3 %v2float_3_2\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 4: fold double fmuls + // (x * 3.0) * 2.0 = x * 6.0 + InstructionFoldingCase( + Header() + + "; CHECK: [[double:%\\w+]] = OpTypeFloat 64\n" + + "; CHECK: [[double_6:%\\w+]] = OpConstant [[double]] 6\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[double]]\n" + + "; CHECK: %4 = OpFMul [[double]] [[ld]] [[double_6]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_double Function\n" + + "%2 = OpLoad %double %var\n" + + "%3 = OpFMul %double %2 %double_3\n" + + "%4 = OpFMul %double %3 %double_2\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 5: fold 32 bit imuls + // (x * 3) * 2 = x * 6 + InstructionFoldingCase( + Header() + + "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" + + "; CHECK: [[int_6:%\\w+]] = OpConstant [[int]] 6\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[int]]\n" + + "; CHECK: %4 = OpIMul [[int]] [[ld]] [[int_6]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_int Function\n" + + "%2 = OpLoad %int %var\n" + + "%3 = OpIMul %int %2 %int_3\n" + + "%4 = OpIMul %int %3 %int_2\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 6: fold 64 bit imuls + // (x * 3) * 2 = x * 6 + InstructionFoldingCase( + Header() + + "; CHECK: [[long:%\\w+]] = OpTypeInt 64\n" + + "; CHECK: [[long_6:%\\w+]] = OpConstant [[long]] 6\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[long]]\n" + + "; CHECK: %4 = OpIMul [[long]] [[ld]] [[long_6]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_long Function\n" + + "%2 = OpLoad %long %var\n" + + "%3 = OpIMul %long %2 %long_3\n" + + "%4 = OpIMul %long %3 %long_2\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 7: merge vector integer mults + InstructionFoldingCase( + Header() + + "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" + + "; CHECK: [[v2int:%\\w+]] = OpTypeVector [[int]] 2\n" + + "; CHECK: [[int_6:%\\w+]] = OpConstant [[int]] 6\n" + + "; CHECK: [[v2int_6_6:%\\w+]] = OpConstantComposite [[v2int]] [[int_6]] [[int_6]]\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[v2int]]\n" + + "; CHECK: %4 = OpIMul [[v2int]] [[ld]] [[v2int_6_6]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_v2int Function\n" + + "%2 = OpLoad %v2int %var\n" + + "%3 = OpIMul %v2int %2 %v2int_2_3\n" + + "%4 = OpIMul %v2int %3 %v2int_3_2\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 8: merge fmul of fdiv + // 2.0 * (2.0 / x) = 4.0 / x + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_4:%\\w+]] = OpConstant [[float]] 4\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFDiv [[float]] [[float_4]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFDiv %float %float_2 %2\n" + + "%4 = OpFMul %float %float_2 %3\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 9: merge fmul of fdiv + // (2.0 / x) * 2.0 = 4.0 / x + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_4:%\\w+]] = OpConstant [[float]] 4\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFDiv [[float]] [[float_4]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFDiv %float %float_2 %2\n" + + "%4 = OpFMul %float %3 %float_2\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 10: Do not merge imul of sdiv + // 4 * (x / 2) + InstructionFoldingCase( + Header() + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_int Function\n" + + "%2 = OpLoad %int %var\n" + + "%3 = OpSDiv %int %2 %int_2\n" + + "%4 = OpIMul %int %int_4 %3\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, false), + // Test case 11: Do not merge imul of sdiv + // (x / 2) * 4 + InstructionFoldingCase( + Header() + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_int Function\n" + + "%2 = OpLoad %int %var\n" + + "%3 = OpSDiv %int %2 %int_2\n" + + "%4 = OpIMul %int %3 %int_4\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, false), + // Test case 12: Do not merge imul of udiv + // 4 * (x / 2) + InstructionFoldingCase( + Header() + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_uint Function\n" + + "%2 = OpLoad %uint %var\n" + + "%3 = OpUDiv %uint %2 %uint_2\n" + + "%4 = OpIMul %uint %uint_4 %3\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, false), + // Test case 13: Do not merge imul of udiv + // (x / 2) * 4 + InstructionFoldingCase( + Header() + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_uint Function\n" + + "%2 = OpLoad %uint %var\n" + + "%3 = OpUDiv %uint %2 %uint_2\n" + + "%4 = OpIMul %uint %3 %uint_4\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, false), + // Test case 14: Don't fold + // (x / 3) * 4 + InstructionFoldingCase( + Header() + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_uint Function\n" + + "%2 = OpLoad %uint %var\n" + + "%3 = OpUDiv %uint %2 %uint_3\n" + + "%4 = OpIMul %uint %3 %uint_4\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, false), + // Test case 15: merge vector fmul of fdiv + // (x / {2,2}) * {4,4} = x * {2,2} + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[v2float:%\\w+]] = OpTypeVector [[float]] 2\n" + + "; CHECK: [[float_2:%\\w+]] = OpConstant [[float]] 2\n" + + "; CHECK: [[v2float_2_2:%\\w+]] = OpConstantComposite [[v2float]] [[float_2]] [[float_2]]\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[v2float]]\n" + + "; CHECK: %4 = OpFMul [[v2float]] [[ld]] [[v2float_2_2]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_v2float Function\n" + + "%2 = OpLoad %v2float %var\n" + + "%3 = OpFDiv %v2float %2 %v2float_2_2\n" + + "%4 = OpFMul %v2float %3 %v2float_4_4\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 16: merge vector imul of snegate + // (-x) * {2,2} = x * {-2,-2} + InstructionFoldingCase( + Header() + + "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" + + "; CHECK: [[v2int:%\\w+]] = OpTypeVector [[int]] 2\n" + + "; CHECK: OpConstant [[int]] -2147483648\n" + + "; CHECK: [[int_n2:%\\w+]] = OpConstant [[int]] -2\n" + + "; CHECK: [[v2int_n2_n2:%\\w+]] = OpConstantComposite [[v2int]] [[int_n2]] [[int_n2]]\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[v2int]]\n" + + "; CHECK: %4 = OpIMul [[v2int]] [[ld]] [[v2int_n2_n2]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_v2int Function\n" + + "%2 = OpLoad %v2int %var\n" + + "%3 = OpSNegate %v2int %2\n" + + "%4 = OpIMul %v2int %3 %v2int_2_2\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 17: merge vector imul of snegate + // {2,2} * (-x) = x * {-2,-2} + InstructionFoldingCase( + Header() + + "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" + + "; CHECK: [[v2int:%\\w+]] = OpTypeVector [[int]] 2\n" + + "; CHECK: OpConstant [[int]] -2147483648\n" + + "; CHECK: [[int_n2:%\\w+]] = OpConstant [[int]] -2\n" + + "; CHECK: [[v2int_n2_n2:%\\w+]] = OpConstantComposite [[v2int]] [[int_n2]] [[int_n2]]\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[v2int]]\n" + + "; CHECK: %4 = OpIMul [[v2int]] [[ld]] [[v2int_n2_n2]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_v2int Function\n" + + "%2 = OpLoad %v2int %var\n" + + "%3 = OpSNegate %v2int %2\n" + + "%4 = OpIMul %v2int %v2int_2_2 %3\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true) +)); + +INSTANTIATE_TEST_CASE_P(MergeDivTest, MatchingInstructionFoldingTest, +::testing::Values( + // Test case 0: merge consecutive fdiv + // 4.0 / (2.0 / x) = 2.0 * x + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_2:%\\w+]] = OpConstant [[float]] 2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFMul [[float]] [[float_2]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFDiv %float %float_2 %2\n" + + "%4 = OpFDiv %float %float_4 %3\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 1: merge consecutive fdiv + // 4.0 / (x / 2.0) = 8.0 / x + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_8:%\\w+]] = OpConstant [[float]] 8\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFDiv [[float]] [[float_8]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFDiv %float %2 %float_2\n" + + "%4 = OpFDiv %float %float_4 %3\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 2: merge consecutive fdiv + // (4.0 / x) / 2.0 = 2.0 / x + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_2:%\\w+]] = OpConstant [[float]] 2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFDiv [[float]] [[float_2]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFDiv %float %float_4 %2\n" + + "%4 = OpFDiv %float %3 %float_2\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 3: Do not merge consecutive sdiv + // 4 / (2 / x) + InstructionFoldingCase( + Header() + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_int Function\n" + + "%2 = OpLoad %int %var\n" + + "%3 = OpSDiv %int %int_2 %2\n" + + "%4 = OpSDiv %int %int_4 %3\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, false), + // Test case 4: Do not merge consecutive sdiv + // 4 / (x / 2) + InstructionFoldingCase( + Header() + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_int Function\n" + + "%2 = OpLoad %int %var\n" + + "%3 = OpSDiv %int %2 %int_2\n" + + "%4 = OpSDiv %int %int_4 %3\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, false), + // Test case 5: Do not merge consecutive sdiv + // (4 / x) / 2 + InstructionFoldingCase( + Header() + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_int Function\n" + + "%2 = OpLoad %int %var\n" + + "%3 = OpSDiv %int %int_4 %2\n" + + "%4 = OpSDiv %int %3 %int_2\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, false), + // Test case 6: Do not merge consecutive sdiv + // (x / 4) / 2 + InstructionFoldingCase( + Header() + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_int Function\n" + + "%2 = OpLoad %int %var\n" + + "%3 = OpSDiv %int %2 %int_4\n" + + "%4 = OpSDiv %int %3 %int_2\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, false), + // Test case 7: Do not merge sdiv of imul + // 4 / (2 * x) + InstructionFoldingCase( + Header() + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_int Function\n" + + "%2 = OpLoad %int %var\n" + + "%3 = OpIMul %int %int_2 %2\n" + + "%4 = OpSDiv %int %int_4 %3\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, false), + // Test case 8: Do not merge sdiv of imul + // 4 / (x * 2) + InstructionFoldingCase( + Header() + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_int Function\n" + + "%2 = OpLoad %int %var\n" + + "%3 = OpIMul %int %2 %int_2\n" + + "%4 = OpSDiv %int %int_4 %3\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, false), + // Test case 9: Do not merge sdiv of imul + // (4 * x) / 2 + InstructionFoldingCase( + Header() + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_int Function\n" + + "%2 = OpLoad %int %var\n" + + "%3 = OpIMul %int %int_4 %2\n" + + "%4 = OpSDiv %int %3 %int_2\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, false), + // Test case 10: Do not merge sdiv of imul + // (x * 4) / 2 + InstructionFoldingCase( + Header() + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_int Function\n" + + "%2 = OpLoad %int %var\n" + + "%3 = OpIMul %int %2 %int_4\n" + + "%4 = OpSDiv %int %3 %int_2\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, false), + // Test case 11: merge sdiv of snegate + // (-x) / 2 = x / -2 + InstructionFoldingCase( + Header() + + "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" + + "; CHECK: OpConstant [[int]] -2147483648\n" + + "; CHECK: [[int_n2:%\\w+]] = OpConstant [[int]] -2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[int]]\n" + + "; CHECK: %4 = OpSDiv [[int]] [[ld]] [[int_n2]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_int Function\n" + + "%2 = OpLoad %int %var\n" + + "%3 = OpSNegate %int %2\n" + + "%4 = OpSDiv %int %3 %int_2\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 12: merge sdiv of snegate + // 2 / (-x) = -2 / x + InstructionFoldingCase( + Header() + + "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" + + "; CHECK: OpConstant [[int]] -2147483648\n" + + "; CHECK: [[int_n2:%\\w+]] = OpConstant [[int]] -2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[int]]\n" + + "; CHECK: %4 = OpSDiv [[int]] [[int_n2]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_int Function\n" + + "%2 = OpLoad %int %var\n" + + "%3 = OpSNegate %int %2\n" + + "%4 = OpSDiv %int %int_2 %3\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 13: Don't merge + // (x / {null}) / {null} + InstructionFoldingCase( + Header() + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_v2float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFDiv %float %2 %v2float_null\n" + + "%4 = OpFDiv %float %3 %v2float_null\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, false) +)); + +INSTANTIATE_TEST_CASE_P(MergeAddTest, MatchingInstructionFoldingTest, +::testing::Values( + // Test case 0: merge add of negate + // (-x) + 2 = 2 - x + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_2:%\\w+]] = OpConstant [[float]] 2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFSub [[float]] [[float_2]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFNegate %float %2\n" + + "%4 = OpFAdd %float %3 %float_2\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 1: merge add of negate + // 2 + (-x) = 2 - x + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_2:%\\w+]] = OpConstant [[float]] 2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFSub [[float]] [[float_2]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpSNegate %float %2\n" + + "%4 = OpIAdd %float %float_2 %3\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 2: merge add of negate + // (-x) + 2 = 2 - x + InstructionFoldingCase( + Header() + + "; CHECK: [[long:%\\w+]] = OpTypeInt 64 1\n" + + "; CHECK: [[long_2:%\\w+]] = OpConstant [[long]] 2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[long]]\n" + + "; CHECK: %4 = OpISub [[long]] [[long_2]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_long Function\n" + + "%2 = OpLoad %long %var\n" + + "%3 = OpSNegate %long %2\n" + + "%4 = OpIAdd %long %3 %long_2\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 3: merge add of negate + // 2 + (-x) = 2 - x + InstructionFoldingCase( + Header() + + "; CHECK: [[long:%\\w+]] = OpTypeInt 64 1\n" + + "; CHECK: [[long_2:%\\w+]] = OpConstant [[long]] 2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[long]]\n" + + "; CHECK: %4 = OpISub [[long]] [[long_2]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_long Function\n" + + "%2 = OpLoad %long %var\n" + + "%3 = OpSNegate %long %2\n" + + "%4 = OpIAdd %long %long_2 %3\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 4: merge add of subtract + // (x - 1) + 2 = x + 1 + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_1:%\\w+]] = OpConstant [[float]] 1\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFAdd [[float]] [[ld]] [[float_1]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFSub %float %2 %float_1\n" + + "%4 = OpFAdd %float %3 %float_2\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 5: merge add of subtract + // (1 - x) + 2 = 3 - x + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_3:%\\w+]] = OpConstant [[float]] 3\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFSub [[float]] [[float_3]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFSub %float %float_1 %2\n" + + "%4 = OpFAdd %float %3 %float_2\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 6: merge add of subtract + // 2 + (x - 1) = x + 1 + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_1:%\\w+]] = OpConstant [[float]] 1\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFAdd [[float]] [[ld]] [[float_1]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFSub %float %2 %float_1\n" + + "%4 = OpFAdd %float %float_2 %3\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 7: merge add of subtract + // 2 + (1 - x) = 3 - x + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_3:%\\w+]] = OpConstant [[float]] 3\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFSub [[float]] [[float_3]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFSub %float %float_1 %2\n" + + "%4 = OpFAdd %float %float_2 %3\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 8: merge add of add + // (x + 1) + 2 = x + 3 + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_3:%\\w+]] = OpConstant [[float]] 3\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFAdd [[float]] [[ld]] [[float_3]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFAdd %float %2 %float_1\n" + + "%4 = OpFAdd %float %3 %float_2\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 9: merge add of add + // (1 + x) + 2 = 3 + x + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_3:%\\w+]] = OpConstant [[float]] 3\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFAdd [[float]] [[ld]] [[float_3]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFAdd %float %float_1 %2\n" + + "%4 = OpFAdd %float %3 %float_2\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 10: merge add of add + // 2 + (x + 1) = x + 1 + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_3:%\\w+]] = OpConstant [[float]] 3\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFAdd [[float]] [[ld]] [[float_3]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFAdd %float %2 %float_1\n" + + "%4 = OpFAdd %float %float_2 %3\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 11: merge add of add + // 2 + (1 + x) = 3 - x + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_3:%\\w+]] = OpConstant [[float]] 3\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFAdd [[float]] [[ld]] [[float_3]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFAdd %float %float_1 %2\n" + + "%4 = OpFAdd %float %float_2 %3\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true) +)); + +INSTANTIATE_TEST_CASE_P(MergeSubTest, MatchingInstructionFoldingTest, +::testing::Values( + // Test case 0: merge sub of negate + // (-x) - 2 = -2 - x + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_n2:%\\w+]] = OpConstant [[float]] -2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFSub [[float]] [[float_n2]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFNegate %float %2\n" + + "%4 = OpFSub %float %3 %float_2\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 1: merge sub of negate + // 2 - (-x) = x + 2 + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_2:%\\w+]] = OpConstant [[float]] 2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFAdd [[float]] [[ld]] [[float_2]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFNegate %float %2\n" + + "%4 = OpFSub %float %float_2 %3\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 2: merge sub of negate + // (-x) - 2 = -2 - x + InstructionFoldingCase( + Header() + + "; CHECK: [[long:%\\w+]] = OpTypeInt 64 1\n" + + "; CHECK: [[long_n2:%\\w+]] = OpConstant [[long]] -2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[long]]\n" + + "; CHECK: %4 = OpISub [[long]] [[long_n2]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_long Function\n" + + "%2 = OpLoad %long %var\n" + + "%3 = OpSNegate %long %2\n" + + "%4 = OpISub %long %3 %long_2\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 3: merge sub of negate + // 2 - (-x) = x + 2 + InstructionFoldingCase( + Header() + + "; CHECK: [[long:%\\w+]] = OpTypeInt 64 1\n" + + "; CHECK: [[long_2:%\\w+]] = OpConstant [[long]] 2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[long]]\n" + + "; CHECK: %4 = OpIAdd [[long]] [[ld]] [[long_2]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_long Function\n" + + "%2 = OpLoad %long %var\n" + + "%3 = OpSNegate %long %2\n" + + "%4 = OpISub %long %long_2 %3\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 4: merge add of subtract + // (x + 2) - 1 = x + 1 + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_1:%\\w+]] = OpConstant [[float]] 1\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFAdd [[float]] [[ld]] [[float_1]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFAdd %float %2 %float_2\n" + + "%4 = OpFSub %float %3 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 5: merge add of subtract + // (2 + x) - 1 = x + 1 + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_1:%\\w+]] = OpConstant [[float]] 1\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFAdd [[float]] [[ld]] [[float_1]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFAdd %float %float_2 %2\n" + + "%4 = OpFSub %float %3 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 6: merge add of subtract + // 2 - (x + 1) = 1 - x + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_1:%\\w+]] = OpConstant [[float]] 1\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFSub [[float]] [[float_1]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFAdd %float %2 %float_1\n" + + "%4 = OpFSub %float %float_2 %3\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 7: merge add of subtract + // 2 - (1 + x) = 1 - x + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_1:%\\w+]] = OpConstant [[float]] 1\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFSub [[float]] [[float_1]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFAdd %float %float_1 %2\n" + + "%4 = OpFSub %float %float_2 %3\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 8: merge subtract of subtract + // (x - 2) - 1 = x - 3 + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_3:%\\w+]] = OpConstant [[float]] 3\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFSub [[float]] [[ld]] [[float_3]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFSub %float %2 %float_2\n" + + "%4 = OpFSub %float %3 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 9: merge subtract of subtract + // (2 - x) - 1 = 1 - x + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_1:%\\w+]] = OpConstant [[float]] 1\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFSub [[float]] [[float_1]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFSub %float %float_2 %2\n" + + "%4 = OpFSub %float %3 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 10: merge subtract of subtract + // 2 - (x - 1) = 3 - x + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_3:%\\w+]] = OpConstant [[float]] 3\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFSub [[float]] [[float_3]] [[ld]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFSub %float %2 %float_1\n" + + "%4 = OpFSub %float %float_2 %3\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 11: merge subtract of subtract + // 1 - (2 - x) = x + (-1) + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_n1:%\\w+]] = OpConstant [[float]] -1\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFAdd [[float]] [[ld]] [[float_n1]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFSub %float %float_2 %2\n" + + "%4 = OpFSub %float %float_1 %3\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 12: merge subtract of subtract + // 2 - (1 - x) = x + 1 + InstructionFoldingCase( + Header() + + "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" + + "; CHECK: [[float_1:%\\w+]] = OpConstant [[float]] 1\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" + + "; CHECK: %4 = OpFAdd [[float]] [[ld]] [[float_1]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_float Function\n" + + "%2 = OpLoad %float %var\n" + + "%3 = OpFSub %float %float_1 %2\n" + + "%4 = OpFSub %float %float_2 %3\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true) +)); + +INSTANTIATE_TEST_CASE_P(SelectFoldingTest, MatchingInstructionFoldingTest, +::testing::Values( + // Test case 0: Fold select with the same values for both sides + InstructionFoldingCase( + Header() + + "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" + + "; CHECK: [[int0:%\\w+]] = OpConstant [[int]] 0\n" + + "; CHECK: %2 = OpCopyObject [[int]] [[int0]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_bool Function\n" + + "%load = OpLoad %bool %n\n" + + "%2 = OpSelect %int %load %100 %100\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 1: Fold select true to left side + InstructionFoldingCase( + Header() + + "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" + + "; CHECK: [[int0:%\\w+]] = OpConstant [[int]] 0\n" + + "; CHECK: %2 = OpCopyObject [[int]] [[int0]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load = OpLoad %bool %n\n" + + "%2 = OpSelect %int %true %100 %n\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 2: Fold select false to right side + InstructionFoldingCase( + Header() + + "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" + + "; CHECK: [[int0:%\\w+]] = OpConstant [[int]] 0\n" + + "; CHECK: %2 = OpCopyObject [[int]] [[int0]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load = OpLoad %bool %n\n" + + "%2 = OpSelect %int %false %n %100\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 3: Fold select null to right side + InstructionFoldingCase( + Header() + + "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" + + "; CHECK: [[int0:%\\w+]] = OpConstant [[int]] 0\n" + + "; CHECK: %2 = OpCopyObject [[int]] [[int0]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_int Function\n" + + "%load = OpLoad %int %n\n" + + "%2 = OpSelect %int %bool_null %load %100\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 4: vector null + InstructionFoldingCase( + Header() + + "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" + + "; CHECK: [[v2int:%\\w+]] = OpTypeVector [[int]] 2\n" + + "; CHECK: [[int2:%\\w+]] = OpConstant [[int]] 2\n" + + "; CHECK: [[v2int2_2:%\\w+]] = OpConstantComposite [[v2int]] [[int2]] [[int2]]\n" + + "; CHECK: %2 = OpCopyObject [[v2int]] [[v2int2_2]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_v2int Function\n" + + "%load = OpLoad %v2int %n\n" + + "%2 = OpSelect %v2int %v2bool_null %load %v2int_2_2\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, true), + // Test case 5: vector select + InstructionFoldingCase( + Header() + + "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" + + "; CHECK: [[v2int:%\\w+]] = OpTypeVector [[int]] 2\n" + + "; CHECK: %4 = OpVectorShuffle [[v2int]] %2 %3 0 3\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%m = OpVariable %_ptr_v2int Function\n" + + "%n = OpVariable %_ptr_v2int Function\n" + + "%2 = OpLoad %v2int %n\n" + + "%3 = OpLoad %v2int %n\n" + + "%4 = OpSelect %v2int %v2bool_true_false %2 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, true), + // Test case 6: vector select + InstructionFoldingCase( + Header() + + "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" + + "; CHECK: [[v2int:%\\w+]] = OpTypeVector [[int]] 2\n" + + "; CHECK: %4 = OpVectorShuffle [[v2int]] %2 %3 2 1\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%m = OpVariable %_ptr_v2int Function\n" + + "%n = OpVariable %_ptr_v2int Function\n" + + "%2 = OpLoad %v2int %n\n" + + "%3 = OpLoad %v2int %n\n" + + "%4 = OpSelect %v2int %v2bool_false_true %2 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, true) +)); + +INSTANTIATE_TEST_CASE_P(CompositeExtractMatchingTest, MatchingInstructionFoldingTest, +::testing::Values( + // Test case 0: Extracting from result of consecutive shuffles of differing + // size. + InstructionFoldingCase( + Header() + + "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" + + "; CHECK: %5 = OpCompositeExtract [[int]] %2 2\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_v4int Function\n" + + "%2 = OpLoad %v4int %n\n" + + "%3 = OpVectorShuffle %v2int %2 %2 2 3\n" + + "%4 = OpVectorShuffle %v4int %2 %3 0 4 2 5\n" + + "%5 = OpCompositeExtract %int %4 1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 5, true), + // Test case 1: Extracting from result of vector shuffle of differing + // input and result sizes. + InstructionFoldingCase( + Header() + + "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" + + "; CHECK: %4 = OpCompositeExtract [[int]] %2 2\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_v4int Function\n" + + "%2 = OpLoad %v4int %n\n" + + "%3 = OpVectorShuffle %v2int %2 %2 2 3\n" + + "%4 = OpCompositeExtract %int %3 0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, true), + // Test case 2: Extracting from result of vector shuffle of differing + // input and result sizes. + InstructionFoldingCase( + Header() + + "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" + + "; CHECK: %4 = OpCompositeExtract [[int]] %2 3\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_v4int Function\n" + + "%2 = OpLoad %v4int %n\n" + + "%3 = OpVectorShuffle %v2int %2 %2 2 3\n" + + "%4 = OpCompositeExtract %int %3 1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, true) +)); +#endif +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/freeze_spec_const_test.cpp b/3rdparty/spirv-tools/test/opt/freeze_spec_const_test.cpp new file mode 100644 index 000000000..e91730077 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/freeze_spec_const_test.cpp @@ -0,0 +1,128 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "pass_fixture.h" +#include "pass_utils.h" + +#include +#include +#include + +namespace { + +using namespace spvtools; + +struct FreezeSpecConstantValueTypeTestCase { + const char* type_decl; + const char* spec_const; + const char* expected_frozen_const; +}; + +using FreezeSpecConstantValueTypeTest = + PassTest<::testing::TestWithParam>; + +TEST_P(FreezeSpecConstantValueTypeTest, PrimaryType) { + auto& test_case = GetParam(); + std::vector text = {"OpCapability Shader", + "OpMemoryModel Logical GLSL450", + test_case.type_decl, test_case.spec_const}; + std::vector expected = { + "OpCapability Shader", "OpMemoryModel Logical GLSL450", + test_case.type_decl, test_case.expected_frozen_const}; + SinglePassRunAndCheck( + JoinAllInsts(text), JoinAllInsts(expected), /* skip_nop = */ false); +} + +// Test each primary type. +INSTANTIATE_TEST_CASE_P( + PrimaryTypeSpecConst, FreezeSpecConstantValueTypeTest, + ::testing::ValuesIn(std::vector({ + // Type declaration, original spec constant definition, expected frozen + // spec constants. + {"%int = OpTypeInt 32 1", "%2 = OpSpecConstant %int 1", + "%int_1 = OpConstant %int 1"}, + {"%uint = OpTypeInt 32 0", "%2 = OpSpecConstant %uint 1", + "%uint_1 = OpConstant %uint 1"}, + {"%float = OpTypeFloat 32", "%2 = OpSpecConstant %float 3.1415", + "%float_3_1415 = OpConstant %float 3.1415"}, + {"%double = OpTypeFloat 64", "%2 = OpSpecConstant %double 3.141592653", + "%double_3_141592653 = OpConstant %double 3.141592653"}, + {"%bool = OpTypeBool", "%2 = OpSpecConstantTrue %bool", + "%true = OpConstantTrue %bool"}, + {"%bool = OpTypeBool", "%2 = OpSpecConstantFalse %bool", + "%false = OpConstantFalse %bool"}, + }))); + +using FreezeSpecConstantValueRemoveDecorationTest = PassTest<::testing::Test>; + +TEST_F(FreezeSpecConstantValueRemoveDecorationTest, + RemoveDecorationInstWithSpecId) { + std::vector text = { + // clang-format off + "OpCapability Shader", + "OpCapability Float64", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Vertex %main \"main\"", + "OpSource GLSL 450", + "OpSourceExtension \"GL_GOOGLE_cpp_style_line_directive\"", + "OpSourceExtension \"GL_GOOGLE_include_directive\"", + "OpName %main \"main\"", + "OpDecorate %3 SpecId 200", + "OpDecorate %4 SpecId 201", + "OpDecorate %5 SpecId 202", + "OpDecorate %6 SpecId 203", + "%void = OpTypeVoid", + "%8 = OpTypeFunction %void", + "%int = OpTypeInt 32 1", + "%3 = OpSpecConstant %int 3", + "%float = OpTypeFloat 32", + "%4 = OpSpecConstant %float 3.1415", + "%double = OpTypeFloat 64", + "%5 = OpSpecConstant %double 3.14159265358979", + "%bool = OpTypeBool", + "%6 = OpSpecConstantTrue %bool", + "%13 = OpSpecConstantFalse %bool", + "%main = OpFunction %void None %8", + "%14 = OpLabel", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + std::string expected_disassembly = SelectiveJoin(text, [](const char* line) { + return std::string(line).find("SpecId") != std::string::npos; + }); + std::vector> replacement_pairs = { + {"%3 = OpSpecConstant %int 3", "%int_3 = OpConstant %int 3"}, + {"%4 = OpSpecConstant %float 3.1415", + "%float_3_1415 = OpConstant %float 3.1415"}, + {"%5 = OpSpecConstant %double 3.14159265358979", + "%double_3_14159265358979 = OpConstant %double 3.14159265358979"}, + {"%6 = OpSpecConstantTrue ", "%true = OpConstantTrue "}, + {"%13 = OpSpecConstantFalse ", "%false = OpConstantFalse "}, + }; + for (auto& p : replacement_pairs) { + EXPECT_TRUE(FindAndReplace(&expected_disassembly, p.first, p.second)) + << "text:\n" + << expected_disassembly << "\n" + << "find_str:\n" + << p.first << "\n" + << "replace_str:\n" + << p.second << "\n"; + } + SinglePassRunAndCheck( + JoinAllInsts(text), expected_disassembly, + /* skip_nop = */ true); +} +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/function_utils.h b/3rdparty/spirv-tools/test/opt/function_utils.h new file mode 100644 index 000000000..a392e319b --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/function_utils.h @@ -0,0 +1,54 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_TEST_OPT_FUNCTION_UTILS_H_ +#define LIBSPIRV_TEST_OPT_FUNCTION_UTILS_H_ + +#include "opt/function.h" +#include "opt/module.h" + +namespace spvtest { + +spvtools::ir::Function* GetFunction(spvtools::ir::Module* module, uint32_t id) { + for (spvtools::ir::Function& f : *module) { + if (f.result_id() == id) { + return &f; + } + } + return nullptr; +} + +const spvtools::ir::Function* GetFunction(const spvtools::ir::Module* module, + uint32_t id) { + for (const spvtools::ir::Function& f : *module) { + if (f.result_id() == id) { + return &f; + } + } + return nullptr; +} + +const spvtools::ir::BasicBlock* GetBasicBlock(const spvtools::ir::Function* fn, + uint32_t id) { + for (const spvtools::ir::BasicBlock& bb : *fn) { + if (bb.id() == id) { + return &bb; + } + } + return nullptr; +} + +} // namespace spvtest + +#endif // LIBSPIRV_TEST_OPT_FUNCTION_UTILS_H_ diff --git a/3rdparty/spirv-tools/test/opt/if_conversion_test.cpp b/3rdparty/spirv-tools/test/opt/if_conversion_test.cpp new file mode 100644 index 000000000..b31147101 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/if_conversion_test.cpp @@ -0,0 +1,332 @@ +// Copyright (c) 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "assembly_builder.h" +#include "gmock/gmock.h" +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; + +using IfConversionTest = PassTest<::testing::Test>; + +#ifdef SPIRV_EFFCEE +TEST_F(IfConversionTest, TestSimpleIfThenElse) { + const std::string text = R"( +; CHECK: OpSelectionMerge [[merge:%\w+]] +; CHECK: [[merge]] = OpLabel +; CHECK-NOT: OpPhi +; CHECK: [[sel:%\w+]] = OpSelect %uint %true %uint_0 %uint_1 +; CHECK OpStore {{%\w+}} [[sel]] +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %1 "func" %2 +%void = OpTypeVoid +%bool = OpTypeBool +%true = OpConstantTrue %bool +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%uint_1 = OpConstant %uint 1 +%_ptr_Output_uint = OpTypePointer Output %uint +%2 = OpVariable %_ptr_Output_uint Output +%11 = OpTypeFunction %void +%1 = OpFunction %void None %11 +%12 = OpLabel +OpSelectionMerge %14 None +OpBranchConditional %true %15 %16 +%15 = OpLabel +OpBranch %14 +%16 = OpLabel +OpBranch %14 +%14 = OpLabel +%18 = OpPhi %uint %uint_0 %15 %uint_1 %16 +OpStore %2 %18 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(IfConversionTest, TestSimpleHalfIfTrue) { + const std::string text = R"( +; CHECK: OpSelectionMerge [[merge:%\w+]] +; CHECK: [[merge]] = OpLabel +; CHECK-NOT: OpPhi +; CHECK: [[sel:%\w+]] = OpSelect %uint %true %uint_0 %uint_1 +; CHECK OpStore {{%\w+}} [[sel]] +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %1 "func" %2 +%void = OpTypeVoid +%bool = OpTypeBool +%true = OpConstantTrue %bool +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%uint_1 = OpConstant %uint 1 +%_ptr_Output_uint = OpTypePointer Output %uint +%2 = OpVariable %_ptr_Output_uint Output +%11 = OpTypeFunction %void +%1 = OpFunction %void None %11 +%12 = OpLabel +OpSelectionMerge %14 None +OpBranchConditional %true %15 %14 +%15 = OpLabel +OpBranch %14 +%14 = OpLabel +%18 = OpPhi %uint %uint_0 %15 %uint_1 %12 +OpStore %2 %18 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(IfConversionTest, TestSimpleHalfIfExtraBlock) { + const std::string text = R"( +; CHECK: OpSelectionMerge [[merge:%\w+]] +; CHECK: [[merge]] = OpLabel +; CHECK-NOT: OpPhi +; CHECK: [[sel:%\w+]] = OpSelect %uint %true %uint_0 %uint_1 +; CHECK OpStore {{%\w+}} [[sel]] +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %1 "func" %2 +%void = OpTypeVoid +%bool = OpTypeBool +%true = OpConstantTrue %bool +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%uint_1 = OpConstant %uint 1 +%_ptr_Output_uint = OpTypePointer Output %uint +%2 = OpVariable %_ptr_Output_uint Output +%11 = OpTypeFunction %void +%1 = OpFunction %void None %11 +%12 = OpLabel +OpSelectionMerge %14 None +OpBranchConditional %true %15 %14 +%15 = OpLabel +OpBranch %16 +%16 = OpLabel +OpBranch %14 +%14 = OpLabel +%18 = OpPhi %uint %uint_0 %15 %uint_1 %12 +OpStore %2 %18 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(IfConversionTest, TestSimpleHalfIfFalse) { + const std::string text = R"( +; CHECK: OpSelectionMerge [[merge:%\w+]] +; CHECK: [[merge]] = OpLabel +; CHECK-NOT: OpPhi +; CHECK: [[sel:%\w+]] = OpSelect %uint %true %uint_0 %uint_1 +; CHECK OpStore {{%\w+}} [[sel]] +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %1 "func" %2 +%void = OpTypeVoid +%bool = OpTypeBool +%true = OpConstantTrue %bool +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%uint_1 = OpConstant %uint 1 +%_ptr_Output_uint = OpTypePointer Output %uint +%2 = OpVariable %_ptr_Output_uint Output +%11 = OpTypeFunction %void +%1 = OpFunction %void None %11 +%12 = OpLabel +OpSelectionMerge %14 None +OpBranchConditional %true %14 %15 +%15 = OpLabel +OpBranch %14 +%14 = OpLabel +%18 = OpPhi %uint %uint_0 %12 %uint_1 %15 +OpStore %2 %18 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(IfConversionTest, TestVectorSplat) { + const std::string text = R"( +; CHECK: [[bool_vec:%\w+]] = OpTypeVector %bool 2 +; CHECK: OpSelectionMerge [[merge:%\w+]] +; CHECK: [[merge]] = OpLabel +; CHECK-NOT: OpPhi +; CHECK: [[comp:%\w+]] = OpCompositeConstruct [[bool_vec]] %true %true +; CHECK: [[sel:%\w+]] = OpSelect {{%\w+}} [[comp]] +; CHECK OpStore {{%\w+}} [[sel]] +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %1 "func" %2 +%void = OpTypeVoid +%bool = OpTypeBool +%true = OpConstantTrue %bool +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%uint_1 = OpConstant %uint 1 +%uint_vec2 = OpTypeVector %uint 2 +%vec2_01 = OpConstantComposite %uint_vec2 %uint_0 %uint_1 +%vec2_10 = OpConstantComposite %uint_vec2 %uint_1 %uint_0 +%_ptr_Output_uint = OpTypePointer Output %uint_vec2 +%2 = OpVariable %_ptr_Output_uint Output +%11 = OpTypeFunction %void +%1 = OpFunction %void None %11 +%12 = OpLabel +OpSelectionMerge %14 None +OpBranchConditional %true %15 %16 +%15 = OpLabel +OpBranch %14 +%16 = OpLabel +OpBranch %14 +%14 = OpLabel +%18 = OpPhi %uint_vec2 %vec2_01 %15 %vec2_10 %16 +OpStore %2 %18 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} +#endif // SPIRV_EFFCEE + +TEST_F(IfConversionTest, NoCommonDominator) { + const std::string text = R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %1 "func" %2 +%void = OpTypeVoid +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%uint_1 = OpConstant %uint 1 +%_ptr_Output_uint = OpTypePointer Output %uint +%2 = OpVariable %_ptr_Output_uint Output +%8 = OpTypeFunction %void +%1 = OpFunction %void None %8 +%9 = OpLabel +OpBranch %10 +%11 = OpLabel +OpBranch %10 +%10 = OpLabel +%12 = OpPhi %uint %uint_0 %9 %uint_1 %11 +OpStore %2 %12 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(text, text, true, true); +} + +TEST_F(IfConversionTest, LoopUntouched) { + const std::string text = R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %1 "func" %2 +%void = OpTypeVoid +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%uint_1 = OpConstant %uint 1 +%_ptr_Output_uint = OpTypePointer Output %uint +%2 = OpVariable %_ptr_Output_uint Output +%8 = OpTypeFunction %void +%bool = OpTypeBool +%true = OpConstantTrue %bool +%1 = OpFunction %void None %8 +%11 = OpLabel +OpBranch %12 +%12 = OpLabel +%13 = OpPhi %uint %uint_0 %11 %uint_1 %12 +OpLoopMerge %14 %12 None +OpBranchConditional %true %14 %12 +%14 = OpLabel +OpStore %2 %13 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(text, text, true, true); +} + +TEST_F(IfConversionTest, TooManyPredecessors) { + const std::string text = R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %1 "func" %2 +%void = OpTypeVoid +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%uint_1 = OpConstant %uint 1 +%_ptr_Output_uint = OpTypePointer Output %uint +%2 = OpVariable %_ptr_Output_uint Output +%8 = OpTypeFunction %void +%bool = OpTypeBool +%true = OpConstantTrue %bool +%1 = OpFunction %void None %8 +%11 = OpLabel +OpSelectionMerge %12 None +OpBranchConditional %true %13 %12 +%13 = OpLabel +OpBranchConditional %true %14 %15 +%14 = OpLabel +OpBranch %12 +%15 = OpLabel +OpBranch %12 +%12 = OpLabel +%16 = OpPhi %uint %uint_0 %11 %uint_0 %14 %uint_1 %15 +OpStore %2 %16 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(text, text, true, true); +} + +TEST_F(IfConversionTest, NoCodeMotion) { + const std::string text = R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %1 "func" %2 +%void = OpTypeVoid +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%uint_1 = OpConstant %uint 1 +%_ptr_Output_uint = OpTypePointer Output %uint +%2 = OpVariable %_ptr_Output_uint Output +%8 = OpTypeFunction %void +%bool = OpTypeBool +%true = OpConstantTrue %bool +%1 = OpFunction %void None %8 +%11 = OpLabel +OpSelectionMerge %12 None +OpBranchConditional %true %13 %12 +%13 = OpLabel +%14 = OpIAdd %uint %uint_0 %uint_1 +OpBranch %12 +%12 = OpLabel +%15 = OpPhi %uint %uint_0 %11 %14 %13 +OpStore %2 %15 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(text, text, true, true); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/inline_opaque_test.cpp b/3rdparty/spirv-tools/test/opt/inline_opaque_test.cpp new file mode 100644 index 000000000..d3588f7ea --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/inline_opaque_test.cpp @@ -0,0 +1,408 @@ +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; + +using InlineOpaqueTest = PassTest<::testing::Test>; + +TEST_F(InlineOpaqueTest, InlineCallWithStructArgContainingSampledImage) { + // Function with opaque argument is inlined. + // TODO(greg-lunarg): Add HLSL code + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %outColor %texCoords +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %S_t "S_t" +OpMemberName %S_t 0 "v0" +OpMemberName %S_t 1 "v1" +OpMemberName %S_t 2 "smp" +OpName %foo_struct_S_t_vf2_vf21_ "foo(struct-S_t-vf2-vf21;" +OpName %s "s" +OpName %outColor "outColor" +OpName %sampler15 "sampler15" +OpName %s0 "s0" +OpName %texCoords "texCoords" +OpName %param "param" +OpDecorate %sampler15 DescriptorSet 0 +%void = OpTypeVoid +%12 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v2float = OpTypeVector %float 2 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%outColor = OpVariable %_ptr_Output_v4float Output +%17 = OpTypeImage %float 2D 0 0 0 1 Unknown +%18 = OpTypeSampledImage %17 +%S_t = OpTypeStruct %v2float %v2float %18 +%_ptr_Function_S_t = OpTypePointer Function %S_t +%20 = OpTypeFunction %void %_ptr_Function_S_t +%_ptr_UniformConstant_18 = OpTypePointer UniformConstant %18 +%_ptr_Function_18 = OpTypePointer Function %18 +%sampler15 = OpVariable %_ptr_UniformConstant_18 UniformConstant +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%int_2 = OpConstant %int 2 +%_ptr_Function_v2float = OpTypePointer Function %v2float +%_ptr_Input_v2float = OpTypePointer Input %v2float +%texCoords = OpVariable %_ptr_Input_v2float Input +)"; + + const std::string before = + R"(%main = OpFunction %void None %12 +%28 = OpLabel +%s0 = OpVariable %_ptr_Function_S_t Function +%param = OpVariable %_ptr_Function_S_t Function +%29 = OpLoad %v2float %texCoords +%30 = OpAccessChain %_ptr_Function_v2float %s0 %int_0 +OpStore %30 %29 +%31 = OpLoad %18 %sampler15 +%32 = OpAccessChain %_ptr_Function_18 %s0 %int_2 +OpStore %32 %31 +%33 = OpLoad %S_t %s0 +OpStore %param %33 +%34 = OpFunctionCall %void %foo_struct_S_t_vf2_vf21_ %param +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %12 +%28 = OpLabel +%s0 = OpVariable %_ptr_Function_S_t Function +%param = OpVariable %_ptr_Function_S_t Function +%29 = OpLoad %v2float %texCoords +%30 = OpAccessChain %_ptr_Function_v2float %s0 %int_0 +OpStore %30 %29 +%31 = OpLoad %18 %sampler15 +%32 = OpAccessChain %_ptr_Function_18 %s0 %int_2 +OpStore %32 %31 +%33 = OpLoad %S_t %s0 +OpStore %param %33 +%41 = OpAccessChain %_ptr_Function_18 %param %int_2 +%42 = OpLoad %18 %41 +%43 = OpAccessChain %_ptr_Function_v2float %param %int_0 +%44 = OpLoad %v2float %43 +%45 = OpImageSampleImplicitLod %v4float %42 %44 +OpStore %outColor %45 +OpReturn +OpFunctionEnd +)"; + + const std::string post_defs = + R"(%foo_struct_S_t_vf2_vf21_ = OpFunction %void None %20 +%s = OpFunctionParameter %_ptr_Function_S_t +%35 = OpLabel +%36 = OpAccessChain %_ptr_Function_18 %s %int_2 +%37 = OpLoad %18 %36 +%38 = OpAccessChain %_ptr_Function_v2float %s %int_0 +%39 = OpLoad %v2float %38 +%40 = OpImageSampleImplicitLod %v4float %37 %39 +OpStore %outColor %40 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before + post_defs, predefs + after + post_defs, true, true); +} + +TEST_F(InlineOpaqueTest, InlineOpaqueReturn) { + // Function with opaque return value is inlined. + // TODO(greg-lunarg): Add HLSL code + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %texCoords %outColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %foo_ "foo(" +OpName %texCoords "texCoords" +OpName %outColor "outColor" +OpName %sampler15 "sampler15" +OpName %sampler16 "sampler16" +OpDecorate %sampler15 DescriptorSet 0 +OpDecorate %sampler16 DescriptorSet 0 +%void = OpTypeVoid +%9 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v2float = OpTypeVector %float 2 +%bool = OpTypeBool +%false = OpConstantFalse %bool +%_ptr_Input_v2float = OpTypePointer Input %v2float +%texCoords = OpVariable %_ptr_Input_v2float Input +%float_0 = OpConstant %float 0 +%16 = OpConstantComposite %v2float %float_0 %float_0 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%outColor = OpVariable %_ptr_Output_v4float Output +%19 = OpTypeImage %float 2D 0 0 0 1 Unknown +%20 = OpTypeSampledImage %19 +%21 = OpTypeFunction %20 +%_ptr_UniformConstant_20 = OpTypePointer UniformConstant %20 +%_ptr_Function_20 = OpTypePointer Function %20 +%sampler15 = OpVariable %_ptr_UniformConstant_20 UniformConstant +%sampler16 = OpVariable %_ptr_UniformConstant_20 UniformConstant +)"; + + const std::string before = + R"(%main = OpFunction %void None %9 +%24 = OpLabel +%25 = OpVariable %_ptr_Function_20 Function +%26 = OpFunctionCall %20 %foo_ +OpStore %25 %26 +%27 = OpLoad %20 %25 +%28 = OpLoad %v2float %texCoords +%29 = OpImageSampleImplicitLod %v4float %27 %28 +OpStore %outColor %29 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %9 +%24 = OpLabel +%34 = OpVariable %_ptr_Function_20 Function +%35 = OpVariable %_ptr_Function_20 Function +%25 = OpVariable %_ptr_Function_20 Function +%36 = OpLoad %20 %sampler16 +OpStore %34 %36 +%37 = OpLoad %20 %34 +OpStore %35 %37 +%26 = OpLoad %20 %35 +OpStore %25 %26 +%27 = OpLoad %20 %25 +%28 = OpLoad %v2float %texCoords +%29 = OpImageSampleImplicitLod %v4float %27 %28 +OpStore %outColor %29 +OpReturn +OpFunctionEnd +)"; + + const std::string post_defs = + R"(%foo_ = OpFunction %20 None %21 +%30 = OpLabel +%31 = OpVariable %_ptr_Function_20 Function +%32 = OpLoad %20 %sampler16 +OpStore %31 %32 +%33 = OpLoad %20 %31 +OpReturnValue %33 +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before + post_defs, predefs + after + post_defs, true, true); +} + +TEST_F(InlineOpaqueTest, InlineInNonEntryPointFunction) { + // This demonstrates opaque inlining in a function that is not + // an entry point function (main2) but is in the call tree of an + // entry point function (main). + // TODO(greg-lunarg): Add HLSL code + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %outColor %texCoords +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %main2 "main2" +OpName %S_t "S_t" +OpMemberName %S_t 0 "v0" +OpMemberName %S_t 1 "v1" +OpMemberName %S_t 2 "smp" +OpName %foo_struct_S_t_vf2_vf21_ "foo(struct-S_t-vf2-vf21;" +OpName %s "s" +OpName %outColor "outColor" +OpName %sampler15 "sampler15" +OpName %s0 "s0" +OpName %texCoords "texCoords" +OpName %param "param" +OpDecorate %sampler15 DescriptorSet 0 +%void = OpTypeVoid +%13 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v2float = OpTypeVector %float 2 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%outColor = OpVariable %_ptr_Output_v4float Output +%18 = OpTypeImage %float 2D 0 0 0 1 Unknown +%19 = OpTypeSampledImage %18 +%S_t = OpTypeStruct %v2float %v2float %19 +%_ptr_Function_S_t = OpTypePointer Function %S_t +%21 = OpTypeFunction %void %_ptr_Function_S_t +%_ptr_UniformConstant_19 = OpTypePointer UniformConstant %19 +%_ptr_Function_19 = OpTypePointer Function %19 +%sampler15 = OpVariable %_ptr_UniformConstant_19 UniformConstant +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%int_2 = OpConstant %int 2 +%_ptr_Function_v2float = OpTypePointer Function %v2float +%_ptr_Input_v2float = OpTypePointer Input %v2float +%texCoords = OpVariable %_ptr_Input_v2float Input +)"; + + const std::string before = + R"(%main2 = OpFunction %void None %13 +%29 = OpLabel +%s0 = OpVariable %_ptr_Function_S_t Function +%param = OpVariable %_ptr_Function_S_t Function +%30 = OpLoad %v2float %texCoords +%31 = OpAccessChain %_ptr_Function_v2float %s0 %int_0 +OpStore %31 %30 +%32 = OpLoad %19 %sampler15 +%33 = OpAccessChain %_ptr_Function_19 %s0 %int_2 +OpStore %33 %32 +%34 = OpLoad %S_t %s0 +OpStore %param %34 +%35 = OpFunctionCall %void %foo_struct_S_t_vf2_vf21_ %param +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main2 = OpFunction %void None %13 +%29 = OpLabel +%s0 = OpVariable %_ptr_Function_S_t Function +%param = OpVariable %_ptr_Function_S_t Function +%30 = OpLoad %v2float %texCoords +%31 = OpAccessChain %_ptr_Function_v2float %s0 %int_0 +OpStore %31 %30 +%32 = OpLoad %19 %sampler15 +%33 = OpAccessChain %_ptr_Function_19 %s0 %int_2 +OpStore %33 %32 +%34 = OpLoad %S_t %s0 +OpStore %param %34 +%44 = OpAccessChain %_ptr_Function_19 %param %int_2 +%45 = OpLoad %19 %44 +%46 = OpAccessChain %_ptr_Function_v2float %param %int_0 +%47 = OpLoad %v2float %46 +%48 = OpImageSampleImplicitLod %v4float %45 %47 +OpStore %outColor %48 +OpReturn +OpFunctionEnd +)"; + + const std::string post_defs = + R"(%main = OpFunction %void None %13 +%36 = OpLabel +%37 = OpFunctionCall %void %main2 +OpReturn +OpFunctionEnd +%foo_struct_S_t_vf2_vf21_ = OpFunction %void None %21 +%s = OpFunctionParameter %_ptr_Function_S_t +%38 = OpLabel +%39 = OpAccessChain %_ptr_Function_19 %s %int_2 +%40 = OpLoad %19 %39 +%41 = OpAccessChain %_ptr_Function_v2float %s %int_0 +%42 = OpLoad %v2float %41 +%43 = OpImageSampleImplicitLod %v4float %40 %42 +OpStore %outColor %43 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before + post_defs, predefs + after + post_defs, true, true); +} + +TEST_F(InlineOpaqueTest, NoInlineNoOpaque) { + // Function without opaque interface is not inlined. + // #version 140 + // + // in vec4 BaseColor; + // + // float foo(vec4 bar) + // { + // return bar.x + bar.y; + // } + // + // void main() + // { + // vec4 color = vec4(foo(BaseColor)); + // gl_FragColor = color; + // } + + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %foo_vf4_ "foo(vf4;" +OpName %bar "bar" +OpName %color "color" +OpName %BaseColor "BaseColor" +OpName %param "param" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%10 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%14 = OpTypeFunction %float %_ptr_Function_v4float +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%_ptr_Function_float = OpTypePointer Function %float +%uint_1 = OpConstant %uint 1 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %10 +%21 = OpLabel +%color = OpVariable %_ptr_Function_v4float Function +%param = OpVariable %_ptr_Function_v4float Function +%22 = OpLoad %v4float %BaseColor +OpStore %param %22 +%23 = OpFunctionCall %float %foo_vf4_ %param +%24 = OpCompositeConstruct %v4float %23 %23 %23 %23 +OpStore %color %24 +%25 = OpLoad %v4float %color +OpStore %gl_FragColor %25 +OpReturn +OpFunctionEnd +%foo_vf4_ = OpFunction %float None %14 +%bar = OpFunctionParameter %_ptr_Function_v4float +%26 = OpLabel +%27 = OpAccessChain %_ptr_Function_float %bar %uint_0 +%28 = OpLoad %float %27 +%29 = OpAccessChain %_ptr_Function_float %bar %uint_1 +%30 = OpLoad %float %29 +%31 = OpFAdd %float %28 %30 +OpReturnValue %31 +OpFunctionEnd +)"; + + SinglePassRunAndCheck(assembly, assembly, true, true); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/inline_test.cpp b/3rdparty/spirv-tools/test/opt/inline_test.cpp new file mode 100644 index 000000000..8f3675ad1 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/inline_test.cpp @@ -0,0 +1,2612 @@ +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; + +using InlineTest = PassTest<::testing::Test>; + +TEST_F(InlineTest, Simple) { + // #version 140 + // + // in vec4 BaseColor; + // + // float foo(vec4 bar) + // { + // return bar.x + bar.y; + // } + // + // void main() + // { + // vec4 color = vec4(foo(BaseColor)); + // gl_FragColor = color; + // } + const std::vector predefs = { + // clang-format off + "OpCapability Shader", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Fragment %main \"main\" %BaseColor %gl_FragColor", + "OpExecutionMode %main OriginUpperLeft", + "OpSource GLSL 140", + "OpName %main \"main\"", + "OpName %foo_vf4_ \"foo(vf4;\"", + "OpName %bar \"bar\"", + "OpName %color \"color\"", + "OpName %BaseColor \"BaseColor\"", + "OpName %param \"param\"", + "OpName %gl_FragColor \"gl_FragColor\"", + "%void = OpTypeVoid", + "%10 = OpTypeFunction %void", + "%float = OpTypeFloat 32", + "%v4float = OpTypeVector %float 4", +"%_ptr_Function_v4float = OpTypePointer Function %v4float", + "%14 = OpTypeFunction %float %_ptr_Function_v4float", + "%uint = OpTypeInt 32 0", + "%uint_0 = OpConstant %uint 0", +"%_ptr_Function_float = OpTypePointer Function %float", + "%uint_1 = OpConstant %uint 1", +"%_ptr_Input_v4float = OpTypePointer Input %v4float", + "%BaseColor = OpVariable %_ptr_Input_v4float Input", +"%_ptr_Output_v4float = OpTypePointer Output %v4float", +"%gl_FragColor = OpVariable %_ptr_Output_v4float Output", + // clang-format on + }; + + const std::vector nonEntryFuncs = { + // clang-format off + "%foo_vf4_ = OpFunction %float None %14", + "%bar = OpFunctionParameter %_ptr_Function_v4float", + "%26 = OpLabel", + "%27 = OpAccessChain %_ptr_Function_float %bar %uint_0", + "%28 = OpLoad %float %27", + "%29 = OpAccessChain %_ptr_Function_float %bar %uint_1", + "%30 = OpLoad %float %29", + "%31 = OpFAdd %float %28 %30", + "OpReturnValue %31", + "OpFunctionEnd", + // clang-format on + }; + + const std::vector before = { + // clang-format off + "%main = OpFunction %void None %10", + "%21 = OpLabel", + "%color = OpVariable %_ptr_Function_v4float Function", + "%param = OpVariable %_ptr_Function_v4float Function", + "%22 = OpLoad %v4float %BaseColor", + "OpStore %param %22", + "%23 = OpFunctionCall %float %foo_vf4_ %param", + "%24 = OpCompositeConstruct %v4float %23 %23 %23 %23", + "OpStore %color %24", + "%25 = OpLoad %v4float %color", + "OpStore %gl_FragColor %25", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + + const std::vector after = { + // clang-format off + "%main = OpFunction %void None %10", + "%21 = OpLabel", + "%32 = OpVariable %_ptr_Function_float Function", + "%color = OpVariable %_ptr_Function_v4float Function", + "%param = OpVariable %_ptr_Function_v4float Function", + "%22 = OpLoad %v4float %BaseColor", + "OpStore %param %22", + "%33 = OpAccessChain %_ptr_Function_float %param %uint_0", + "%34 = OpLoad %float %33", + "%35 = OpAccessChain %_ptr_Function_float %param %uint_1", + "%36 = OpLoad %float %35", + "%37 = OpFAdd %float %34 %36", + "OpStore %32 %37", + "%23 = OpLoad %float %32", + "%24 = OpCompositeConstruct %v4float %23 %23 %23 %23", + "OpStore %color %24", + "%25 = OpLoad %v4float %color", + "OpStore %gl_FragColor %25", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + SinglePassRunAndCheck( + JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)), + JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)), + /* skip_nop = */ false, /* do_validate = */ true); +} + +TEST_F(InlineTest, Nested) { + // #version 140 + // + // in vec4 BaseColor; + // + // float foo2(float f, float f2) + // { + // return f * f2; + // } + // + // float foo(vec4 bar) + // { + // return foo2(bar.x + bar.y, bar.z); + // } + // + // void main() + // { + // vec4 color = vec4(foo(BaseColor)); + // gl_FragColor = color; + // } + const std::vector predefs = { + // clang-format off + "OpCapability Shader", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Fragment %main \"main\" %BaseColor %gl_FragColor", + "OpExecutionMode %main OriginUpperLeft", + "OpSource GLSL 140", + "OpName %main \"main\"", + "OpName %foo2_f1_f1_ \"foo2(f1;f1;\"", + "OpName %f \"f\"", + "OpName %f2 \"f2\"", + "OpName %foo_vf4_ \"foo(vf4;\"", + "OpName %bar \"bar\"", + "OpName %param \"param\"", + "OpName %param_0 \"param\"", + "OpName %color \"color\"", + "OpName %BaseColor \"BaseColor\"", + "OpName %param_1 \"param\"", + "OpName %gl_FragColor \"gl_FragColor\"", + "%void = OpTypeVoid", + "%15 = OpTypeFunction %void", + "%float = OpTypeFloat 32", +"%_ptr_Function_float = OpTypePointer Function %float", + "%18 = OpTypeFunction %float %_ptr_Function_float %_ptr_Function_float", + "%v4float = OpTypeVector %float 4", +"%_ptr_Function_v4float = OpTypePointer Function %v4float", + "%21 = OpTypeFunction %float %_ptr_Function_v4float", + "%uint = OpTypeInt 32 0", + "%uint_0 = OpConstant %uint 0", + "%uint_1 = OpConstant %uint 1", + "%uint_2 = OpConstant %uint 2", +"%_ptr_Input_v4float = OpTypePointer Input %v4float", + "%BaseColor = OpVariable %_ptr_Input_v4float Input", +"%_ptr_Output_v4float = OpTypePointer Output %v4float", +"%gl_FragColor = OpVariable %_ptr_Output_v4float Output", + // clang-format on + }; + + const std::vector nonEntryFuncs = { + // clang-format off +"%foo2_f1_f1_ = OpFunction %float None %18", + "%f = OpFunctionParameter %_ptr_Function_float", + "%f2 = OpFunctionParameter %_ptr_Function_float", + "%33 = OpLabel", + "%34 = OpLoad %float %f", + "%35 = OpLoad %float %f2", + "%36 = OpFMul %float %34 %35", + "OpReturnValue %36", + "OpFunctionEnd", + "%foo_vf4_ = OpFunction %float None %21", + "%bar = OpFunctionParameter %_ptr_Function_v4float", + "%37 = OpLabel", + "%param = OpVariable %_ptr_Function_float Function", + "%param_0 = OpVariable %_ptr_Function_float Function", + "%38 = OpAccessChain %_ptr_Function_float %bar %uint_0", + "%39 = OpLoad %float %38", + "%40 = OpAccessChain %_ptr_Function_float %bar %uint_1", + "%41 = OpLoad %float %40", + "%42 = OpFAdd %float %39 %41", + "OpStore %param %42", + "%43 = OpAccessChain %_ptr_Function_float %bar %uint_2", + "%44 = OpLoad %float %43", + "OpStore %param_0 %44", + "%45 = OpFunctionCall %float %foo2_f1_f1_ %param %param_0", + "OpReturnValue %45", + "OpFunctionEnd", + // clang-format on + }; + + const std::vector before = { + // clang-format off + "%main = OpFunction %void None %15", + "%28 = OpLabel", + "%color = OpVariable %_ptr_Function_v4float Function", + "%param_1 = OpVariable %_ptr_Function_v4float Function", + "%29 = OpLoad %v4float %BaseColor", + "OpStore %param_1 %29", + "%30 = OpFunctionCall %float %foo_vf4_ %param_1", + "%31 = OpCompositeConstruct %v4float %30 %30 %30 %30", + "OpStore %color %31", + "%32 = OpLoad %v4float %color", + "OpStore %gl_FragColor %32", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + + const std::vector after = { + // clang-format off + "%main = OpFunction %void None %15", + "%28 = OpLabel", + "%57 = OpVariable %_ptr_Function_float Function", + "%46 = OpVariable %_ptr_Function_float Function", + "%47 = OpVariable %_ptr_Function_float Function", + "%48 = OpVariable %_ptr_Function_float Function", + "%color = OpVariable %_ptr_Function_v4float Function", + "%param_1 = OpVariable %_ptr_Function_v4float Function", + "%29 = OpLoad %v4float %BaseColor", + "OpStore %param_1 %29", + "%49 = OpAccessChain %_ptr_Function_float %param_1 %uint_0", + "%50 = OpLoad %float %49", + "%51 = OpAccessChain %_ptr_Function_float %param_1 %uint_1", + "%52 = OpLoad %float %51", + "%53 = OpFAdd %float %50 %52", + "OpStore %46 %53", + "%54 = OpAccessChain %_ptr_Function_float %param_1 %uint_2", + "%55 = OpLoad %float %54", + "OpStore %47 %55", + "%58 = OpLoad %float %46", + "%59 = OpLoad %float %47", + "%60 = OpFMul %float %58 %59", + "OpStore %57 %60", + "%56 = OpLoad %float %57", + "OpStore %48 %56", + "%30 = OpLoad %float %48", + "%31 = OpCompositeConstruct %v4float %30 %30 %30 %30", + "OpStore %color %31", + "%32 = OpLoad %v4float %color", + "OpStore %gl_FragColor %32", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + SinglePassRunAndCheck( + JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)), + JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)), + /* skip_nop = */ false, /* do_validate = */ true); +} + +TEST_F(InlineTest, InOutParameter) { + // #version 400 + // + // in vec4 Basecolor; + // + // void foo(inout vec4 bar) + // { + // bar.z = bar.x + bar.y; + // } + // + // void main() + // { + // vec4 b = Basecolor; + // foo(b); + // vec4 color = vec4(b.z); + // gl_FragColor = color; + // } + const std::vector predefs = { + // clang-format off + "OpCapability Shader", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Fragment %main \"main\" %Basecolor %gl_FragColor", + "OpExecutionMode %main OriginUpperLeft", + "OpSource GLSL 400", + "OpName %main \"main\"", + "OpName %foo_vf4_ \"foo(vf4;\"", + "OpName %bar \"bar\"", + "OpName %b \"b\"", + "OpName %Basecolor \"Basecolor\"", + "OpName %param \"param\"", + "OpName %color \"color\"", + "OpName %gl_FragColor \"gl_FragColor\"", + "%void = OpTypeVoid", + "%11 = OpTypeFunction %void", + "%float = OpTypeFloat 32", + "%v4float = OpTypeVector %float 4", +"%_ptr_Function_v4float = OpTypePointer Function %v4float", + "%15 = OpTypeFunction %void %_ptr_Function_v4float", + "%uint = OpTypeInt 32 0", + "%uint_0 = OpConstant %uint 0", +"%_ptr_Function_float = OpTypePointer Function %float", + "%uint_1 = OpConstant %uint 1", + "%uint_2 = OpConstant %uint 2", +"%_ptr_Input_v4float = OpTypePointer Input %v4float", + "%Basecolor = OpVariable %_ptr_Input_v4float Input", +"%_ptr_Output_v4float = OpTypePointer Output %v4float", +"%gl_FragColor = OpVariable %_ptr_Output_v4float Output", + // clang-format on + }; + + const std::vector nonEntryFuncs = { + // clang-format off + "%foo_vf4_ = OpFunction %void None %15", + "%bar = OpFunctionParameter %_ptr_Function_v4float", + "%32 = OpLabel", + "%33 = OpAccessChain %_ptr_Function_float %bar %uint_0", + "%34 = OpLoad %float %33", + "%35 = OpAccessChain %_ptr_Function_float %bar %uint_1", + "%36 = OpLoad %float %35", + "%37 = OpFAdd %float %34 %36", + "%38 = OpAccessChain %_ptr_Function_float %bar %uint_2", + "OpStore %38 %37", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + + const std::vector before = { + // clang-format off + "%main = OpFunction %void None %11", + "%23 = OpLabel", + "%b = OpVariable %_ptr_Function_v4float Function", + "%param = OpVariable %_ptr_Function_v4float Function", + "%color = OpVariable %_ptr_Function_v4float Function", + "%24 = OpLoad %v4float %Basecolor", + "OpStore %b %24", + "%25 = OpLoad %v4float %b", + "OpStore %param %25", + "%26 = OpFunctionCall %void %foo_vf4_ %param", + "%27 = OpLoad %v4float %param", + "OpStore %b %27", + "%28 = OpAccessChain %_ptr_Function_float %b %uint_2", + "%29 = OpLoad %float %28", + "%30 = OpCompositeConstruct %v4float %29 %29 %29 %29", + "OpStore %color %30", + "%31 = OpLoad %v4float %color", + "OpStore %gl_FragColor %31", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + + const std::vector after = { + // clang-format off + "%main = OpFunction %void None %11", + "%23 = OpLabel", + "%b = OpVariable %_ptr_Function_v4float Function", + "%param = OpVariable %_ptr_Function_v4float Function", + "%color = OpVariable %_ptr_Function_v4float Function", + "%24 = OpLoad %v4float %Basecolor", + "OpStore %b %24", + "%25 = OpLoad %v4float %b", + "OpStore %param %25", + "%39 = OpAccessChain %_ptr_Function_float %param %uint_0", + "%40 = OpLoad %float %39", + "%41 = OpAccessChain %_ptr_Function_float %param %uint_1", + "%42 = OpLoad %float %41", + "%43 = OpFAdd %float %40 %42", + "%44 = OpAccessChain %_ptr_Function_float %param %uint_2", + "OpStore %44 %43", + "%27 = OpLoad %v4float %param", + "OpStore %b %27", + "%28 = OpAccessChain %_ptr_Function_float %b %uint_2", + "%29 = OpLoad %float %28", + "%30 = OpCompositeConstruct %v4float %29 %29 %29 %29", + "OpStore %color %30", + "%31 = OpLoad %v4float %color", + "OpStore %gl_FragColor %31", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + SinglePassRunAndCheck( + JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)), + JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)), + /* skip_nop = */ false, /* do_validate = */ true); +} + +TEST_F(InlineTest, BranchInCallee) { + // #version 140 + // + // in vec4 BaseColor; + // + // float foo(vec4 bar) + // { + // float r = bar.x; + // if (r < 0.0) + // r = -r; + // return r; + // } + // + // void main() + // { + // vec4 color = vec4(foo(BaseColor)); + // + // gl_FragColor = color; + // } + const std::vector predefs = { + // clang-format off + "OpCapability Shader", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Fragment %main \"main\" %BaseColor %gl_FragColor", + "OpExecutionMode %main OriginUpperLeft", + "OpSource GLSL 140", + "OpName %main \"main\"", + "OpName %foo_vf4_ \"foo(vf4;\"", + "OpName %bar \"bar\"", + "OpName %r \"r\"", + "OpName %color \"color\"", + "OpName %BaseColor \"BaseColor\"", + "OpName %param \"param\"", + "OpName %gl_FragColor \"gl_FragColor\"", + "%void = OpTypeVoid", + "%11 = OpTypeFunction %void", + "%float = OpTypeFloat 32", + "%v4float = OpTypeVector %float 4", +"%_ptr_Function_v4float = OpTypePointer Function %v4float", + "%15 = OpTypeFunction %float %_ptr_Function_v4float", +"%_ptr_Function_float = OpTypePointer Function %float", + "%uint = OpTypeInt 32 0", + "%uint_0 = OpConstant %uint 0", + "%float_0 = OpConstant %float 0", + "%bool = OpTypeBool", +"%_ptr_Input_v4float = OpTypePointer Input %v4float", + "%BaseColor = OpVariable %_ptr_Input_v4float Input", +"%_ptr_Output_v4float = OpTypePointer Output %v4float", +"%gl_FragColor = OpVariable %_ptr_Output_v4float Output", + // clang-format on + }; + + const std::vector nonEntryFuncs = { + // clang-format off + "%foo_vf4_ = OpFunction %float None %15", + "%bar = OpFunctionParameter %_ptr_Function_v4float", + "%28 = OpLabel", + "%r = OpVariable %_ptr_Function_float Function", + "%29 = OpAccessChain %_ptr_Function_float %bar %uint_0", + "%30 = OpLoad %float %29", + "OpStore %r %30", + "%31 = OpLoad %float %r", + "%32 = OpFOrdLessThan %bool %31 %float_0", + "OpSelectionMerge %33 None", + "OpBranchConditional %32 %34 %33", + "%34 = OpLabel", + "%35 = OpLoad %float %r", + "%36 = OpFNegate %float %35", + "OpStore %r %36", + "OpBranch %33", + "%33 = OpLabel", + "%37 = OpLoad %float %r", + "OpReturnValue %37", + "OpFunctionEnd", + // clang-format on + }; + + const std::vector before = { + // clang-format off + "%main = OpFunction %void None %11", + "%23 = OpLabel", + "%color = OpVariable %_ptr_Function_v4float Function", + "%param = OpVariable %_ptr_Function_v4float Function", + "%24 = OpLoad %v4float %BaseColor", + "OpStore %param %24", + "%25 = OpFunctionCall %float %foo_vf4_ %param", + "%26 = OpCompositeConstruct %v4float %25 %25 %25 %25", + "OpStore %color %26", + "%27 = OpLoad %v4float %color", + "OpStore %gl_FragColor %27", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + + const std::vector after = { + // clang-format off + "%main = OpFunction %void None %11", + "%23 = OpLabel", + "%38 = OpVariable %_ptr_Function_float Function", + "%39 = OpVariable %_ptr_Function_float Function", + "%color = OpVariable %_ptr_Function_v4float Function", + "%param = OpVariable %_ptr_Function_v4float Function", + "%24 = OpLoad %v4float %BaseColor", + "OpStore %param %24", + "%40 = OpAccessChain %_ptr_Function_float %param %uint_0", + "%41 = OpLoad %float %40", + "OpStore %38 %41", + "%42 = OpLoad %float %38", + "%43 = OpFOrdLessThan %bool %42 %float_0", + "OpSelectionMerge %44 None", + "OpBranchConditional %43 %45 %44", + "%45 = OpLabel", + "%46 = OpLoad %float %38", + "%47 = OpFNegate %float %46", + "OpStore %38 %47", + "OpBranch %44", + "%44 = OpLabel", + "%48 = OpLoad %float %38", + "OpStore %39 %48", + "%25 = OpLoad %float %39", + "%26 = OpCompositeConstruct %v4float %25 %25 %25 %25", + "OpStore %color %26", + "%27 = OpLoad %v4float %color", + "OpStore %gl_FragColor %27", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + SinglePassRunAndCheck( + JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)), + JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)), + /* skip_nop = */ false, /* do_validate = */ true); +} + +TEST_F(InlineTest, PhiAfterCall) { + // #version 140 + // + // in vec4 BaseColor; + // + // float foo(float bar) + // { + // float r = bar; + // if (r < 0.0) + // r = -r; + // return r; + // } + // + // void main() + // { + // vec4 color = BaseColor; + // if (foo(color.x) > 2.0 && foo(color.y) > 2.0) + // color = vec4(0.0); + // gl_FragColor = color; + // } + const std::vector predefs = { + // clang-format off + "OpCapability Shader", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Fragment %main \"main\" %BaseColor %gl_FragColor", + "OpExecutionMode %main OriginUpperLeft", + "OpSource GLSL 140", + "OpName %main \"main\"", + "OpName %foo_f1_ \"foo(f1;\"", + "OpName %bar \"bar\"", + "OpName %r \"r\"", + "OpName %color \"color\"", + "OpName %BaseColor \"BaseColor\"", + "OpName %param \"param\"", + "OpName %param_0 \"param\"", + "OpName %gl_FragColor \"gl_FragColor\"", + "%void = OpTypeVoid", + "%12 = OpTypeFunction %void", + "%float = OpTypeFloat 32", +"%_ptr_Function_float = OpTypePointer Function %float", + "%15 = OpTypeFunction %float %_ptr_Function_float", + "%float_0 = OpConstant %float 0", + "%bool = OpTypeBool", + "%v4float = OpTypeVector %float 4", +"%_ptr_Function_v4float = OpTypePointer Function %v4float", +"%_ptr_Input_v4float = OpTypePointer Input %v4float", + "%BaseColor = OpVariable %_ptr_Input_v4float Input", + "%uint = OpTypeInt 32 0", + "%uint_0 = OpConstant %uint 0", + "%float_2 = OpConstant %float 2", + "%uint_1 = OpConstant %uint 1", + "%25 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0", +"%_ptr_Output_v4float = OpTypePointer Output %v4float", +"%gl_FragColor = OpVariable %_ptr_Output_v4float Output", + // clang-format on + }; + + const std::vector nonEntryFuncs = { + // clang-format off + "%foo_f1_ = OpFunction %float None %15", + "%bar = OpFunctionParameter %_ptr_Function_float", + "%43 = OpLabel", + "%r = OpVariable %_ptr_Function_float Function", + "%44 = OpLoad %float %bar", + "OpStore %r %44", + "%45 = OpLoad %float %r", + "%46 = OpFOrdLessThan %bool %45 %float_0", + "OpSelectionMerge %47 None", + "OpBranchConditional %46 %48 %47", + "%48 = OpLabel", + "%49 = OpLoad %float %r", + "%50 = OpFNegate %float %49", + "OpStore %r %50", + "OpBranch %47", + "%47 = OpLabel", + "%51 = OpLoad %float %r", + "OpReturnValue %51", + "OpFunctionEnd", + // clang-format on + }; + + const std::vector before = { + // clang-format off + "%main = OpFunction %void None %12", + "%27 = OpLabel", + "%color = OpVariable %_ptr_Function_v4float Function", + "%param = OpVariable %_ptr_Function_float Function", + "%param_0 = OpVariable %_ptr_Function_float Function", + "%28 = OpLoad %v4float %BaseColor", + "OpStore %color %28", + "%29 = OpAccessChain %_ptr_Function_float %color %uint_0", + "%30 = OpLoad %float %29", + "OpStore %param %30", + "%31 = OpFunctionCall %float %foo_f1_ %param", + "%32 = OpFOrdGreaterThan %bool %31 %float_2", + "OpSelectionMerge %33 None", + "OpBranchConditional %32 %34 %33", + "%34 = OpLabel", + "%35 = OpAccessChain %_ptr_Function_float %color %uint_1", + "%36 = OpLoad %float %35", + "OpStore %param_0 %36", + "%37 = OpFunctionCall %float %foo_f1_ %param_0", + "%38 = OpFOrdGreaterThan %bool %37 %float_2", + "OpBranch %33", + "%33 = OpLabel", + "%39 = OpPhi %bool %32 %27 %38 %34", + "OpSelectionMerge %40 None", + "OpBranchConditional %39 %41 %40", + "%41 = OpLabel", + "OpStore %color %25", + "OpBranch %40", + "%40 = OpLabel", + "%42 = OpLoad %v4float %color", + "OpStore %gl_FragColor %42", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + + const std::vector after = { + // clang-format off + "%main = OpFunction %void None %12", + "%27 = OpLabel", + "%62 = OpVariable %_ptr_Function_float Function", + "%63 = OpVariable %_ptr_Function_float Function", + "%52 = OpVariable %_ptr_Function_float Function", + "%53 = OpVariable %_ptr_Function_float Function", + "%color = OpVariable %_ptr_Function_v4float Function", + "%param = OpVariable %_ptr_Function_float Function", + "%param_0 = OpVariable %_ptr_Function_float Function", + "%28 = OpLoad %v4float %BaseColor", + "OpStore %color %28", + "%29 = OpAccessChain %_ptr_Function_float %color %uint_0", + "%30 = OpLoad %float %29", + "OpStore %param %30", + "%54 = OpLoad %float %param", + "OpStore %52 %54", + "%55 = OpLoad %float %52", + "%56 = OpFOrdLessThan %bool %55 %float_0", + "OpSelectionMerge %57 None", + "OpBranchConditional %56 %58 %57", + "%58 = OpLabel", + "%59 = OpLoad %float %52", + "%60 = OpFNegate %float %59", + "OpStore %52 %60", + "OpBranch %57", + "%57 = OpLabel", + "%61 = OpLoad %float %52", + "OpStore %53 %61", + "%31 = OpLoad %float %53", + "%32 = OpFOrdGreaterThan %bool %31 %float_2", + "OpSelectionMerge %33 None", + "OpBranchConditional %32 %34 %33", + "%34 = OpLabel", + "%35 = OpAccessChain %_ptr_Function_float %color %uint_1", + "%36 = OpLoad %float %35", + "OpStore %param_0 %36", + "%64 = OpLoad %float %param_0", + "OpStore %62 %64", + "%65 = OpLoad %float %62", + "%66 = OpFOrdLessThan %bool %65 %float_0", + "OpSelectionMerge %67 None", + "OpBranchConditional %66 %68 %67", + "%68 = OpLabel", + "%69 = OpLoad %float %62", + "%70 = OpFNegate %float %69", + "OpStore %62 %70", + "OpBranch %67", + "%67 = OpLabel", + "%71 = OpLoad %float %62", + "OpStore %63 %71", + "%37 = OpLoad %float %63", + "%38 = OpFOrdGreaterThan %bool %37 %float_2", + "OpBranch %33", + "%33 = OpLabel", + "%39 = OpPhi %bool %32 %57 %38 %67", + "OpSelectionMerge %40 None", + "OpBranchConditional %39 %41 %40", + "%41 = OpLabel", + "OpStore %color %25", + "OpBranch %40", + "%40 = OpLabel", + "%42 = OpLoad %v4float %color", + "OpStore %gl_FragColor %42", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + SinglePassRunAndCheck( + JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)), + JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)), + /* skip_nop = */ false, /* do_validate = */ true); +} + +TEST_F(InlineTest, OpSampledImageOutOfBlock) { + // #version 450 + // + // uniform texture2D t2D; + // uniform sampler samp; + // out vec4 FragColor; + // in vec4 BaseColor; + // + // float foo(vec4 bar) + // { + // float r = bar.x; + // if (r < 0.0) + // r = -r; + // return r; + // } + // + // void main() + // { + // vec4 color1 = texture(sampler2D(t2D, samp), vec2(1.0)); + // vec4 color2 = vec4(foo(BaseColor)); + // vec4 color3 = texture(sampler2D(t2D, samp), vec2(0.5)); + // FragColor = (color1 + color2 + color3)/3; + // } + // + // Note: the before SPIR-V will need to be edited to create a use of + // the OpSampledImage across the function call. + const std::vector predefs = { + // clang-format off + "OpCapability Shader", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Fragment %main \"main\" %BaseColor %FragColor", + "OpExecutionMode %main OriginUpperLeft", + "OpSource GLSL 450", + "OpName %main \"main\"", + "OpName %foo_vf4_ \"foo(vf4;\"", + "OpName %bar \"bar\"", + "OpName %r \"r\"", + "OpName %color1 \"color1\"", + "OpName %t2D \"t2D\"", + "OpName %samp \"samp\"", + "OpName %color2 \"color2\"", + "OpName %BaseColor \"BaseColor\"", + "OpName %param \"param\"", + "OpName %color3 \"color3\"", + "OpName %FragColor \"FragColor\"", + "OpDecorate %t2D DescriptorSet 0", + "OpDecorate %samp DescriptorSet 0", + "%void = OpTypeVoid", + "%15 = OpTypeFunction %void", + "%float = OpTypeFloat 32", + "%v4float = OpTypeVector %float 4", +"%_ptr_Function_v4float = OpTypePointer Function %v4float", + "%19 = OpTypeFunction %float %_ptr_Function_v4float", +"%_ptr_Function_float = OpTypePointer Function %float", + "%uint = OpTypeInt 32 0", + "%uint_0 = OpConstant %uint 0", + "%float_0 = OpConstant %float 0", + "%bool = OpTypeBool", + "%25 = OpTypeImage %float 2D 0 0 0 1 Unknown", +"%_ptr_UniformConstant_25 = OpTypePointer UniformConstant %25", + "%t2D = OpVariable %_ptr_UniformConstant_25 UniformConstant", + "%27 = OpTypeSampler", +"%_ptr_UniformConstant_27 = OpTypePointer UniformConstant %27", + "%samp = OpVariable %_ptr_UniformConstant_27 UniformConstant", + "%29 = OpTypeSampledImage %25", + "%v2float = OpTypeVector %float 2", + "%float_1 = OpConstant %float 1", + "%32 = OpConstantComposite %v2float %float_1 %float_1", +"%_ptr_Input_v4float = OpTypePointer Input %v4float", + "%BaseColor = OpVariable %_ptr_Input_v4float Input", + "%float_0_5 = OpConstant %float 0.5", + "%35 = OpConstantComposite %v2float %float_0_5 %float_0_5", +"%_ptr_Output_v4float = OpTypePointer Output %v4float", + "%FragColor = OpVariable %_ptr_Output_v4float Output", + "%float_3 = OpConstant %float 3", + // clang-format on + }; + + const std::vector nonEntryFuncs = { + // clang-format off + "%foo_vf4_ = OpFunction %float None %19", + "%bar = OpFunctionParameter %_ptr_Function_v4float", + "%56 = OpLabel", + "%r = OpVariable %_ptr_Function_float Function", + "%57 = OpAccessChain %_ptr_Function_float %bar %uint_0", + "%58 = OpLoad %float %57", + "OpStore %r %58", + "%59 = OpLoad %float %r", + "%60 = OpFOrdLessThan %bool %59 %float_0", + "OpSelectionMerge %61 None", + "OpBranchConditional %60 %62 %61", + "%62 = OpLabel", + "%63 = OpLoad %float %r", + "%64 = OpFNegate %float %63", + "OpStore %r %64", + "OpBranch %61", + "%61 = OpLabel", + "%65 = OpLoad %float %r", + "OpReturnValue %65", + "OpFunctionEnd", + // clang-format on + }; + + const std::vector before = { + // clang-format off + "%main = OpFunction %void None %15", + "%38 = OpLabel", + "%color1 = OpVariable %_ptr_Function_v4float Function", + "%color2 = OpVariable %_ptr_Function_v4float Function", + "%param = OpVariable %_ptr_Function_v4float Function", + "%color3 = OpVariable %_ptr_Function_v4float Function", + "%39 = OpLoad %25 %t2D", + "%40 = OpLoad %27 %samp", + "%41 = OpSampledImage %29 %39 %40", + "%42 = OpImageSampleImplicitLod %v4float %41 %32", + "OpStore %color1 %42", + "%43 = OpLoad %v4float %BaseColor", + "OpStore %param %43", + "%44 = OpFunctionCall %float %foo_vf4_ %param", + "%45 = OpCompositeConstruct %v4float %44 %44 %44 %44", + "OpStore %color2 %45", + "%46 = OpLoad %25 %t2D", + "%47 = OpLoad %27 %samp", + "%48 = OpImageSampleImplicitLod %v4float %41 %35", + "OpStore %color3 %48", + "%49 = OpLoad %v4float %color1", + "%50 = OpLoad %v4float %color2", + "%51 = OpFAdd %v4float %49 %50", + "%52 = OpLoad %v4float %color3", + "%53 = OpFAdd %v4float %51 %52", + "%54 = OpCompositeConstruct %v4float %float_3 %float_3 %float_3 %float_3", + "%55 = OpFDiv %v4float %53 %54", + "OpStore %FragColor %55", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + + const std::vector after = { + // clang-format off + "%main = OpFunction %void None %15", + "%38 = OpLabel", + "%66 = OpVariable %_ptr_Function_float Function", + "%67 = OpVariable %_ptr_Function_float Function", + "%color1 = OpVariable %_ptr_Function_v4float Function", + "%color2 = OpVariable %_ptr_Function_v4float Function", + "%param = OpVariable %_ptr_Function_v4float Function", + "%color3 = OpVariable %_ptr_Function_v4float Function", + "%39 = OpLoad %25 %t2D", + "%40 = OpLoad %27 %samp", + "%41 = OpSampledImage %29 %39 %40", + "%42 = OpImageSampleImplicitLod %v4float %41 %32", + "OpStore %color1 %42", + "%43 = OpLoad %v4float %BaseColor", + "OpStore %param %43", + "%68 = OpAccessChain %_ptr_Function_float %param %uint_0", + "%69 = OpLoad %float %68", + "OpStore %66 %69", + "%70 = OpLoad %float %66", + "%71 = OpFOrdLessThan %bool %70 %float_0", + "OpSelectionMerge %72 None", + "OpBranchConditional %71 %73 %72", + "%73 = OpLabel", + "%74 = OpLoad %float %66", + "%75 = OpFNegate %float %74", + "OpStore %66 %75", + "OpBranch %72", + "%72 = OpLabel", + "%76 = OpLoad %float %66", + "OpStore %67 %76", + "%44 = OpLoad %float %67", + "%45 = OpCompositeConstruct %v4float %44 %44 %44 %44", + "OpStore %color2 %45", + "%46 = OpLoad %25 %t2D", + "%47 = OpLoad %27 %samp", + "%77 = OpSampledImage %29 %39 %40", + "%48 = OpImageSampleImplicitLod %v4float %77 %35", + "OpStore %color3 %48", + "%49 = OpLoad %v4float %color1", + "%50 = OpLoad %v4float %color2", + "%51 = OpFAdd %v4float %49 %50", + "%52 = OpLoad %v4float %color3", + "%53 = OpFAdd %v4float %51 %52", + "%54 = OpCompositeConstruct %v4float %float_3 %float_3 %float_3 %float_3", + "%55 = OpFDiv %v4float %53 %54", + "OpStore %FragColor %55", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + SinglePassRunAndCheck( + JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)), + JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)), + /* skip_nop = */ false, /* do_validate = */ true); +} + +TEST_F(InlineTest, OpImageOutOfBlock) { + // #version 450 + // + // uniform texture2D t2D; + // uniform sampler samp; + // uniform sampler samp2; + // + // out vec4 FragColor; + // + // in vec4 BaseColor; + // + // float foo(vec4 bar) + // { + // float r = bar.x; + // if (r < 0.0) + // r = -r; + // return r; + // } + // + // void main() + // { + // vec4 color1 = texture(sampler2D(t2D, samp), vec2(1.0)); + // vec4 color2 = vec4(foo(BaseColor)); + // vec4 color3 = texture(sampler2D(t2D, samp2), vec2(0.5)); + // FragColor = (color1 + color2 + color3)/3; + // } + // Note: the before SPIR-V will need to be edited to create an OpImage + // from the first OpSampledImage, place it before the call and use it + // in the second OpSampledImage following the call. + const std::vector predefs = { + // clang-format off + "OpCapability Shader", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Fragment %main \"main\" %BaseColor %FragColor", + "OpExecutionMode %main OriginUpperLeft", + "OpSource GLSL 450", + "OpName %main \"main\"", + "OpName %foo_vf4_ \"foo(vf4;\"", + "OpName %bar \"bar\"", + "OpName %r \"r\"", + "OpName %color1 \"color1\"", + "OpName %t2D \"t2D\"", + "OpName %samp \"samp\"", + "OpName %color2 \"color2\"", + "OpName %BaseColor \"BaseColor\"", + "OpName %param \"param\"", + "OpName %color3 \"color3\"", + "OpName %samp2 \"samp2\"", + "OpName %FragColor \"FragColor\"", + "OpDecorate %t2D DescriptorSet 0", + "OpDecorate %samp DescriptorSet 0", + "OpDecorate %samp2 DescriptorSet 0", + "%void = OpTypeVoid", + "%16 = OpTypeFunction %void", + "%float = OpTypeFloat 32", + "%v4float = OpTypeVector %float 4", +"%_ptr_Function_v4float = OpTypePointer Function %v4float", + "%20 = OpTypeFunction %float %_ptr_Function_v4float", +"%_ptr_Function_float = OpTypePointer Function %float", + "%uint = OpTypeInt 32 0", + "%uint_0 = OpConstant %uint 0", + "%float_0 = OpConstant %float 0", + "%bool = OpTypeBool", + "%26 = OpTypeImage %float 2D 0 0 0 1 Unknown", +"%_ptr_UniformConstant_26 = OpTypePointer UniformConstant %26", + "%t2D = OpVariable %_ptr_UniformConstant_26 UniformConstant", + "%28 = OpTypeSampler", +"%_ptr_UniformConstant_28 = OpTypePointer UniformConstant %28", + "%samp = OpVariable %_ptr_UniformConstant_28 UniformConstant", + "%30 = OpTypeSampledImage %26", + "%v2float = OpTypeVector %float 2", + "%float_1 = OpConstant %float 1", + "%33 = OpConstantComposite %v2float %float_1 %float_1", +"%_ptr_Input_v4float = OpTypePointer Input %v4float", + "%BaseColor = OpVariable %_ptr_Input_v4float Input", + "%samp2 = OpVariable %_ptr_UniformConstant_28 UniformConstant", + "%float_0_5 = OpConstant %float 0.5", + "%36 = OpConstantComposite %v2float %float_0_5 %float_0_5", +"%_ptr_Output_v4float = OpTypePointer Output %v4float", + "%FragColor = OpVariable %_ptr_Output_v4float Output", + "%float_3 = OpConstant %float 3", + // clang-format on + }; + + const std::vector nonEntryFuncs = { + // clang-format off + "%foo_vf4_ = OpFunction %float None %20", + "%bar = OpFunctionParameter %_ptr_Function_v4float", + "%58 = OpLabel", + "%r = OpVariable %_ptr_Function_float Function", + "%59 = OpAccessChain %_ptr_Function_float %bar %uint_0", + "%60 = OpLoad %float %59", + "OpStore %r %60", + "%61 = OpLoad %float %r", + "%62 = OpFOrdLessThan %bool %61 %float_0", + "OpSelectionMerge %63 None", + "OpBranchConditional %62 %64 %63", + "%64 = OpLabel", + "%65 = OpLoad %float %r", + "%66 = OpFNegate %float %65", + "OpStore %r %66", + "OpBranch %63", + "%63 = OpLabel", + "%67 = OpLoad %float %r", + "OpReturnValue %67", + "OpFunctionEnd", + // clang-format on + }; + + const std::vector before = { + // clang-format off + "%main = OpFunction %void None %16", + "%39 = OpLabel", + "%color1 = OpVariable %_ptr_Function_v4float Function", + "%color2 = OpVariable %_ptr_Function_v4float Function", + "%param = OpVariable %_ptr_Function_v4float Function", + "%color3 = OpVariable %_ptr_Function_v4float Function", + "%40 = OpLoad %26 %t2D", + "%41 = OpLoad %28 %samp", + "%42 = OpSampledImage %30 %40 %41", + "%43 = OpImageSampleImplicitLod %v4float %42 %33", + "%44 = OpImage %26 %42", + "%45 = OpLoad %28 %samp2", + "OpStore %color1 %43", + "%46 = OpLoad %v4float %BaseColor", + "OpStore %param %46", + "%47 = OpFunctionCall %float %foo_vf4_ %param", + "%48 = OpCompositeConstruct %v4float %47 %47 %47 %47", + "OpStore %color2 %48", + "%49 = OpSampledImage %30 %44 %45", + "%50 = OpImageSampleImplicitLod %v4float %49 %36", + "OpStore %color3 %50", + "%51 = OpLoad %v4float %color1", + "%52 = OpLoad %v4float %color2", + "%53 = OpFAdd %v4float %51 %52", + "%54 = OpLoad %v4float %color3", + "%55 = OpFAdd %v4float %53 %54", + "%56 = OpCompositeConstruct %v4float %float_3 %float_3 %float_3 %float_3", + "%57 = OpFDiv %v4float %55 %56", + "OpStore %FragColor %57", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + + const std::vector after = { + // clang-format off + "%main = OpFunction %void None %16", + "%39 = OpLabel", + "%68 = OpVariable %_ptr_Function_float Function", + "%69 = OpVariable %_ptr_Function_float Function", + "%color1 = OpVariable %_ptr_Function_v4float Function", + "%color2 = OpVariable %_ptr_Function_v4float Function", + "%param = OpVariable %_ptr_Function_v4float Function", + "%color3 = OpVariable %_ptr_Function_v4float Function", + "%40 = OpLoad %26 %t2D", + "%41 = OpLoad %28 %samp", + "%42 = OpSampledImage %30 %40 %41", + "%43 = OpImageSampleImplicitLod %v4float %42 %33", + "%44 = OpImage %26 %42", + "%45 = OpLoad %28 %samp2", + "OpStore %color1 %43", + "%46 = OpLoad %v4float %BaseColor", + "OpStore %param %46", + "%70 = OpAccessChain %_ptr_Function_float %param %uint_0", + "%71 = OpLoad %float %70", + "OpStore %68 %71", + "%72 = OpLoad %float %68", + "%73 = OpFOrdLessThan %bool %72 %float_0", + "OpSelectionMerge %74 None", + "OpBranchConditional %73 %75 %74", + "%75 = OpLabel", + "%76 = OpLoad %float %68", + "%77 = OpFNegate %float %76", + "OpStore %68 %77", + "OpBranch %74", + "%74 = OpLabel", + "%78 = OpLoad %float %68", + "OpStore %69 %78", + "%47 = OpLoad %float %69", + "%48 = OpCompositeConstruct %v4float %47 %47 %47 %47", + "OpStore %color2 %48", + "%79 = OpSampledImage %30 %40 %41", + "%80 = OpImage %26 %79", + "%49 = OpSampledImage %30 %80 %45", + "%50 = OpImageSampleImplicitLod %v4float %49 %36", + "OpStore %color3 %50", + "%51 = OpLoad %v4float %color1", + "%52 = OpLoad %v4float %color2", + "%53 = OpFAdd %v4float %51 %52", + "%54 = OpLoad %v4float %color3", + "%55 = OpFAdd %v4float %53 %54", + "%56 = OpCompositeConstruct %v4float %float_3 %float_3 %float_3 %float_3", + "%57 = OpFDiv %v4float %55 %56", + "OpStore %FragColor %57", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + SinglePassRunAndCheck( + JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)), + JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)), + /* skip_nop = */ false, /* do_validate = */ true); +} + +TEST_F(InlineTest, OpImageAndOpSampledImageOutOfBlock) { + // #version 450 + // + // uniform texture2D t2D; + // uniform sampler samp; + // uniform sampler samp2; + // + // out vec4 FragColor; + // + // in vec4 BaseColor; + // + // float foo(vec4 bar) + // { + // float r = bar.x; + // if (r < 0.0) + // r = -r; + // return r; + // } + // + // void main() + // { + // vec4 color1 = texture(sampler2D(t2D, samp), vec2(1.0)); + // vec4 color2 = vec4(foo(BaseColor)); + // vec4 color3 = texture(sampler2D(t2D, samp2), vec2(0.5)); + // FragColor = (color1 + color2 + color3)/3; + // } + // Note: the before SPIR-V will need to be edited to create an OpImage + // and subsequent OpSampledImage that is used across the function call. + const std::vector predefs = { + // clang-format off + "OpCapability Shader", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Fragment %main \"main\" %BaseColor %FragColor", + "OpExecutionMode %main OriginUpperLeft", + "OpSource GLSL 450", + "OpName %main \"main\"", + "OpName %foo_vf4_ \"foo(vf4;\"", + "OpName %bar \"bar\"", + "OpName %r \"r\"", + "OpName %color1 \"color1\"", + "OpName %t2D \"t2D\"", + "OpName %samp \"samp\"", + "OpName %color2 \"color2\"", + "OpName %BaseColor \"BaseColor\"", + "OpName %param \"param\"", + "OpName %color3 \"color3\"", + "OpName %samp2 \"samp2\"", + "OpName %FragColor \"FragColor\"", + "OpDecorate %t2D DescriptorSet 0", + "OpDecorate %samp DescriptorSet 0", + "OpDecorate %samp2 DescriptorSet 0", + "%void = OpTypeVoid", + "%16 = OpTypeFunction %void", + "%float = OpTypeFloat 32", + "%v4float = OpTypeVector %float 4", +"%_ptr_Function_v4float = OpTypePointer Function %v4float", + "%20 = OpTypeFunction %float %_ptr_Function_v4float", +"%_ptr_Function_float = OpTypePointer Function %float", + "%uint = OpTypeInt 32 0", + "%uint_0 = OpConstant %uint 0", + "%float_0 = OpConstant %float 0", + "%bool = OpTypeBool", + "%26 = OpTypeImage %float 2D 0 0 0 1 Unknown", +"%_ptr_UniformConstant_26 = OpTypePointer UniformConstant %26", + "%t2D = OpVariable %_ptr_UniformConstant_26 UniformConstant", + "%28 = OpTypeSampler", +"%_ptr_UniformConstant_28 = OpTypePointer UniformConstant %28", + "%samp = OpVariable %_ptr_UniformConstant_28 UniformConstant", + "%30 = OpTypeSampledImage %26", + "%v2float = OpTypeVector %float 2", + "%float_1 = OpConstant %float 1", + "%33 = OpConstantComposite %v2float %float_1 %float_1", +"%_ptr_Input_v4float = OpTypePointer Input %v4float", + "%BaseColor = OpVariable %_ptr_Input_v4float Input", + "%samp2 = OpVariable %_ptr_UniformConstant_28 UniformConstant", + "%float_0_5 = OpConstant %float 0.5", + "%36 = OpConstantComposite %v2float %float_0_5 %float_0_5", +"%_ptr_Output_v4float = OpTypePointer Output %v4float", + "%FragColor = OpVariable %_ptr_Output_v4float Output", + "%float_3 = OpConstant %float 3", + // clang-format on + }; + + const std::vector nonEntryFuncs = { + // clang-format off + "%foo_vf4_ = OpFunction %float None %20", + "%bar = OpFunctionParameter %_ptr_Function_v4float", + "%58 = OpLabel", + "%r = OpVariable %_ptr_Function_float Function", + "%59 = OpAccessChain %_ptr_Function_float %bar %uint_0", + "%60 = OpLoad %float %59", + "OpStore %r %60", + "%61 = OpLoad %float %r", + "%62 = OpFOrdLessThan %bool %61 %float_0", + "OpSelectionMerge %63 None", + "OpBranchConditional %62 %64 %63", + "%64 = OpLabel", + "%65 = OpLoad %float %r", + "%66 = OpFNegate %float %65", + "OpStore %r %66", + "OpBranch %63", + "%63 = OpLabel", + "%67 = OpLoad %float %r", + "OpReturnValue %67", + "OpFunctionEnd", + // clang-format on + }; + + const std::vector before = { + // clang-format off + "%main = OpFunction %void None %16", + "%39 = OpLabel", + "%color1 = OpVariable %_ptr_Function_v4float Function", + "%color2 = OpVariable %_ptr_Function_v4float Function", + "%param = OpVariable %_ptr_Function_v4float Function", + "%color3 = OpVariable %_ptr_Function_v4float Function", + "%40 = OpLoad %26 %t2D", + "%41 = OpLoad %28 %samp", + "%42 = OpSampledImage %30 %40 %41", + "%43 = OpImageSampleImplicitLod %v4float %42 %33", + "%44 = OpImage %26 %42", + "%45 = OpLoad %28 %samp2", + "%46 = OpSampledImage %30 %44 %45", + "OpStore %color1 %43", + "%47 = OpLoad %v4float %BaseColor", + "OpStore %param %47", + "%48 = OpFunctionCall %float %foo_vf4_ %param", + "%49 = OpCompositeConstruct %v4float %48 %48 %48 %48", + "OpStore %color2 %49", + "%50 = OpImageSampleImplicitLod %v4float %46 %36", + "OpStore %color3 %50", + "%51 = OpLoad %v4float %color1", + "%52 = OpLoad %v4float %color2", + "%53 = OpFAdd %v4float %51 %52", + "%54 = OpLoad %v4float %color3", + "%55 = OpFAdd %v4float %53 %54", + "%56 = OpCompositeConstruct %v4float %float_3 %float_3 %float_3 %float_3", + "%57 = OpFDiv %v4float %55 %56", + "OpStore %FragColor %57", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + + const std::vector after = { + // clang-format off + "%main = OpFunction %void None %16", + "%39 = OpLabel", + "%68 = OpVariable %_ptr_Function_float Function", + "%69 = OpVariable %_ptr_Function_float Function", + "%color1 = OpVariable %_ptr_Function_v4float Function", + "%color2 = OpVariable %_ptr_Function_v4float Function", + "%param = OpVariable %_ptr_Function_v4float Function", + "%color3 = OpVariable %_ptr_Function_v4float Function", + "%40 = OpLoad %26 %t2D", + "%41 = OpLoad %28 %samp", + "%42 = OpSampledImage %30 %40 %41", + "%43 = OpImageSampleImplicitLod %v4float %42 %33", + "%44 = OpImage %26 %42", + "%45 = OpLoad %28 %samp2", + "%46 = OpSampledImage %30 %44 %45", + "OpStore %color1 %43", + "%47 = OpLoad %v4float %BaseColor", + "OpStore %param %47", + "%70 = OpAccessChain %_ptr_Function_float %param %uint_0", + "%71 = OpLoad %float %70", + "OpStore %68 %71", + "%72 = OpLoad %float %68", + "%73 = OpFOrdLessThan %bool %72 %float_0", + "OpSelectionMerge %74 None", + "OpBranchConditional %73 %75 %74", + "%75 = OpLabel", + "%76 = OpLoad %float %68", + "%77 = OpFNegate %float %76", + "OpStore %68 %77", + "OpBranch %74", + "%74 = OpLabel", + "%78 = OpLoad %float %68", + "OpStore %69 %78", + "%48 = OpLoad %float %69", + "%49 = OpCompositeConstruct %v4float %48 %48 %48 %48", + "OpStore %color2 %49", + "%79 = OpSampledImage %30 %40 %41", + "%80 = OpImage %26 %79", + "%81 = OpSampledImage %30 %80 %45", + "%50 = OpImageSampleImplicitLod %v4float %81 %36", + "OpStore %color3 %50", + "%51 = OpLoad %v4float %color1", + "%52 = OpLoad %v4float %color2", + "%53 = OpFAdd %v4float %51 %52", + "%54 = OpLoad %v4float %color3", + "%55 = OpFAdd %v4float %53 %54", + "%56 = OpCompositeConstruct %v4float %float_3 %float_3 %float_3 %float_3", + "%57 = OpFDiv %v4float %55 %56", + "OpStore %FragColor %57", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + SinglePassRunAndCheck( + JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)), + JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)), + /* skip_nop = */ false, /* do_validate = */ true); +} + +TEST_F(InlineTest, EarlyReturnFunctionInlined) { + // #version 140 + // + // in vec4 BaseColor; + // + // float foo(vec4 bar) + // { + // if (bar.x < 0.0) + // return 0.0; + // return bar.x; + // } + // + // void main() + // { + // vec4 color = vec4(foo(BaseColor)); + // gl_FragColor = color; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %foo_vf4_ "foo(vf4;" +OpName %bar "bar" +OpName %color "color" +OpName %BaseColor "BaseColor" +OpName %param "param" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%10 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%14 = OpTypeFunction %float %_ptr_Function_v4float +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%_ptr_Function_float = OpTypePointer Function %float +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string nonEntryFuncs = + R"(%foo_vf4_ = OpFunction %float None %14 +%bar = OpFunctionParameter %_ptr_Function_v4float +%27 = OpLabel +%28 = OpAccessChain %_ptr_Function_float %bar %uint_0 +%29 = OpLoad %float %28 +%30 = OpFOrdLessThan %bool %29 %float_0 +OpSelectionMerge %31 None +OpBranchConditional %30 %32 %31 +%32 = OpLabel +OpReturnValue %float_0 +%31 = OpLabel +%33 = OpAccessChain %_ptr_Function_float %bar %uint_0 +%34 = OpLoad %float %33 +OpReturnValue %34 +OpFunctionEnd +)"; + + const std::string before = + R"(%main = OpFunction %void None %10 +%22 = OpLabel +%color = OpVariable %_ptr_Function_v4float Function +%param = OpVariable %_ptr_Function_v4float Function +%23 = OpLoad %v4float %BaseColor +OpStore %param %23 +%24 = OpFunctionCall %float %foo_vf4_ %param +%25 = OpCompositeConstruct %v4float %24 %24 %24 %24 +OpStore %color %25 +%26 = OpLoad %v4float %color +OpStore %gl_FragColor %26 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%false = OpConstantFalse %bool +%main = OpFunction %void None %10 +%22 = OpLabel +%35 = OpVariable %_ptr_Function_float Function +%color = OpVariable %_ptr_Function_v4float Function +%param = OpVariable %_ptr_Function_v4float Function +%23 = OpLoad %v4float %BaseColor +OpStore %param %23 +OpBranch %36 +%36 = OpLabel +OpLoopMerge %37 %38 None +OpBranch %39 +%39 = OpLabel +%40 = OpAccessChain %_ptr_Function_float %param %uint_0 +%41 = OpLoad %float %40 +%42 = OpFOrdLessThan %bool %41 %float_0 +OpSelectionMerge %43 None +OpBranchConditional %42 %44 %43 +%44 = OpLabel +OpStore %35 %float_0 +OpBranch %37 +%43 = OpLabel +%45 = OpAccessChain %_ptr_Function_float %param %uint_0 +%46 = OpLoad %float %45 +OpStore %35 %46 +OpBranch %37 +%38 = OpLabel +OpBranchConditional %false %36 %37 +%37 = OpLabel +%24 = OpLoad %float %35 +%25 = OpCompositeConstruct %v4float %24 %24 %24 %24 +OpStore %color %25 +%26 = OpLoad %v4float %color +OpStore %gl_FragColor %26 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before + nonEntryFuncs, predefs + after + nonEntryFuncs, false, + true); +} + +TEST_F(InlineTest, EarlyReturnNotAppearingLastInFunctionInlined) { + // Example from https://github.com/KhronosGroup/SPIRV-Tools/issues/755 + // + // Original example is derived from: + // + // #version 450 + // + // float foo() { + // if (true) { + // } + // } + // + // void main() { foo(); } + // + // But the order of basic blocks in foo is changed so that the return + // block is listed second-last. There is only one return in the callee + // but it does not appear last. + + const std::string predefs = + R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %main "main" +OpSource GLSL 450 +OpName %main "main" +OpName %foo_ "foo(" +%void = OpTypeVoid +%4 = OpTypeFunction %void +%bool = OpTypeBool +%true = OpConstantTrue %bool +)"; + + const std::string nonEntryFuncs = + R"(%foo_ = OpFunction %void None %4 +%7 = OpLabel +OpSelectionMerge %8 None +OpBranchConditional %true %9 %8 +%8 = OpLabel +OpReturn +%9 = OpLabel +OpBranch %8 +OpFunctionEnd +)"; + + const std::string before = + R"(%main = OpFunction %void None %4 +%10 = OpLabel +%11 = OpFunctionCall %void %foo_ +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %4 +%10 = OpLabel +OpSelectionMerge %12 None +OpBranchConditional %true %13 %12 +%12 = OpLabel +OpBranch %14 +%13 = OpLabel +OpBranch %12 +%14 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + nonEntryFuncs + before, predefs + nonEntryFuncs + after, false, + true); +} + +TEST_F(InlineTest, ForwardReferencesInPhiInlined) { + // The basic structure of the test case is like this: + // + // int foo() { + // int result = 1; + // if (true) { + // result = 1; + // } + // return result; + // } + // + // void main() { + // int x = foo(); + // } + // + // but with modifications: Using Phi instead of load/store, and the + // return block in foo appears before the "then" block. + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %main "main" +OpSource GLSL 450 +OpName %main "main" +OpName %foo_ "foo(" +OpName %x "x" +%void = OpTypeVoid +%6 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%8 = OpTypeFunction %int +%bool = OpTypeBool +%true = OpConstantTrue %bool +%int_0 = OpConstant %int 0 +%_ptr_Function_int = OpTypePointer Function %int +)"; + + const std::string nonEntryFuncs = + R"(%foo_ = OpFunction %int None %8 +%13 = OpLabel +%14 = OpCopyObject %int %int_0 +OpSelectionMerge %15 None +OpBranchConditional %true %16 %15 +%15 = OpLabel +%17 = OpPhi %int %14 %13 %18 %16 +OpReturnValue %17 +%16 = OpLabel +%18 = OpCopyObject %int %int_0 +OpBranch %15 +OpFunctionEnd +)"; + + const std::string before = + R"(%main = OpFunction %void None %6 +%19 = OpLabel +%x = OpVariable %_ptr_Function_int Function +%20 = OpFunctionCall %int %foo_ +OpStore %x %20 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %6 +%19 = OpLabel +%21 = OpVariable %_ptr_Function_int Function +%x = OpVariable %_ptr_Function_int Function +%22 = OpCopyObject %int %int_0 +OpSelectionMerge %23 None +OpBranchConditional %true %24 %23 +%23 = OpLabel +%26 = OpPhi %int %22 %19 %25 %24 +OpStore %21 %26 +OpBranch %27 +%24 = OpLabel +%25 = OpCopyObject %int %int_0 +OpBranch %23 +%27 = OpLabel +%20 = OpLoad %int %21 +OpStore %x %20 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + nonEntryFuncs + before, predefs + nonEntryFuncs + after, false, + true); +} + +TEST_F(InlineTest, EarlyReturnInLoopIsNotInlined) { + // #version 140 + // + // in vec4 BaseColor; + // + // float foo(vec4 bar) + // { + // while (true) { + // if (bar.x < 0.0) + // return 0.0; + // return bar.x; + // } + // } + // + // void main() + // { + // vec4 color = vec4(foo(BaseColor)); + // gl_FragColor = color; + // } + + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %foo_vf4_ "foo(vf4;" +OpName %bar "bar" +OpName %color "color" +OpName %BaseColor "BaseColor" +OpName %param "param" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%10 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%14 = OpTypeFunction %float %_ptr_Function_v4float +%bool = OpTypeBool +%true = OpConstantTrue %bool +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%_ptr_Function_float = OpTypePointer Function %float +%float_0 = OpConstant %float 0 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %10 +%23 = OpLabel +%color = OpVariable %_ptr_Function_v4float Function +%param = OpVariable %_ptr_Function_v4float Function +%24 = OpLoad %v4float %BaseColor +OpStore %param %24 +%25 = OpFunctionCall %float %foo_vf4_ %param +%26 = OpCompositeConstruct %v4float %25 %25 %25 %25 +OpStore %color %26 +%27 = OpLoad %v4float %color +OpStore %gl_FragColor %27 +OpReturn +OpFunctionEnd +%foo_vf4_ = OpFunction %float None %14 +%bar = OpFunctionParameter %_ptr_Function_v4float +%28 = OpLabel +OpBranch %29 +%29 = OpLabel +OpLoopMerge %30 %31 None +OpBranch %32 +%32 = OpLabel +OpBranchConditional %true %33 %30 +%33 = OpLabel +%34 = OpAccessChain %_ptr_Function_float %bar %uint_0 +%35 = OpLoad %float %34 +%36 = OpFOrdLessThan %bool %35 %float_0 +OpSelectionMerge %37 None +OpBranchConditional %36 %38 %37 +%38 = OpLabel +OpReturnValue %float_0 +%37 = OpLabel +%39 = OpAccessChain %_ptr_Function_float %bar %uint_0 +%40 = OpLoad %float %39 +OpReturnValue %40 +%31 = OpLabel +OpBranch %29 +%30 = OpLabel +%41 = OpUndef %float +OpReturnValue %41 +OpFunctionEnd +)"; + + SinglePassRunAndCheck(assembly, assembly, false, + true); +} + +TEST_F(InlineTest, ExternalFunctionIsNotInlined) { + // In particular, don't crash. + // See report https://github.com/KhronosGroup/SPIRV-Tools/issues/605 + const std::string assembly = + R"(OpCapability Addresses +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Physical32 OpenCL +OpEntryPoint Kernel %1 "entry_pt" +OpDecorate %2 LinkageAttributes "external" Import +%void = OpTypeVoid +%4 = OpTypeFunction %void +%2 = OpFunction %void None %4 +OpFunctionEnd +%1 = OpFunction %void None %4 +%5 = OpLabel +%6 = OpFunctionCall %void %2 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(assembly, assembly, false, + true); +} + +TEST_F(InlineTest, SingleBlockLoopCallsMultiBlockCallee) { + // Example from https://github.com/KhronosGroup/SPIRV-Tools/issues/787 + // + // CFG structure is: + // foo: + // fooentry -> fooexit + // + // main: + // entry -> loop + // loop -> loop, merge + // loop calls foo() + // merge + // + // Since the callee has multiple blocks, it will split the calling block + // into at least two, resulting in a new "back-half" block that contains + // the instructions after the inlined function call. If the calling block + // has an OpLoopMerge that points back to the calling block itself, then + // the OpLoopMerge can't remain in the back-half block, but must be + // moved to the end of the original calling block, and it continue target + // operand updated to point to the back-half block. + + const std::string predefs = + R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %1 "main" +OpSource OpenCL_C 120 +%bool = OpTypeBool +%true = OpConstantTrue %bool +%void = OpTypeVoid +)"; + + const std::string nonEntryFuncs = + R"(%5 = OpTypeFunction %void +%6 = OpFunction %void None %5 +%7 = OpLabel +OpBranch %8 +%8 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string before = + R"(%1 = OpFunction %void None %5 +%9 = OpLabel +OpBranch %10 +%10 = OpLabel +%11 = OpFunctionCall %void %6 +OpLoopMerge %12 %10 None +OpBranchConditional %true %10 %12 +%12 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%1 = OpFunction %void None %5 +%9 = OpLabel +OpBranch %10 +%10 = OpLabel +OpLoopMerge %12 %13 None +OpBranch %13 +%13 = OpLabel +OpBranchConditional %true %10 %12 +%12 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + nonEntryFuncs + before, predefs + nonEntryFuncs + after, false, + true); +} + +TEST_F(InlineTest, MultiBlockLoopHeaderCallsMultiBlockCallee) { + // Like SingleBlockLoopCallsMultiBlockCallee but the loop has several + // blocks, but the function call still occurs in the loop header. + // Example from https://github.com/KhronosGroup/SPIRV-Tools/issues/800 + + const std::string predefs = + R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %1 "main" +OpSource OpenCL_C 120 +%bool = OpTypeBool +%true = OpConstantTrue %bool +%int = OpTypeInt 32 1 +%int_1 = OpConstant %int 1 +%int_2 = OpConstant %int 2 +%int_3 = OpConstant %int 3 +%int_4 = OpConstant %int 4 +%int_5 = OpConstant %int 5 +%void = OpTypeVoid +%11 = OpTypeFunction %void +)"; + + const std::string nonEntryFuncs = + R"(%12 = OpFunction %void None %11 +%13 = OpLabel +%14 = OpCopyObject %int %int_1 +OpBranch %15 +%15 = OpLabel +%16 = OpCopyObject %int %int_2 +OpReturn +OpFunctionEnd +)"; + + const std::string before = + R"(%1 = OpFunction %void None %11 +%17 = OpLabel +OpBranch %18 +%18 = OpLabel +%19 = OpCopyObject %int %int_3 +%20 = OpFunctionCall %void %12 +%21 = OpCopyObject %int %int_4 +OpLoopMerge %22 %23 None +OpBranchConditional %true %23 %22 +%23 = OpLabel +%24 = OpCopyObject %int %int_5 +OpBranchConditional %true %18 %22 +%22 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%1 = OpFunction %void None %11 +%17 = OpLabel +OpBranch %18 +%18 = OpLabel +%19 = OpCopyObject %int %int_3 +%25 = OpCopyObject %int %int_1 +OpLoopMerge %22 %23 None +OpBranch %26 +%26 = OpLabel +%27 = OpCopyObject %int %int_2 +%21 = OpCopyObject %int %int_4 +OpBranchConditional %true %23 %22 +%23 = OpLabel +%24 = OpCopyObject %int %int_5 +OpBranchConditional %true %18 %22 +%22 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + nonEntryFuncs + before, predefs + nonEntryFuncs + after, false, + true); +} + +TEST_F(InlineTest, SingleBlockLoopCallsMultiBlockCalleeHavingSelectionMerge) { + // This is similar to SingleBlockLoopCallsMultiBlockCallee except + // that calleee block also has a merge instruction in its first block. + // That merge instruction must be an OpSelectionMerge (because the entry + // block of a function can't be the header of a loop since the entry + // block can't be the target of a branch). + // + // In this case the OpLoopMerge can't be placed in the same block as + // the OpSelectionMerge, so inlining must create a new block to contain + // the callee contents. + // + // Additionally, we have two dummy OpCopyObject instructions to prove that + // the OpLoopMerge is moved to the right location. + // + // Also ensure that OpPhis within the cloned callee code are valid. + // We need to test that the predecessor blocks are remapped correctly so that + // dominance rules are satisfied + + const std::string predefs = + R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %1 "main" +OpSource OpenCL_C 120 +%bool = OpTypeBool +%true = OpConstantTrue %bool +%false = OpConstantFalse %bool +%void = OpTypeVoid +%6 = OpTypeFunction %void +)"; + + // This callee has multiple blocks, and an OpPhi in the last block + // that references a value from the first block. This tests that + // cloned block IDs are remapped appropriately. The OpPhi dominance + // requires that the remapped %9 must be in a block that dominates + // the remapped %8. + const std::string nonEntryFuncs = + R"(%7 = OpFunction %void None %6 +%8 = OpLabel +%9 = OpCopyObject %bool %true +OpSelectionMerge %10 None +OpBranchConditional %true %10 %10 +%10 = OpLabel +%11 = OpPhi %bool %9 %8 +OpReturn +OpFunctionEnd +)"; + + const std::string before = + R"(%1 = OpFunction %void None %6 +%12 = OpLabel +OpBranch %13 +%13 = OpLabel +%14 = OpCopyObject %bool %false +%15 = OpFunctionCall %void %7 +OpLoopMerge %16 %13 None +OpBranchConditional %true %13 %16 +%16 = OpLabel +OpReturn +OpFunctionEnd +)"; + + // Note the remapped Phi uses %17 as the parent instead + // of %13, demonstrating that the parent block has been remapped + // correctly. + const std::string after = + R"(%1 = OpFunction %void None %6 +%12 = OpLabel +OpBranch %13 +%13 = OpLabel +%14 = OpCopyObject %bool %false +OpLoopMerge %16 %19 None +OpBranch %17 +%17 = OpLabel +%18 = OpCopyObject %bool %true +OpSelectionMerge %19 None +OpBranchConditional %true %19 %19 +%19 = OpLabel +%20 = OpPhi %bool %18 %17 +OpBranchConditional %true %13 %16 +%16 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + nonEntryFuncs + before, predefs + nonEntryFuncs + after, false, + true); +} + +TEST_F(InlineTest, + MultiBlockLoopHeaderCallsFromToMultiBlockCalleeHavingSelectionMerge) { + // This is similar to SingleBlockLoopCallsMultiBlockCalleeHavingSelectionMerge + // but the call is in the header block of a multi block loop. + + const std::string predefs = + R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %1 "main" +OpSource OpenCL_C 120 +%bool = OpTypeBool +%true = OpConstantTrue %bool +%int = OpTypeInt 32 1 +%int_1 = OpConstant %int 1 +%int_2 = OpConstant %int 2 +%int_3 = OpConstant %int 3 +%int_4 = OpConstant %int 4 +%int_5 = OpConstant %int 5 +%void = OpTypeVoid +%11 = OpTypeFunction %void +)"; + + const std::string nonEntryFuncs = + R"(%12 = OpFunction %void None %11 +%13 = OpLabel +%14 = OpCopyObject %int %int_1 +OpSelectionMerge %15 None +OpBranchConditional %true %15 %15 +%15 = OpLabel +%16 = OpCopyObject %int %int_2 +OpReturn +OpFunctionEnd +)"; + + const std::string before = + R"(%1 = OpFunction %void None %11 +%17 = OpLabel +OpBranch %18 +%18 = OpLabel +%19 = OpCopyObject %int %int_3 +%20 = OpFunctionCall %void %12 +%21 = OpCopyObject %int %int_4 +OpLoopMerge %22 %23 None +OpBranchConditional %true %23 %22 +%23 = OpLabel +%24 = OpCopyObject %int %int_5 +OpBranchConditional %true %18 %22 +%22 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%1 = OpFunction %void None %11 +%17 = OpLabel +OpBranch %18 +%18 = OpLabel +%19 = OpCopyObject %int %int_3 +OpLoopMerge %22 %23 None +OpBranch %25 +%25 = OpLabel +%26 = OpCopyObject %int %int_1 +OpSelectionMerge %27 None +OpBranchConditional %true %27 %27 +%27 = OpLabel +%28 = OpCopyObject %int %int_2 +%21 = OpCopyObject %int %int_4 +OpBranchConditional %true %23 %22 +%23 = OpLabel +%24 = OpCopyObject %int %int_5 +OpBranchConditional %true %18 %22 +%22 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + nonEntryFuncs + before, predefs + nonEntryFuncs + after, false, + true); +} + +TEST_F( + InlineTest, + SingleBlockLoopCallsMultiBlockCalleeHavingSelectionMergeAndMultiReturns) { + // This is similar to SingleBlockLoopCallsMultiBlockCalleeHavingSelectionMerge + // except that in addition to starting with a selection header, the + // callee also has multi returns. + // + // So now we have to accommodate: + // - The caller's OpLoopMerge (which must move to the first block) + // - The single-trip loop to wrap the multi returns, and + // - The callee's selection merge in its first block. + // Each of these must go into their own blocks. + + const std::string predefs = + R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %1 "main" +OpSource OpenCL_C 120 +%bool = OpTypeBool +%int = OpTypeInt 32 1 +%true = OpConstantTrue %bool +%false = OpConstantFalse %bool +%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 +%void = OpTypeVoid +%12 = OpTypeFunction %void +)"; + + const std::string nonEntryFuncs = + R"(%13 = OpFunction %void None %12 +%14 = OpLabel +%15 = OpCopyObject %int %int_0 +OpReturn +%16 = OpLabel +%17 = OpCopyObject %int %int_1 +OpReturn +OpFunctionEnd +)"; + + const std::string before = + R"(%1 = OpFunction %void None %12 +%18 = OpLabel +OpBranch %19 +%19 = OpLabel +%20 = OpCopyObject %int %int_2 +%21 = OpFunctionCall %void %13 +%22 = OpCopyObject %int %int_3 +OpLoopMerge %23 %19 None +OpBranchConditional %true %19 %23 +%23 = OpLabel +%24 = OpCopyObject %int %int_4 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%1 = OpFunction %void None %12 +%18 = OpLabel +OpBranch %19 +%19 = OpLabel +%20 = OpCopyObject %int %int_2 +OpLoopMerge %23 %26 None +OpBranch %25 +%25 = OpLabel +OpLoopMerge %26 %27 None +OpBranch %28 +%28 = OpLabel +%29 = OpCopyObject %int %int_0 +OpBranch %26 +%30 = OpLabel +%31 = OpCopyObject %int %int_1 +OpBranch %26 +%27 = OpLabel +OpBranchConditional %false %25 %26 +%26 = OpLabel +%22 = OpCopyObject %int %int_3 +OpBranchConditional %true %19 %23 +%23 = OpLabel +%24 = OpCopyObject %int %int_4 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + nonEntryFuncs + before, predefs + nonEntryFuncs + after, false, + true); +} + +TEST_F(InlineTest, CalleeWithMultiReturnAndPhiRequiresEntryBlockRemapping) { + // The case from https://github.com/KhronosGroup/SPIRV-Tools/issues/790 + // + // The callee has multiple returns, and so must be wrapped with a single-trip + // loop. That code must remap the callee entry block ID to the introduced + // loop body's ID. Otherwise you can get a dominance error in a cloned OpPhi. + + const std::string predefs = + R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %1 "main" +OpSource OpenCL_C 120 +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%int_1 = OpConstant %int 1 +%int_2 = OpConstant %int 2 +%int_3 = OpConstant %int 3 +%int_4 = OpConstant %int 4 +%void = OpTypeVoid +%9 = OpTypeFunction %void +%bool = OpTypeBool +%false = OpConstantFalse %bool +)"; + + // This callee has multiple returns, and a Phi in the second block referencing + // a value generated in the entry block. + const std::string nonEntryFuncs = + R"(%12 = OpFunction %void None %9 +%13 = OpLabel +%14 = OpCopyObject %int %int_0 +OpBranch %15 +%15 = OpLabel +%16 = OpPhi %int %14 %13 +%17 = OpCopyObject %int %int_1 +OpReturn +%18 = OpLabel +%19 = OpCopyObject %int %int_2 +OpReturn +OpFunctionEnd +)"; + + const std::string before = + R"(%1 = OpFunction %void None %9 +%20 = OpLabel +%21 = OpCopyObject %int %int_3 +%22 = OpFunctionCall %void %12 +%23 = OpCopyObject %int %int_4 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%1 = OpFunction %void None %9 +%20 = OpLabel +%21 = OpCopyObject %int %int_3 +OpBranch %24 +%24 = OpLabel +OpLoopMerge %25 %26 None +OpBranch %27 +%27 = OpLabel +%28 = OpCopyObject %int %int_0 +OpBranch %29 +%29 = OpLabel +%30 = OpPhi %int %28 %27 +%31 = OpCopyObject %int %int_1 +OpBranch %25 +%32 = OpLabel +%33 = OpCopyObject %int %int_2 +OpBranch %25 +%26 = OpLabel +OpBranchConditional %false %24 %25 +%25 = OpLabel +%23 = OpCopyObject %int %int_4 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + nonEntryFuncs + before, predefs + nonEntryFuncs + after, false, + true); +} + +TEST_F(InlineTest, Decorated1) { + // Same test as Simple with the difference + // that OpFAdd in the outlined function is + // decorated with RelaxedPrecision + // Expected result is an equal decoration + // of the corresponding inlined instruction + // + // #version 140 + // + // in vec4 BaseColor; + // + // float foo(vec4 bar) + // { + // return bar.x + bar.y; + // } + // + // void main() + // { + // vec4 color = vec4(foo(BaseColor)); + // gl_FragColor = color; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %foo_vf4_ "foo(vf4;" +OpName %bar "bar" +OpName %color "color" +OpName %BaseColor "BaseColor" +OpName %param "param" +OpName %gl_FragColor "gl_FragColor" +OpDecorate %9 RelaxedPrecision +)"; + + const std::string before = + R"(%void = OpTypeVoid +%11 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%15 = OpTypeFunction %float %_ptr_Function_v4float +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%_ptr_Function_float = OpTypePointer Function %float +%uint_1 = OpConstant %uint 1 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %11 +%22 = OpLabel +%color = OpVariable %_ptr_Function_v4float Function +%param = OpVariable %_ptr_Function_v4float Function +%23 = OpLoad %v4float %BaseColor +OpStore %param %23 +%24 = OpFunctionCall %float %foo_vf4_ %param +%25 = OpCompositeConstruct %v4float %24 %24 %24 %24 +OpStore %color %25 +%26 = OpLoad %v4float %color +OpStore %gl_FragColor %26 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(OpDecorate %37 RelaxedPrecision +%void = OpTypeVoid +%11 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%15 = OpTypeFunction %float %_ptr_Function_v4float +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%_ptr_Function_float = OpTypePointer Function %float +%uint_1 = OpConstant %uint 1 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %11 +%22 = OpLabel +%32 = OpVariable %_ptr_Function_float Function +%color = OpVariable %_ptr_Function_v4float Function +%param = OpVariable %_ptr_Function_v4float Function +%23 = OpLoad %v4float %BaseColor +OpStore %param %23 +%33 = OpAccessChain %_ptr_Function_float %param %uint_0 +%34 = OpLoad %float %33 +%35 = OpAccessChain %_ptr_Function_float %param %uint_1 +%36 = OpLoad %float %35 +%37 = OpFAdd %float %34 %36 +OpStore %32 %37 +%24 = OpLoad %float %32 +%25 = OpCompositeConstruct %v4float %24 %24 %24 %24 +OpStore %color %25 +%26 = OpLoad %v4float %color +OpStore %gl_FragColor %26 +OpReturn +OpFunctionEnd +)"; + + const std::string nonEntryFuncs = + R"(%foo_vf4_ = OpFunction %float None %15 +%bar = OpFunctionParameter %_ptr_Function_v4float +%27 = OpLabel +%28 = OpAccessChain %_ptr_Function_float %bar %uint_0 +%29 = OpLoad %float %28 +%30 = OpAccessChain %_ptr_Function_float %bar %uint_1 +%31 = OpLoad %float %30 +%9 = OpFAdd %float %29 %31 +OpReturnValue %9 +OpFunctionEnd +)"; + SinglePassRunAndCheck( + predefs + before + nonEntryFuncs, predefs + after + nonEntryFuncs, false, + true); +} + +TEST_F(InlineTest, Decorated2) { + // Same test as Simple with the difference + // that the Result of the outlined OpFunction + // is decorated with RelaxedPrecision + // Expected result is an equal decoration + // of the created return variable + // + // #version 140 + // + // in vec4 BaseColor; + // + // float foo(vec4 bar) + // { + // return bar.x + bar.y; + // } + // + // void main() + // { + // vec4 color = vec4(foo(BaseColor)); + // gl_FragColor = color; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %foo_vf4_ "foo(vf4;" +OpName %bar "bar" +OpName %color "color" +OpName %BaseColor "BaseColor" +OpName %param "param" +OpName %gl_FragColor "gl_FragColor" +OpDecorate %foo_vf4_ RelaxedPrecision +)"; + + const std::string before = + R"(%void = OpTypeVoid +%10 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%14 = OpTypeFunction %float %_ptr_Function_v4float +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%_ptr_Function_float = OpTypePointer Function %float +%uint_1 = OpConstant %uint 1 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %10 +%21 = OpLabel +%color = OpVariable %_ptr_Function_v4float Function +%param = OpVariable %_ptr_Function_v4float Function +%22 = OpLoad %v4float %BaseColor +OpStore %param %22 +%23 = OpFunctionCall %float %foo_vf4_ %param +%24 = OpCompositeConstruct %v4float %23 %23 %23 %23 +OpStore %color %24 +%25 = OpLoad %v4float %color +OpStore %gl_FragColor %25 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(OpDecorate %32 RelaxedPrecision +%void = OpTypeVoid +%10 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%14 = OpTypeFunction %float %_ptr_Function_v4float +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%_ptr_Function_float = OpTypePointer Function %float +%uint_1 = OpConstant %uint 1 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %10 +%21 = OpLabel +%32 = OpVariable %_ptr_Function_float Function +%color = OpVariable %_ptr_Function_v4float Function +%param = OpVariable %_ptr_Function_v4float Function +%22 = OpLoad %v4float %BaseColor +OpStore %param %22 +%33 = OpAccessChain %_ptr_Function_float %param %uint_0 +%34 = OpLoad %float %33 +%35 = OpAccessChain %_ptr_Function_float %param %uint_1 +%36 = OpLoad %float %35 +%37 = OpFAdd %float %34 %36 +OpStore %32 %37 +%23 = OpLoad %float %32 +%24 = OpCompositeConstruct %v4float %23 %23 %23 %23 +OpStore %color %24 +%25 = OpLoad %v4float %color +OpStore %gl_FragColor %25 +OpReturn +OpFunctionEnd +)"; + + const std::string nonEntryFuncs = + R"(%foo_vf4_ = OpFunction %float None %14 +%bar = OpFunctionParameter %_ptr_Function_v4float +%26 = OpLabel +%27 = OpAccessChain %_ptr_Function_float %bar %uint_0 +%28 = OpLoad %float %27 +%29 = OpAccessChain %_ptr_Function_float %bar %uint_1 +%30 = OpLoad %float %29 +%31 = OpFAdd %float %28 %30 +OpReturnValue %31 +OpFunctionEnd +)"; + SinglePassRunAndCheck( + predefs + before + nonEntryFuncs, predefs + after + nonEntryFuncs, false, + true); +} + +TEST_F(InlineTest, DeleteName) { + // Test that the name of the result id of the call is deleted. + const std::string before = + R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" + OpName %main "main" + OpName %main_entry "main_entry" + OpName %foo_result "foo_result" + OpName %void_fn "void_fn" + OpName %foo "foo" + OpName %foo_entry "foo_entry" + %void = OpTypeVoid + %void_fn = OpTypeFunction %void + %foo = OpFunction %void None %void_fn + %foo_entry = OpLabel + OpReturn + OpFunctionEnd + %main = OpFunction %void None %void_fn + %main_entry = OpLabel + %foo_result = OpFunctionCall %void %foo + OpReturn + OpFunctionEnd +)"; + + const std::string after = + R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %main "main" +OpName %main "main" +OpName %main_entry "main_entry" +OpName %void_fn "void_fn" +OpName %foo "foo" +OpName %foo_entry "foo_entry" +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%foo = OpFunction %void None %void_fn +%foo_entry = OpLabel +OpReturn +OpFunctionEnd +%main = OpFunction %void None %void_fn +%main_entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(before, after, false, true); +} + +TEST_F(InlineTest, SetParent) { + // Test that after inlining all basic blocks have the correct parent. + const std::string text = + R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" + OpName %main "main" + OpName %main_entry "main_entry" + OpName %foo_result "foo_result" + OpName %void_fn "void_fn" + OpName %foo "foo" + OpName %foo_entry "foo_entry" + %void = OpTypeVoid + %void_fn = OpTypeFunction %void + %foo = OpFunction %void None %void_fn + %foo_entry = OpLabel + OpReturn + OpFunctionEnd + %main = OpFunction %void None %void_fn + %main_entry = OpLabel + %foo_result = OpFunctionCall %void %foo + OpReturn + OpFunctionEnd +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + opt::InlineExhaustivePass pass; + pass.Run(context.get()); + + for (ir::Function& func : *context->module()) { + for (ir::BasicBlock& bb : func) { + EXPECT_TRUE(bb.GetParent() == &func); + } + } +} + +// TODO(greg-lunarg): Add tests to verify handling of these cases: +// +// Empty modules +// Modules without function definitions +// Modules in which all functions do not call other functions +// Recursive functions (calling self & calling each other) +// Caller and callee both accessing the same global variable +// Functions with OpLine & OpNoLine +// Others? + +// TODO(dneto): Test suggestions from code review +// https://github.com/KhronosGroup/SPIRV-Tools/pull/534 +// +// Callee function returns a value generated outside the callee, +// e.g. a constant value. This might exercise some logic not yet +// exercised by the current tests: the false branch in the "if" +// inside the SpvOpReturnValue case in InlinePass::GenInlineCode? +// SampledImage before function call, but callee is only single block. +// Then the SampledImage instruction is not cloned. Documents existing +// behaviour. +// SampledImage after function call. It is not cloned or changed. + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/insert_extract_elim_test.cpp b/3rdparty/spirv-tools/test/opt/insert_extract_elim_test.cpp new file mode 100644 index 000000000..afb0036f7 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/insert_extract_elim_test.cpp @@ -0,0 +1,863 @@ +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; + +using InsertExtractElimTest = PassTest<::testing::Test>; + +TEST_F(InsertExtractElimTest, Simple) { + // Note: The SPIR-V assembly has had store/load elimination + // performed to allow the inserts and extracts to directly + // reference each other. + // + // #version 140 + // + // in vec4 BaseColor; + // + // struct S_t { + // vec4 v0; + // vec4 v1; + // }; + // + // void main() + // { + // S_t s0; + // s0.v1 = BaseColor; + // gl_FragColor = s0.v1; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %S_t "S_t" +OpMemberName %S_t 0 "v0" +OpMemberName %S_t 1 "v1" +OpName %s0 "s0" +OpName %BaseColor "BaseColor" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%S_t = OpTypeStruct %v4float %v4float +%_ptr_Function_S_t = OpTypePointer Function %S_t +%int = OpTypeInt 32 1 +%int_1 = OpConstant %int 1 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %8 +%17 = OpLabel +%s0 = OpVariable %_ptr_Function_S_t Function +%18 = OpLoad %v4float %BaseColor +%19 = OpLoad %S_t %s0 +%20 = OpCompositeInsert %S_t %18 %19 1 +OpStore %s0 %20 +%21 = OpCompositeExtract %v4float %20 1 +OpStore %gl_FragColor %21 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %8 +%17 = OpLabel +%s0 = OpVariable %_ptr_Function_S_t Function +%18 = OpLoad %v4float %BaseColor +%19 = OpLoad %S_t %s0 +%20 = OpCompositeInsert %S_t %18 %19 1 +OpStore %s0 %20 +OpStore %gl_FragColor %18 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(InsertExtractElimTest, OptimizeAcrossNonConflictingInsert) { + // Note: The SPIR-V assembly has had store/load elimination + // performed to allow the inserts and extracts to directly + // reference each other. + // + // #version 140 + // + // in vec4 BaseColor; + // + // struct S_t { + // vec4 v0; + // vec4 v1; + // }; + // + // void main() + // { + // S_t s0; + // s0.v1 = BaseColor; + // s0.v0[2] = 0.0; + // gl_FragColor = s0.v1; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %S_t "S_t" +OpMemberName %S_t 0 "v0" +OpMemberName %S_t 1 "v1" +OpName %s0 "s0" +OpName %BaseColor "BaseColor" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%S_t = OpTypeStruct %v4float %v4float +%_ptr_Function_S_t = OpTypePointer Function %S_t +%int = OpTypeInt 32 1 +%int_1 = OpConstant %int 1 +%float_0 = OpConstant %float 0 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %8 +%18 = OpLabel +%s0 = OpVariable %_ptr_Function_S_t Function +%19 = OpLoad %v4float %BaseColor +%20 = OpLoad %S_t %s0 +%21 = OpCompositeInsert %S_t %19 %20 1 +%22 = OpCompositeInsert %S_t %float_0 %21 0 2 +OpStore %s0 %22 +%23 = OpCompositeExtract %v4float %22 1 +OpStore %gl_FragColor %23 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %8 +%18 = OpLabel +%s0 = OpVariable %_ptr_Function_S_t Function +%19 = OpLoad %v4float %BaseColor +%20 = OpLoad %S_t %s0 +%21 = OpCompositeInsert %S_t %19 %20 1 +%22 = OpCompositeInsert %S_t %float_0 %21 0 2 +OpStore %s0 %22 +OpStore %gl_FragColor %19 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(InsertExtractElimTest, OptimizeOpaque) { + // SPIR-V not representable in GLSL; not generatable from HLSL + // for the moment. + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %outColor %texCoords +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %S_t "S_t" +OpMemberName %S_t 0 "v0" +OpMemberName %S_t 1 "v1" +OpMemberName %S_t 2 "smp" +OpName %outColor "outColor" +OpName %sampler15 "sampler15" +OpName %s0 "s0" +OpName %texCoords "texCoords" +OpDecorate %sampler15 DescriptorSet 0 +%void = OpTypeVoid +%9 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v2float = OpTypeVector %float 2 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%outColor = OpVariable %_ptr_Output_v4float Output +%14 = OpTypeImage %float 2D 0 0 0 1 Unknown +%15 = OpTypeSampledImage %14 +%S_t = OpTypeStruct %v2float %v2float %15 +%_ptr_Function_S_t = OpTypePointer Function %S_t +%17 = OpTypeFunction %void %_ptr_Function_S_t +%_ptr_UniformConstant_15 = OpTypePointer UniformConstant %15 +%_ptr_Function_15 = OpTypePointer Function %15 +%sampler15 = OpVariable %_ptr_UniformConstant_15 UniformConstant +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%int_2 = OpConstant %int 2 +%_ptr_Function_v2float = OpTypePointer Function %v2float +%_ptr_Input_v2float = OpTypePointer Input %v2float +%texCoords = OpVariable %_ptr_Input_v2float Input +)"; + + const std::string before = + R"(%main = OpFunction %void None %9 +%25 = OpLabel +%s0 = OpVariable %_ptr_Function_S_t Function +%26 = OpLoad %v2float %texCoords +%27 = OpLoad %S_t %s0 +%28 = OpCompositeInsert %S_t %26 %27 0 +%29 = OpLoad %15 %sampler15 +%30 = OpCompositeInsert %S_t %29 %28 2 +OpStore %s0 %30 +%31 = OpCompositeExtract %15 %30 2 +%32 = OpCompositeExtract %v2float %30 0 +%33 = OpImageSampleImplicitLod %v4float %31 %32 +OpStore %outColor %33 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %9 +%25 = OpLabel +%s0 = OpVariable %_ptr_Function_S_t Function +%26 = OpLoad %v2float %texCoords +%27 = OpLoad %S_t %s0 +%28 = OpCompositeInsert %S_t %26 %27 0 +%29 = OpLoad %15 %sampler15 +%30 = OpCompositeInsert %S_t %29 %28 2 +OpStore %s0 %30 +%33 = OpImageSampleImplicitLod %v4float %29 %26 +OpStore %outColor %33 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(InsertExtractElimTest, OptimizeNestedStruct) { + // The following HLSL has been pre-optimized to get the SPIR-V: + // struct S0 + // { + // int x; + // SamplerState ss; + // }; + // + // struct S1 + // { + // float b; + // S0 s0; + // }; + // + // struct S2 + // { + // int a1; + // S1 resources; + // }; + // + // SamplerState samp; + // Texture2D tex; + // + // float4 main(float4 vpos : VPOS) : COLOR0 + // { + // S1 s1; + // S2 s2; + // s1.s0.ss = samp; + // s2.resources = s1; + // return tex.Sample(s2.resources.s0.ss, float2(0.5)); + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %_entryPointOutput +OpExecutionMode %main OriginUpperLeft +OpSource HLSL 500 +OpName %main "main" +OpName %S0 "S0" +OpMemberName %S0 0 "x" +OpMemberName %S0 1 "ss" +OpName %S1 "S1" +OpMemberName %S1 0 "b" +OpMemberName %S1 1 "s0" +OpName %samp "samp" +OpName %S2 "S2" +OpMemberName %S2 0 "a1" +OpMemberName %S2 1 "resources" +OpName %tex "tex" +OpName %_entryPointOutput "@entryPointOutput" +OpDecorate %samp DescriptorSet 0 +OpDecorate %tex DescriptorSet 0 +OpDecorate %_entryPointOutput Location 0 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%14 = OpTypeFunction %v4float %_ptr_Function_v4float +%int = OpTypeInt 32 1 +%16 = OpTypeSampler +%S0 = OpTypeStruct %int %16 +%S1 = OpTypeStruct %float %S0 +%_ptr_Function_S1 = OpTypePointer Function %S1 +%int_1 = OpConstant %int 1 +%_ptr_UniformConstant_16 = OpTypePointer UniformConstant %16 +%samp = OpVariable %_ptr_UniformConstant_16 UniformConstant +%_ptr_Function_16 = OpTypePointer Function %16 +%S2 = OpTypeStruct %int %S1 +%_ptr_Function_S2 = OpTypePointer Function %S2 +%22 = OpTypeImage %float 2D 0 0 0 1 Unknown +%_ptr_UniformConstant_22 = OpTypePointer UniformConstant %22 +%tex = OpVariable %_ptr_UniformConstant_22 UniformConstant +%24 = OpTypeSampledImage %22 +%v2float = OpTypeVector %float 2 +%float_0_5 = OpConstant %float 0.5 +%27 = OpConstantComposite %v2float %float_0_5 %float_0_5 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_entryPointOutput = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %10 +%30 = OpLabel +%31 = OpVariable %_ptr_Function_S1 Function +%32 = OpVariable %_ptr_Function_S2 Function +%33 = OpLoad %16 %samp +%34 = OpLoad %S1 %31 +%35 = OpCompositeInsert %S1 %33 %34 1 1 +OpStore %31 %35 +%36 = OpLoad %S2 %32 +%37 = OpCompositeInsert %S2 %35 %36 1 +OpStore %32 %37 +%38 = OpLoad %22 %tex +%39 = OpCompositeExtract %16 %37 1 1 1 +%40 = OpSampledImage %24 %38 %39 +%41 = OpImageSampleImplicitLod %v4float %40 %27 +OpStore %_entryPointOutput %41 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %10 +%30 = OpLabel +%31 = OpVariable %_ptr_Function_S1 Function +%32 = OpVariable %_ptr_Function_S2 Function +%33 = OpLoad %16 %samp +%34 = OpLoad %S1 %31 +%35 = OpCompositeInsert %S1 %33 %34 1 1 +OpStore %31 %35 +%36 = OpLoad %S2 %32 +%37 = OpCompositeInsert %S2 %35 %36 1 +OpStore %32 %37 +%38 = OpLoad %22 %tex +%40 = OpSampledImage %24 %38 %33 +%41 = OpImageSampleImplicitLod %v4float %40 %27 +OpStore %_entryPointOutput %41 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(InsertExtractElimTest, ConflictingInsertPreventsOptimization) { + // Note: The SPIR-V assembly has had store/load elimination + // performed to allow the inserts and extracts to directly + // reference each other. + // + // #version 140 + // + // in vec4 BaseColor; + // + // struct S_t { + // vec4 v0; + // vec4 v1; + // }; + // + // void main() + // { + // S_t s0; + // s0.v1 = BaseColor; + // s0.v1[2] = 0.0; + // gl_FragColor = s0.v1; + // } + + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %S_t "S_t" +OpMemberName %S_t 0 "v0" +OpMemberName %S_t 1 "v1" +OpName %s0 "s0" +OpName %BaseColor "BaseColor" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%S_t = OpTypeStruct %v4float %v4float +%_ptr_Function_S_t = OpTypePointer Function %S_t +%int = OpTypeInt 32 1 +%int_1 = OpConstant %int 1 +%float_0 = OpConstant %float 0 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %8 +%18 = OpLabel +%s0 = OpVariable %_ptr_Function_S_t Function +%19 = OpLoad %v4float %BaseColor +%20 = OpLoad %S_t %s0 +%21 = OpCompositeInsert %S_t %19 %20 1 +%22 = OpCompositeInsert %S_t %float_0 %21 1 2 +OpStore %s0 %22 +%23 = OpCompositeExtract %v4float %22 1 +OpStore %gl_FragColor %23 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(assembly, assembly, true, + true); +} + +TEST_F(InsertExtractElimTest, ConflictingInsertPreventsOptimization2) { + // Note: The SPIR-V assembly has had store/load elimination + // performed to allow the inserts and extracts to directly + // reference each other. + // + // #version 140 + // + // in vec4 BaseColor; + // + // struct S_t { + // vec4 v0; + // vec4 v1; + // }; + // + // void main() + // { + // S_t s0; + // s0.v1[1] = 1.0; // dead + // s0.v1 = Baseline; + // gl_FragColor = vec4(s0.v1[1], 0.0, 0.0, 0.0); + // } + + const std::string before_predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %S_t "S_t" +OpMemberName %S_t 0 "v0" +OpMemberName %S_t 1 "v1" +OpName %s0 "s0" +OpName %BaseColor "BaseColor" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%S_t = OpTypeStruct %v4float %v4float +%_ptr_Function_S_t = OpTypePointer Function %S_t +%int = OpTypeInt 32 1 +%int_1 = OpConstant %int 1 +%float_1 = OpConstant %float 1 +%uint = OpTypeInt 32 0 +%uint_1 = OpConstant %uint 1 +%_ptr_Function_float = OpTypePointer Function %float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +%float_0 = OpConstant %float 0 +)"; + + const std::string after_predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %S_t "S_t" +OpMemberName %S_t 0 "v0" +OpMemberName %S_t 1 "v1" +OpName %s0 "s0" +OpName %BaseColor "BaseColor" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%S_t = OpTypeStruct %v4float %v4float +%_ptr_Function_S_t = OpTypePointer Function %S_t +%int = OpTypeInt 32 1 +%int_1 = OpConstant %int 1 +%float_1 = OpConstant %float 1 +%uint = OpTypeInt 32 0 +%uint_1 = OpConstant %uint 1 +%_ptr_Function_float = OpTypePointer Function %float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +%float_0 = OpConstant %float 0 +)"; + + const std::string before = + R"(%main = OpFunction %void None %8 +%22 = OpLabel +%s0 = OpVariable %_ptr_Function_S_t Function +%23 = OpLoad %S_t %s0 +%24 = OpCompositeInsert %S_t %float_1 %23 1 1 +%25 = OpLoad %v4float %BaseColor +%26 = OpCompositeInsert %S_t %25 %24 1 +%27 = OpCompositeExtract %float %26 1 1 +%28 = OpCompositeConstruct %v4float %27 %float_0 %float_0 %float_0 +OpStore %gl_FragColor %28 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %8 +%22 = OpLabel +%s0 = OpVariable %_ptr_Function_S_t Function +%23 = OpLoad %S_t %s0 +%24 = OpCompositeInsert %S_t %float_1 %23 1 1 +%25 = OpLoad %v4float %BaseColor +%26 = OpCompositeInsert %S_t %25 %24 1 +%27 = OpCompositeExtract %float %26 1 1 +%28 = OpCompositeConstruct %v4float %27 %float_0 %float_0 %float_0 +OpStore %gl_FragColor %28 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + before_predefs + before, after_predefs + after, true, true); +} + +TEST_F(InsertExtractElimTest, MixWithConstants) { + // Extract component of FMix with 0.0 or 1.0 as the a-value. + // + // Note: The SPIR-V assembly has had store/load elimination + // performed to allow the inserts and extracts to directly + // reference each other. + // + // #version 450 + // + // layout (location=0) in float bc; + // layout (location=1) in float bc2; + // layout (location=2) in float m; + // layout (location=3) in float m2; + // layout (location=0) out vec4 OutColor; + // + // void main() + // { + // vec4 bcv = vec4(bc, bc2, 0.0, 1.0); + // vec4 bcv2 = vec4(bc2, bc, 1.0, 0.0); + // vec4 v = mix(bcv, bcv2, vec4(0.0,1.0,m,m2)); + // OutColor = vec4(v.y); + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %bc %bc2 %m %m2 %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %bc "bc" +OpName %bc2 "bc2" +OpName %m "m" +OpName %m2 "m2" +OpName %OutColor "OutColor" +OpDecorate %bc Location 0 +OpDecorate %bc2 Location 1 +OpDecorate %m Location 2 +OpDecorate %m2 Location 3 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%9 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_float = OpTypePointer Input %float +%bc = OpVariable %_ptr_Input_float Input +%bc2 = OpVariable %_ptr_Input_float Input +%float_0 = OpConstant %float 0 +%float_1 = OpConstant %float 1 +%m = OpVariable %_ptr_Input_float Input +%m2 = OpVariable %_ptr_Input_float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%uint = OpTypeInt 32 0 +%_ptr_Function_float = OpTypePointer Function %float +)"; + + const std::string before = + R"(%main = OpFunction %void None %9 +%19 = OpLabel +%20 = OpLoad %float %bc +%21 = OpLoad %float %bc2 +%22 = OpCompositeConstruct %v4float %20 %21 %float_0 %float_1 +%23 = OpLoad %float %bc2 +%24 = OpLoad %float %bc +%25 = OpCompositeConstruct %v4float %23 %24 %float_1 %float_0 +%26 = OpLoad %float %m +%27 = OpLoad %float %m2 +%28 = OpCompositeConstruct %v4float %float_0 %float_1 %26 %27 +%29 = OpExtInst %v4float %1 FMix %22 %25 %28 +%30 = OpCompositeExtract %float %29 1 +%31 = OpCompositeConstruct %v4float %30 %30 %30 %30 +OpStore %OutColor %31 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %9 +%19 = OpLabel +%20 = OpLoad %float %bc +%21 = OpLoad %float %bc2 +%22 = OpCompositeConstruct %v4float %20 %21 %float_0 %float_1 +%23 = OpLoad %float %bc2 +%24 = OpLoad %float %bc +%25 = OpCompositeConstruct %v4float %23 %24 %float_1 %float_0 +%26 = OpLoad %float %m +%27 = OpLoad %float %m2 +%28 = OpCompositeConstruct %v4float %float_0 %float_1 %26 %27 +%29 = OpExtInst %v4float %1 FMix %22 %25 %28 +%31 = OpCompositeConstruct %v4float %24 %24 %24 %24 +OpStore %OutColor %31 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(InsertExtractElimTest, VectorShuffle1) { + // Extract component from first vector in VectorShuffle + // + // Note: The SPIR-V assembly has had store/load elimination + // performed to allow the inserts and extracts to directly + // reference each other. + // + // #version 450 + // + // layout (location=0) in float bc; + // layout (location=1) in float bc2; + // layout (location=0) out vec4 OutColor; + // + // void main() + // { + // vec4 bcv = vec4(bc, bc2, 0.0, 1.0); + // vec4 v = bcv.zwxy; + // OutColor = vec4(v.y); + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %bc %bc2 %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %bc "bc" +OpName %bc2 "bc2" +OpName %OutColor "OutColor" +OpDecorate %bc Location 0 +OpDecorate %bc2 Location 1 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_float = OpTypePointer Input %float +%bc = OpVariable %_ptr_Input_float Input +%bc2 = OpVariable %_ptr_Input_float Input +%float_0 = OpConstant %float 0 +%float_1 = OpConstant %float 1 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%uint = OpTypeInt 32 0 +%_ptr_Function_float = OpTypePointer Function %float +)"; + + const std::string before = + R"(%main = OpFunction %void None %7 +%17 = OpLabel +%18 = OpLoad %float %bc +%19 = OpLoad %float %bc2 +%20 = OpCompositeConstruct %v4float %18 %19 %float_0 %float_1 +%21 = OpVectorShuffle %v4float %20 %20 2 3 0 1 +%22 = OpCompositeExtract %float %21 1 +%23 = OpCompositeConstruct %v4float %22 %22 %22 %22 +OpStore %OutColor %23 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %7 +%17 = OpLabel +%18 = OpLoad %float %bc +%19 = OpLoad %float %bc2 +%20 = OpCompositeConstruct %v4float %18 %19 %float_0 %float_1 +%21 = OpVectorShuffle %v4float %20 %20 2 3 0 1 +%23 = OpCompositeConstruct %v4float %float_1 %float_1 %float_1 %float_1 +OpStore %OutColor %23 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(InsertExtractElimTest, VectorShuffle2) { + // Extract component from second vector in VectorShuffle + // Identical to test VectorShuffle1 except for the vector + // shuffle index of 7. + // + // Note: The SPIR-V assembly has had store/load elimination + // performed to allow the inserts and extracts to directly + // reference each other. + // + // #version 450 + // + // layout (location=0) in float bc; + // layout (location=1) in float bc2; + // layout (location=0) out vec4 OutColor; + // + // void main() + // { + // vec4 bcv = vec4(bc, bc2, 0.0, 1.0); + // vec4 v = bcv.zwxy; + // OutColor = vec4(v.y); + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %bc %bc2 %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %bc "bc" +OpName %bc2 "bc2" +OpName %OutColor "OutColor" +OpDecorate %bc Location 0 +OpDecorate %bc2 Location 1 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_float = OpTypePointer Input %float +%bc = OpVariable %_ptr_Input_float Input +%bc2 = OpVariable %_ptr_Input_float Input +%float_0 = OpConstant %float 0 +%float_1 = OpConstant %float 1 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%uint = OpTypeInt 32 0 +%_ptr_Function_float = OpTypePointer Function %float +)"; + + const std::string before = + R"(%main = OpFunction %void None %7 +%17 = OpLabel +%18 = OpLoad %float %bc +%19 = OpLoad %float %bc2 +%20 = OpCompositeConstruct %v4float %18 %19 %float_0 %float_1 +%21 = OpVectorShuffle %v4float %20 %20 2 7 0 1 +%22 = OpCompositeExtract %float %21 1 +%23 = OpCompositeConstruct %v4float %22 %22 %22 %22 +OpStore %OutColor %23 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %7 +%17 = OpLabel +%18 = OpLoad %float %bc +%19 = OpLoad %float %bc2 +%20 = OpCompositeConstruct %v4float %18 %19 %float_0 %float_1 +%21 = OpVectorShuffle %v4float %20 %20 2 7 0 1 +%23 = OpCompositeConstruct %v4float %float_1 %float_1 %float_1 %float_1 +OpStore %OutColor %23 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +// TODO(greg-lunarg): Add tests to verify handling of these cases: +// + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/instruction_list_test.cpp b/3rdparty/spirv-tools/test/opt/instruction_list_test.cpp new file mode 100644 index 000000000..b8dec2f77 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/instruction_list_test.cpp @@ -0,0 +1,112 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "opt/instruction.h" +#include "opt/instruction_list.h" + +namespace { + +using Instruction = spvtools::ir::Instruction; +using InstructionList = spvtools::ir::InstructionList; +using ::testing::ContainerEq; +using ::testing::ElementsAre; +using InstructionListTest = ::testing::Test; + +// A class that overrides the destructor, so we can trace it. +class TestInstruction : public Instruction { + public: + TestInstruction() : Instruction() { created_instructions_.push_back(this); } + + ~TestInstruction() { deleted_instructions_.push_back(this); } + + static std::vector created_instructions_; + static std::vector deleted_instructions_; +}; + +std::vector TestInstruction::created_instructions_; +std::vector TestInstruction::deleted_instructions_; + +// Test that the destructor for InstructionList is calling the destructor +// for every element that is in the list. +TEST(InstructionListTest, Destructor) { + InstructionList* list = new InstructionList(); + list->push_back(std::unique_ptr(new Instruction())); + list->push_back(std::unique_ptr(new Instruction())); + delete list; + + // Sorting because we do not care if the order of create and destruction is + // the same. Using generic sort just incase things are changed above. + std::sort(TestInstruction::created_instructions_.begin(), + TestInstruction::created_instructions_.end()); + std::sort(TestInstruction::deleted_instructions_.begin(), + TestInstruction::deleted_instructions_.end()); + EXPECT_THAT(TestInstruction::created_instructions_, + ContainerEq(TestInstruction::deleted_instructions_)); +} + +// Test the |InsertBefore| with a single instruction in the iterator class. +// Need to make sure the elements are inserted in the correct order, and the +// return value points to the correct location. +// +// Comparing addresses to make sure they remain stable, so other data structures +// can have pointers to instructions in InstructionList. +TEST(InstructionListTest, InsertBefore1) { + InstructionList list; + std::vector inserted_instructions; + for (int i = 0; i < 4; i++) { + std::unique_ptr inst(new Instruction()); + inserted_instructions.push_back(inst.get()); + auto new_element = list.end().InsertBefore(std::move(inst)); + EXPECT_EQ(&*new_element, inserted_instructions.back()); + } + + std::vector output; + for (auto& i : list) { + output.push_back(&i); + } + EXPECT_THAT(output, ContainerEq(inserted_instructions)); +} + +// Test inserting an entire vector of instructions using InsertBefore. Checking +// the order of insertion and the return value. +// +// Comparing addresses to make sure they remain stable, so other data structures +// can have pointers to instructions in InstructionList. +TEST(InstructionListTest, InsertBefore2) { + InstructionList list; + std::vector> new_instructions; + std::vector created_instructions; + for (int i = 0; i < 4; i++) { + std::unique_ptr inst(new Instruction()); + created_instructions.push_back(inst.get()); + new_instructions.push_back(std::move(inst)); + } + auto new_element = list.begin().InsertBefore(std::move(new_instructions)); + EXPECT_TRUE(new_instructions.empty()); + EXPECT_EQ(&*new_element, created_instructions.front()); + + std::vector output; + for (auto& i : list) { + output.push_back(&i); + } + EXPECT_THAT(output, ContainerEq(created_instructions)); +} +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/instruction_test.cpp b/3rdparty/spirv-tools/test/opt/instruction_test.cpp new file mode 100644 index 000000000..0a632a98b --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/instruction_test.cpp @@ -0,0 +1,658 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "opt/instruction.h" +#include "opt/ir_context.h" + +#include "gmock/gmock.h" + +#include "pass_fixture.h" +#include "pass_utils.h" +#include "spirv-tools/libspirv.h" +#include "unit_spirv.h" + +namespace { + +using namespace spvtools; +using ir::Instruction; +using ir::IRContext; +using ir::Operand; +using spvtest::MakeInstruction; +using ::testing::Eq; +using DescriptorTypeTest = PassTest<::testing::Test>; +using OpaqueTypeTest = PassTest<::testing::Test>; +using GetBaseTest = PassTest<::testing::Test>; + +TEST(InstructionTest, CreateTrivial) { + Instruction empty; + EXPECT_EQ(SpvOpNop, empty.opcode()); + EXPECT_EQ(0u, empty.type_id()); + EXPECT_EQ(0u, empty.result_id()); + EXPECT_EQ(0u, empty.NumOperands()); + EXPECT_EQ(0u, empty.NumOperandWords()); + EXPECT_EQ(0u, empty.NumInOperandWords()); + EXPECT_EQ(empty.cend(), empty.cbegin()); + EXPECT_EQ(empty.end(), empty.begin()); +} + +TEST(InstructionTest, CreateWithOpcodeAndNoOperands) { + IRContext context(SPV_ENV_UNIVERSAL_1_2, nullptr); + Instruction inst(&context, SpvOpReturn); + EXPECT_EQ(SpvOpReturn, inst.opcode()); + EXPECT_EQ(0u, inst.type_id()); + EXPECT_EQ(0u, inst.result_id()); + EXPECT_EQ(0u, inst.NumOperands()); + EXPECT_EQ(0u, inst.NumOperandWords()); + EXPECT_EQ(0u, inst.NumInOperandWords()); + EXPECT_EQ(inst.cend(), inst.cbegin()); + EXPECT_EQ(inst.end(), inst.begin()); +} + +// The words for an OpTypeInt for 32-bit signed integer resulting in Id 44. +uint32_t kSampleInstructionWords[] = {(4 << 16) | uint32_t(SpvOpTypeInt), 44, + 32, 1}; +// The operands that would be parsed from kSampleInstructionWords +spv_parsed_operand_t kSampleParsedOperands[] = { + {1, 1, SPV_OPERAND_TYPE_RESULT_ID, SPV_NUMBER_NONE, 0}, + {2, 1, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_NUMBER_UNSIGNED_INT, 32}, + {3, 1, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_NUMBER_UNSIGNED_INT, 1}, +}; + +// A valid parse of kSampleParsedOperands. +spv_parsed_instruction_t kSampleParsedInstruction = {kSampleInstructionWords, + uint16_t(4), + uint16_t(SpvOpTypeInt), + SPV_EXT_INST_TYPE_NONE, + 0, // type id + 44, // result id + kSampleParsedOperands, + 3}; + +// The words for an OpAccessChain instruction. +uint32_t kSampleAccessChainInstructionWords[] = { + (7 << 16) | uint32_t(SpvOpAccessChain), 100, 101, 102, 103, 104, 105}; + +// The operands that would be parsed from kSampleAccessChainInstructionWords. +spv_parsed_operand_t kSampleAccessChainOperands[] = { + {1, 1, SPV_OPERAND_TYPE_RESULT_ID, SPV_NUMBER_NONE, 0}, + {2, 1, SPV_OPERAND_TYPE_TYPE_ID, SPV_NUMBER_NONE, 0}, + {3, 1, SPV_OPERAND_TYPE_ID, SPV_NUMBER_NONE, 0}, + {4, 1, SPV_OPERAND_TYPE_ID, SPV_NUMBER_NONE, 0}, + {5, 1, SPV_OPERAND_TYPE_ID, SPV_NUMBER_NONE, 0}, + {6, 1, SPV_OPERAND_TYPE_ID, SPV_NUMBER_NONE, 0}, +}; + +// A valid parse of kSampleAccessChainInstructionWords +spv_parsed_instruction_t kSampleAccessChainInstruction = { + kSampleAccessChainInstructionWords, + uint16_t(7), + uint16_t(SpvOpAccessChain), + SPV_EXT_INST_TYPE_NONE, + 100, // type id + 101, // result id + kSampleAccessChainOperands, + 6}; + +// The words for an OpControlBarrier instruction. +uint32_t kSampleControlBarrierInstructionWords[] = { + (4 << 16) | uint32_t(SpvOpControlBarrier), 100, 101, 102}; + +// The operands that would be parsed from kSampleControlBarrierInstructionWords. +spv_parsed_operand_t kSampleControlBarrierOperands[] = { + {1, 1, SPV_OPERAND_TYPE_SCOPE_ID, SPV_NUMBER_NONE, 0}, // Execution + {2, 1, SPV_OPERAND_TYPE_SCOPE_ID, SPV_NUMBER_NONE, 0}, // Memory + {3, 1, SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID, SPV_NUMBER_NONE, + 0}, // Semantics +}; + +// A valid parse of kSampleControlBarrierInstructionWords +spv_parsed_instruction_t kSampleControlBarrierInstruction = { + kSampleControlBarrierInstructionWords, + uint16_t(4), + uint16_t(SpvOpControlBarrier), + SPV_EXT_INST_TYPE_NONE, + 0, // type id + 0, // result id + kSampleControlBarrierOperands, + 3}; + +TEST(InstructionTest, CreateWithOpcodeAndOperands) { + IRContext context(SPV_ENV_UNIVERSAL_1_2, nullptr); + Instruction inst(&context, kSampleParsedInstruction); + EXPECT_EQ(SpvOpTypeInt, inst.opcode()); + EXPECT_EQ(0u, inst.type_id()); + EXPECT_EQ(44u, inst.result_id()); + EXPECT_EQ(3u, inst.NumOperands()); + EXPECT_EQ(3u, inst.NumOperandWords()); + EXPECT_EQ(2u, inst.NumInOperandWords()); +} + +TEST(InstructionTest, GetOperand) { + IRContext context(SPV_ENV_UNIVERSAL_1_2, nullptr); + Instruction inst(&context, kSampleParsedInstruction); + EXPECT_THAT(inst.GetOperand(0).words, Eq(std::vector{44})); + EXPECT_THAT(inst.GetOperand(1).words, Eq(std::vector{32})); + EXPECT_THAT(inst.GetOperand(2).words, Eq(std::vector{1})); +} + +TEST(InstructionTest, GetInOperand) { + IRContext context(SPV_ENV_UNIVERSAL_1_2, nullptr); + Instruction inst(&context, kSampleParsedInstruction); + EXPECT_THAT(inst.GetInOperand(0).words, Eq(std::vector{32})); + EXPECT_THAT(inst.GetInOperand(1).words, Eq(std::vector{1})); +} + +TEST(InstructionTest, OperandConstIterators) { + IRContext context(SPV_ENV_UNIVERSAL_1_2, nullptr); + Instruction inst(&context, kSampleParsedInstruction); + // Spot check iteration across operands. + auto cbegin = inst.cbegin(); + auto cend = inst.cend(); + EXPECT_NE(cend, inst.cbegin()); + + auto citer = inst.cbegin(); + for (int i = 0; i < 3; ++i, ++citer) { + const auto& operand = *citer; + EXPECT_THAT(operand.type, Eq(kSampleParsedOperands[i].type)); + EXPECT_THAT(operand.words, + Eq(std::vector{kSampleInstructionWords[i + 1]})); + EXPECT_NE(cend, citer); + } + EXPECT_EQ(cend, citer); + + // Check that cbegin and cend have not changed. + EXPECT_EQ(cbegin, inst.cbegin()); + EXPECT_EQ(cend, inst.cend()); + + // Check arithmetic. + const Operand& operand2 = *(inst.cbegin() + 2); + EXPECT_EQ(SPV_OPERAND_TYPE_LITERAL_INTEGER, operand2.type); +} + +TEST(InstructionTest, OperandIterators) { + IRContext context(SPV_ENV_UNIVERSAL_1_2, nullptr); + Instruction inst(&context, kSampleParsedInstruction); + // Spot check iteration across operands, with mutable iterators. + auto begin = inst.begin(); + auto end = inst.end(); + EXPECT_NE(end, inst.begin()); + + auto iter = inst.begin(); + for (int i = 0; i < 3; ++i, ++iter) { + const auto& operand = *iter; + EXPECT_THAT(operand.type, Eq(kSampleParsedOperands[i].type)); + EXPECT_THAT(operand.words, + Eq(std::vector{kSampleInstructionWords[i + 1]})); + EXPECT_NE(end, iter); + } + EXPECT_EQ(end, iter); + + // Check that begin and end have not changed. + EXPECT_EQ(begin, inst.begin()); + EXPECT_EQ(end, inst.end()); + + // Check arithmetic. + Operand& operand2 = *(inst.begin() + 2); + EXPECT_EQ(SPV_OPERAND_TYPE_LITERAL_INTEGER, operand2.type); + + // Check mutation through an iterator. + operand2.type = SPV_OPERAND_TYPE_TYPE_ID; + EXPECT_EQ(SPV_OPERAND_TYPE_TYPE_ID, (*(inst.cbegin() + 2)).type); +} + +TEST(InstructionTest, ForInIdStandardIdTypes) { + IRContext context(SPV_ENV_UNIVERSAL_1_2, nullptr); + Instruction inst(&context, kSampleAccessChainInstruction); + + std::vector ids; + inst.ForEachInId([&ids](const uint32_t* idptr) { ids.push_back(*idptr); }); + EXPECT_THAT(ids, Eq(std::vector{102, 103, 104, 105})); + + ids.clear(); + inst.ForEachInId([&ids](uint32_t* idptr) { ids.push_back(*idptr); }); + EXPECT_THAT(ids, Eq(std::vector{102, 103, 104, 105})); +} + +TEST(InstructionTest, ForInIdNonstandardIdTypes) { + IRContext context(SPV_ENV_UNIVERSAL_1_2, nullptr); + Instruction inst(&context, kSampleControlBarrierInstruction); + + std::vector ids; + inst.ForEachInId([&ids](const uint32_t* idptr) { ids.push_back(*idptr); }); + EXPECT_THAT(ids, Eq(std::vector{100, 101, 102})); + + ids.clear(); + inst.ForEachInId([&ids](uint32_t* idptr) { ids.push_back(*idptr); }); + EXPECT_THAT(ids, Eq(std::vector{100, 101, 102})); +} + +TEST(InstructionTest, UniqueIds) { + IRContext context(SPV_ENV_UNIVERSAL_1_2, nullptr); + Instruction inst1(&context); + Instruction inst2(&context); + EXPECT_NE(inst1.unique_id(), inst2.unique_id()); +} + +TEST(InstructionTest, CloneUniqueIdDifferent) { + IRContext context(SPV_ENV_UNIVERSAL_1_2, nullptr); + Instruction inst(&context); + std::unique_ptr clone(inst.Clone(&context)); + EXPECT_EQ(inst.context(), clone->context()); + EXPECT_NE(inst.unique_id(), clone->unique_id()); +} + +TEST(InstructionTest, CloneDifferentContext) { + IRContext c1(SPV_ENV_UNIVERSAL_1_2, nullptr); + IRContext c2(SPV_ENV_UNIVERSAL_1_2, nullptr); + Instruction inst(&c1); + std::unique_ptr clone(inst.Clone(&c2)); + EXPECT_EQ(&c1, inst.context()); + EXPECT_EQ(&c2, clone->context()); + EXPECT_NE(&c1, &c2); +} + +TEST(InstructionTest, CloneDifferentContextDifferentUniqueId) { + IRContext c1(SPV_ENV_UNIVERSAL_1_2, nullptr); + IRContext c2(SPV_ENV_UNIVERSAL_1_2, nullptr); + Instruction inst(&c1); + Instruction other(&c2); + std::unique_ptr clone(inst.Clone(&c2)); + EXPECT_EQ(&c2, clone->context()); + EXPECT_NE(other.unique_id(), clone->unique_id()); +} + +TEST(InstructionTest, EqualsEqualsOperator) { + IRContext context(SPV_ENV_UNIVERSAL_1_2, nullptr); + Instruction i1(&context); + Instruction i2(&context); + std::unique_ptr clone(i1.Clone(&context)); + EXPECT_TRUE(i1 == i1); + EXPECT_FALSE(i1 == i2); + EXPECT_FALSE(i1 == *clone); + EXPECT_FALSE(i2 == *clone); +} + +TEST(InstructionTest, LessThanOperator) { + IRContext context(SPV_ENV_UNIVERSAL_1_2, nullptr); + Instruction i1(&context); + Instruction i2(&context); + std::unique_ptr clone(i1.Clone(&context)); + EXPECT_TRUE(i1 < i2); + EXPECT_TRUE(i1 < *clone); + EXPECT_TRUE(i2 < *clone); +} + +TEST_F(DescriptorTypeTest, StorageImage) { + 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 + OpName %3 "myStorageImage" + OpDecorate %3 DescriptorSet 0 + OpDecorate %3 Binding 0 + %4 = OpTypeVoid + %5 = OpTypeFunction %4 + %6 = OpTypeFloat 32 + %7 = OpTypeImage %6 2D 0 0 0 2 R32f + %8 = OpTypePointer UniformConstant %7 + %3 = OpVariable %8 UniformConstant + %2 = OpFunction %4 None %5 + %9 = OpLabel + OpReturn + OpFunctionEnd +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + Instruction* type = context->get_def_use_mgr()->GetDef(8); + EXPECT_TRUE(type->IsVulkanStorageImage()); + EXPECT_FALSE(type->IsVulkanSampledImage()); + EXPECT_FALSE(type->IsVulkanStorageTexelBuffer()); + EXPECT_FALSE(type->IsVulkanStorageBuffer()); + EXPECT_FALSE(type->IsVulkanUniformBuffer()); + + Instruction* variable = context->get_def_use_mgr()->GetDef(3); + EXPECT_FALSE(variable->IsReadOnlyVariable()); +} + +TEST_F(DescriptorTypeTest, SampledImage) { + 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 + OpName %3 "myStorageImage" + OpDecorate %3 DescriptorSet 0 + OpDecorate %3 Binding 0 + %4 = OpTypeVoid + %5 = OpTypeFunction %4 + %6 = OpTypeFloat 32 + %7 = OpTypeImage %6 2D 0 0 0 1 Unknown + %8 = OpTypePointer UniformConstant %7 + %3 = OpVariable %8 UniformConstant + %2 = OpFunction %4 None %5 + %9 = OpLabel + OpReturn + OpFunctionEnd +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + Instruction* type = context->get_def_use_mgr()->GetDef(8); + EXPECT_FALSE(type->IsVulkanStorageImage()); + EXPECT_TRUE(type->IsVulkanSampledImage()); + EXPECT_FALSE(type->IsVulkanStorageTexelBuffer()); + EXPECT_FALSE(type->IsVulkanStorageBuffer()); + EXPECT_FALSE(type->IsVulkanUniformBuffer()); + + Instruction* variable = context->get_def_use_mgr()->GetDef(3); + EXPECT_TRUE(variable->IsReadOnlyVariable()); +} + +TEST_F(DescriptorTypeTest, StorageTexelBuffer) { + 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 + OpName %3 "myStorageImage" + OpDecorate %3 DescriptorSet 0 + OpDecorate %3 Binding 0 + %4 = OpTypeVoid + %5 = OpTypeFunction %4 + %6 = OpTypeFloat 32 + %7 = OpTypeImage %6 Buffer 0 0 0 2 R32f + %8 = OpTypePointer UniformConstant %7 + %3 = OpVariable %8 UniformConstant + %2 = OpFunction %4 None %5 + %9 = OpLabel + OpReturn + OpFunctionEnd +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + Instruction* type = context->get_def_use_mgr()->GetDef(8); + EXPECT_FALSE(type->IsVulkanStorageImage()); + EXPECT_FALSE(type->IsVulkanSampledImage()); + EXPECT_TRUE(type->IsVulkanStorageTexelBuffer()); + EXPECT_FALSE(type->IsVulkanStorageBuffer()); + EXPECT_FALSE(type->IsVulkanUniformBuffer()); + + Instruction* variable = context->get_def_use_mgr()->GetDef(3); + EXPECT_FALSE(variable->IsReadOnlyVariable()); +} + +TEST_F(DescriptorTypeTest, StorageBuffer) { + 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 + OpName %3 "myStorageImage" + OpDecorate %3 DescriptorSet 0 + OpDecorate %3 Binding 0 + OpDecorate %9 BufferBlock + %4 = OpTypeVoid + %5 = OpTypeFunction %4 + %6 = OpTypeFloat 32 + %7 = OpTypeVector %6 4 + %8 = OpTypeRuntimeArray %7 + %9 = OpTypeStruct %8 + %10 = OpTypePointer Uniform %9 + %3 = OpVariable %10 Uniform + %2 = OpFunction %4 None %5 + %11 = OpLabel + OpReturn + OpFunctionEnd +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + Instruction* type = context->get_def_use_mgr()->GetDef(10); + EXPECT_FALSE(type->IsVulkanStorageImage()); + EXPECT_FALSE(type->IsVulkanSampledImage()); + EXPECT_FALSE(type->IsVulkanStorageTexelBuffer()); + EXPECT_TRUE(type->IsVulkanStorageBuffer()); + EXPECT_FALSE(type->IsVulkanUniformBuffer()); + + Instruction* variable = context->get_def_use_mgr()->GetDef(3); + EXPECT_FALSE(variable->IsReadOnlyVariable()); +} + +TEST_F(DescriptorTypeTest, UniformBuffer) { + 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 + OpName %3 "myStorageImage" + OpDecorate %3 DescriptorSet 0 + OpDecorate %3 Binding 0 + OpDecorate %9 Block + %4 = OpTypeVoid + %5 = OpTypeFunction %4 + %6 = OpTypeFloat 32 + %7 = OpTypeVector %6 4 + %8 = OpTypeRuntimeArray %7 + %9 = OpTypeStruct %8 + %10 = OpTypePointer Uniform %9 + %3 = OpVariable %10 Uniform + %2 = OpFunction %4 None %5 + %11 = OpLabel + OpReturn + OpFunctionEnd +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + Instruction* type = context->get_def_use_mgr()->GetDef(10); + EXPECT_FALSE(type->IsVulkanStorageImage()); + EXPECT_FALSE(type->IsVulkanSampledImage()); + EXPECT_FALSE(type->IsVulkanStorageTexelBuffer()); + EXPECT_FALSE(type->IsVulkanStorageBuffer()); + EXPECT_TRUE(type->IsVulkanUniformBuffer()); + + Instruction* variable = context->get_def_use_mgr()->GetDef(3); + EXPECT_TRUE(variable->IsReadOnlyVariable()); +} + +TEST_F(DescriptorTypeTest, NonWritableIsReadOnly) { + 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 + OpName %3 "myStorageImage" + OpDecorate %3 DescriptorSet 0 + OpDecorate %3 Binding 0 + OpDecorate %9 BufferBlock + OpDecorate %3 NonWritable + %4 = OpTypeVoid + %5 = OpTypeFunction %4 + %6 = OpTypeFloat 32 + %7 = OpTypeVector %6 4 + %8 = OpTypeRuntimeArray %7 + %9 = OpTypeStruct %8 + %10 = OpTypePointer Uniform %9 + %3 = OpVariable %10 Uniform + %2 = OpFunction %4 None %5 + %11 = OpLabel + OpReturn + OpFunctionEnd +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + Instruction* variable = context->get_def_use_mgr()->GetDef(3); + EXPECT_TRUE(variable->IsReadOnlyVariable()); +} + +TEST_F(OpaqueTypeTest, BaseOpaqueTypesShader) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypeImage %5 2D 1 0 0 1 Unknown + %7 = OpTypeSampler + %8 = OpTypeSampledImage %6 + %9 = OpTypeRuntimeArray %5 + %2 = OpFunction %3 None %4 + %10 = OpLabel + OpReturn + OpFunctionEnd +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + Instruction* image_type = context->get_def_use_mgr()->GetDef(6); + EXPECT_TRUE(image_type->IsOpaqueType()); + Instruction* sampler_type = context->get_def_use_mgr()->GetDef(7); + EXPECT_TRUE(sampler_type->IsOpaqueType()); + Instruction* sampled_image_type = context->get_def_use_mgr()->GetDef(8); + EXPECT_TRUE(sampled_image_type->IsOpaqueType()); + Instruction* runtime_array_type = context->get_def_use_mgr()->GetDef(9); + EXPECT_TRUE(runtime_array_type->IsOpaqueType()); + Instruction* float_type = context->get_def_use_mgr()->GetDef(5); + EXPECT_FALSE(float_type->IsOpaqueType()); + Instruction* void_type = context->get_def_use_mgr()->GetDef(3); + EXPECT_FALSE(void_type->IsOpaqueType()); +} + +TEST_F(OpaqueTypeTest, OpaqueStructTypes) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypeRuntimeArray %5 + %7 = OpTypeStruct %6 %6 + %8 = OpTypeStruct %5 %6 + %9 = OpTypeStruct %6 %5 + %10 = OpTypeStruct %7 + %2 = OpFunction %3 None %4 + %11 = OpLabel + OpReturn + OpFunctionEnd +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + for (int i = 7; i <= 10; i++) { + Instruction* type = context->get_def_use_mgr()->GetDef(i); + EXPECT_TRUE(type->IsOpaqueType()); + } +} + +TEST_F(GetBaseTest, SampleImage) { + 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 + OpName %3 "myStorageImage" + OpDecorate %3 DescriptorSet 0 + OpDecorate %3 Binding 0 + %4 = OpTypeVoid + %5 = OpTypeFunction %4 + %6 = OpTypeFloat 32 + %7 = OpTypeVector %6 2 + %8 = OpTypeVector %6 4 + %9 = OpConstant %6 0 + %10 = OpConstantComposite %7 %9 %9 + %11 = OpTypeImage %6 2D 0 0 0 1 R32f + %12 = OpTypePointer UniformConstant %11 + %3 = OpVariable %12 UniformConstant + %13 = OpTypeSampledImage %11 + %14 = OpTypeSampler + %15 = OpTypePointer UniformConstant %14 + %16 = OpVariable %15 UniformConstant + %2 = OpFunction %4 None %5 + %17 = OpLabel + %18 = OpLoad %11 %3 + %19 = OpLoad %14 %16 + %20 = OpSampledImage %13 %18 %19 + %21 = OpImageSampleImplicitLod %8 %20 %10 + OpReturn + OpFunctionEnd +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + Instruction* load = context->get_def_use_mgr()->GetDef(21); + Instruction* base = context->get_def_use_mgr()->GetDef(20); + EXPECT_TRUE(load->GetBaseAddress() == base); +} + +TEST_F(GetBaseTest, ImageRead) { + 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 + OpName %3 "myStorageImage" + OpDecorate %3 DescriptorSet 0 + OpDecorate %3 Binding 0 + %4 = OpTypeVoid + %5 = OpTypeFunction %4 + %6 = OpTypeInt 32 0 + %7 = OpTypeVector %6 2 + %8 = OpConstant %6 0 + %9 = OpConstantComposite %7 %8 %8 + %10 = OpTypeImage %6 2D 0 0 0 2 R32f + %11 = OpTypePointer UniformConstant %10 + %3 = OpVariable %11 UniformConstant + %2 = OpFunction %4 None %5 + %12 = OpLabel + %13 = OpLoad %10 %3 + %14 = OpImageRead %6 %13 %9 + OpReturn + OpFunctionEnd +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + Instruction* load = context->get_def_use_mgr()->GetDef(14); + Instruction* base = context->get_def_use_mgr()->GetDef(13); + EXPECT_TRUE(load->GetBaseAddress() == base); +} +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/ir_builder.cpp b/3rdparty/spirv-tools/test/opt/ir_builder.cpp new file mode 100644 index 000000000..6096b49db --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/ir_builder.cpp @@ -0,0 +1,415 @@ +// Copyright (c) 2018 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#ifdef SPIRV_EFFCEE +#include "effcee/effcee.h" +#endif + +#include "opt/basic_block.h" +#include "opt/ir_builder.h" + +#include "opt/build_module.h" +#include "opt/instruction.h" +#include "opt/type_manager.h" +#include "spirv-tools/libspirv.hpp" + +namespace { + +using namespace spvtools; +using ir::IRContext; +using Analysis = IRContext::Analysis; + +#ifdef SPIRV_EFFCEE + +using IRBuilderTest = ::testing::Test; + +bool Validate(const std::vector& bin) { + spv_target_env target_env = SPV_ENV_UNIVERSAL_1_2; + spv_context spvContext = spvContextCreate(target_env); + spv_diagnostic diagnostic = nullptr; + spv_const_binary_t binary = {bin.data(), bin.size()}; + spv_result_t error = spvValidate(spvContext, &binary, &diagnostic); + if (error != 0) spvDiagnosticPrint(diagnostic); + spvDiagnosticDestroy(diagnostic); + spvContextDestroy(spvContext); + return error == 0; +} + +void Match(const std::string& original, ir::IRContext* context, + bool do_validation = true) { + std::vector bin; + context->module()->ToBinary(&bin, true); + if (do_validation) { + EXPECT_TRUE(Validate(bin)); + } + std::string assembly; + SpirvTools tools(SPV_ENV_UNIVERSAL_1_2); + EXPECT_TRUE( + tools.Disassemble(bin, &assembly, SpirvTools::kDefaultDisassembleOption)) + << "Disassembling failed for shader:\n" + << assembly << std::endl; + auto match_result = effcee::Match(assembly, original); + EXPECT_EQ(effcee::Result::Status::Ok, match_result.status()) + << match_result.message() << "\nChecking result:\n" + << assembly; +} + +TEST_F(IRBuilderTest, TestInsnAddition) { + const std::string text = R"( +; CHECK: %18 = OpLabel +; CHECK: OpPhi %int %int_0 %14 +; CHECK: OpPhi %bool %16 %14 +; CHECK: OpBranch %17 + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 330 + OpName %2 "main" + OpName %4 "i" + OpName %3 "c" + OpDecorate %3 Location 0 + %5 = OpTypeVoid + %6 = OpTypeFunction %5 + %7 = OpTypeInt 32 1 + %8 = OpTypePointer Function %7 + %9 = OpConstant %7 0 + %10 = OpTypeBool + %11 = OpTypeFloat 32 + %12 = OpTypeVector %11 4 + %13 = OpTypePointer Output %12 + %3 = OpVariable %13 Output + %2 = OpFunction %5 None %6 + %14 = OpLabel + %4 = OpVariable %8 Function + OpStore %4 %9 + %15 = OpLoad %7 %4 + %16 = OpINotEqual %10 %15 %9 + OpSelectionMerge %17 None + OpBranchConditional %16 %18 %17 + %18 = OpLabel + OpBranch %17 + %17 = OpLabel + OpReturn + OpFunctionEnd +)"; + + { + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + + ir::BasicBlock* bb = context->cfg()->block(18); + + // Build managers. + context->get_def_use_mgr(); + context->get_instr_block(nullptr); + + opt::InstructionBuilder builder(context.get(), &*bb->begin()); + ir::Instruction* phi1 = builder.AddPhi(7, {9, 14}); + ir::Instruction* phi2 = builder.AddPhi(10, {16, 14}); + + // Make sure the InstructionBuilder did not update the def/use manager. + EXPECT_EQ(context->get_def_use_mgr()->GetDef(phi1->result_id()), nullptr); + EXPECT_EQ(context->get_def_use_mgr()->GetDef(phi2->result_id()), nullptr); + EXPECT_EQ(context->get_instr_block(phi1), nullptr); + EXPECT_EQ(context->get_instr_block(phi2), nullptr); + + Match(text, context.get()); + } + + { + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + + // Build managers. + context->get_def_use_mgr(); + context->get_instr_block(nullptr); + + ir::BasicBlock* bb = context->cfg()->block(18); + opt::InstructionBuilder builder( + context.get(), &*bb->begin(), + ir::IRContext::kAnalysisDefUse | + ir::IRContext::kAnalysisInstrToBlockMapping); + ir::Instruction* phi1 = builder.AddPhi(7, {9, 14}); + ir::Instruction* phi2 = builder.AddPhi(10, {16, 14}); + + // Make sure InstructionBuilder updated the def/use manager + EXPECT_NE(context->get_def_use_mgr()->GetDef(phi1->result_id()), nullptr); + EXPECT_NE(context->get_def_use_mgr()->GetDef(phi2->result_id()), nullptr); + EXPECT_NE(context->get_instr_block(phi1), nullptr); + EXPECT_NE(context->get_instr_block(phi2), nullptr); + + Match(text, context.get()); + } +} + +TEST_F(IRBuilderTest, TestCondBranchAddition) { + const std::string text = R"( +; CHECK: %main = OpFunction %void None %6 +; CHECK-NEXT: %15 = OpLabel +; CHECK-NEXT: OpSelectionMerge %13 None +; CHECK-NEXT: OpBranchConditional %true %14 %13 +; CHECK-NEXT: %14 = OpLabel +; CHECK-NEXT: OpBranch %13 +; CHECK-NEXT: %13 = OpLabel +; CHECK-NEXT: OpReturn + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 330 + OpName %2 "main" + OpName %4 "i" + OpName %3 "c" + OpDecorate %3 Location 0 + %5 = OpTypeVoid + %6 = OpTypeFunction %5 + %7 = OpTypeBool + %8 = OpTypePointer Function %7 + %9 = OpConstantTrue %7 + %10 = OpTypeFloat 32 + %11 = OpTypeVector %10 4 + %12 = OpTypePointer Output %11 + %3 = OpVariable %12 Output + %4 = OpVariable %8 Private + %2 = OpFunction %5 None %6 + %13 = OpLabel + OpReturn + OpFunctionEnd +)"; + + { + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + + ir::Function& fn = *context->module()->begin(); + + ir::BasicBlock& bb_merge = *fn.begin(); + + fn.begin().InsertBefore(std::unique_ptr( + new ir::BasicBlock(std::unique_ptr(new ir::Instruction( + context.get(), SpvOpLabel, 0, context->TakeNextId(), {}))))); + ir::BasicBlock& bb_true = *fn.begin(); + { + opt::InstructionBuilder builder(context.get(), &*bb_true.begin()); + builder.AddBranch(bb_merge.id()); + } + + fn.begin().InsertBefore(std::unique_ptr( + new ir::BasicBlock(std::unique_ptr(new ir::Instruction( + context.get(), SpvOpLabel, 0, context->TakeNextId(), {}))))); + ir::BasicBlock& bb_cond = *fn.begin(); + + opt::InstructionBuilder builder(context.get(), &bb_cond); + // This also test consecutive instruction insertion: merge selection + + // branch. + builder.AddConditionalBranch(9, bb_true.id(), bb_merge.id(), bb_merge.id()); + + Match(text, context.get()); + } +} + +TEST_F(IRBuilderTest, AddSelect) { + const std::string text = R"( +; CHECK: [[bool:%\w+]] = OpTypeBool +; CHECK: [[uint:%\w+]] = OpTypeInt 32 0 +; CHECK: [[true:%\w+]] = OpConstantTrue [[bool]] +; CHECK: [[u0:%\w+]] = OpConstant [[uint]] 0 +; CHECK: [[u1:%\w+]] = OpConstant [[uint]] 1 +; CHECK: OpSelect [[uint]] [[true]] [[u0]] [[u1]] +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical OpenCL +%1 = OpTypeVoid +%2 = OpTypeBool +%3 = OpTypeInt 32 0 +%4 = OpConstantTrue %2 +%5 = OpConstant %3 0 +%6 = OpConstant %3 1 +%7 = OpTypeFunction %1 +%8 = OpFunction %1 None %7 +%9 = OpLabel +OpReturn +OpFunctionEnd +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + EXPECT_NE(nullptr, context); + + opt::InstructionBuilder builder( + context.get(), &*context->module()->begin()->begin()->begin()); + EXPECT_NE(nullptr, builder.AddSelect(3u, 4u, 5u, 6u)); + + Match(text, context.get()); +} + +TEST_F(IRBuilderTest, AddCompositeConstruct) { + const std::string text = R"( +; CHECK: [[uint:%\w+]] = OpTypeInt +; CHECK: [[u0:%\w+]] = OpConstant [[uint]] 0 +; CHECK: [[u1:%\w+]] = OpConstant [[uint]] 1 +; CHECK: [[struct:%\w+]] = OpTypeStruct [[uint]] [[uint]] [[uint]] [[uint]] +; CHECK: OpCompositeConstruct [[struct]] [[u0]] [[u1]] [[u1]] [[u0]] +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical OpenCL +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpConstant %2 0 +%4 = OpConstant %2 1 +%5 = OpTypeStruct %2 %2 %2 %2 +%6 = OpTypeFunction %1 +%7 = OpFunction %1 None %6 +%8 = OpLabel +OpReturn +OpFunctionEnd +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + EXPECT_NE(nullptr, context); + + opt::InstructionBuilder builder( + context.get(), &*context->module()->begin()->begin()->begin()); + std::vector ids = {3u, 4u, 4u, 3u}; + EXPECT_NE(nullptr, builder.AddCompositeConstruct(5u, ids)); + + Match(text, context.get()); +} + +TEST_F(IRBuilderTest, ConstantAdder) { + const std::string text = R"( +; CHECK: [[uint:%\w+]] = OpTypeInt 32 0 +; CHECK: OpConstant [[uint]] 13 +; CHECK: [[sint:%\w+]] = OpTypeInt 32 1 +; CHECK: OpConstant [[sint]] -1 +; CHECK: OpConstant [[uint]] 1 +; CHECK: OpConstant [[sint]] 34 +; CHECK: OpConstant [[uint]] 0 +; CHECK: OpConstant [[sint]] 0 +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +%1 = OpTypeVoid +%2 = OpTypeFunction %1 +%3 = OpFunction %1 None %2 +%4 = OpLabel +OpReturn +OpFunctionEnd +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + EXPECT_NE(nullptr, context); + + opt::InstructionBuilder builder( + context.get(), &*context->module()->begin()->begin()->begin()); + EXPECT_NE(nullptr, builder.Add32BitUnsignedIntegerConstant(13)); + EXPECT_NE(nullptr, builder.Add32BitSignedIntegerConstant(-1)); + + // Try adding the same constants again to make sure they aren't added. + EXPECT_NE(nullptr, builder.Add32BitUnsignedIntegerConstant(13)); + EXPECT_NE(nullptr, builder.Add32BitSignedIntegerConstant(-1)); + + // Try adding different constants to make sure the type is reused. + EXPECT_NE(nullptr, builder.Add32BitUnsignedIntegerConstant(1)); + EXPECT_NE(nullptr, builder.Add32BitSignedIntegerConstant(34)); + + // Try adding 0 as both signed and unsigned. + EXPECT_NE(nullptr, builder.Add32BitUnsignedIntegerConstant(0)); + EXPECT_NE(nullptr, builder.Add32BitSignedIntegerConstant(0)); + + Match(text, context.get()); +} + +TEST_F(IRBuilderTest, ConstantAdderTypeAlreadyExists) { + const std::string text = R"( +; CHECK: OpConstant %uint 13 +; CHECK: OpConstant %int -1 +; CHECK: OpConstant %uint 1 +; CHECK: OpConstant %int 34 +; CHECK: OpConstant %uint 0 +; CHECK: OpConstant %int 0 +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +%1 = OpTypeVoid +%uint = OpTypeInt 32 0 +%int = OpTypeInt 32 1 +%4 = OpTypeFunction %1 +%5 = OpFunction %1 None %4 +%6 = OpLabel +OpReturn +OpFunctionEnd +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + EXPECT_NE(nullptr, context); + + opt::InstructionBuilder builder( + context.get(), &*context->module()->begin()->begin()->begin()); + ir::Instruction* const_1 = builder.Add32BitUnsignedIntegerConstant(13); + ir::Instruction* const_2 = builder.Add32BitSignedIntegerConstant(-1); + + EXPECT_NE(nullptr, const_1); + EXPECT_NE(nullptr, const_2); + + // Try adding the same constants again to make sure they aren't added. + EXPECT_EQ(const_1, builder.Add32BitUnsignedIntegerConstant(13)); + EXPECT_EQ(const_2, builder.Add32BitSignedIntegerConstant(-1)); + + ir::Instruction* const_3 = builder.Add32BitUnsignedIntegerConstant(1); + ir::Instruction* const_4 = builder.Add32BitSignedIntegerConstant(34); + + // Try adding different constants to make sure the type is reused. + EXPECT_NE(nullptr, const_3); + EXPECT_NE(nullptr, const_4); + + ir::Instruction* const_5 = builder.Add32BitUnsignedIntegerConstant(0); + ir::Instruction* const_6 = builder.Add32BitSignedIntegerConstant(0); + + // Try adding 0 as both signed and unsigned. + EXPECT_NE(nullptr, const_5); + EXPECT_NE(nullptr, const_6); + + // They have the same value but different types so should be unique. + EXPECT_NE(const_5, const_6); + + // Check the types are correct. + uint32_t type_id_unsigned = const_1->GetSingleWordOperand(0); + uint32_t type_id_signed = const_2->GetSingleWordOperand(0); + + EXPECT_NE(type_id_unsigned, type_id_signed); + + EXPECT_EQ(const_3->GetSingleWordOperand(0), type_id_unsigned); + EXPECT_EQ(const_5->GetSingleWordOperand(0), type_id_unsigned); + + EXPECT_EQ(const_4->GetSingleWordOperand(0), type_id_signed); + EXPECT_EQ(const_6->GetSingleWordOperand(0), type_id_signed); + + Match(text, context.get()); +} + +#endif // SPIRV_EFFCEE + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/ir_context_test.cpp b/3rdparty/spirv-tools/test/opt/ir_context_test.cpp new file mode 100644 index 000000000..ad851ed72 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/ir_context_test.cpp @@ -0,0 +1,215 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "opt/ir_context.h" +#include "opt/pass.h" +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; +using ir::IRContext; +using Analysis = IRContext::Analysis; +using ::testing::Each; + +class DummyPassPreservesNothing : public opt::Pass { + public: + DummyPassPreservesNothing(Status s) : opt::Pass(), status_to_return_(s) {} + const char* name() const override { return "dummy-pass"; } + Status Process(IRContext*) override { return status_to_return_; } + Status status_to_return_; +}; + +class DummyPassPreservesAll : public opt::Pass { + public: + DummyPassPreservesAll(Status s) : opt::Pass(), status_to_return_(s) {} + const char* name() const override { return "dummy-pass"; } + Status Process(IRContext*) override { return status_to_return_; } + Status status_to_return_; + virtual Analysis GetPreservedAnalyses() override { + return Analysis(IRContext::kAnalysisEnd - 1); + } +}; + +class DummyPassPreservesFirst : public opt::Pass { + public: + DummyPassPreservesFirst(Status s) : opt::Pass(), status_to_return_(s) {} + const char* name() const override { return "dummy-pass"; } + Status Process(IRContext*) override { return status_to_return_; } + Status status_to_return_; + virtual Analysis GetPreservedAnalyses() override { + return IRContext::kAnalysisBegin; + } +}; + +using IRContextTest = PassTest<::testing::Test>; + +TEST_F(IRContextTest, IndividualValidAfterBuild) { + std::unique_ptr module(new ir::Module()); + IRContext localContext(SPV_ENV_UNIVERSAL_1_2, std::move(module), + spvtools::MessageConsumer()); + + for (Analysis i = IRContext::kAnalysisBegin; i < IRContext::kAnalysisEnd; + i <<= 1) { + localContext.BuildInvalidAnalyses(i); + EXPECT_TRUE(localContext.AreAnalysesValid(i)); + } +} + +TEST_F(IRContextTest, AllValidAfterBuild) { + std::unique_ptr module = MakeUnique(); + IRContext localContext(SPV_ENV_UNIVERSAL_1_2, std::move(module), + spvtools::MessageConsumer()); + + Analysis built_analyses = IRContext::kAnalysisNone; + for (Analysis i = IRContext::kAnalysisBegin; i < IRContext::kAnalysisEnd; + i <<= 1) { + localContext.BuildInvalidAnalyses(i); + built_analyses |= i; + } + EXPECT_TRUE(localContext.AreAnalysesValid(built_analyses)); +} + +TEST_F(IRContextTest, AllValidAfterPassNoChange) { + std::unique_ptr module = MakeUnique(); + IRContext localContext(SPV_ENV_UNIVERSAL_1_2, std::move(module), + spvtools::MessageConsumer()); + + Analysis built_analyses = IRContext::kAnalysisNone; + for (Analysis i = IRContext::kAnalysisBegin; i < IRContext::kAnalysisEnd; + i <<= 1) { + localContext.BuildInvalidAnalyses(i); + built_analyses |= i; + } + + DummyPassPreservesNothing pass(opt::Pass::Status::SuccessWithoutChange); + opt::Pass::Status s = pass.Run(&localContext); + EXPECT_EQ(s, opt::Pass::Status::SuccessWithoutChange); + EXPECT_TRUE(localContext.AreAnalysesValid(built_analyses)); +} + +TEST_F(IRContextTest, NoneValidAfterPassWithChange) { + std::unique_ptr module = MakeUnique(); + IRContext localContext(SPV_ENV_UNIVERSAL_1_2, std::move(module), + spvtools::MessageConsumer()); + + for (Analysis i = IRContext::kAnalysisBegin; i < IRContext::kAnalysisEnd; + i <<= 1) { + localContext.BuildInvalidAnalyses(i); + } + + DummyPassPreservesNothing pass(opt::Pass::Status::SuccessWithChange); + opt::Pass::Status s = pass.Run(&localContext); + EXPECT_EQ(s, opt::Pass::Status::SuccessWithChange); + for (Analysis i = IRContext::kAnalysisBegin; i < IRContext::kAnalysisEnd; + i <<= 1) { + EXPECT_FALSE(localContext.AreAnalysesValid(i)); + } +} + +TEST_F(IRContextTest, AllPreservedAfterPassWithChange) { + std::unique_ptr module = MakeUnique(); + IRContext localContext(SPV_ENV_UNIVERSAL_1_2, std::move(module), + spvtools::MessageConsumer()); + + for (Analysis i = IRContext::kAnalysisBegin; i < IRContext::kAnalysisEnd; + i <<= 1) { + localContext.BuildInvalidAnalyses(i); + } + + DummyPassPreservesAll pass(opt::Pass::Status::SuccessWithChange); + opt::Pass::Status s = pass.Run(&localContext); + EXPECT_EQ(s, opt::Pass::Status::SuccessWithChange); + for (Analysis i = IRContext::kAnalysisBegin; i < IRContext::kAnalysisEnd; + i <<= 1) { + EXPECT_TRUE(localContext.AreAnalysesValid(i)); + } +} + +TEST_F(IRContextTest, PreserveFirstOnlyAfterPassWithChange) { + std::unique_ptr module = MakeUnique(); + IRContext localContext(SPV_ENV_UNIVERSAL_1_2, std::move(module), + spvtools::MessageConsumer()); + + for (Analysis i = IRContext::kAnalysisBegin; i < IRContext::kAnalysisEnd; + i <<= 1) { + localContext.BuildInvalidAnalyses(i); + } + + DummyPassPreservesFirst pass(opt::Pass::Status::SuccessWithChange); + opt::Pass::Status s = pass.Run(&localContext); + EXPECT_EQ(s, opt::Pass::Status::SuccessWithChange); + EXPECT_TRUE(localContext.AreAnalysesValid(IRContext::kAnalysisBegin)); + for (Analysis i = IRContext::kAnalysisBegin << 1; i < IRContext::kAnalysisEnd; + i <<= 1) { + EXPECT_FALSE(localContext.AreAnalysesValid(i)); + } +} + +TEST_F(IRContextTest, KillMemberName) { + 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 + OpName %3 "stuff" + OpMemberName %3 0 "refZ" + OpMemberDecorate %3 0 Offset 0 + OpDecorate %3 Block + %4 = OpTypeFloat 32 + %3 = OpTypeStruct %4 + %5 = OpTypeVoid + %6 = OpTypeFunction %5 + %2 = OpFunction %5 None %6 + %7 = OpLabel + OpReturn + OpFunctionEnd +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + + // Build the decoration manager. + context->get_decoration_mgr(); + + // Delete the OpTypeStruct. Should delete the OpName, OpMemberName, and + // OpMemberDecorate associated with it. + context->KillDef(3); + + // Make sure all of the name are removed. + for (auto& inst : context->debugs2()) { + EXPECT_EQ(inst.opcode(), SpvOpNop); + } + + // Make sure all of the decorations are removed. + for (auto& inst : context->annotations()) { + EXPECT_EQ(inst.opcode(), SpvOpNop); + } +} + +TEST_F(IRContextTest, TakeNextUniqueIdIncrementing) { + const uint32_t NUM_TESTS = 1000; + IRContext localContext(SPV_ENV_UNIVERSAL_1_2, nullptr); + for (uint32_t i = 1; i < NUM_TESTS; ++i) + EXPECT_EQ(i, localContext.TakeNextUniqueId()); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/ir_loader_test.cpp b/3rdparty/spirv-tools/test/opt/ir_loader_test.cpp new file mode 100644 index 000000000..87e7e9777 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/ir_loader_test.cpp @@ -0,0 +1,449 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "message.h" +#include "opt/build_module.h" +#include "opt/ir_context.h" +#include "spirv-tools/libspirv.hpp" + +namespace { + +using namespace spvtools; + +void DoRoundTripCheck(const std::string& text) { + SpirvTools t(SPV_ENV_UNIVERSAL_1_1); + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text); + ASSERT_NE(nullptr, context) << "Failed to assemble\n" << text; + + std::vector binary; + context->module()->ToBinary(&binary, /* skip_nop = */ false); + + std::string disassembled_text; + EXPECT_TRUE(t.Disassemble(binary, &disassembled_text)); + EXPECT_EQ(text, disassembled_text); +} + +TEST(IrBuilder, RoundTrip) { + // #version 310 es + // int add(int a, int b) { return a + b; } + // void main() { add(1, 2); } + DoRoundTripCheck( + // clang-format off + "OpCapability Shader\n" + "%1 = OpExtInstImport \"GLSL.std.450\"\n" + "OpMemoryModel Logical GLSL450\n" + "OpEntryPoint Vertex %main \"main\"\n" + "OpSource ESSL 310\n" + "OpSourceExtension \"GL_GOOGLE_cpp_style_line_directive\"\n" + "OpSourceExtension \"GL_GOOGLE_include_directive\"\n" + "OpName %main \"main\"\n" + "OpName %add_i1_i1_ \"add(i1;i1;\"\n" + "OpName %a \"a\"\n" + "OpName %b \"b\"\n" + "OpName %param \"param\"\n" + "OpName %param_0 \"param\"\n" + "%void = OpTypeVoid\n" + "%9 = OpTypeFunction %void\n" + "%int = OpTypeInt 32 1\n" + "%_ptr_Function_int = OpTypePointer Function %int\n" + "%12 = OpTypeFunction %int %_ptr_Function_int %_ptr_Function_int\n" + "%int_1 = OpConstant %int 1\n" + "%int_2 = OpConstant %int 2\n" + "%main = OpFunction %void None %9\n" + "%15 = OpLabel\n" + "%param = OpVariable %_ptr_Function_int Function\n" + "%param_0 = OpVariable %_ptr_Function_int Function\n" + "OpStore %param %int_1\n" + "OpStore %param_0 %int_2\n" + "%16 = OpFunctionCall %int %add_i1_i1_ %param %param_0\n" + "OpReturn\n" + "OpFunctionEnd\n" + "%add_i1_i1_ = OpFunction %int None %12\n" + "%a = OpFunctionParameter %_ptr_Function_int\n" + "%b = OpFunctionParameter %_ptr_Function_int\n" + "%17 = OpLabel\n" + "%18 = OpLoad %int %a\n" + "%19 = OpLoad %int %b\n" + "%20 = OpIAdd %int %18 %19\n" + "OpReturnValue %20\n" + "OpFunctionEnd\n"); + // clang-format on +} + +TEST(IrBuilder, RoundTripIncompleteBasicBlock) { + DoRoundTripCheck( + "%2 = OpFunction %1 None %3\n" + "%4 = OpLabel\n" + "OpNop\n"); +} + +TEST(IrBuilder, RoundTripIncompleteFunction) { + DoRoundTripCheck("%2 = OpFunction %1 None %3\n"); +} + +TEST(IrBuilder, KeepLineDebugInfo) { + // #version 310 es + // void main() {} + DoRoundTripCheck( + // clang-format off + "OpCapability Shader\n" + "%1 = OpExtInstImport \"GLSL.std.450\"\n" + "OpMemoryModel Logical GLSL450\n" + "OpEntryPoint Vertex %main \"main\"\n" + "%3 = OpString \"minimal.vert\"\n" + "OpSource ESSL 310\n" + "OpName %main \"main\"\n" + "OpLine %3 10 10\n" + "%void = OpTypeVoid\n" + "OpLine %3 100 100\n" + "%5 = OpTypeFunction %void\n" + "%main = OpFunction %void None %5\n" + "OpLine %3 1 1\n" + "OpNoLine\n" + "OpLine %3 2 2\n" + "OpLine %3 3 3\n" + "%6 = OpLabel\n" + "OpLine %3 4 4\n" + "OpNoLine\n" + "OpReturn\n" + "OpFunctionEnd\n"); + // clang-format on +} + +TEST(IrBuilder, LocalGlobalVariables) { + // #version 310 es + // + // float gv1 = 10.; + // float gv2 = 100.; + // + // float f() { + // float lv1 = gv1 + gv2; + // float lv2 = gv1 * gv2; + // return lv1 / lv2; + // } + // + // void main() { + // float lv1 = gv1 - gv2; + // } + DoRoundTripCheck( + // clang-format off + "OpCapability Shader\n" + "%1 = OpExtInstImport \"GLSL.std.450\"\n" + "OpMemoryModel Logical GLSL450\n" + "OpEntryPoint Vertex %main \"main\"\n" + "OpSource ESSL 310\n" + "OpName %main \"main\"\n" + "OpName %f_ \"f(\"\n" + "OpName %gv1 \"gv1\"\n" + "OpName %gv2 \"gv2\"\n" + "OpName %lv1 \"lv1\"\n" + "OpName %lv2 \"lv2\"\n" + "OpName %lv1_0 \"lv1\"\n" + "%void = OpTypeVoid\n" + "%10 = OpTypeFunction %void\n" + "%float = OpTypeFloat 32\n" + "%12 = OpTypeFunction %float\n" + "%_ptr_Private_float = OpTypePointer Private %float\n" + "%gv1 = OpVariable %_ptr_Private_float Private\n" + "%float_10 = OpConstant %float 10\n" + "%gv2 = OpVariable %_ptr_Private_float Private\n" + "%float_100 = OpConstant %float 100\n" + "%_ptr_Function_float = OpTypePointer Function %float\n" + "%main = OpFunction %void None %10\n" + "%17 = OpLabel\n" + "%lv1_0 = OpVariable %_ptr_Function_float Function\n" + "OpStore %gv1 %float_10\n" + "OpStore %gv2 %float_100\n" + "%18 = OpLoad %float %gv1\n" + "%19 = OpLoad %float %gv2\n" + "%20 = OpFSub %float %18 %19\n" + "OpStore %lv1_0 %20\n" + "OpReturn\n" + "OpFunctionEnd\n" + "%f_ = OpFunction %float None %12\n" + "%21 = OpLabel\n" + "%lv1 = OpVariable %_ptr_Function_float Function\n" + "%lv2 = OpVariable %_ptr_Function_float Function\n" + "%22 = OpLoad %float %gv1\n" + "%23 = OpLoad %float %gv2\n" + "%24 = OpFAdd %float %22 %23\n" + "OpStore %lv1 %24\n" + "%25 = OpLoad %float %gv1\n" + "%26 = OpLoad %float %gv2\n" + "%27 = OpFMul %float %25 %26\n" + "OpStore %lv2 %27\n" + "%28 = OpLoad %float %lv1\n" + "%29 = OpLoad %float %lv2\n" + "%30 = OpFDiv %float %28 %29\n" + "OpReturnValue %30\n" + "OpFunctionEnd\n"); + // clang-format on +} + +TEST(IrBuilder, OpUndefOutsideFunction) { + // #version 310 es + // void main() {} + const std::string text = + // clang-format off + "OpMemoryModel Logical GLSL450\n" + "%int = OpTypeInt 32 1\n" + "%uint = OpTypeInt 32 0\n" + "%float = OpTypeFloat 32\n" + "%4 = OpUndef %int\n" + "%int_10 = OpConstant %int 10\n" + "%6 = OpUndef %uint\n" + "%bool = OpTypeBool\n" + "%8 = OpUndef %float\n" + "%double = OpTypeFloat 64\n"; + // clang-format on + + SpirvTools t(SPV_ENV_UNIVERSAL_1_1); + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text); + ASSERT_NE(nullptr, context); + + const auto opundef_count = std::count_if( + context->module()->types_values_begin(), + context->module()->types_values_end(), + [](const ir::Instruction& inst) { return inst.opcode() == SpvOpUndef; }); + EXPECT_EQ(3, opundef_count); + + std::vector binary; + context->module()->ToBinary(&binary, /* skip_nop = */ false); + + std::string disassembled_text; + EXPECT_TRUE(t.Disassemble(binary, &disassembled_text)); + EXPECT_EQ(text, disassembled_text); +} + +TEST(IrBuilder, OpUndefInBasicBlock) { + DoRoundTripCheck( + // clang-format off + "OpMemoryModel Logical GLSL450\n" + "OpName %main \"main\"\n" + "%void = OpTypeVoid\n" + "%uint = OpTypeInt 32 0\n" + "%double = OpTypeFloat 64\n" + "%5 = OpTypeFunction %void\n" + "%main = OpFunction %void None %5\n" + "%6 = OpLabel\n" + "%7 = OpUndef %uint\n" + "%8 = OpUndef %double\n" + "OpReturn\n" + "OpFunctionEnd\n"); + // clang-format on +} + +TEST(IrBuilder, KeepLineDebugInfoBeforeType) { + DoRoundTripCheck( + // clang-format off + "OpCapability Shader\n" + "OpMemoryModel Logical GLSL450\n" + "%1 = OpString \"minimal.vert\"\n" + "OpLine %1 1 1\n" + "OpNoLine\n" + "%void = OpTypeVoid\n" + "OpLine %1 2 2\n" + "%3 = OpTypeFunction %void\n"); + // clang-format on +} + +TEST(IrBuilder, KeepLineDebugInfoBeforeLabel) { + DoRoundTripCheck( + // clang-format off + "OpCapability Shader\n" + "OpMemoryModel Logical GLSL450\n" + "%1 = OpString \"minimal.vert\"\n" + "%void = OpTypeVoid\n" + "%3 = OpTypeFunction %void\n" + "%4 = OpFunction %void None %3\n" + "%5 = OpLabel\n" + "OpBranch %6\n" + "OpLine %1 1 1\n" + "OpLine %1 2 2\n" + "%6 = OpLabel\n" + "OpBranch %7\n" + "OpLine %1 100 100\n" + "%7 = OpLabel\n" + "OpReturn\n" + "OpFunctionEnd\n"); + // clang-format on +} + +TEST(IrBuilder, KeepLineDebugInfoBeforeFunctionEnd) { + DoRoundTripCheck( + // clang-format off + "OpCapability Shader\n" + "OpMemoryModel Logical GLSL450\n" + "%1 = OpString \"minimal.vert\"\n" + "%void = OpTypeVoid\n" + "%3 = OpTypeFunction %void\n" + "%4 = OpFunction %void None %3\n" + "OpLine %1 1 1\n" + "OpLine %1 2 2\n" + "OpFunctionEnd\n"); + // clang-format on +} + +TEST(IrBuilder, KeepModuleProcessedInRightPlace) { + DoRoundTripCheck( + // clang-format off + "OpCapability Shader\n" + "OpMemoryModel Logical GLSL450\n" + "%1 = OpString \"minimal.vert\"\n" + "OpName %void \"void\"\n" + "OpModuleProcessed \"Made it faster\"\n" + "OpModuleProcessed \".. and smaller\"\n" + "%void = OpTypeVoid\n"); + // clang-format on +} + +// Checks the given |error_message| is reported when trying to build a module +// from the given |assembly|. +void DoErrorMessageCheck(const std::string& assembly, + const std::string& error_message) { + auto consumer = [error_message](spv_message_level_t level, const char* source, + const spv_position_t& position, + const char* m) { + EXPECT_EQ(error_message, StringifyMessage(level, source, position, m)); + }; + + SpirvTools t(SPV_ENV_UNIVERSAL_1_1); + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, std::move(consumer), assembly); + EXPECT_EQ(nullptr, context); +} + +TEST(IrBuilder, FunctionInsideFunction) { + DoErrorMessageCheck("%2 = OpFunction %1 None %3\n%5 = OpFunction %4 None %6", + "error: :2:0:0: function inside function"); +} + +TEST(IrBuilder, MismatchOpFunctionEnd) { + DoErrorMessageCheck("OpFunctionEnd", + "error: :1:0:0: OpFunctionEnd without " + "corresponding OpFunction"); +} + +TEST(IrBuilder, OpFunctionEndInsideBasicBlock) { + DoErrorMessageCheck( + "%2 = OpFunction %1 None %3\n" + "%4 = OpLabel\n" + "OpFunctionEnd", + "error: :3:0:0: OpFunctionEnd inside basic block"); +} + +TEST(IrBuilder, BasicBlockOutsideFunction) { + DoErrorMessageCheck("OpCapability Shader\n%1 = OpLabel", + "error: :2:0:0: OpLabel outside function"); +} + +TEST(IrBuilder, OpLabelInsideBasicBlock) { + DoErrorMessageCheck( + "%2 = OpFunction %1 None %3\n" + "%4 = OpLabel\n" + "%5 = OpLabel", + "error: :3:0:0: OpLabel inside basic block"); +} + +TEST(IrBuilder, TerminatorOutsideFunction) { + DoErrorMessageCheck( + "OpReturn", + "error: :1:0:0: terminator instruction outside function"); +} + +TEST(IrBuilder, TerminatorOutsideBasicBlock) { + DoErrorMessageCheck("%2 = OpFunction %1 None %3\nOpReturn", + "error: :2:0:0: terminator instruction " + "outside basic block"); +} + +TEST(IrBuilder, NotAllowedInstAppearingInFunction) { + DoErrorMessageCheck("%2 = OpFunction %1 None %3\n%5 = OpVariable %4 Function", + "error: :2:0:0: Non-OpFunctionParameter " + "(opcode: 59) found inside function but outside basic " + "block"); +} + +TEST(IrBuilder, UniqueIds) { + const std::string text = + // clang-format off + "OpCapability Shader\n" + "%1 = OpExtInstImport \"GLSL.std.450\"\n" + "OpMemoryModel Logical GLSL450\n" + "OpEntryPoint Vertex %main \"main\"\n" + "OpSource ESSL 310\n" + "OpName %main \"main\"\n" + "OpName %f_ \"f(\"\n" + "OpName %gv1 \"gv1\"\n" + "OpName %gv2 \"gv2\"\n" + "OpName %lv1 \"lv1\"\n" + "OpName %lv2 \"lv2\"\n" + "OpName %lv1_0 \"lv1\"\n" + "%void = OpTypeVoid\n" + "%10 = OpTypeFunction %void\n" + "%float = OpTypeFloat 32\n" + "%12 = OpTypeFunction %float\n" + "%_ptr_Private_float = OpTypePointer Private %float\n" + "%gv1 = OpVariable %_ptr_Private_float Private\n" + "%float_10 = OpConstant %float 10\n" + "%gv2 = OpVariable %_ptr_Private_float Private\n" + "%float_100 = OpConstant %float 100\n" + "%_ptr_Function_float = OpTypePointer Function %float\n" + "%main = OpFunction %void None %10\n" + "%17 = OpLabel\n" + "%lv1_0 = OpVariable %_ptr_Function_float Function\n" + "OpStore %gv1 %float_10\n" + "OpStore %gv2 %float_100\n" + "%18 = OpLoad %float %gv1\n" + "%19 = OpLoad %float %gv2\n" + "%20 = OpFSub %float %18 %19\n" + "OpStore %lv1_0 %20\n" + "OpReturn\n" + "OpFunctionEnd\n" + "%f_ = OpFunction %float None %12\n" + "%21 = OpLabel\n" + "%lv1 = OpVariable %_ptr_Function_float Function\n" + "%lv2 = OpVariable %_ptr_Function_float Function\n" + "%22 = OpLoad %float %gv1\n" + "%23 = OpLoad %float %gv2\n" + "%24 = OpFAdd %float %22 %23\n" + "OpStore %lv1 %24\n" + "%25 = OpLoad %float %gv1\n" + "%26 = OpLoad %float %gv2\n" + "%27 = OpFMul %float %25 %26\n" + "OpStore %lv2 %27\n" + "%28 = OpLoad %float %lv1\n" + "%29 = OpLoad %float %lv2\n" + "%30 = OpFDiv %float %28 %29\n" + "OpReturnValue %30\n" + "OpFunctionEnd\n"; + // clang-format on + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text); + ASSERT_NE(nullptr, context); + + std::unordered_set ids; + context->module()->ForEachInst([&ids](const ir::Instruction* inst) { + EXPECT_TRUE(ids.insert(inst->unique_id()).second); + }); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/iterator_test.cpp b/3rdparty/spirv-tools/test/opt/iterator_test.cpp new file mode 100644 index 000000000..5afe88c15 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/iterator_test.cpp @@ -0,0 +1,217 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "gmock/gmock.h" + +#include "opt/iterator.h" +#include "opt/make_unique.h" + +namespace { + +using namespace spvtools; +using ::testing::ContainerEq; + +TEST(Iterator, IncrementDeref) { + const int count = 100; + std::vector> data; + for (int i = 0; i < count; ++i) { + data.emplace_back(new int(i)); + } + + ir::UptrVectorIterator it(&data, data.begin()); + ir::UptrVectorIterator end(&data, data.end()); + + EXPECT_EQ(*data[0], *it); + for (int i = 1; i < count; ++i) { + EXPECT_NE(end, it); + EXPECT_EQ(*data[i], *(++it)); + } + EXPECT_EQ(end, ++it); +} + +TEST(Iterator, DecrementDeref) { + const int count = 100; + std::vector> data; + for (int i = 0; i < count; ++i) { + data.emplace_back(new int(i)); + } + + ir::UptrVectorIterator begin(&data, data.begin()); + ir::UptrVectorIterator it(&data, data.end()); + + for (int i = count - 1; i >= 0; --i) { + EXPECT_NE(begin, it); + EXPECT_EQ(*data[i], *(--it)); + } + EXPECT_EQ(begin, it); +} + +TEST(Iterator, PostIncrementDeref) { + const int count = 100; + std::vector> data; + for (int i = 0; i < count; ++i) { + data.emplace_back(new int(i)); + } + + ir::UptrVectorIterator it(&data, data.begin()); + ir::UptrVectorIterator end(&data, data.end()); + + for (int i = 0; i < count; ++i) { + EXPECT_NE(end, it); + EXPECT_EQ(*data[i], *(it++)); + } + EXPECT_EQ(end, it); +} + +TEST(Iterator, PostDecrementDeref) { + const int count = 100; + std::vector> data; + for (int i = 0; i < count; ++i) { + data.emplace_back(new int(i)); + } + + ir::UptrVectorIterator begin(&data, data.begin()); + ir::UptrVectorIterator end(&data, data.end()); + ir::UptrVectorIterator it(&data, data.end()); + + EXPECT_EQ(end, it--); + for (int i = count - 1; i >= 1; --i) { + EXPECT_EQ(*data[i], *(it--)); + } + // Decrementing .begin() is undefined behavior. + EXPECT_EQ(*data[0], *it); +} + +TEST(Iterator, Access) { + const int count = 100; + std::vector> data; + for (int i = 0; i < count; ++i) { + data.emplace_back(new int(i)); + } + + ir::UptrVectorIterator it(&data, data.begin()); + + for (int i = 0; i < count; ++i) EXPECT_EQ(*data[i], it[i]); +} + +TEST(Iterator, Comparison) { + const int count = 100; + std::vector> data; + for (int i = 0; i < count; ++i) { + data.emplace_back(new int(i)); + } + + ir::UptrVectorIterator it(&data, data.begin()); + ir::UptrVectorIterator end(&data, data.end()); + + for (int i = 0; i < count; ++i, ++it) EXPECT_TRUE(it < end); + EXPECT_EQ(end, it); +} + +TEST(Iterator, InsertBeginEnd) { + const int count = 100; + + std::vector> data; + std::vector expected; + std::vector actual; + + for (int i = 0; i < count; ++i) { + data.emplace_back(new int(i)); + expected.push_back(i); + } + + // Insert at the beginning + expected.insert(expected.begin(), -100); + ir::UptrVectorIterator begin(&data, data.begin()); + auto insert_point = begin.InsertBefore(MakeUnique(-100)); + for (int i = 0; i < count + 1; ++i) { + actual.push_back(*(insert_point++)); + } + EXPECT_THAT(actual, ContainerEq(expected)); + + // Insert at the end + expected.push_back(-42); + expected.push_back(-36); + expected.push_back(-77); + ir::UptrVectorIterator end(&data, data.end()); + end = end.InsertBefore(MakeUnique(-77)); + end = end.InsertBefore(MakeUnique(-36)); + end = end.InsertBefore(MakeUnique(-42)); + + actual.clear(); + begin = ir::UptrVectorIterator(&data, data.begin()); + for (int i = 0; i < count + 4; ++i) { + actual.push_back(*(begin++)); + } + EXPECT_THAT(actual, ContainerEq(expected)); +} + +TEST(Iterator, InsertMiddle) { + const int count = 100; + + std::vector> data; + std::vector expected; + std::vector actual; + + for (int i = 0; i < count; ++i) { + data.emplace_back(new int(i)); + expected.push_back(i); + } + + const int insert_pos = 42; + expected.insert(expected.begin() + insert_pos, -100); + expected.insert(expected.begin() + insert_pos, -42); + + ir::UptrVectorIterator it(&data, data.begin()); + for (int i = 0; i < insert_pos; ++i) ++it; + it = it.InsertBefore(MakeUnique(-100)); + it = it.InsertBefore(MakeUnique(-42)); + auto begin = ir::UptrVectorIterator(&data, data.begin()); + for (int i = 0; i < count + 2; ++i) { + actual.push_back(*(begin++)); + } + EXPECT_THAT(actual, ContainerEq(expected)); +} + +TEST(IteratorRange, Interface) { + const uint32_t count = 100; + + std::vector> data; + + for (uint32_t i = 0; i < count; ++i) { + data.emplace_back(new uint32_t(i)); + } + + auto b = ir::UptrVectorIterator(&data, data.begin()); + auto e = ir::UptrVectorIterator(&data, data.end()); + auto range = ir::IteratorRange(b, e); + + EXPECT_EQ(b, range.begin()); + EXPECT_EQ(e, range.end()); + EXPECT_FALSE(range.empty()); + EXPECT_EQ(count, range.size()); + EXPECT_EQ(0u, *range.begin()); + EXPECT_EQ(99u, *(--range.end())); + + // IteratorRange itself is immutable. + ++b, --e; + EXPECT_EQ(count, range.size()); + ++range.begin(), --range.end(); + EXPECT_EQ(count, range.size()); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/line_debug_info_test.cpp b/3rdparty/spirv-tools/test/opt/line_debug_info_test.cpp new file mode 100644 index 000000000..2bb794864 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/line_debug_info_test.cpp @@ -0,0 +1,111 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; + +// A pass turning all none debug line instructions into Nop. +class NopifyPass : public opt::Pass { + public: + const char* name() const override { return "NopifyPass"; } + Status Process(ir::IRContext* irContext) override { + bool modified = false; + irContext->module()->ForEachInst( + [&modified](ir::Instruction* inst) { + inst->ToNop(); + modified = true; + }, + /* run_on_debug_line_insts = */ false); + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; + } +}; + +using PassTestForLineDebugInfo = PassTest<::testing::Test>; + +// This test's purpose to show our implementation choice: line debug info is +// preserved even if the following instruction is killed. It serves as a guard +// of potential behavior changes. +TEST_F(PassTestForLineDebugInfo, KeepLineDebugInfo) { + // clang-format off + const char* text = + "OpCapability Shader " + "%1 = OpExtInstImport \"GLSL.std.450\" " + "OpMemoryModel Logical GLSL450 " + "OpEntryPoint Vertex %2 \"main\" " + "%3 = OpString \"minimal.vert\" " + "OpNoLine " + "OpLine %3 10 10 " + "%void = OpTypeVoid " + "OpLine %3 100 100 " + "%5 = OpTypeFunction %void " + "%2 = OpFunction %void None %5 " + "OpLine %3 1 1 " + "OpNoLine " + "OpLine %3 2 2 " + "OpLine %3 3 3 " + "%6 = OpLabel " + "OpLine %3 4 4 " + "OpNoLine " + "OpReturn " + "OpLine %3 4 4 " + "OpNoLine " + "OpFunctionEnd "; + // clang-format on + + const char* result_keep_nop = + "OpNop\n" + "OpNop\n" + "OpNop\n" + "OpNop\n" + "OpNop\n" + "OpNoLine\n" + "OpLine %3 10 10\n" + "OpNop\n" + "OpLine %3 100 100\n" + "OpNop\n" + "OpNop\n" + "OpLine %3 1 1\n" + "OpNoLine\n" + "OpLine %3 2 2\n" + "OpLine %3 3 3\n" + "OpNop\n" + "OpLine %3 4 4\n" + "OpNoLine\n" + "OpNop\n" + "OpLine %3 4 4\n" + "OpNoLine\n" + "OpNop\n"; + SinglePassRunAndCheck(text, result_keep_nop, + /* skip_nop = */ false); + const char* result_skip_nop = + "OpNoLine\n" + "OpLine %3 10 10\n" + "OpLine %3 100 100\n" + "OpLine %3 1 1\n" + "OpNoLine\n" + "OpLine %3 2 2\n" + "OpLine %3 3 3\n" + "OpLine %3 4 4\n" + "OpNoLine\n" + "OpLine %3 4 4\n" + "OpNoLine\n"; + SinglePassRunAndCheck(text, result_skip_nop, + /* skip_nop = */ true); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/local_access_chain_convert_test.cpp b/3rdparty/spirv-tools/test/opt/local_access_chain_convert_test.cpp new file mode 100644 index 000000000..b559a814e --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/local_access_chain_convert_test.cpp @@ -0,0 +1,722 @@ +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; + +using LocalAccessChainConvertTest = PassTest<::testing::Test>; + +TEST_F(LocalAccessChainConvertTest, StructOfVecsOfFloatConverted) { + // #version 140 + // + // in vec4 BaseColor; + // + // struct S_t { + // vec4 v0; + // vec4 v1; + // }; + // + // void main() + // { + // S_t s0; + // s0.v1 = BaseColor; + // gl_FragColor = s0.v1; + // } + + const std::string predefs_before = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %S_t "S_t" +OpMemberName %S_t 0 "v0" +OpMemberName %S_t 1 "v1" +OpName %s0 "s0" +OpName %BaseColor "BaseColor" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%S_t = OpTypeStruct %v4float %v4float +%_ptr_Function_S_t = OpTypePointer Function %S_t +%int = OpTypeInt 32 1 +%int_1 = OpConstant %int 1 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string predefs_after = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %S_t "S_t" +OpMemberName %S_t 0 "v0" +OpMemberName %S_t 1 "v1" +OpName %s0 "s0" +OpName %BaseColor "BaseColor" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%S_t = OpTypeStruct %v4float %v4float +%_ptr_Function_S_t = OpTypePointer Function %S_t +%int = OpTypeInt 32 1 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %8 +%17 = OpLabel +%s0 = OpVariable %_ptr_Function_S_t Function +%18 = OpLoad %v4float %BaseColor +%19 = OpAccessChain %_ptr_Function_v4float %s0 %int_1 +OpStore %19 %18 +%20 = OpAccessChain %_ptr_Function_v4float %s0 %int_1 +%21 = OpLoad %v4float %20 +OpStore %gl_FragColor %21 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %8 +%17 = OpLabel +%s0 = OpVariable %_ptr_Function_S_t Function +%18 = OpLoad %v4float %BaseColor +%22 = OpLoad %S_t %s0 +%23 = OpCompositeInsert %S_t %18 %22 1 +OpStore %s0 %23 +%24 = OpLoad %S_t %s0 +%25 = OpCompositeExtract %v4float %24 1 +OpStore %gl_FragColor %25 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs_before + before, predefs_after + after, true, true); +} + +TEST_F(LocalAccessChainConvertTest, InBoundsAccessChainsConverted) { + // #version 140 + // + // in vec4 BaseColor; + // + // struct S_t { + // vec4 v0; + // vec4 v1; + // }; + // + // void main() + // { + // S_t s0; + // s0.v1 = BaseColor; + // gl_FragColor = s0.v1; + // } + + const std::string predefs_before = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %S_t "S_t" +OpMemberName %S_t 0 "v0" +OpMemberName %S_t 1 "v1" +OpName %s0 "s0" +OpName %BaseColor "BaseColor" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%S_t = OpTypeStruct %v4float %v4float +%_ptr_Function_S_t = OpTypePointer Function %S_t +%int = OpTypeInt 32 1 +%int_1 = OpConstant %int 1 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string predefs_after = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %S_t "S_t" +OpMemberName %S_t 0 "v0" +OpMemberName %S_t 1 "v1" +OpName %s0 "s0" +OpName %BaseColor "BaseColor" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%S_t = OpTypeStruct %v4float %v4float +%_ptr_Function_S_t = OpTypePointer Function %S_t +%int = OpTypeInt 32 1 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %8 +%17 = OpLabel +%s0 = OpVariable %_ptr_Function_S_t Function +%18 = OpLoad %v4float %BaseColor +%19 = OpInBoundsAccessChain %_ptr_Function_v4float %s0 %int_1 +OpStore %19 %18 +%20 = OpInBoundsAccessChain %_ptr_Function_v4float %s0 %int_1 +%21 = OpLoad %v4float %20 +OpStore %gl_FragColor %21 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %8 +%17 = OpLabel +%s0 = OpVariable %_ptr_Function_S_t Function +%18 = OpLoad %v4float %BaseColor +%22 = OpLoad %S_t %s0 +%23 = OpCompositeInsert %S_t %18 %22 1 +OpStore %s0 %23 +%24 = OpLoad %S_t %s0 +%25 = OpCompositeExtract %v4float %24 1 +OpStore %gl_FragColor %25 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs_before + before, predefs_after + after, true, true); +} + +TEST_F(LocalAccessChainConvertTest, TwoUsesofSingleChainConverted) { + // #version 140 + // + // in vec4 BaseColor; + // + // struct S_t { + // vec4 v0; + // vec4 v1; + // }; + // + // void main() + // { + // S_t s0; + // s0.v1 = BaseColor; + // gl_FragColor = s0.v1; + // } + + const std::string predefs_before = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %S_t "S_t" +OpMemberName %S_t 0 "v0" +OpMemberName %S_t 1 "v1" +OpName %s0 "s0" +OpName %BaseColor "BaseColor" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%S_t = OpTypeStruct %v4float %v4float +%_ptr_Function_S_t = OpTypePointer Function %S_t +%int = OpTypeInt 32 1 +%int_1 = OpConstant %int 1 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string predefs_after = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %S_t "S_t" +OpMemberName %S_t 0 "v0" +OpMemberName %S_t 1 "v1" +OpName %s0 "s0" +OpName %BaseColor "BaseColor" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%S_t = OpTypeStruct %v4float %v4float +%_ptr_Function_S_t = OpTypePointer Function %S_t +%int = OpTypeInt 32 1 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %8 +%17 = OpLabel +%s0 = OpVariable %_ptr_Function_S_t Function +%18 = OpLoad %v4float %BaseColor +%19 = OpAccessChain %_ptr_Function_v4float %s0 %int_1 +OpStore %19 %18 +%20 = OpLoad %v4float %19 +OpStore %gl_FragColor %20 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %8 +%17 = OpLabel +%s0 = OpVariable %_ptr_Function_S_t Function +%18 = OpLoad %v4float %BaseColor +%21 = OpLoad %S_t %s0 +%22 = OpCompositeInsert %S_t %18 %21 1 +OpStore %s0 %22 +%23 = OpLoad %S_t %s0 +%24 = OpCompositeExtract %v4float %23 1 +OpStore %gl_FragColor %24 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs_before + before, predefs_after + after, true, true); +} + +TEST_F(LocalAccessChainConvertTest, OpaqueConverted) { + // SPIR-V not representable in GLSL; not generatable from HLSL + // at the moment + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %outColor %texCoords +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %S_t "S_t" +OpMemberName %S_t 0 "v0" +OpMemberName %S_t 1 "v1" +OpMemberName %S_t 2 "smp" +OpName %foo_struct_S_t_vf2_vf21_ "foo(struct-S_t-vf2-vf21;" +OpName %s "s" +OpName %outColor "outColor" +OpName %sampler15 "sampler15" +OpName %s0 "s0" +OpName %texCoords "texCoords" +OpName %param "param" +OpDecorate %sampler15 DescriptorSet 0 +%void = OpTypeVoid +%12 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v2float = OpTypeVector %float 2 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%outColor = OpVariable %_ptr_Output_v4float Output +%17 = OpTypeImage %float 2D 0 0 0 1 Unknown +%18 = OpTypeSampledImage %17 +%S_t = OpTypeStruct %v2float %v2float %18 +%_ptr_Function_S_t = OpTypePointer Function %S_t +%20 = OpTypeFunction %void %_ptr_Function_S_t +%_ptr_UniformConstant_18 = OpTypePointer UniformConstant %18 +%_ptr_Function_18 = OpTypePointer Function %18 +%sampler15 = OpVariable %_ptr_UniformConstant_18 UniformConstant +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%int_2 = OpConstant %int 2 +%_ptr_Function_v2float = OpTypePointer Function %v2float +%_ptr_Input_v2float = OpTypePointer Input %v2float +%texCoords = OpVariable %_ptr_Input_v2float Input +)"; + + const std::string before = + R"(%main = OpFunction %void None %12 +%28 = OpLabel +%s0 = OpVariable %_ptr_Function_S_t Function +%param = OpVariable %_ptr_Function_S_t Function +%29 = OpLoad %v2float %texCoords +%30 = OpAccessChain %_ptr_Function_v2float %s0 %int_0 +OpStore %30 %29 +%31 = OpLoad %18 %sampler15 +%32 = OpAccessChain %_ptr_Function_18 %s0 %int_2 +OpStore %32 %31 +%33 = OpLoad %S_t %s0 +OpStore %param %33 +%34 = OpAccessChain %_ptr_Function_18 %param %int_2 +%35 = OpLoad %18 %34 +%36 = OpAccessChain %_ptr_Function_v2float %param %int_0 +%37 = OpLoad %v2float %36 +%38 = OpImageSampleImplicitLod %v4float %35 %37 +OpStore %outColor %38 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %12 +%28 = OpLabel +%s0 = OpVariable %_ptr_Function_S_t Function +%param = OpVariable %_ptr_Function_S_t Function +%29 = OpLoad %v2float %texCoords +%45 = OpLoad %S_t %s0 +%46 = OpCompositeInsert %S_t %29 %45 0 +OpStore %s0 %46 +%31 = OpLoad %18 %sampler15 +%47 = OpLoad %S_t %s0 +%48 = OpCompositeInsert %S_t %31 %47 2 +OpStore %s0 %48 +%33 = OpLoad %S_t %s0 +OpStore %param %33 +%49 = OpLoad %S_t %param +%50 = OpCompositeExtract %18 %49 2 +%51 = OpLoad %S_t %param +%52 = OpCompositeExtract %v2float %51 0 +%38 = OpImageSampleImplicitLod %v4float %50 %52 +OpStore %outColor %38 +OpReturn +OpFunctionEnd +)"; + + const std::string remain = + R"(%foo_struct_S_t_vf2_vf21_ = OpFunction %void None %20 +%s = OpFunctionParameter %_ptr_Function_S_t +%39 = OpLabel +%40 = OpAccessChain %_ptr_Function_18 %s %int_2 +%41 = OpLoad %18 %40 +%42 = OpAccessChain %_ptr_Function_v2float %s %int_0 +%43 = OpLoad %v2float %42 +%44 = OpImageSampleImplicitLod %v4float %41 %43 +OpStore %outColor %44 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before + remain, predefs + after + remain, true, true); +} + +TEST_F(LocalAccessChainConvertTest, NestedStructsConverted) { + // #version 140 + // + // in vec4 BaseColor; + // + // struct S1_t { + // vec4 v1; + // }; + // + // struct S2_t { + // vec4 v2; + // S1_t s1; + // }; + // + // void main() + // { + // S2_t s2; + // s2.s1.v1 = BaseColor; + // gl_FragColor = s2.s1.v1; + // } + + const std::string predefs_before = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %S1_t "S1_t" +OpMemberName %S1_t 0 "v1" +OpName %S2_t "S2_t" +OpMemberName %S2_t 0 "v2" +OpMemberName %S2_t 1 "s1" +OpName %s2 "s2" +OpName %BaseColor "BaseColor" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%9 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%S1_t = OpTypeStruct %v4float +%S2_t = OpTypeStruct %v4float %S1_t +%_ptr_Function_S2_t = OpTypePointer Function %S2_t +%int = OpTypeInt 32 1 +%int_1 = OpConstant %int 1 +%int_0 = OpConstant %int 0 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string predefs_after = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %S1_t "S1_t" +OpMemberName %S1_t 0 "v1" +OpName %S2_t "S2_t" +OpMemberName %S2_t 0 "v2" +OpMemberName %S2_t 1 "s1" +OpName %s2 "s2" +OpName %BaseColor "BaseColor" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%9 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%S1_t = OpTypeStruct %v4float +%S2_t = OpTypeStruct %v4float %S1_t +%_ptr_Function_S2_t = OpTypePointer Function %S2_t +%int = OpTypeInt 32 1 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %9 +%19 = OpLabel +%s2 = OpVariable %_ptr_Function_S2_t Function +%20 = OpLoad %v4float %BaseColor +%21 = OpAccessChain %_ptr_Function_v4float %s2 %int_1 %int_0 +OpStore %21 %20 +%22 = OpAccessChain %_ptr_Function_v4float %s2 %int_1 %int_0 +%23 = OpLoad %v4float %22 +OpStore %gl_FragColor %23 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %9 +%19 = OpLabel +%s2 = OpVariable %_ptr_Function_S2_t Function +%20 = OpLoad %v4float %BaseColor +%24 = OpLoad %S2_t %s2 +%25 = OpCompositeInsert %S2_t %20 %24 1 0 +OpStore %s2 %25 +%26 = OpLoad %S2_t %s2 +%27 = OpCompositeExtract %v4float %26 1 0 +OpStore %gl_FragColor %27 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs_before + before, predefs_after + after, true, true); +} + +TEST_F(LocalAccessChainConvertTest, DynamicallyIndexedVarNotConverted) { + // #version 140 + // + // in vec4 BaseColor; + // flat in int Idx; + // in float Bi; + // + // struct S_t { + // vec4 v0; + // vec4 v1; + // }; + // + // void main() + // { + // S_t s0; + // s0.v1 = BaseColor; + // s0.v1[Idx] = Bi; + // gl_FragColor = s0.v1; + // } + + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %Idx %Bi %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %S_t "S_t" +OpMemberName %S_t 0 "v0" +OpMemberName %S_t 1 "v1" +OpName %s0 "s0" +OpName %BaseColor "BaseColor" +OpName %Idx "Idx" +OpName %Bi "Bi" +OpName %gl_FragColor "gl_FragColor" +OpDecorate %Idx Flat +%void = OpTypeVoid +%10 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%S_t = OpTypeStruct %v4float %v4float +%_ptr_Function_S_t = OpTypePointer Function %S_t +%int = OpTypeInt 32 1 +%int_1 = OpConstant %int 1 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_int = OpTypePointer Input %int +%Idx = OpVariable %_ptr_Input_int Input +%_ptr_Input_float = OpTypePointer Input %float +%Bi = OpVariable %_ptr_Input_float Input +%_ptr_Function_float = OpTypePointer Function %float +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %10 +%22 = OpLabel +%s0 = OpVariable %_ptr_Function_S_t Function +%23 = OpLoad %v4float %BaseColor +%24 = OpAccessChain %_ptr_Function_v4float %s0 %int_1 +OpStore %24 %23 +%25 = OpLoad %int %Idx +%26 = OpLoad %float %Bi +%27 = OpAccessChain %_ptr_Function_float %s0 %int_1 %25 +OpStore %27 %26 +%28 = OpAccessChain %_ptr_Function_v4float %s0 %int_1 +%29 = OpLoad %v4float %28 +OpStore %gl_FragColor %29 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(assembly, assembly, + false, true); +} + +TEST_F(LocalAccessChainConvertTest, SomeAccessChainsHaveNoUse) { + // Based on HLSL source code: + // struct S { + // float f; + // }; + + // float main(float input : A) : B { + // S local = { input }; + // return local.f; + // } + + const std::string predefs = R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %main "main" %in_var_A %out_var_B +OpName %main "main" +OpName %in_var_A "in.var.A" +OpName %out_var_B "out.var.B" +OpName %S "S" +OpName %local "local" +%int = OpTypeInt 32 1 +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float +%_ptr_Input_float = OpTypePointer Input %float +%_ptr_Output_float = OpTypePointer Output %float +%S = OpTypeStruct %float +%_ptr_Function_S = OpTypePointer Function %S +%int_0 = OpConstant %int 0 +%in_var_A = OpVariable %_ptr_Input_float Input +%out_var_B = OpVariable %_ptr_Output_float Output +%main = OpFunction %void None %8 +%15 = OpLabel +%local = OpVariable %_ptr_Function_S Function +%16 = OpLoad %float %in_var_A +%17 = OpCompositeConstruct %S %16 +OpStore %local %17 +)"; + + const std::string before = + R"(%18 = OpAccessChain %_ptr_Function_float %local %int_0 +%19 = OpAccessChain %_ptr_Function_float %local %int_0 +%20 = OpLoad %float %18 +OpStore %out_var_B %20 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%19 = OpAccessChain %_ptr_Function_float %local %int_0 +%21 = OpLoad %S %local +%22 = OpCompositeExtract %float %21 0 +OpStore %out_var_B %22 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +// TODO(greg-lunarg): Add tests to verify handling of these cases: +// +// Assorted vector and matrix types +// Assorted struct array types +// Assorted scalar types +// Assorted non-target types +// OpInBoundsAccessChain +// Others? + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/local_redundancy_elimination_test.cpp b/3rdparty/spirv-tools/test/opt/local_redundancy_elimination_test.cpp new file mode 100644 index 000000000..70ccf7bde --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/local_redundancy_elimination_test.cpp @@ -0,0 +1,158 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "opt/value_number_table.h" + +#include "assembly_builder.h" +#include "gmock/gmock.h" +#include "opt/build_module.h" +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; + +using ::testing::HasSubstr; +using ::testing::MatchesRegex; + +using LocalRedundancyEliminationTest = PassTest<::testing::Test>; + +#ifdef SPIRV_EFFCEE +// Remove an instruction when it was already computed. +TEST_F(LocalRedundancyEliminationTest, RemoveRedundantAdd) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Function %5 + %2 = OpFunction %3 None %4 + %7 = OpLabel + %8 = OpVariable %6 Function + %9 = OpLoad %5 %8 + %10 = OpFAdd %5 %9 %9 +; CHECK: OpFAdd +; CHECK-NOT: OpFAdd + %11 = OpFAdd %5 %9 %9 + OpReturn + OpFunctionEnd + )"; + SinglePassRunAndMatch(text, false); +} + +// Make sure we keep instruction that are different, but look similar. +TEST_F(LocalRedundancyEliminationTest, KeepDifferentAdd) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Function %5 + %2 = OpFunction %3 None %4 + %7 = OpLabel + %8 = OpVariable %6 Function + %9 = OpLoad %5 %8 + %10 = OpFAdd %5 %9 %9 +; CHECK: OpFAdd + OpStore %8 %10 + %11 = OpLoad %5 %8 +; CHECK: %11 = OpLoad + %12 = OpFAdd %5 %11 %11 +; CHECK: OpFAdd [[:%\w+]] %11 %11 + OpReturn + OpFunctionEnd + )"; + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndMatch(text, false); +} + +// This test is check that the values are being propagated properly, and that +// we are able to identify sequences of instruction that are not needed. +TEST_F(LocalRedundancyEliminationTest, RemoveMultipleInstructions) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Uniform %5 + %8 = OpVariable %6 Uniform + %2 = OpFunction %3 None %4 + %7 = OpLabel +; CHECK: [[r1:%\w+]] = OpLoad + %9 = OpLoad %5 %8 +; CHECK-NEXT: [[r2:%\w+]] = OpFAdd [[:%\w+]] [[r1]] [[r1]] + %10 = OpFAdd %5 %9 %9 +; CHECK-NEXT: [[r3:%\w+]] = OpFMul [[:%\w+]] [[r2]] [[r1]] + %11 = OpFMul %5 %10 %9 +; CHECK-NOT: OpLoad + %12 = OpLoad %5 %8 +; CHECK-NOT: OpFAdd [[:\w+]] %12 %12 + %13 = OpFAdd %5 %12 %12 +; CHECK-NOT: OpFMul + %14 = OpFMul %5 %13 %12 +; CHECK-NEXT: [[:%\w+]] = OpFAdd [[:%\w+]] [[r3]] [[r3]] + %15 = OpFAdd %5 %14 %11 + OpReturn + OpFunctionEnd + )"; + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndMatch(text, false); +} + +// Redundant instructions in different blocks should be kept. +TEST_F(LocalRedundancyEliminationTest, KeepInstructionsInDifferentBlocks) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Function %5 + %2 = OpFunction %3 None %4 + %bb1 = OpLabel + %8 = OpVariable %6 Function + %9 = OpLoad %5 %8 + %10 = OpFAdd %5 %9 %9 +; CHECK: OpFAdd + OpBranch %bb2 + %bb2 = OpLabel +; CHECK: OpFAdd + %11 = OpFAdd %5 %9 %9 + OpReturn + OpFunctionEnd + )"; + SinglePassRunAndMatch(text, false); +} +#endif +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/local_single_block_elim.cpp b/3rdparty/spirv-tools/test/opt/local_single_block_elim.cpp new file mode 100644 index 000000000..870103509 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/local_single_block_elim.cpp @@ -0,0 +1,801 @@ +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; + +using LocalSingleBlockLoadStoreElimTest = PassTest<::testing::Test>; + +TEST_F(LocalSingleBlockLoadStoreElimTest, SimpleStoreLoadElim) { + // #version 140 + // + // in vec4 BaseColor; + // + // void main() + // { + // vec4 v = BaseColor; + // gl_FragColor = v; + // } + + const std::string predefs_before = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %7 +%13 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%14 = OpLoad %v4float %BaseColor +OpStore %v %14 +%15 = OpLoad %v4float %v +OpStore %gl_FragColor %15 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %7 +%13 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%14 = OpLoad %v4float %BaseColor +OpStore %v %14 +%15 = OpLoad %v4float %v +OpStore %gl_FragColor %14 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs_before + before, predefs_before + after, true, true); +} + +TEST_F(LocalSingleBlockLoadStoreElimTest, SimpleLoadLoadElim) { + // #version 140 + // + // in vec4 BaseColor; + // in float fi; + // + // void main() + // { + // vec4 v = BaseColor; + // if (fi < 0) + // v = vec4(0.0); + // gl_FragData[0] = v; + // gl_FragData[1] = v; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %fi %gl_FragData +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %fi "fi" +OpName %gl_FragData "gl_FragData" +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Input_float = OpTypePointer Input %float +%fi = OpVariable %_ptr_Input_float Input +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%16 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 +%uint = OpTypeInt 32 0 +%uint_32 = OpConstant %uint 32 +%_arr_v4float_uint_32 = OpTypeArray %v4float %uint_32 +%_ptr_Output__arr_v4float_uint_32 = OpTypePointer Output %_arr_v4float_uint_32 +%gl_FragData = OpVariable %_ptr_Output__arr_v4float_uint_32 Output +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%int_1 = OpConstant %int 1 +)"; + + const std::string before = + R"(%main = OpFunction %void None %8 +%25 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%26 = OpLoad %v4float %BaseColor +OpStore %v %26 +%27 = OpLoad %float %fi +%28 = OpFOrdLessThan %bool %27 %float_0 +OpSelectionMerge %29 None +OpBranchConditional %28 %30 %29 +%30 = OpLabel +OpStore %v %16 +OpBranch %29 +%29 = OpLabel +%31 = OpLoad %v4float %v +%32 = OpAccessChain %_ptr_Output_v4float %gl_FragData %int_0 +OpStore %32 %31 +%33 = OpLoad %v4float %v +%34 = OpAccessChain %_ptr_Output_v4float %gl_FragData %int_1 +OpStore %34 %33 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %8 +%25 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%26 = OpLoad %v4float %BaseColor +OpStore %v %26 +%27 = OpLoad %float %fi +%28 = OpFOrdLessThan %bool %27 %float_0 +OpSelectionMerge %29 None +OpBranchConditional %28 %30 %29 +%30 = OpLabel +OpStore %v %16 +OpBranch %29 +%29 = OpLabel +%31 = OpLoad %v4float %v +%32 = OpAccessChain %_ptr_Output_v4float %gl_FragData %int_0 +OpStore %32 %31 +%33 = OpLoad %v4float %v +%34 = OpAccessChain %_ptr_Output_v4float %gl_FragData %int_1 +OpStore %34 %31 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(LocalSingleBlockLoadStoreElimTest, + NoStoreElimIfInterveningAccessChainLoad) { + // + // Note that even though the Load to %v is eliminated, the Store to %v + // is not eliminated due to the following access chain reference. + // + // #version 140 + // + // in vec4 BaseColor; + // flat in int Idx; + // + // void main() + // { + // vec4 v = BaseColor; + // float f = v[Idx]; + // gl_FragColor = v/f; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %Idx %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %f "f" +OpName %Idx "Idx" +OpName %gl_FragColor "gl_FragColor" +OpDecorate %Idx Flat +%void = OpTypeVoid +%9 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Function_float = OpTypePointer Function %float +%int = OpTypeInt 32 1 +%_ptr_Input_int = OpTypePointer Input %int +%Idx = OpVariable %_ptr_Input_int Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %9 +%18 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%f = OpVariable %_ptr_Function_float Function +%19 = OpLoad %v4float %BaseColor +OpStore %v %19 +%20 = OpLoad %int %Idx +%21 = OpAccessChain %_ptr_Function_float %v %20 +%22 = OpLoad %float %21 +OpStore %f %22 +%23 = OpLoad %v4float %v +%24 = OpLoad %float %f +%25 = OpCompositeConstruct %v4float %24 %24 %24 %24 +%26 = OpFDiv %v4float %23 %25 +OpStore %gl_FragColor %26 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %9 +%18 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%f = OpVariable %_ptr_Function_float Function +%19 = OpLoad %v4float %BaseColor +OpStore %v %19 +%20 = OpLoad %int %Idx +%21 = OpAccessChain %_ptr_Function_float %v %20 +%22 = OpLoad %float %21 +OpStore %f %22 +%23 = OpLoad %v4float %v +%24 = OpLoad %float %f +%25 = OpCompositeConstruct %v4float %22 %22 %22 %22 +%26 = OpFDiv %v4float %19 %25 +OpStore %gl_FragColor %26 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(LocalSingleBlockLoadStoreElimTest, NoElimIfInterveningAccessChainStore) { + // #version 140 + // + // in vec4 BaseColor; + // flat in int Idx; + // + // void main() + // { + // vec4 v = BaseColor; + // v[Idx] = 0; + // gl_FragColor = v; + // } + + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %Idx %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %Idx "Idx" +OpName %gl_FragColor "gl_FragColor" +OpDecorate %Idx Flat +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%int = OpTypeInt 32 1 +%_ptr_Input_int = OpTypePointer Input %int +%Idx = OpVariable %_ptr_Input_int Input +%float_0 = OpConstant %float 0 +%_ptr_Function_float = OpTypePointer Function %float +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %8 +%18 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%19 = OpLoad %v4float %BaseColor +OpStore %v %19 +%20 = OpLoad %int %Idx +%21 = OpAccessChain %_ptr_Function_float %v %20 +OpStore %21 %float_0 +%22 = OpLoad %v4float %v +OpStore %gl_FragColor %22 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + assembly, assembly, false, true); +} + +TEST_F(LocalSingleBlockLoadStoreElimTest, NoElimIfInterveningFunctionCall) { + // #version 140 + // + // in vec4 BaseColor; + // + // void foo() { + // } + // + // void main() + // { + // vec4 v = BaseColor; + // foo(); + // gl_FragColor = v; + // } + + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %foo_ "foo(" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %8 +%14 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%15 = OpLoad %v4float %BaseColor +OpStore %v %15 +%16 = OpFunctionCall %void %foo_ +%17 = OpLoad %v4float %v +OpStore %gl_FragColor %17 +OpReturn +OpFunctionEnd +%foo_ = OpFunction %void None %8 +%18 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + assembly, assembly, false, true); +} + +TEST_F(LocalSingleBlockLoadStoreElimTest, ElimIfCopyObjectInFunction) { + // Note: SPIR-V hand edited to insert CopyObject + // + // #version 140 + // + // in vec4 BaseColor; + // + // void main() + // { + // vec4 v1 = BaseColor; + // gl_FragData[0] = v1; + // vec4 v2 = BaseColor * 0.5; + // gl_FragData[1] = v2; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragData +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %v1 "v1" +OpName %BaseColor "BaseColor" +OpName %gl_FragData "gl_FragData" +OpName %v2 "v2" +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%uint = OpTypeInt 32 0 +%uint_32 = OpConstant %uint 32 +%_arr_v4float_uint_32 = OpTypeArray %v4float %uint_32 +%_ptr_Output__arr_v4float_uint_32 = OpTypePointer Output %_arr_v4float_uint_32 +%gl_FragData = OpVariable %_ptr_Output__arr_v4float_uint_32 Output +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%float_0_5 = OpConstant %float 0.5 +%int_1 = OpConstant %int 1 +)"; + + const std::string before = + R"(%main = OpFunction %void None %8 +%22 = OpLabel +%v1 = OpVariable %_ptr_Function_v4float Function +%v2 = OpVariable %_ptr_Function_v4float Function +%23 = OpLoad %v4float %BaseColor +OpStore %v1 %23 +%24 = OpLoad %v4float %v1 +%25 = OpAccessChain %_ptr_Output_v4float %gl_FragData %int_0 +OpStore %25 %24 +%26 = OpLoad %v4float %BaseColor +%27 = OpVectorTimesScalar %v4float %26 %float_0_5 +%28 = OpCopyObject %_ptr_Function_v4float %v2 +OpStore %28 %27 +%29 = OpLoad %v4float %28 +%30 = OpAccessChain %_ptr_Output_v4float %gl_FragData %int_1 +OpStore %30 %29 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %8 +%22 = OpLabel +%v1 = OpVariable %_ptr_Function_v4float Function +%v2 = OpVariable %_ptr_Function_v4float Function +%23 = OpLoad %v4float %BaseColor +OpStore %v1 %23 +%24 = OpLoad %v4float %v1 +%25 = OpAccessChain %_ptr_Output_v4float %gl_FragData %int_0 +OpStore %25 %23 +%26 = OpLoad %v4float %BaseColor +%27 = OpVectorTimesScalar %v4float %26 %float_0_5 +%28 = OpCopyObject %_ptr_Function_v4float %v2 +OpStore %28 %27 +%29 = OpLoad %v4float %28 +%30 = OpAccessChain %_ptr_Output_v4float %gl_FragData %int_1 +OpStore %30 %27 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(LocalSingleBlockLoadStoreElimTest, ElimOpaque) { + // SPIR-V not representable in GLSL; not generatable from HLSL + // at the moment + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %outColor %texCoords +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %S_t "S_t" +OpMemberName %S_t 0 "v0" +OpMemberName %S_t 1 "v1" +OpMemberName %S_t 2 "smp" +OpName %outColor "outColor" +OpName %sampler15 "sampler15" +OpName %s0 "s0" +OpName %texCoords "texCoords" +OpName %param "param" +OpDecorate %sampler15 DescriptorSet 0 +%void = OpTypeVoid +%12 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v2float = OpTypeVector %float 2 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%outColor = OpVariable %_ptr_Output_v4float Output +%17 = OpTypeImage %float 2D 0 0 0 1 Unknown +%18 = OpTypeSampledImage %17 +%S_t = OpTypeStruct %v2float %v2float %18 +%_ptr_Function_S_t = OpTypePointer Function %S_t +%20 = OpTypeFunction %void %_ptr_Function_S_t +%_ptr_UniformConstant_18 = OpTypePointer UniformConstant %18 +%_ptr_Function_18 = OpTypePointer Function %18 +%sampler15 = OpVariable %_ptr_UniformConstant_18 UniformConstant +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%int_2 = OpConstant %int 2 +%_ptr_Function_v2float = OpTypePointer Function %v2float +%_ptr_Input_v2float = OpTypePointer Input %v2float +%texCoords = OpVariable %_ptr_Input_v2float Input +)"; + + const std::string before = + R"(%main = OpFunction %void None %12 +%28 = OpLabel +%s0 = OpVariable %_ptr_Function_S_t Function +%param = OpVariable %_ptr_Function_S_t Function +%29 = OpLoad %v2float %texCoords +%30 = OpLoad %S_t %s0 +%31 = OpCompositeInsert %S_t %29 %30 0 +OpStore %s0 %31 +%32 = OpLoad %18 %sampler15 +%33 = OpLoad %S_t %s0 +%34 = OpCompositeInsert %S_t %32 %33 2 +OpStore %s0 %34 +%35 = OpLoad %S_t %s0 +OpStore %param %35 +%36 = OpLoad %S_t %param +%37 = OpCompositeExtract %18 %36 2 +%38 = OpLoad %S_t %param +%39 = OpCompositeExtract %v2float %38 0 +%40 = OpImageSampleImplicitLod %v4float %37 %39 +OpStore %outColor %40 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %12 +%28 = OpLabel +%s0 = OpVariable %_ptr_Function_S_t Function +%param = OpVariable %_ptr_Function_S_t Function +%29 = OpLoad %v2float %texCoords +%30 = OpLoad %S_t %s0 +%31 = OpCompositeInsert %S_t %29 %30 0 +OpStore %s0 %31 +%32 = OpLoad %18 %sampler15 +%33 = OpLoad %S_t %s0 +%34 = OpCompositeInsert %S_t %32 %31 2 +OpStore %s0 %34 +%35 = OpLoad %S_t %s0 +OpStore %param %34 +%36 = OpLoad %S_t %param +%37 = OpCompositeExtract %18 %34 2 +%38 = OpLoad %S_t %param +%39 = OpCompositeExtract %v2float %34 0 +%40 = OpImageSampleImplicitLod %v4float %37 %39 +OpStore %outColor %40 +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(LocalSingleBlockLoadStoreElimTest, PositiveAndNegativeCallTree) { + // Note that the call tree function bar is optimized, but foo is not + // + // #version 140 + // + // in vec4 BaseColor; + // + // vec4 foo(vec4 v1) + // { + // vec4 t = v1; + // return t; + // } + // + // vec4 bar(vec4 v1) + // { + // vec4 t = v1; + // return t; + // } + // + // void main() + // { + // gl_FragColor = bar(BaseColor); + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %gl_FragColor %BaseColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %foo_vf4_ "foo(vf4;" +OpName %v1 "v1" +OpName %bar_vf4_ "bar(vf4;" +OpName %v1_0 "v1" +OpName %t "t" +OpName %t_0 "t" +OpName %gl_FragColor "gl_FragColor" +OpName %BaseColor "BaseColor" +OpName %param "param" +%void = OpTypeVoid +%13 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%17 = OpTypeFunction %v4float %_ptr_Function_v4float +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%main = OpFunction %void None %13 +%20 = OpLabel +%param = OpVariable %_ptr_Function_v4float Function +%21 = OpLoad %v4float %BaseColor +OpStore %param %21 +%22 = OpFunctionCall %v4float %bar_vf4_ %param +OpStore %gl_FragColor %22 +OpReturn +OpFunctionEnd +)"; + + const std::string before = + R"(%foo_vf4_ = OpFunction %v4float None %17 +%v1 = OpFunctionParameter %_ptr_Function_v4float +%23 = OpLabel +%t = OpVariable %_ptr_Function_v4float Function +%24 = OpLoad %v4float %v1 +OpStore %t %24 +%25 = OpLoad %v4float %t +OpReturnValue %25 +OpFunctionEnd +%bar_vf4_ = OpFunction %v4float None %17 +%v1_0 = OpFunctionParameter %_ptr_Function_v4float +%26 = OpLabel +%t_0 = OpVariable %_ptr_Function_v4float Function +%27 = OpLoad %v4float %v1_0 +OpStore %t_0 %27 +%28 = OpLoad %v4float %t_0 +OpReturnValue %28 +OpFunctionEnd +)"; + + const std::string after = + R"(%foo_vf4_ = OpFunction %v4float None %17 +%v1 = OpFunctionParameter %_ptr_Function_v4float +%23 = OpLabel +%t = OpVariable %_ptr_Function_v4float Function +%24 = OpLoad %v4float %v1 +OpStore %t %24 +%25 = OpLoad %v4float %t +OpReturnValue %25 +OpFunctionEnd +%bar_vf4_ = OpFunction %v4float None %17 +%v1_0 = OpFunctionParameter %_ptr_Function_v4float +%26 = OpLabel +%t_0 = OpVariable %_ptr_Function_v4float Function +%27 = OpLoad %v4float %v1_0 +OpStore %t_0 %27 +%28 = OpLoad %v4float %t_0 +OpReturnValue %27 +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(LocalSingleBlockLoadStoreElimTest, PointerVariable) { + // Test that checks if a pointer variable is removed. + + const std::string before = + R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %1 "main" %2 +OpExecutionMode %1 OriginUpperLeft +OpMemberDecorate %_struct_3 0 Offset 0 +OpDecorate %_runtimearr__struct_3 ArrayStride 16 +OpMemberDecorate %_struct_5 0 Offset 0 +OpDecorate %_struct_5 BufferBlock +OpMemberDecorate %_struct_6 0 Offset 0 +OpDecorate %_struct_6 BufferBlock +OpDecorate %2 Location 0 +OpDecorate %7 DescriptorSet 0 +OpDecorate %7 Binding 0 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%uint = OpTypeInt 32 0 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float +%_struct_3 = OpTypeStruct %v4float +%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3 +%_struct_5 = OpTypeStruct %_runtimearr__struct_3 +%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5 +%_struct_6 = OpTypeStruct %int +%_ptr_Uniform__struct_6 = OpTypePointer Uniform %_struct_6 +%_ptr_Function__ptr_Uniform__struct_5 = OpTypePointer Function %_ptr_Uniform__struct_5 +%_ptr_Function__ptr_Uniform__struct_6 = OpTypePointer Function %_ptr_Uniform__struct_6 +%int_0 = OpConstant %int 0 +%uint_0 = OpConstant %uint 0 +%2 = OpVariable %_ptr_Output_v4float Output +%7 = OpVariable %_ptr_Uniform__struct_5 Uniform +%1 = OpFunction %void None %10 +%23 = OpLabel +%24 = OpVariable %_ptr_Function__ptr_Uniform__struct_5 Function +OpStore %24 %7 +%26 = OpLoad %_ptr_Uniform__struct_5 %24 +%27 = OpAccessChain %_ptr_Uniform_v4float %26 %int_0 %uint_0 %int_0 +%28 = OpLoad %v4float %27 +%29 = OpCopyObject %v4float %28 +OpStore %2 %28 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %1 "main" %2 +OpExecutionMode %1 OriginUpperLeft +OpMemberDecorate %_struct_3 0 Offset 0 +OpDecorate %_runtimearr__struct_3 ArrayStride 16 +OpMemberDecorate %_struct_5 0 Offset 0 +OpDecorate %_struct_5 BufferBlock +OpMemberDecorate %_struct_6 0 Offset 0 +OpDecorate %_struct_6 BufferBlock +OpDecorate %2 Location 0 +OpDecorate %7 DescriptorSet 0 +OpDecorate %7 Binding 0 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%uint = OpTypeInt 32 0 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float +%_struct_3 = OpTypeStruct %v4float +%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3 +%_struct_5 = OpTypeStruct %_runtimearr__struct_3 +%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5 +%_struct_6 = OpTypeStruct %int +%_ptr_Uniform__struct_6 = OpTypePointer Uniform %_struct_6 +%_ptr_Function__ptr_Uniform__struct_5 = OpTypePointer Function %_ptr_Uniform__struct_5 +%_ptr_Function__ptr_Uniform__struct_6 = OpTypePointer Function %_ptr_Uniform__struct_6 +%int_0 = OpConstant %int 0 +%uint_0 = OpConstant %uint 0 +%2 = OpVariable %_ptr_Output_v4float Output +%7 = OpVariable %_ptr_Uniform__struct_5 Uniform +%1 = OpFunction %void None %10 +%23 = OpLabel +%24 = OpVariable %_ptr_Function__ptr_Uniform__struct_5 Function +OpStore %24 %7 +%26 = OpLoad %_ptr_Uniform__struct_5 %24 +%27 = OpAccessChain %_ptr_Uniform_v4float %7 %int_0 %uint_0 %int_0 +%28 = OpLoad %v4float %27 +%29 = OpCopyObject %v4float %28 +OpStore %2 %28 +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck(before, after, + true, true); +} +// TODO(greg-lunarg): Add tests to verify handling of these cases: +// +// Other target variable types +// InBounds Access Chains +// Check for correctness in the presence of function calls +// Others? + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/local_single_store_elim_test.cpp b/3rdparty/spirv-tools/test/opt/local_single_store_elim_test.cpp new file mode 100644 index 000000000..a7e0e9093 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/local_single_store_elim_test.cpp @@ -0,0 +1,720 @@ +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; + +using LocalSingleStoreElimTest = PassTest<::testing::Test>; + +TEST_F(LocalSingleStoreElimTest, PositiveAndNegative) { + // Single store to v is optimized. Multiple store to + // f is not optimized. + // + // #version 140 + // + // in vec4 BaseColor; + // in float fi; + // + // void main() + // { + // vec4 v = BaseColor; + // float f = fi; + // if (f < 0) + // f = 0.0; + // gl_FragColor = v + f; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %fi %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %f "f" +OpName %fi "fi" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%9 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Function_float = OpTypePointer Function %float +%_ptr_Input_float = OpTypePointer Input %float +%fi = OpVariable %_ptr_Input_float Input +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %9 +%19 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%f = OpVariable %_ptr_Function_float Function +%20 = OpLoad %v4float %BaseColor +OpStore %v %20 +%21 = OpLoad %float %fi +OpStore %f %21 +%22 = OpLoad %float %f +%23 = OpFOrdLessThan %bool %22 %float_0 +OpSelectionMerge %24 None +OpBranchConditional %23 %25 %24 +%25 = OpLabel +OpStore %f %float_0 +OpBranch %24 +%24 = OpLabel +%26 = OpLoad %v4float %v +%27 = OpLoad %float %f +%28 = OpCompositeConstruct %v4float %27 %27 %27 %27 +%29 = OpFAdd %v4float %26 %28 +OpStore %gl_FragColor %29 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %9 +%19 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%f = OpVariable %_ptr_Function_float Function +%20 = OpLoad %v4float %BaseColor +OpStore %v %20 +%21 = OpLoad %float %fi +OpStore %f %21 +%22 = OpLoad %float %f +%23 = OpFOrdLessThan %bool %22 %float_0 +OpSelectionMerge %24 None +OpBranchConditional %23 %25 %24 +%25 = OpLabel +OpStore %f %float_0 +OpBranch %24 +%24 = OpLabel +%26 = OpLoad %v4float %v +%27 = OpLoad %float %f +%28 = OpCompositeConstruct %v4float %27 %27 %27 %27 +%29 = OpFAdd %v4float %20 %28 +OpStore %gl_FragColor %29 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(LocalSingleStoreElimTest, MultipleLoads) { + // Single store to multiple loads of v is optimized. + // + // #version 140 + // + // in vec4 BaseColor; + // in float fi; + // + // void main() + // { + // vec4 v = BaseColor; + // float f = fi; + // if (f < 0) + // f = 0.0; + // gl_FragColor = v + f; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %fi %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %fi "fi" +OpName %r "r" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%9 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Input_float = OpTypePointer Input %float +%fi = OpVariable %_ptr_Input_float Input +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%float_1 = OpConstant %float 1 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %9 +%19 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%r = OpVariable %_ptr_Function_v4float Function +%20 = OpLoad %v4float %BaseColor +OpStore %v %20 +%21 = OpLoad %float %fi +%22 = OpFOrdLessThan %bool %21 %float_0 +OpSelectionMerge %23 None +OpBranchConditional %22 %24 %25 +%24 = OpLabel +%26 = OpLoad %v4float %v +OpStore %r %26 +OpBranch %23 +%25 = OpLabel +%27 = OpLoad %v4float %v +%28 = OpCompositeConstruct %v4float %float_1 %float_1 %float_1 %float_1 +%29 = OpFSub %v4float %28 %27 +OpStore %r %29 +OpBranch %23 +%23 = OpLabel +%30 = OpLoad %v4float %r +OpStore %gl_FragColor %30 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %9 +%19 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%r = OpVariable %_ptr_Function_v4float Function +%20 = OpLoad %v4float %BaseColor +OpStore %v %20 +%21 = OpLoad %float %fi +%22 = OpFOrdLessThan %bool %21 %float_0 +OpSelectionMerge %23 None +OpBranchConditional %22 %24 %25 +%24 = OpLabel +%26 = OpLoad %v4float %v +OpStore %r %20 +OpBranch %23 +%25 = OpLabel +%27 = OpLoad %v4float %v +%28 = OpCompositeConstruct %v4float %float_1 %float_1 %float_1 %float_1 +%29 = OpFSub %v4float %28 %20 +OpStore %r %29 +OpBranch %23 +%23 = OpLabel +%30 = OpLoad %v4float %r +OpStore %gl_FragColor %30 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(LocalSingleStoreElimTest, NoStoreElimWithInterveningAccessChainLoad) { + // Last load of v is eliminated, but access chain load and store of v isn't + // + // #version 140 + // + // in vec4 BaseColor; + // + // void main() + // { + // vec4 v = BaseColor; + // float f = v[3]; + // gl_FragColor = v * f; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %f "f" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Function_float = OpTypePointer Function %float +%uint = OpTypeInt 32 0 +%uint_3 = OpConstant %uint 3 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %8 +%17 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%f = OpVariable %_ptr_Function_float Function +%18 = OpLoad %v4float %BaseColor +OpStore %v %18 +%19 = OpAccessChain %_ptr_Function_float %v %uint_3 +%20 = OpLoad %float %19 +OpStore %f %20 +%21 = OpLoad %v4float %v +%22 = OpLoad %float %f +%23 = OpVectorTimesScalar %v4float %21 %22 +OpStore %gl_FragColor %23 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %8 +%17 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%f = OpVariable %_ptr_Function_float Function +%18 = OpLoad %v4float %BaseColor +OpStore %v %18 +%19 = OpAccessChain %_ptr_Function_float %v %uint_3 +%20 = OpLoad %float %19 +OpStore %f %20 +%21 = OpLoad %v4float %v +%22 = OpLoad %float %f +%23 = OpVectorTimesScalar %v4float %18 %20 +OpStore %gl_FragColor %23 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(LocalSingleStoreElimTest, NoReplaceOfDominatingPartialStore) { + // Note: SPIR-V hand edited to initialize v to vec4(0.0) + // + // #version 140 + // + // in vec4 BaseColor; + // + // void main() + // { + // vec4 v; + // float v[1] = 1.0; + // gl_FragColor = v; + // } + + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %gl_FragColor %BaseColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %v "v" +OpName %gl_FragColor "gl_FragColor" +OpName %BaseColor "BaseColor" +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%float_0 = OpConstant %float 0 +%12 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 +%float_1 = OpConstant %float 1 +%uint = OpTypeInt 32 0 +%uint_1 = OpConstant %uint 1 +%_ptr_Function_float = OpTypePointer Function %float +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%main = OpFunction %void None %7 +%19 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function %12 +%20 = OpAccessChain %_ptr_Function_float %v %uint_1 +OpStore %20 %float_1 +%21 = OpLoad %v4float %v +OpStore %gl_FragColor %21 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(assembly, assembly, true, + true); +} + +TEST_F(LocalSingleStoreElimTest, ElimIfCopyObjectInFunction) { + // Note: hand edited to insert OpCopyObject + // + // #version 140 + // + // in vec4 BaseColor; + // in float fi; + // + // void main() + // { + // vec4 v = BaseColor; + // float f = fi; + // if (f < 0) + // f = 0.0; + // gl_FragColor = v + f; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %fi %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %f "f" +OpName %fi "fi" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%9 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Function_float = OpTypePointer Function %float +%_ptr_Input_float = OpTypePointer Input %float +%fi = OpVariable %_ptr_Input_float Input +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %9 +%19 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%f = OpVariable %_ptr_Function_float Function +%20 = OpLoad %v4float %BaseColor +OpStore %v %20 +%21 = OpLoad %float %fi +OpStore %f %21 +%22 = OpLoad %float %f +%23 = OpFOrdLessThan %bool %22 %float_0 +OpSelectionMerge %24 None +OpBranchConditional %23 %25 %24 +%25 = OpLabel +OpStore %f %float_0 +OpBranch %24 +%24 = OpLabel +%26 = OpCopyObject %_ptr_Function_v4float %v +%27 = OpLoad %v4float %26 +%28 = OpLoad %float %f +%29 = OpCompositeConstruct %v4float %28 %28 %28 %28 +%30 = OpFAdd %v4float %27 %29 +OpStore %gl_FragColor %30 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %9 +%19 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%f = OpVariable %_ptr_Function_float Function +%20 = OpLoad %v4float %BaseColor +OpStore %v %20 +%21 = OpLoad %float %fi +OpStore %f %21 +%22 = OpLoad %float %f +%23 = OpFOrdLessThan %bool %22 %float_0 +OpSelectionMerge %24 None +OpBranchConditional %23 %25 %24 +%25 = OpLabel +OpStore %f %float_0 +OpBranch %24 +%24 = OpLabel +%26 = OpCopyObject %_ptr_Function_v4float %v +%27 = OpLoad %v4float %26 +%28 = OpLoad %float %f +%29 = OpCompositeConstruct %v4float %28 %28 %28 %28 +%30 = OpFAdd %v4float %20 %29 +OpStore %gl_FragColor %30 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(LocalSingleStoreElimTest, NoOptIfStoreNotDominating) { + // Single store to f not optimized because it does not dominate + // the load. + // + // #version 140 + // + // in vec4 BaseColor; + // in float fi; + // + // void main() + // { + // float f; + // if (fi < 0) + // f = 0.5; + // if (fi < 0) + // gl_FragColor = BaseColor * f; + // else + // gl_FragColor = BaseColor; + // } + + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %fi %gl_FragColor %BaseColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %fi "fi" +OpName %f "f" +OpName %gl_FragColor "gl_FragColor" +OpName %BaseColor "BaseColor" +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Input_float = OpTypePointer Input %float +%fi = OpVariable %_ptr_Input_float Input +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%_ptr_Function_float = OpTypePointer Function %float +%float_0_5 = OpConstant %float 0.5 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%main = OpFunction %void None %8 +%18 = OpLabel +%f = OpVariable %_ptr_Function_float Function +%19 = OpLoad %float %fi +%20 = OpFOrdLessThan %bool %19 %float_0 +OpSelectionMerge %21 None +OpBranchConditional %20 %22 %21 +%22 = OpLabel +OpStore %f %float_0_5 +OpBranch %21 +%21 = OpLabel +%23 = OpLoad %float %fi +%24 = OpFOrdLessThan %bool %23 %float_0 +OpSelectionMerge %25 None +OpBranchConditional %24 %26 %27 +%26 = OpLabel +%28 = OpLoad %v4float %BaseColor +%29 = OpLoad %float %f +%30 = OpVectorTimesScalar %v4float %28 %29 +OpStore %gl_FragColor %30 +OpBranch %25 +%27 = OpLabel +%31 = OpLoad %v4float %BaseColor +OpStore %gl_FragColor %31 +OpBranch %25 +%25 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(assembly, assembly, true, + true); +} + +TEST_F(LocalSingleStoreElimTest, OptInitializedVariableLikeStore) { + // Initialized variable f is optimized like it was a store. + // Note: The SPIR-V was edited to turn the store to f to an + // an initialization. + // + // #version 140 + // + // void main() + // { + // float f = 0.0; + // gl_FragColor = vec4(f); + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %f "f" +OpName %gl_FragColor "gl_FragColor" +OpDecorate %gl_FragColor Location 0 +%void = OpTypeVoid +%6 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float +%float_0 = OpConstant %float 0 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %6 +%12 = OpLabel +%f = OpVariable %_ptr_Function_float Function %float_0 +%13 = OpLoad %float %f +%14 = OpCompositeConstruct %v4float %13 %13 %13 %13 +OpStore %gl_FragColor %14 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %6 +%12 = OpLabel +%f = OpVariable %_ptr_Function_float Function %float_0 +%13 = OpLoad %float %f +%14 = OpCompositeConstruct %v4float %float_0 %float_0 %float_0 %float_0 +OpStore %gl_FragColor %14 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(LocalSingleStoreElimTest, PointerVariable) { + // Test that checks if a pointer variable is removed. + + const std::string before = + R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %1 "main" %2 +OpExecutionMode %1 OriginUpperLeft +OpMemberDecorate %_struct_3 0 Offset 0 +OpDecorate %_runtimearr__struct_3 ArrayStride 16 +OpMemberDecorate %_struct_5 0 Offset 0 +OpDecorate %_struct_5 BufferBlock +OpMemberDecorate %_struct_6 0 Offset 0 +OpDecorate %_struct_6 BufferBlock +OpDecorate %2 Location 0 +OpDecorate %7 DescriptorSet 0 +OpDecorate %7 Binding 0 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%uint = OpTypeInt 32 0 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float +%_struct_3 = OpTypeStruct %v4float +%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3 +%_struct_5 = OpTypeStruct %_runtimearr__struct_3 +%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5 +%_struct_6 = OpTypeStruct %int +%_ptr_Uniform__struct_6 = OpTypePointer Uniform %_struct_6 +%_ptr_Function__ptr_Uniform__struct_5 = OpTypePointer Function %_ptr_Uniform__struct_5 +%_ptr_Function__ptr_Uniform__struct_6 = OpTypePointer Function %_ptr_Uniform__struct_6 +%int_0 = OpConstant %int 0 +%uint_0 = OpConstant %uint 0 +%2 = OpVariable %_ptr_Output_v4float Output +%7 = OpVariable %_ptr_Uniform__struct_5 Uniform +%1 = OpFunction %void None %10 +%23 = OpLabel +%24 = OpVariable %_ptr_Function__ptr_Uniform__struct_5 Function +OpStore %24 %7 +%26 = OpLoad %_ptr_Uniform__struct_5 %24 +%27 = OpAccessChain %_ptr_Uniform_v4float %26 %int_0 %uint_0 %int_0 +%28 = OpLoad %v4float %27 +%29 = OpCopyObject %v4float %28 +OpStore %2 %28 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %1 "main" %2 +OpExecutionMode %1 OriginUpperLeft +OpMemberDecorate %_struct_3 0 Offset 0 +OpDecorate %_runtimearr__struct_3 ArrayStride 16 +OpMemberDecorate %_struct_5 0 Offset 0 +OpDecorate %_struct_5 BufferBlock +OpMemberDecorate %_struct_6 0 Offset 0 +OpDecorate %_struct_6 BufferBlock +OpDecorate %2 Location 0 +OpDecorate %7 DescriptorSet 0 +OpDecorate %7 Binding 0 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%uint = OpTypeInt 32 0 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float +%_struct_3 = OpTypeStruct %v4float +%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3 +%_struct_5 = OpTypeStruct %_runtimearr__struct_3 +%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5 +%_struct_6 = OpTypeStruct %int +%_ptr_Uniform__struct_6 = OpTypePointer Uniform %_struct_6 +%_ptr_Function__ptr_Uniform__struct_5 = OpTypePointer Function %_ptr_Uniform__struct_5 +%_ptr_Function__ptr_Uniform__struct_6 = OpTypePointer Function %_ptr_Uniform__struct_6 +%int_0 = OpConstant %int 0 +%uint_0 = OpConstant %uint 0 +%2 = OpVariable %_ptr_Output_v4float Output +%7 = OpVariable %_ptr_Uniform__struct_5 Uniform +%1 = OpFunction %void None %10 +%23 = OpLabel +%24 = OpVariable %_ptr_Function__ptr_Uniform__struct_5 Function +OpStore %24 %7 +%26 = OpLoad %_ptr_Uniform__struct_5 %24 +%27 = OpAccessChain %_ptr_Uniform_v4float %7 %int_0 %uint_0 %int_0 +%28 = OpLoad %v4float %27 +%29 = OpCopyObject %v4float %28 +OpStore %2 %28 +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck(before, after, true, + true); +} + +// TODO(greg-lunarg): Add tests to verify handling of these cases: +// +// Other types +// Others? + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/local_ssa_elim_test.cpp b/3rdparty/spirv-tools/test/opt/local_ssa_elim_test.cpp new file mode 100644 index 000000000..06ecc254b --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/local_ssa_elim_test.cpp @@ -0,0 +1,1732 @@ +// Copyright (c) 2017 Valve Corporation +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; + +using LocalSSAElimTest = PassTest<::testing::Test>; + +TEST_F(LocalSSAElimTest, ForLoop) { + // #version 140 + // + // in vec4 BC; + // out float fo; + // + // void main() + // { + // float f = 0.0; + // for (int i=0; i<4; i++) { + // f = f + BC[i]; + // } + // fo = f; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BC %fo +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %f "f" +OpName %i "i" +OpName %BC "BC" +OpName %fo "fo" +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float +%float_0 = OpConstant %float 0 +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_0 = OpConstant %int 0 +%int_4 = OpConstant %int 4 +%bool = OpTypeBool +%v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BC = OpVariable %_ptr_Input_v4float Input +%_ptr_Input_float = OpTypePointer Input %float +%int_1 = OpConstant %int 1 +%_ptr_Output_float = OpTypePointer Output %float +%fo = OpVariable %_ptr_Output_float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %8 +%22 = OpLabel +%f = OpVariable %_ptr_Function_float Function +%i = OpVariable %_ptr_Function_int Function +OpStore %f %float_0 +OpStore %i %int_0 +OpBranch %23 +%23 = OpLabel +OpLoopMerge %24 %25 None +OpBranch %26 +%26 = OpLabel +%27 = OpLoad %int %i +%28 = OpSLessThan %bool %27 %int_4 +OpBranchConditional %28 %29 %24 +%29 = OpLabel +%30 = OpLoad %float %f +%31 = OpLoad %int %i +%32 = OpAccessChain %_ptr_Input_float %BC %31 +%33 = OpLoad %float %32 +%34 = OpFAdd %float %30 %33 +OpStore %f %34 +OpBranch %25 +%25 = OpLabel +%35 = OpLoad %int %i +%36 = OpIAdd %int %35 %int_1 +OpStore %i %36 +OpBranch %23 +%24 = OpLabel +%37 = OpLoad %float %f +OpStore %fo %37 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %8 +%22 = OpLabel +%f = OpVariable %_ptr_Function_float Function +%i = OpVariable %_ptr_Function_int Function +OpStore %f %float_0 +OpStore %i %int_0 +OpBranch %23 +%23 = OpLabel +%39 = OpPhi %float %float_0 %22 %34 %25 +%38 = OpPhi %int %int_0 %22 %36 %25 +OpLoopMerge %24 %25 None +OpBranch %26 +%26 = OpLabel +%28 = OpSLessThan %bool %38 %int_4 +OpBranchConditional %28 %29 %24 +%29 = OpLabel +%32 = OpAccessChain %_ptr_Input_float %BC %38 +%33 = OpLoad %float %32 +%34 = OpFAdd %float %39 %33 +OpStore %f %34 +OpBranch %25 +%25 = OpLabel +%36 = OpIAdd %int %38 %int_1 +OpStore %i %36 +OpBranch %23 +%24 = OpLabel +OpStore %fo %39 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(LocalSSAElimTest, NestedForLoop) { + // #version 450 + // + // layout (location=0) in mat4 BC; + // layout (location=0) out float fo; + // + // void main() + // { + // float f = 0.0; + // for (int i=0; i<4; i++) + // for (int j=0; j<4; j++) + // f = f + BC[i][j]; + // fo = f; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BC %fo +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %f "f" +OpName %i "i" +OpName %j "j" +OpName %BC "BC" +OpName %fo "fo" +OpDecorate %BC Location 0 +OpDecorate %fo Location 0 +%void = OpTypeVoid +%9 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float +%float_0 = OpConstant %float 0 +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_0 = OpConstant %int 0 +%int_4 = OpConstant %int 4 +%bool = OpTypeBool +%v4float = OpTypeVector %float 4 +%mat4v4float = OpTypeMatrix %v4float 4 +%_ptr_Input_mat4v4float = OpTypePointer Input %mat4v4float +%BC = OpVariable %_ptr_Input_mat4v4float Input +%_ptr_Input_float = OpTypePointer Input %float +%int_1 = OpConstant %int 1 +%_ptr_Output_float = OpTypePointer Output %float +%fo = OpVariable %_ptr_Output_float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %9 +%24 = OpLabel +%f = OpVariable %_ptr_Function_float Function +%i = OpVariable %_ptr_Function_int Function +%j = OpVariable %_ptr_Function_int Function +OpStore %f %float_0 +OpStore %i %int_0 +OpBranch %25 +%25 = OpLabel +%26 = OpLoad %int %i +%27 = OpSLessThan %bool %26 %int_4 +OpLoopMerge %28 %29 None +OpBranchConditional %27 %30 %28 +%30 = OpLabel +OpStore %j %int_0 +OpBranch %31 +%31 = OpLabel +%32 = OpLoad %int %j +%33 = OpSLessThan %bool %32 %int_4 +OpLoopMerge %29 %34 None +OpBranchConditional %33 %34 %29 +%34 = OpLabel +%35 = OpLoad %float %f +%36 = OpLoad %int %i +%37 = OpLoad %int %j +%38 = OpAccessChain %_ptr_Input_float %BC %36 %37 +%39 = OpLoad %float %38 +%40 = OpFAdd %float %35 %39 +OpStore %f %40 +%41 = OpLoad %int %j +%42 = OpIAdd %int %41 %int_1 +OpStore %j %42 +OpBranch %31 +%29 = OpLabel +%43 = OpLoad %int %i +%44 = OpIAdd %int %43 %int_1 +OpStore %i %44 +OpBranch %25 +%28 = OpLabel +%45 = OpLoad %float %f +OpStore %fo %45 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %9 +%24 = OpLabel +%f = OpVariable %_ptr_Function_float Function +%i = OpVariable %_ptr_Function_int Function +%j = OpVariable %_ptr_Function_int Function +OpStore %f %float_0 +OpStore %i %int_0 +OpBranch %25 +%25 = OpLabel +%47 = OpPhi %float %float_0 %24 %50 %29 +%46 = OpPhi %int %int_0 %24 %44 %29 +%27 = OpSLessThan %bool %46 %int_4 +OpLoopMerge %28 %29 None +OpBranchConditional %27 %30 %28 +%30 = OpLabel +OpStore %j %int_0 +OpBranch %31 +%31 = OpLabel +%50 = OpPhi %float %47 %30 %40 %34 +%48 = OpPhi %int %int_0 %30 %42 %34 +%33 = OpSLessThan %bool %48 %int_4 +OpLoopMerge %29 %34 None +OpBranchConditional %33 %34 %29 +%34 = OpLabel +%38 = OpAccessChain %_ptr_Input_float %BC %46 %48 +%39 = OpLoad %float %38 +%40 = OpFAdd %float %50 %39 +OpStore %f %40 +%42 = OpIAdd %int %48 %int_1 +OpStore %j %42 +OpBranch %31 +%29 = OpLabel +%44 = OpIAdd %int %46 %int_1 +OpStore %i %44 +OpBranch %25 +%28 = OpLabel +OpStore %fo %47 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(LocalSSAElimTest, ForLoopWithContinue) { + // #version 140 + // + // in vec4 BC; + // out float fo; + // + // void main() + // { + // float f = 0.0; + // for (int i=0; i<4; i++) { + // float t = BC[i]; + // if (t < 0.0) + // continue; + // f = f + t; + // } + // fo = f; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BC %fo +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +)"; + + const std::string names = + R"(OpName %main "main" +OpName %f "f" +OpName %i "i" +OpName %t "t" +OpName %BC "BC" +OpName %fo "fo" +)"; + + const std::string predefs2 = + R"(%void = OpTypeVoid +%9 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float +%float_0 = OpConstant %float 0 +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_0 = OpConstant %int 0 +%int_4 = OpConstant %int 4 +%bool = OpTypeBool +%v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BC = OpVariable %_ptr_Input_v4float Input +%_ptr_Input_float = OpTypePointer Input %float +%int_1 = OpConstant %int 1 +%_ptr_Output_float = OpTypePointer Output %float +%fo = OpVariable %_ptr_Output_float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %9 +%23 = OpLabel +%f = OpVariable %_ptr_Function_float Function +%i = OpVariable %_ptr_Function_int Function +%t = OpVariable %_ptr_Function_float Function +OpStore %f %float_0 +OpStore %i %int_0 +OpBranch %24 +%24 = OpLabel +OpLoopMerge %25 %26 None +OpBranch %27 +%27 = OpLabel +%28 = OpLoad %int %i +%29 = OpSLessThan %bool %28 %int_4 +OpBranchConditional %29 %30 %25 +%30 = OpLabel +%31 = OpLoad %int %i +%32 = OpAccessChain %_ptr_Input_float %BC %31 +%33 = OpLoad %float %32 +OpStore %t %33 +%34 = OpLoad %float %t +%35 = OpFOrdLessThan %bool %34 %float_0 +OpSelectionMerge %36 None +OpBranchConditional %35 %37 %36 +%37 = OpLabel +OpBranch %26 +%36 = OpLabel +%38 = OpLoad %float %f +%39 = OpLoad %float %t +%40 = OpFAdd %float %38 %39 +OpStore %f %40 +OpBranch %26 +%26 = OpLabel +%41 = OpLoad %int %i +%42 = OpIAdd %int %41 %int_1 +OpStore %i %42 +OpBranch %24 +%25 = OpLabel +%43 = OpLoad %float %f +OpStore %fo %43 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %9 +%23 = OpLabel +%f = OpVariable %_ptr_Function_float Function +%i = OpVariable %_ptr_Function_int Function +%t = OpVariable %_ptr_Function_float Function +OpStore %f %float_0 +OpStore %i %int_0 +OpBranch %24 +%24 = OpLabel +%45 = OpPhi %float %float_0 %23 %47 %26 +%44 = OpPhi %int %int_0 %23 %42 %26 +OpLoopMerge %25 %26 None +OpBranch %27 +%27 = OpLabel +%29 = OpSLessThan %bool %44 %int_4 +OpBranchConditional %29 %30 %25 +%30 = OpLabel +%32 = OpAccessChain %_ptr_Input_float %BC %44 +%33 = OpLoad %float %32 +OpStore %t %33 +%35 = OpFOrdLessThan %bool %33 %float_0 +OpSelectionMerge %36 None +OpBranchConditional %35 %37 %36 +%37 = OpLabel +OpBranch %26 +%36 = OpLabel +%40 = OpFAdd %float %45 %33 +OpStore %f %40 +OpBranch %26 +%26 = OpLabel +%47 = OpPhi %float %45 %37 %40 %36 +%42 = OpIAdd %int %44 %int_1 +OpStore %i %42 +OpBranch %24 +%25 = OpLabel +OpStore %fo %45 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + names + predefs2 + before, predefs + names + predefs2 + after, + true, true); +} + +TEST_F(LocalSSAElimTest, ForLoopWithBreak) { + // #version 140 + // + // in vec4 BC; + // out float fo; + // + // void main() + // { + // float f = 0.0; + // for (int i=0; i<4; i++) { + // float t = f + BC[i]; + // if (t > 1.0) + // break; + // f = t; + // } + // fo = f; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BC %fo +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %f "f" +OpName %i "i" +OpName %t "t" +OpName %BC "BC" +OpName %fo "fo" +%void = OpTypeVoid +%9 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float +%float_0 = OpConstant %float 0 +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_0 = OpConstant %int 0 +%int_4 = OpConstant %int 4 +%bool = OpTypeBool +%v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BC = OpVariable %_ptr_Input_v4float Input +%_ptr_Input_float = OpTypePointer Input %float +%float_1 = OpConstant %float 1 +%int_1 = OpConstant %int 1 +%_ptr_Output_float = OpTypePointer Output %float +%fo = OpVariable %_ptr_Output_float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %9 +%24 = OpLabel +%f = OpVariable %_ptr_Function_float Function +%i = OpVariable %_ptr_Function_int Function +%t = OpVariable %_ptr_Function_float Function +OpStore %f %float_0 +OpStore %i %int_0 +OpBranch %25 +%25 = OpLabel +OpLoopMerge %26 %27 None +OpBranch %28 +%28 = OpLabel +%29 = OpLoad %int %i +%30 = OpSLessThan %bool %29 %int_4 +OpBranchConditional %30 %31 %26 +%31 = OpLabel +%32 = OpLoad %float %f +%33 = OpLoad %int %i +%34 = OpAccessChain %_ptr_Input_float %BC %33 +%35 = OpLoad %float %34 +%36 = OpFAdd %float %32 %35 +OpStore %t %36 +%37 = OpLoad %float %t +%38 = OpFOrdGreaterThan %bool %37 %float_1 +OpSelectionMerge %39 None +OpBranchConditional %38 %40 %39 +%40 = OpLabel +OpBranch %26 +%39 = OpLabel +%41 = OpLoad %float %t +OpStore %f %41 +OpBranch %27 +%27 = OpLabel +%42 = OpLoad %int %i +%43 = OpIAdd %int %42 %int_1 +OpStore %i %43 +OpBranch %25 +%26 = OpLabel +%44 = OpLoad %float %f +OpStore %fo %44 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %9 +%24 = OpLabel +%f = OpVariable %_ptr_Function_float Function +%i = OpVariable %_ptr_Function_int Function +%t = OpVariable %_ptr_Function_float Function +OpStore %f %float_0 +OpStore %i %int_0 +OpBranch %25 +%25 = OpLabel +%46 = OpPhi %float %float_0 %24 %36 %27 +%45 = OpPhi %int %int_0 %24 %43 %27 +OpLoopMerge %26 %27 None +OpBranch %28 +%28 = OpLabel +%30 = OpSLessThan %bool %45 %int_4 +OpBranchConditional %30 %31 %26 +%31 = OpLabel +%34 = OpAccessChain %_ptr_Input_float %BC %45 +%35 = OpLoad %float %34 +%36 = OpFAdd %float %46 %35 +OpStore %t %36 +%38 = OpFOrdGreaterThan %bool %36 %float_1 +OpSelectionMerge %39 None +OpBranchConditional %38 %40 %39 +%40 = OpLabel +OpBranch %26 +%39 = OpLabel +OpStore %f %36 +OpBranch %27 +%27 = OpLabel +%43 = OpIAdd %int %45 %int_1 +OpStore %i %43 +OpBranch %25 +%26 = OpLabel +OpStore %fo %46 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(LocalSSAElimTest, SwapProblem) { + // #version 140 + // + // in float fe; + // out float fo; + // + // void main() + // { + // float f1 = 0.0; + // float f2 = 1.0; + // int ie = int(fe); + // for (int i=0; i( + predefs + before, predefs + after, true, true); +} + +TEST_F(LocalSSAElimTest, LostCopyProblem) { + // #version 140 + // + // in vec4 BC; + // out float fo; + // + // void main() + // { + // float f = 0.0; + // float t; + // for (int i=0; i<4; i++) { + // t = f; + // f = f + BC[i]; + // if (f > 1.0) + // break; + // } + // fo = t; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BC %fo +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %f "f" +OpName %i "i" +OpName %t "t" +OpName %BC "BC" +OpName %fo "fo" +%void = OpTypeVoid +%9 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float +%float_0 = OpConstant %float 0 +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_0 = OpConstant %int 0 +%int_4 = OpConstant %int 4 +%bool = OpTypeBool +%v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BC = OpVariable %_ptr_Input_v4float Input +%_ptr_Input_float = OpTypePointer Input %float +%float_1 = OpConstant %float 1 +%int_1 = OpConstant %int 1 +%_ptr_Output_float = OpTypePointer Output %float +%fo = OpVariable %_ptr_Output_float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %9 +%24 = OpLabel +%f = OpVariable %_ptr_Function_float Function +%i = OpVariable %_ptr_Function_int Function +%t = OpVariable %_ptr_Function_float Function +OpStore %f %float_0 +OpStore %i %int_0 +OpBranch %25 +%25 = OpLabel +OpLoopMerge %26 %27 None +OpBranch %28 +%28 = OpLabel +%29 = OpLoad %int %i +%30 = OpSLessThan %bool %29 %int_4 +OpBranchConditional %30 %31 %26 +%31 = OpLabel +%32 = OpLoad %float %f +OpStore %t %32 +%33 = OpLoad %float %f +%34 = OpLoad %int %i +%35 = OpAccessChain %_ptr_Input_float %BC %34 +%36 = OpLoad %float %35 +%37 = OpFAdd %float %33 %36 +OpStore %f %37 +%38 = OpLoad %float %f +%39 = OpFOrdGreaterThan %bool %38 %float_1 +OpSelectionMerge %40 None +OpBranchConditional %39 %41 %40 +%41 = OpLabel +OpBranch %26 +%40 = OpLabel +OpBranch %27 +%27 = OpLabel +%42 = OpLoad %int %i +%43 = OpIAdd %int %42 %int_1 +OpStore %i %43 +OpBranch %25 +%26 = OpLabel +%44 = OpLoad %float %t +OpStore %fo %44 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%49 = OpUndef %float +%main = OpFunction %void None %9 +%24 = OpLabel +%f = OpVariable %_ptr_Function_float Function +%i = OpVariable %_ptr_Function_int Function +%t = OpVariable %_ptr_Function_float Function +OpStore %f %float_0 +OpStore %i %int_0 +OpBranch %25 +%25 = OpLabel +%46 = OpPhi %float %float_0 %24 %37 %27 +%45 = OpPhi %int %int_0 %24 %43 %27 +%48 = OpPhi %float %49 %24 %46 %27 +OpLoopMerge %26 %27 None +OpBranch %28 +%28 = OpLabel +%30 = OpSLessThan %bool %45 %int_4 +OpBranchConditional %30 %31 %26 +%31 = OpLabel +OpStore %t %46 +%35 = OpAccessChain %_ptr_Input_float %BC %45 +%36 = OpLoad %float %35 +%37 = OpFAdd %float %46 %36 +OpStore %f %37 +%39 = OpFOrdGreaterThan %bool %37 %float_1 +OpSelectionMerge %40 None +OpBranchConditional %39 %41 %40 +%41 = OpLabel +OpBranch %26 +%40 = OpLabel +OpBranch %27 +%27 = OpLabel +%43 = OpIAdd %int %45 %int_1 +OpStore %i %43 +OpBranch %25 +%26 = OpLabel +%47 = OpPhi %float %48 %28 %46 %41 +OpStore %fo %47 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(LocalSSAElimTest, IfThenElse) { + // #version 140 + // + // in vec4 BaseColor; + // in float f; + // + // void main() + // { + // vec4 v; + // if (f >= 0) + // v = BaseColor * 0.5; + // else + // v = BaseColor + vec4(1.0,1.0,1.0,1.0); + // gl_FragColor = v; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %f %BaseColor %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %f "f" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Input_float = OpTypePointer Input %float +%f = OpVariable %_ptr_Input_float Input +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%float_0_5 = OpConstant %float 0.5 +%float_1 = OpConstant %float 1 +%18 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %8 +%20 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%21 = OpLoad %float %f +%22 = OpFOrdGreaterThanEqual %bool %21 %float_0 +OpSelectionMerge %23 None +OpBranchConditional %22 %24 %25 +%24 = OpLabel +%26 = OpLoad %v4float %BaseColor +%27 = OpVectorTimesScalar %v4float %26 %float_0_5 +OpStore %v %27 +OpBranch %23 +%25 = OpLabel +%28 = OpLoad %v4float %BaseColor +%29 = OpFAdd %v4float %28 %18 +OpStore %v %29 +OpBranch %23 +%23 = OpLabel +%30 = OpLoad %v4float %v +OpStore %gl_FragColor %30 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %8 +%20 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%21 = OpLoad %float %f +%22 = OpFOrdGreaterThanEqual %bool %21 %float_0 +OpSelectionMerge %23 None +OpBranchConditional %22 %24 %25 +%24 = OpLabel +%26 = OpLoad %v4float %BaseColor +%27 = OpVectorTimesScalar %v4float %26 %float_0_5 +OpStore %v %27 +OpBranch %23 +%25 = OpLabel +%28 = OpLoad %v4float %BaseColor +%29 = OpFAdd %v4float %28 %18 +OpStore %v %29 +OpBranch %23 +%23 = OpLabel +%31 = OpPhi %v4float %27 %24 %29 %25 +OpStore %gl_FragColor %31 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(LocalSSAElimTest, IfThen) { + // #version 140 + // + // in vec4 BaseColor; + // in float f; + // + // void main() + // { + // vec4 v = BaseColor; + // if (f <= 0) + // v = v * 0.5; + // gl_FragColor = v; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %f %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %f "f" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%_ptr_Input_float = OpTypePointer Input %float +%f = OpVariable %_ptr_Input_float Input +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%float_0_5 = OpConstant %float 0.5 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %8 +%18 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%19 = OpLoad %v4float %BaseColor +OpStore %v %19 +%20 = OpLoad %float %f +%21 = OpFOrdLessThanEqual %bool %20 %float_0 +OpSelectionMerge %22 None +OpBranchConditional %21 %23 %22 +%23 = OpLabel +%24 = OpLoad %v4float %v +%25 = OpVectorTimesScalar %v4float %24 %float_0_5 +OpStore %v %25 +OpBranch %22 +%22 = OpLabel +%26 = OpLoad %v4float %v +OpStore %gl_FragColor %26 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %8 +%18 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%19 = OpLoad %v4float %BaseColor +OpStore %v %19 +%20 = OpLoad %float %f +%21 = OpFOrdLessThanEqual %bool %20 %float_0 +OpSelectionMerge %22 None +OpBranchConditional %21 %23 %22 +%23 = OpLabel +%25 = OpVectorTimesScalar %v4float %19 %float_0_5 +OpStore %v %25 +OpBranch %22 +%22 = OpLabel +%27 = OpPhi %v4float %19 %18 %25 %23 +OpStore %gl_FragColor %27 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(LocalSSAElimTest, Switch) { + // #version 140 + // + // in vec4 BaseColor; + // in float f; + // + // void main() + // { + // vec4 v = BaseColor; + // int i = int(f); + // switch (i) { + // case 0: + // v = v * 0.25; + // break; + // case 1: + // v = v * 0.625; + // break; + // case 2: + // v = v * 0.75; + // break; + // default: + // break; + // } + // gl_FragColor = v; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %f %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %i "i" +OpName %f "f" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%9 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%_ptr_Input_float = OpTypePointer Input %float +%f = OpVariable %_ptr_Input_float Input +%float_0_25 = OpConstant %float 0.25 +%float_0_625 = OpConstant %float 0.625 +%float_0_75 = OpConstant %float 0.75 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %9 +%21 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%i = OpVariable %_ptr_Function_int Function +%22 = OpLoad %v4float %BaseColor +OpStore %v %22 +%23 = OpLoad %float %f +%24 = OpConvertFToS %int %23 +OpStore %i %24 +%25 = OpLoad %int %i +OpSelectionMerge %26 None +OpSwitch %25 %27 0 %28 1 %29 2 %30 +%27 = OpLabel +OpBranch %26 +%28 = OpLabel +%31 = OpLoad %v4float %v +%32 = OpVectorTimesScalar %v4float %31 %float_0_25 +OpStore %v %32 +OpBranch %26 +%29 = OpLabel +%33 = OpLoad %v4float %v +%34 = OpVectorTimesScalar %v4float %33 %float_0_625 +OpStore %v %34 +OpBranch %26 +%30 = OpLabel +%35 = OpLoad %v4float %v +%36 = OpVectorTimesScalar %v4float %35 %float_0_75 +OpStore %v %36 +OpBranch %26 +%26 = OpLabel +%37 = OpLoad %v4float %v +OpStore %gl_FragColor %37 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %9 +%21 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%i = OpVariable %_ptr_Function_int Function +%22 = OpLoad %v4float %BaseColor +OpStore %v %22 +%23 = OpLoad %float %f +%24 = OpConvertFToS %int %23 +OpStore %i %24 +OpSelectionMerge %26 None +OpSwitch %24 %27 0 %28 1 %29 2 %30 +%27 = OpLabel +OpBranch %26 +%28 = OpLabel +%32 = OpVectorTimesScalar %v4float %22 %float_0_25 +OpStore %v %32 +OpBranch %26 +%29 = OpLabel +%34 = OpVectorTimesScalar %v4float %22 %float_0_625 +OpStore %v %34 +OpBranch %26 +%30 = OpLabel +%36 = OpVectorTimesScalar %v4float %22 %float_0_75 +OpStore %v %36 +OpBranch %26 +%26 = OpLabel +%38 = OpPhi %v4float %22 %27 %32 %28 %34 %29 %36 %30 +OpStore %gl_FragColor %38 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(LocalSSAElimTest, SwitchWithFallThrough) { + // #version 140 + // + // in vec4 BaseColor; + // in float f; + // + // void main() + // { + // vec4 v = BaseColor; + // int i = int(f); + // switch (i) { + // case 0: + // v = v * 0.25; + // break; + // case 1: + // v = v + 0.25; + // case 2: + // v = v * 0.75; + // break; + // default: + // break; + // } + // gl_FragColor = v; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %f %gl_FragColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %i "i" +OpName %f "f" +OpName %gl_FragColor "gl_FragColor" +%void = OpTypeVoid +%9 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%_ptr_Input_float = OpTypePointer Input %float +%f = OpVariable %_ptr_Input_float Input +%float_0_25 = OpConstant %float 0.25 +%float_0_75 = OpConstant %float 0.75 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%gl_FragColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string before = + R"(%main = OpFunction %void None %9 +%20 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%i = OpVariable %_ptr_Function_int Function +%21 = OpLoad %v4float %BaseColor +OpStore %v %21 +%22 = OpLoad %float %f +%23 = OpConvertFToS %int %22 +OpStore %i %23 +%24 = OpLoad %int %i +OpSelectionMerge %25 None +OpSwitch %24 %26 0 %27 1 %28 2 %29 +%26 = OpLabel +OpBranch %25 +%27 = OpLabel +%30 = OpLoad %v4float %v +%31 = OpVectorTimesScalar %v4float %30 %float_0_25 +OpStore %v %31 +OpBranch %25 +%28 = OpLabel +%32 = OpLoad %v4float %v +%33 = OpCompositeConstruct %v4float %float_0_25 %float_0_25 %float_0_25 %float_0_25 +%34 = OpFAdd %v4float %32 %33 +OpStore %v %34 +OpBranch %29 +%29 = OpLabel +%35 = OpLoad %v4float %v +%36 = OpVectorTimesScalar %v4float %35 %float_0_75 +OpStore %v %36 +OpBranch %25 +%25 = OpLabel +%37 = OpLoad %v4float %v +OpStore %gl_FragColor %37 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = OpFunction %void None %9 +%20 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%i = OpVariable %_ptr_Function_int Function +%21 = OpLoad %v4float %BaseColor +OpStore %v %21 +%22 = OpLoad %float %f +%23 = OpConvertFToS %int %22 +OpStore %i %23 +OpSelectionMerge %25 None +OpSwitch %23 %26 0 %27 1 %28 2 %29 +%26 = OpLabel +OpBranch %25 +%27 = OpLabel +%31 = OpVectorTimesScalar %v4float %21 %float_0_25 +OpStore %v %31 +OpBranch %25 +%28 = OpLabel +%33 = OpCompositeConstruct %v4float %float_0_25 %float_0_25 %float_0_25 %float_0_25 +%34 = OpFAdd %v4float %21 %33 +OpStore %v %34 +OpBranch %29 +%29 = OpLabel +%38 = OpPhi %v4float %21 %20 %34 %28 +%36 = OpVectorTimesScalar %v4float %38 %float_0_75 +OpStore %v %36 +OpBranch %25 +%25 = OpLabel +%39 = OpPhi %v4float %21 %26 %31 %27 %36 %29 +OpStore %gl_FragColor %39 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + before, predefs + after, true, true); +} + +TEST_F(LocalSSAElimTest, DontPatchPhiInLoopHeaderThatIsNotAVar) { + // From https://github.com/KhronosGroup/SPIRV-Tools/issues/826 + // Don't try patching the (%16 %7) value/predecessor pair in the OpPhi. + // That OpPhi is unrelated to this optimization: we did not set that up + // in the SSA initialization for the loop header block. + // The pass should be a no-op on this module. + + const std::string before = R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %1 "main" +%void = OpTypeVoid +%3 = OpTypeFunction %void +%float = OpTypeFloat 32 +%float_1 = OpConstant %float 1 +%1 = OpFunction %void None %3 +%6 = OpLabel +OpBranch %7 +%7 = OpLabel +%8 = OpPhi %float %float_1 %6 %9 %7 +%9 = OpFAdd %float %8 %float_1 +OpLoopMerge %10 %7 None +OpBranch %7 +%10 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(before, before, true, + true); +} + +TEST_F(LocalSSAElimTest, OptInitializedVariableLikeStore) { + // Note: SPIR-V edited to change store to v into variable initialization + // + // #version 450 + // + // layout (location=0) in vec4 iColor; + // layout (location=1) in float fi; + // layout (location=0) out vec4 oColor; + // + // void main() + // { + // vec4 v = vec4(0.0); + // if (fi < 0.0) + // v.x = iColor.x; + // oColor = v; + // } + + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %fi %iColor %oColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %v "v" +OpName %fi "fi" +OpName %iColor "iColor" +OpName %oColor "oColor" +OpDecorate %fi Location 1 +OpDecorate %iColor Location 0 +OpDecorate %oColor Location 0 +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%float_0 = OpConstant %float 0 +%13 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 +%_ptr_Input_float = OpTypePointer Input %float +%fi = OpVariable %_ptr_Input_float Input +%bool = OpTypeBool +%_ptr_Input_v4float = OpTypePointer Input %v4float +%iColor = OpVariable %_ptr_Input_v4float Input +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%_ptr_Function_float = OpTypePointer Function %float +%_ptr_Output_v4float = OpTypePointer Output %v4float +%oColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string func_before = + R"(%main = OpFunction %void None %8 +%21 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function %13 +%22 = OpLoad %float %fi +%23 = OpFOrdLessThan %bool %22 %float_0 +OpSelectionMerge %24 None +OpBranchConditional %23 %25 %24 +%25 = OpLabel +%26 = OpAccessChain %_ptr_Input_float %iColor %uint_0 +%27 = OpLoad %float %26 +%28 = OpLoad %v4float %v +%29 = OpCompositeInsert %v4float %27 %28 0 +OpStore %v %29 +OpBranch %24 +%24 = OpLabel +%30 = OpLoad %v4float %v +OpStore %oColor %30 +OpReturn +OpFunctionEnd +)"; + + const std::string func_after = + R"(%main = OpFunction %void None %8 +%21 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function %13 +%22 = OpLoad %float %fi +%23 = OpFOrdLessThan %bool %22 %float_0 +OpSelectionMerge %24 None +OpBranchConditional %23 %25 %24 +%25 = OpLabel +%26 = OpAccessChain %_ptr_Input_float %iColor %uint_0 +%27 = OpLoad %float %26 +%29 = OpCompositeInsert %v4float %27 %13 0 +OpStore %v %29 +OpBranch %24 +%24 = OpLabel +%31 = OpPhi %v4float %13 %21 %29 %25 +OpStore %oColor %31 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs + func_before, predefs + func_after, true, true); +} + +TEST_F(LocalSSAElimTest, PointerVariable) { + // Test that checks if a pointer variable is removed. + + const std::string before = + R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %1 "main" %2 +OpExecutionMode %1 OriginUpperLeft +OpMemberDecorate %_struct_3 0 Offset 0 +OpDecorate %_runtimearr__struct_3 ArrayStride 16 +OpMemberDecorate %_struct_5 0 Offset 0 +OpDecorate %_struct_5 BufferBlock +OpMemberDecorate %_struct_6 0 Offset 0 +OpDecorate %_struct_6 BufferBlock +OpDecorate %2 Location 0 +OpDecorate %7 DescriptorSet 0 +OpDecorate %7 Binding 0 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%uint = OpTypeInt 32 0 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float +%_struct_3 = OpTypeStruct %v4float +%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3 +%_struct_5 = OpTypeStruct %_runtimearr__struct_3 +%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5 +%_struct_6 = OpTypeStruct %int +%_ptr_Uniform__struct_6 = OpTypePointer Uniform %_struct_6 +%_ptr_Function__ptr_Uniform__struct_5 = OpTypePointer Function %_ptr_Uniform__struct_5 +%_ptr_Function__ptr_Uniform__struct_6 = OpTypePointer Function %_ptr_Uniform__struct_6 +%int_0 = OpConstant %int 0 +%uint_0 = OpConstant %uint 0 +%2 = OpVariable %_ptr_Output_v4float Output +%7 = OpVariable %_ptr_Uniform__struct_5 Uniform +%1 = OpFunction %void None %10 +%23 = OpLabel +%24 = OpVariable %_ptr_Function__ptr_Uniform__struct_5 Function +OpStore %24 %7 +%26 = OpLoad %_ptr_Uniform__struct_5 %24 +%27 = OpAccessChain %_ptr_Uniform_v4float %26 %int_0 %uint_0 %int_0 +%28 = OpLoad %v4float %27 +%29 = OpCopyObject %v4float %28 +OpStore %2 %28 +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %1 "main" %2 +OpExecutionMode %1 OriginUpperLeft +OpMemberDecorate %_struct_3 0 Offset 0 +OpDecorate %_runtimearr__struct_3 ArrayStride 16 +OpMemberDecorate %_struct_5 0 Offset 0 +OpDecorate %_struct_5 BufferBlock +OpMemberDecorate %_struct_6 0 Offset 0 +OpDecorate %_struct_6 BufferBlock +OpDecorate %2 Location 0 +OpDecorate %7 DescriptorSet 0 +OpDecorate %7 Binding 0 +%void = OpTypeVoid +%10 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%uint = OpTypeInt 32 0 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float +%_struct_3 = OpTypeStruct %v4float +%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3 +%_struct_5 = OpTypeStruct %_runtimearr__struct_3 +%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5 +%_struct_6 = OpTypeStruct %int +%_ptr_Uniform__struct_6 = OpTypePointer Uniform %_struct_6 +%_ptr_Function__ptr_Uniform__struct_5 = OpTypePointer Function %_ptr_Uniform__struct_5 +%_ptr_Function__ptr_Uniform__struct_6 = OpTypePointer Function %_ptr_Uniform__struct_6 +%int_0 = OpConstant %int 0 +%uint_0 = OpConstant %uint 0 +%2 = OpVariable %_ptr_Output_v4float Output +%7 = OpVariable %_ptr_Uniform__struct_5 Uniform +%1 = OpFunction %void None %10 +%23 = OpLabel +%24 = OpVariable %_ptr_Function__ptr_Uniform__struct_5 Function +OpStore %24 %7 +%27 = OpAccessChain %_ptr_Uniform_v4float %7 %int_0 %uint_0 %int_0 +%28 = OpLoad %v4float %27 +%29 = OpCopyObject %v4float %28 +OpStore %2 %28 +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck(before, after, true, + true); +} + +TEST_F(LocalSSAElimTest, VerifyInstToBlockMap) { + // #version 140 + // + // in vec4 BC; + // out float fo; + // + // void main() + // { + // float f = 0.0; + // for (int i=0; i<4; i++) { + // f = f + BC[i]; + // } + // fo = f; + // } + + const std::string text = R"( +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BC %fo +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 140 +OpName %main "main" +OpName %f "f" +OpName %i "i" +OpName %BC "BC" +OpName %fo "fo" +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%_ptr_Function_float = OpTypePointer Function %float +%float_0 = OpConstant %float 0 +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_0 = OpConstant %int 0 +%int_4 = OpConstant %int 4 +%bool = OpTypeBool +%v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BC = OpVariable %_ptr_Input_v4float Input +%_ptr_Input_float = OpTypePointer Input %float +%int_1 = OpConstant %int 1 +%_ptr_Output_float = OpTypePointer Output %float +%fo = OpVariable %_ptr_Output_float Output +%main = OpFunction %void None %8 +%22 = OpLabel +%f = OpVariable %_ptr_Function_float Function +%i = OpVariable %_ptr_Function_int Function +OpStore %f %float_0 +OpStore %i %int_0 +OpBranch %23 +%23 = OpLabel +OpLoopMerge %24 %25 None +OpBranch %26 +%26 = OpLabel +%27 = OpLoad %int %i +%28 = OpSLessThan %bool %27 %int_4 +OpBranchConditional %28 %29 %24 +%29 = OpLabel +%30 = OpLoad %float %f +%31 = OpLoad %int %i +%32 = OpAccessChain %_ptr_Input_float %BC %31 +%33 = OpLoad %float %32 +%34 = OpFAdd %float %30 %33 +OpStore %f %34 +OpBranch %25 +%25 = OpLabel +%35 = OpLoad %int %i +%36 = OpIAdd %int %35 %int_1 +OpStore %i %36 +OpBranch %23 +%24 = OpLabel +%37 = OpLoad %float %f +OpStore %fo %37 +OpReturn +OpFunctionEnd +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + EXPECT_NE(nullptr, context); + + // Force the instruction to block mapping to get built. + context->get_instr_block(27u); + + auto pass = MakeUnique(); + pass->SetMessageConsumer(nullptr); + const auto status = pass->Run(context.get()); + EXPECT_TRUE(status == opt::Pass::Status::SuccessWithChange); +} + +// TODO(dneto): Add Effcee as required dependency, and make this unconditional. +#ifdef SPIRV_EFFCEE +TEST_F(LocalSSAElimTest, CompositeExtractProblem) { + const std::string spv_asm = R"( + OpCapability Tessellation + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint TessellationControl %2 "main" + %void = OpTypeVoid + %4 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 + %uint = OpTypeInt 32 0 + %uint_3 = OpConstant %uint 3 + %v3float = OpTypeVector %float 3 + %v2float = OpTypeVector %float 2 + %_struct_11 = OpTypeStruct %v4float %v4float %v4float %v3float %v3float %v2float %v2float +%_arr__struct_11_uint_3 = OpTypeArray %_struct_11 %uint_3 +%_ptr_Function__arr__struct_11_uint_3 = OpTypePointer Function %_arr__struct_11_uint_3 +%_arr_v4float_uint_3 = OpTypeArray %v4float %uint_3 +%_ptr_Input__arr_v4float_uint_3 = OpTypePointer Input %_arr_v4float_uint_3 + %16 = OpVariable %_ptr_Input__arr_v4float_uint_3 Input + %17 = OpVariable %_ptr_Input__arr_v4float_uint_3 Input + %18 = OpVariable %_ptr_Input__arr_v4float_uint_3 Input +%_ptr_Input_uint = OpTypePointer Input %uint + %20 = OpVariable %_ptr_Input_uint Input +%_ptr_Output__arr_v4float_uint_3 = OpTypePointer Output %_arr_v4float_uint_3 + %22 = OpVariable %_ptr_Output__arr_v4float_uint_3 Output +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_arr_v3float_uint_3 = OpTypeArray %v3float %uint_3 +%_ptr_Input__arr_v3float_uint_3 = OpTypePointer Input %_arr_v3float_uint_3 + %26 = OpVariable %_ptr_Input__arr_v3float_uint_3 Input + %27 = OpVariable %_ptr_Input__arr_v3float_uint_3 Input +%_arr_v2float_uint_3 = OpTypeArray %v2float %uint_3 +%_ptr_Input__arr_v2float_uint_3 = OpTypePointer Input %_arr_v2float_uint_3 + %30 = OpVariable %_ptr_Input__arr_v2float_uint_3 Input + %31 = OpVariable %_ptr_Input__arr_v2float_uint_3 Input +%_ptr_Function__struct_11 = OpTypePointer Function %_struct_11 + %2 = OpFunction %void None %4 + %33 = OpLabel + %34 = OpLoad %_arr_v4float_uint_3 %16 + %35 = OpLoad %_arr_v4float_uint_3 %17 + %36 = OpLoad %_arr_v4float_uint_3 %18 + %37 = OpLoad %_arr_v3float_uint_3 %26 + %38 = OpLoad %_arr_v3float_uint_3 %27 + %39 = OpLoad %_arr_v2float_uint_3 %30 + %40 = OpLoad %_arr_v2float_uint_3 %31 + %41 = OpCompositeExtract %v4float %34 0 + %42 = OpCompositeExtract %v4float %35 0 + %43 = OpCompositeExtract %v4float %36 0 + %44 = OpCompositeExtract %v3float %37 0 + %45 = OpCompositeExtract %v3float %38 0 + %46 = OpCompositeExtract %v2float %39 0 + %47 = OpCompositeExtract %v2float %40 0 + %48 = OpCompositeConstruct %_struct_11 %41 %42 %43 %44 %45 %46 %47 + %49 = OpCompositeExtract %v4float %34 1 + %50 = OpCompositeExtract %v4float %35 1 + %51 = OpCompositeExtract %v4float %36 1 + %52 = OpCompositeExtract %v3float %37 1 + %53 = OpCompositeExtract %v3float %38 1 + %54 = OpCompositeExtract %v2float %39 1 + %55 = OpCompositeExtract %v2float %40 1 + %56 = OpCompositeConstruct %_struct_11 %49 %50 %51 %52 %53 %54 %55 + %57 = OpCompositeExtract %v4float %34 2 + %58 = OpCompositeExtract %v4float %35 2 + %59 = OpCompositeExtract %v4float %36 2 + %60 = OpCompositeExtract %v3float %37 2 + %61 = OpCompositeExtract %v3float %38 2 + %62 = OpCompositeExtract %v2float %39 2 + %63 = OpCompositeExtract %v2float %40 2 + %64 = OpCompositeConstruct %_struct_11 %57 %58 %59 %60 %61 %62 %63 + %65 = OpCompositeConstruct %_arr__struct_11_uint_3 %48 %56 %64 + %66 = OpVariable %_ptr_Function__arr__struct_11_uint_3 Function + %67 = OpLoad %uint %20 + +; CHECK OpStore {{%\d+}} [[store_source:%\d+]] + OpStore %66 %65 + %68 = OpAccessChain %_ptr_Function__struct_11 %66 %67 + +; This load was being removed, because %_ptr_Function__struct_11 was being +; wrongfully considered an SSA target. +; CHECK OpLoad %_struct_11 %68 + %69 = OpLoad %_struct_11 %68 + +; Similarly, %69 cannot be replaced with %65. +; CHECK-NOT: OpCompositeExtract %v4float [[store_source]] 0 + %70 = OpCompositeExtract %v4float %69 0 + + %71 = OpAccessChain %_ptr_Output_v4float %22 %67 + OpStore %71 %70 + OpReturn + OpFunctionEnd)"; + + SinglePassRunAndMatch(spv_asm, true); +} +#endif + +// TODO(greg-lunarg): Add tests to verify handling of these cases: +// +// No optimization in the presence of +// access chains +// function calls +// OpCopyMemory? +// unsupported extensions +// Others? + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/loop_optimizations/CMakeLists.txt b/3rdparty/spirv-tools/test/opt/loop_optimizations/CMakeLists.txt new file mode 100644 index 000000000..53054814e --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/loop_optimizations/CMakeLists.txt @@ -0,0 +1,92 @@ +# Copyright (c) 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +add_spvtools_unittest(TARGET loop_descriptor_simple + SRCS ../function_utils.h + loop_descriptions.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET loop_descriptor_nested + SRCS ../function_utils.h + nested_loops.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET lcssa_test + SRCS ../function_utils.h + lcssa.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET licm_all_loop_types + SRCS ../function_utils.h + hoist_all_loop_types.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET licm_hoist_independent_loops + SRCS ../function_utils.h + hoist_from_independent_loops.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET licm_hoist_double_nested_loops + SRCS ../function_utils.h + hoist_double_nested_loops.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET licm_hoist_single_nested_loops + SRCS ../function_utils.h + hoist_single_nested_loops.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET licm_hoist_simple_case + SRCS ../function_utils.h + hoist_simple_case.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET licm_hoist_no_preheader + SRCS ../function_utils.h + hoist_without_preheader.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET loop_unroll_simple + SRCS ../function_utils.h + unroll_simple.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET loop_unroll_assumtion_checks + SRCS ../function_utils.h + unroll_assumptions.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET unswitch_test + SRCS ../function_utils.h + unswitch.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET peeling_test + SRCS ../function_utils.h + peeling.cpp + LIBS SPIRV-Tools-opt +) diff --git a/3rdparty/spirv-tools/test/opt/loop_optimizations/hoist_all_loop_types.cpp b/3rdparty/spirv-tools/test/opt/loop_optimizations/hoist_all_loop_types.cpp new file mode 100644 index 000000000..bb42fdd0c --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/loop_optimizations/hoist_all_loop_types.cpp @@ -0,0 +1,284 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include + +#include "../pass_fixture.h" +#include "opt/licm_pass.h" + +namespace { + +using namespace spvtools; +using ::testing::UnorderedElementsAre; + +using PassClassTest = PassTest<::testing::Test>; + +/* + Tests that all loop types are handled appropriately by the LICM pass. + + Generated from the following GLSL fragment shader +--eliminate-local-multi-store has also been run on the spv binary +#version 440 core +void main(){ + int i_1 = 0; + for (i_1 = 0; i_1 < 10; i_1++) { + } + int i_2 = 0; + while (i_2 < 10) { + i_2++; + } + int i_3 = 0; + do { + i_3++; + } while (i_3 < 10); + int hoist = 0; + int i_4 = 0; + int i_5 = 0; + int i_6 = 0; + for (i_4 = 0; i_4 < 10; i_4++) { + while (i_5 < 10) { + do { + hoist = i_1 + i_2 + i_3; + i_6++; + } while (i_6 < 10); + i_5++; + } + } +} +*/ +TEST_F(PassClassTest, AllLoopTypes) { + const std::string before_hoist = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 440 +OpName %main "main" +%void = OpTypeVoid +%4 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_0 = OpConstant %int 0 +%int_10 = OpConstant %int 10 +%bool = OpTypeBool +%int_1 = OpConstant %int 1 +%main = OpFunction %void None %4 +%11 = OpLabel +OpBranch %12 +%12 = OpLabel +%13 = OpPhi %int %int_0 %11 %14 %15 +OpLoopMerge %16 %15 None +OpBranch %17 +%17 = OpLabel +%18 = OpSLessThan %bool %13 %int_10 +OpBranchConditional %18 %19 %16 +%19 = OpLabel +OpBranch %15 +%15 = OpLabel +%14 = OpIAdd %int %13 %int_1 +OpBranch %12 +%16 = OpLabel +OpBranch %20 +%20 = OpLabel +%21 = OpPhi %int %int_0 %16 %22 %23 +OpLoopMerge %24 %23 None +OpBranch %25 +%25 = OpLabel +%26 = OpSLessThan %bool %21 %int_10 +OpBranchConditional %26 %27 %24 +%27 = OpLabel +%22 = OpIAdd %int %21 %int_1 +OpBranch %23 +%23 = OpLabel +OpBranch %20 +%24 = OpLabel +OpBranch %28 +%28 = OpLabel +%29 = OpPhi %int %int_0 %24 %30 %31 +OpLoopMerge %32 %31 None +OpBranch %33 +%33 = OpLabel +%30 = OpIAdd %int %29 %int_1 +OpBranch %31 +%31 = OpLabel +%34 = OpSLessThan %bool %30 %int_10 +OpBranchConditional %34 %28 %32 +%32 = OpLabel +OpBranch %35 +%35 = OpLabel +%36 = OpPhi %int %int_0 %32 %37 %38 +%39 = OpPhi %int %int_0 %32 %40 %38 +%41 = OpPhi %int %int_0 %32 %42 %38 +%43 = OpPhi %int %int_0 %32 %44 %38 +OpLoopMerge %45 %38 None +OpBranch %46 +%46 = OpLabel +%47 = OpSLessThan %bool %39 %int_10 +OpBranchConditional %47 %48 %45 +%48 = OpLabel +OpBranch %49 +%49 = OpLabel +%37 = OpPhi %int %36 %48 %50 %51 +%42 = OpPhi %int %41 %48 %52 %51 +%44 = OpPhi %int %43 %48 %53 %51 +OpLoopMerge %54 %51 None +OpBranch %55 +%55 = OpLabel +%56 = OpSLessThan %bool %42 %int_10 +OpBranchConditional %56 %57 %54 +%57 = OpLabel +OpBranch %58 +%58 = OpLabel +%59 = OpPhi %int %37 %57 %50 %60 +%61 = OpPhi %int %44 %57 %53 %60 +OpLoopMerge %62 %60 None +OpBranch %63 +%63 = OpLabel +%64 = OpIAdd %int %13 %21 +%50 = OpIAdd %int %64 %30 +%53 = OpIAdd %int %61 %int_1 +OpBranch %60 +%60 = OpLabel +%65 = OpSLessThan %bool %53 %int_10 +OpBranchConditional %65 %58 %62 +%62 = OpLabel +%52 = OpIAdd %int %42 %int_1 +OpBranch %51 +%51 = OpLabel +OpBranch %49 +%54 = OpLabel +OpBranch %38 +%38 = OpLabel +%40 = OpIAdd %int %39 %int_1 +OpBranch %35 +%45 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string after_hoist = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 440 +OpName %main "main" +%void = OpTypeVoid +%4 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_0 = OpConstant %int 0 +%int_10 = OpConstant %int 10 +%bool = OpTypeBool +%int_1 = OpConstant %int 1 +%main = OpFunction %void None %4 +%11 = OpLabel +OpBranch %12 +%12 = OpLabel +%13 = OpPhi %int %int_0 %11 %14 %15 +OpLoopMerge %16 %15 None +OpBranch %17 +%17 = OpLabel +%18 = OpSLessThan %bool %13 %int_10 +OpBranchConditional %18 %19 %16 +%19 = OpLabel +OpBranch %15 +%15 = OpLabel +%14 = OpIAdd %int %13 %int_1 +OpBranch %12 +%16 = OpLabel +OpBranch %20 +%20 = OpLabel +%21 = OpPhi %int %int_0 %16 %22 %23 +OpLoopMerge %24 %23 None +OpBranch %25 +%25 = OpLabel +%26 = OpSLessThan %bool %21 %int_10 +OpBranchConditional %26 %27 %24 +%27 = OpLabel +%22 = OpIAdd %int %21 %int_1 +OpBranch %23 +%23 = OpLabel +OpBranch %20 +%24 = OpLabel +OpBranch %28 +%28 = OpLabel +%29 = OpPhi %int %int_0 %24 %30 %31 +OpLoopMerge %32 %31 None +OpBranch %33 +%33 = OpLabel +%30 = OpIAdd %int %29 %int_1 +OpBranch %31 +%31 = OpLabel +%34 = OpSLessThan %bool %30 %int_10 +OpBranchConditional %34 %28 %32 +%32 = OpLabel +%64 = OpIAdd %int %13 %21 +%50 = OpIAdd %int %64 %30 +OpBranch %35 +%35 = OpLabel +%36 = OpPhi %int %int_0 %32 %37 %38 +%39 = OpPhi %int %int_0 %32 %40 %38 +%41 = OpPhi %int %int_0 %32 %42 %38 +%43 = OpPhi %int %int_0 %32 %44 %38 +OpLoopMerge %45 %38 None +OpBranch %46 +%46 = OpLabel +%47 = OpSLessThan %bool %39 %int_10 +OpBranchConditional %47 %48 %45 +%48 = OpLabel +OpBranch %49 +%49 = OpLabel +%37 = OpPhi %int %36 %48 %50 %51 +%42 = OpPhi %int %41 %48 %52 %51 +%44 = OpPhi %int %43 %48 %53 %51 +OpLoopMerge %54 %51 None +OpBranch %55 +%55 = OpLabel +%56 = OpSLessThan %bool %42 %int_10 +OpBranchConditional %56 %57 %54 +%57 = OpLabel +OpBranch %58 +%58 = OpLabel +%59 = OpPhi %int %37 %57 %50 %60 +%61 = OpPhi %int %44 %57 %53 %60 +OpLoopMerge %62 %60 None +OpBranch %63 +%63 = OpLabel +%53 = OpIAdd %int %61 %int_1 +OpBranch %60 +%60 = OpLabel +%65 = OpSLessThan %bool %53 %int_10 +OpBranchConditional %65 %58 %62 +%62 = OpLabel +%52 = OpIAdd %int %42 %int_1 +OpBranch %51 +%51 = OpLabel +OpBranch %49 +%54 = OpLabel +OpBranch %38 +%38 = OpLabel +%40 = OpIAdd %int %39 %int_1 +OpBranch %35 +%45 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(before_hoist, after_hoist, true); +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/loop_optimizations/hoist_double_nested_loops.cpp b/3rdparty/spirv-tools/test/opt/loop_optimizations/hoist_double_nested_loops.cpp new file mode 100644 index 000000000..46bb562b2 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/loop_optimizations/hoist_double_nested_loops.cpp @@ -0,0 +1,161 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include + +#include "../pass_fixture.h" +#include "opt/licm_pass.h" + +namespace { + +using namespace spvtools; +using ::testing::UnorderedElementsAre; + +using PassClassTest = PassTest<::testing::Test>; + +/* + Tests that the LICM pass will move invariants through multiple loops + + Generated from the following GLSL fragment shader +--eliminate-local-multi-store has also been run on the spv binary +#version 440 core +void main(){ + int a = 2; + int b = 1; + int hoist = 0; + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + // hoist 'hoist = a - b' out of both loops + hoist = a - b; + } + } +} +*/ +TEST_F(PassClassTest, NestedDoubleHoist) { + const std::string before_hoist = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 440 +OpName %main "main" +%void = OpTypeVoid +%4 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_2 = OpConstant %int 2 +%int_1 = OpConstant %int 1 +%int_0 = OpConstant %int 0 +%int_10 = OpConstant %int 10 +%bool = OpTypeBool +%12 = OpUndef %int +%main = OpFunction %void None %4 +%13 = OpLabel +OpBranch %14 +%14 = OpLabel +%15 = OpPhi %int %int_0 %13 %16 %17 +%18 = OpPhi %int %int_0 %13 %19 %17 +%20 = OpPhi %int %12 %13 %21 %17 +OpLoopMerge %22 %17 None +OpBranch %23 +%23 = OpLabel +%24 = OpSLessThan %bool %18 %int_10 +OpBranchConditional %24 %25 %22 +%25 = OpLabel +OpBranch %26 +%26 = OpLabel +%16 = OpPhi %int %15 %25 %27 %28 +%21 = OpPhi %int %int_0 %25 %29 %28 +OpLoopMerge %30 %28 None +OpBranch %31 +%31 = OpLabel +%32 = OpSLessThan %bool %21 %int_10 +OpBranchConditional %32 %33 %30 +%33 = OpLabel +%27 = OpISub %int %int_2 %int_1 +OpBranch %28 +%28 = OpLabel +%29 = OpIAdd %int %21 %int_1 +OpBranch %26 +%30 = OpLabel +OpBranch %17 +%17 = OpLabel +%19 = OpIAdd %int %18 %int_1 +OpBranch %14 +%22 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string after_hoist = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 440 +OpName %main "main" +%void = OpTypeVoid +%4 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_2 = OpConstant %int 2 +%int_1 = OpConstant %int 1 +%int_0 = OpConstant %int 0 +%int_10 = OpConstant %int 10 +%bool = OpTypeBool +%12 = OpUndef %int +%main = OpFunction %void None %4 +%13 = OpLabel +%27 = OpISub %int %int_2 %int_1 +OpBranch %14 +%14 = OpLabel +%15 = OpPhi %int %int_0 %13 %16 %17 +%18 = OpPhi %int %int_0 %13 %19 %17 +%20 = OpPhi %int %12 %13 %21 %17 +OpLoopMerge %22 %17 None +OpBranch %23 +%23 = OpLabel +%24 = OpSLessThan %bool %18 %int_10 +OpBranchConditional %24 %25 %22 +%25 = OpLabel +OpBranch %26 +%26 = OpLabel +%16 = OpPhi %int %15 %25 %27 %28 +%21 = OpPhi %int %int_0 %25 %29 %28 +OpLoopMerge %30 %28 None +OpBranch %31 +%31 = OpLabel +%32 = OpSLessThan %bool %21 %int_10 +OpBranchConditional %32 %33 %30 +%33 = OpLabel +OpBranch %28 +%28 = OpLabel +%29 = OpIAdd %int %21 %int_1 +OpBranch %26 +%30 = OpLabel +OpBranch %17 +%17 = OpLabel +%19 = OpIAdd %int %18 %int_1 +OpBranch %14 +%22 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(before_hoist, after_hoist, true); +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/loop_optimizations/hoist_from_independent_loops.cpp b/3rdparty/spirv-tools/test/opt/loop_optimizations/hoist_from_independent_loops.cpp new file mode 100644 index 000000000..1c7bebaf1 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/loop_optimizations/hoist_from_independent_loops.cpp @@ -0,0 +1,200 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include + +#include "../pass_fixture.h" +#include "opt/licm_pass.h" + +namespace { + +using namespace spvtools; +using ::testing::UnorderedElementsAre; + +using PassClassTest = PassTest<::testing::Test>; + +/* + Tests that the LICM pass will analyse multiple independent loops in a function + + Generated from the following GLSL fragment shader +--eliminate-local-multi-store has also been run on the spv binary +#version 440 core +void main(){ + int a = 1; + int b = 2; + int hoist = 0; + for (int i = 0; i < 10; i++) { + // invariant + hoist = a + b; + } + for (int i = 0; i < 10; i++) { + // invariant + hoist = a + b; + } + int c = 1; + int d = 2; + int hoist2 = 0; + for (int i = 0; i < 10; i++) { + // invariant + hoist2 = c + d; + } +} +*/ +TEST_F(PassClassTest, HoistFromIndependentLoops) { + const std::string before_hoist = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 440 +OpName %main "main" +%void = OpTypeVoid +%4 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_1 = OpConstant %int 1 +%int_2 = OpConstant %int 2 +%int_0 = OpConstant %int 0 +%int_10 = OpConstant %int 10 +%bool = OpTypeBool +%main = OpFunction %void None %4 +%12 = OpLabel +OpBranch %13 +%13 = OpLabel +%14 = OpPhi %int %int_0 %12 %15 %16 +%17 = OpPhi %int %int_0 %12 %18 %16 +OpLoopMerge %19 %16 None +OpBranch %20 +%20 = OpLabel +%21 = OpSLessThan %bool %17 %int_10 +OpBranchConditional %21 %22 %19 +%22 = OpLabel +%15 = OpIAdd %int %int_1 %int_2 +OpBranch %16 +%16 = OpLabel +%18 = OpIAdd %int %17 %int_1 +OpBranch %13 +%19 = OpLabel +OpBranch %23 +%23 = OpLabel +%24 = OpPhi %int %14 %19 %25 %26 +%27 = OpPhi %int %int_0 %19 %28 %26 +OpLoopMerge %29 %26 None +OpBranch %30 +%30 = OpLabel +%31 = OpSLessThan %bool %27 %int_10 +OpBranchConditional %31 %32 %29 +%32 = OpLabel +%25 = OpIAdd %int %int_1 %int_2 +OpBranch %26 +%26 = OpLabel +%28 = OpIAdd %int %27 %int_1 +OpBranch %23 +%29 = OpLabel +OpBranch %33 +%33 = OpLabel +%34 = OpPhi %int %int_0 %29 %35 %36 +%37 = OpPhi %int %int_0 %29 %38 %36 +OpLoopMerge %39 %36 None +OpBranch %40 +%40 = OpLabel +%41 = OpSLessThan %bool %37 %int_10 +OpBranchConditional %41 %42 %39 +%42 = OpLabel +%35 = OpIAdd %int %int_1 %int_2 +OpBranch %36 +%36 = OpLabel +%38 = OpIAdd %int %37 %int_1 +OpBranch %33 +%39 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string after_hoist = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 440 +OpName %main "main" +%void = OpTypeVoid +%4 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_1 = OpConstant %int 1 +%int_2 = OpConstant %int 2 +%int_0 = OpConstant %int 0 +%int_10 = OpConstant %int 10 +%bool = OpTypeBool +%main = OpFunction %void None %4 +%12 = OpLabel +%15 = OpIAdd %int %int_1 %int_2 +OpBranch %13 +%13 = OpLabel +%14 = OpPhi %int %int_0 %12 %15 %16 +%17 = OpPhi %int %int_0 %12 %18 %16 +OpLoopMerge %19 %16 None +OpBranch %20 +%20 = OpLabel +%21 = OpSLessThan %bool %17 %int_10 +OpBranchConditional %21 %22 %19 +%22 = OpLabel +OpBranch %16 +%16 = OpLabel +%18 = OpIAdd %int %17 %int_1 +OpBranch %13 +%19 = OpLabel +%25 = OpIAdd %int %int_1 %int_2 +OpBranch %23 +%23 = OpLabel +%24 = OpPhi %int %14 %19 %25 %26 +%27 = OpPhi %int %int_0 %19 %28 %26 +OpLoopMerge %29 %26 None +OpBranch %30 +%30 = OpLabel +%31 = OpSLessThan %bool %27 %int_10 +OpBranchConditional %31 %32 %29 +%32 = OpLabel +OpBranch %26 +%26 = OpLabel +%28 = OpIAdd %int %27 %int_1 +OpBranch %23 +%29 = OpLabel +%35 = OpIAdd %int %int_1 %int_2 +OpBranch %33 +%33 = OpLabel +%34 = OpPhi %int %int_0 %29 %35 %36 +%37 = OpPhi %int %int_0 %29 %38 %36 +OpLoopMerge %39 %36 None +OpBranch %40 +%40 = OpLabel +%41 = OpSLessThan %bool %37 %int_10 +OpBranchConditional %41 %42 %39 +%42 = OpLabel +OpBranch %36 +%36 = OpLabel +%38 = OpIAdd %int %37 %int_1 +OpBranch %33 +%39 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(before_hoist, after_hoist, true); +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/loop_optimizations/hoist_simple_case.cpp b/3rdparty/spirv-tools/test/opt/loop_optimizations/hoist_simple_case.cpp new file mode 100644 index 000000000..ce29c6302 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/loop_optimizations/hoist_simple_case.cpp @@ -0,0 +1,125 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include + +#include "../pass_fixture.h" +#include "opt/licm_pass.h" + +namespace { + +using namespace spvtools; +using ::testing::UnorderedElementsAre; + +using PassClassTest = PassTest<::testing::Test>; + +/* + A simple test for the LICM pass + + Generated from the following GLSL fragment shader +--eliminate-local-multi-store has also been run on the spv binary +#version 440 core +void main(){ + int a = 1; + int b = 2; + int hoist = 0; + for (int i = 0; i < 10; i++) { + // invariant + hoist = a + b; + } +} +*/ +TEST_F(PassClassTest, SimpleHoist) { + const std::string before_hoist = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 440 +OpName %main "main" +%void = OpTypeVoid +%4 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_1 = OpConstant %int 1 +%int_2 = OpConstant %int 2 +%int_0 = OpConstant %int 0 +%int_10 = OpConstant %int 10 +%bool = OpTypeBool +%main = OpFunction %void None %4 +%12 = OpLabel +OpBranch %13 +%13 = OpLabel +%14 = OpPhi %int %int_0 %12 %15 %16 +%17 = OpPhi %int %int_0 %12 %18 %16 +OpLoopMerge %19 %16 None +OpBranch %20 +%20 = OpLabel +%21 = OpSLessThan %bool %17 %int_10 +OpBranchConditional %21 %22 %19 +%22 = OpLabel +%15 = OpIAdd %int %int_1 %int_2 +OpBranch %16 +%16 = OpLabel +%18 = OpIAdd %int %17 %int_1 +OpBranch %13 +%19 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string after_hoist = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 440 +OpName %main "main" +%void = OpTypeVoid +%4 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_1 = OpConstant %int 1 +%int_2 = OpConstant %int 2 +%int_0 = OpConstant %int 0 +%int_10 = OpConstant %int 10 +%bool = OpTypeBool +%main = OpFunction %void None %4 +%12 = OpLabel +%15 = OpIAdd %int %int_1 %int_2 +OpBranch %13 +%13 = OpLabel +%14 = OpPhi %int %int_0 %12 %15 %16 +%17 = OpPhi %int %int_0 %12 %18 %16 +OpLoopMerge %19 %16 None +OpBranch %20 +%20 = OpLabel +%21 = OpSLessThan %bool %17 %int_10 +OpBranchConditional %21 %22 %19 +%22 = OpLabel +OpBranch %16 +%16 = OpLabel +%18 = OpIAdd %int %17 %int_1 +OpBranch %13 +%19 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(before_hoist, after_hoist, true); +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/loop_optimizations/hoist_single_nested_loops.cpp b/3rdparty/spirv-tools/test/opt/loop_optimizations/hoist_single_nested_loops.cpp new file mode 100644 index 000000000..563c48960 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/loop_optimizations/hoist_single_nested_loops.cpp @@ -0,0 +1,161 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include + +#include "../pass_fixture.h" +#include "opt/licm_pass.h" + +namespace { + +using namespace spvtools; +using ::testing::UnorderedElementsAre; + +using PassClassTest = PassTest<::testing::Test>; + +/* + Tests that the LICM pass will detect an move an invariant from a nested loop, + but not it's parent loop + + Generated from the following GLSL fragment shader +--eliminate-local-multi-store has also been run on the spv binary +#version 440 core +void main(){ + int a = 2; + int hoist = 0; + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + // hoist 'hoist = a - i' out of j loop, but not i loop + hoist = a - i; + } + } +} +*/ +TEST_F(PassClassTest, NestedSingleHoist) { + const std::string before_hoist = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 440 +OpName %main "main" +%void = OpTypeVoid +%4 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_2 = OpConstant %int 2 +%int_0 = OpConstant %int 0 +%int_10 = OpConstant %int 10 +%bool = OpTypeBool +%int_1 = OpConstant %int 1 +%12 = OpUndef %int +%main = OpFunction %void None %4 +%13 = OpLabel +OpBranch %14 +%14 = OpLabel +%15 = OpPhi %int %int_0 %13 %16 %17 +%18 = OpPhi %int %int_0 %13 %19 %17 +%20 = OpPhi %int %12 %13 %21 %17 +OpLoopMerge %22 %17 None +OpBranch %23 +%23 = OpLabel +%24 = OpSLessThan %bool %18 %int_10 +OpBranchConditional %24 %25 %22 +%25 = OpLabel +OpBranch %26 +%26 = OpLabel +%16 = OpPhi %int %15 %25 %27 %28 +%21 = OpPhi %int %int_0 %25 %29 %28 +OpLoopMerge %30 %28 None +OpBranch %31 +%31 = OpLabel +%32 = OpSLessThan %bool %21 %int_10 +OpBranchConditional %32 %33 %30 +%33 = OpLabel +%27 = OpISub %int %int_2 %18 +OpBranch %28 +%28 = OpLabel +%29 = OpIAdd %int %21 %int_1 +OpBranch %26 +%30 = OpLabel +OpBranch %17 +%17 = OpLabel +%19 = OpIAdd %int %18 %int_1 +OpBranch %14 +%22 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string after_hoist = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 440 +OpName %main "main" +%void = OpTypeVoid +%4 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_2 = OpConstant %int 2 +%int_0 = OpConstant %int 0 +%int_10 = OpConstant %int 10 +%bool = OpTypeBool +%int_1 = OpConstant %int 1 +%12 = OpUndef %int +%main = OpFunction %void None %4 +%13 = OpLabel +OpBranch %14 +%14 = OpLabel +%15 = OpPhi %int %int_0 %13 %16 %17 +%18 = OpPhi %int %int_0 %13 %19 %17 +%20 = OpPhi %int %12 %13 %21 %17 +OpLoopMerge %22 %17 None +OpBranch %23 +%23 = OpLabel +%24 = OpSLessThan %bool %18 %int_10 +OpBranchConditional %24 %25 %22 +%25 = OpLabel +%27 = OpISub %int %int_2 %18 +OpBranch %26 +%26 = OpLabel +%16 = OpPhi %int %15 %25 %27 %28 +%21 = OpPhi %int %int_0 %25 %29 %28 +OpLoopMerge %30 %28 None +OpBranch %31 +%31 = OpLabel +%32 = OpSLessThan %bool %21 %int_10 +OpBranchConditional %32 %33 %30 +%33 = OpLabel +OpBranch %28 +%28 = OpLabel +%29 = OpIAdd %int %21 %int_1 +OpBranch %26 +%30 = OpLabel +OpBranch %17 +%17 = OpLabel +%19 = OpIAdd %int %18 %int_1 +OpBranch %14 +%22 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(before_hoist, after_hoist, true); +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/loop_optimizations/hoist_without_preheader.cpp b/3rdparty/spirv-tools/test/opt/loop_optimizations/hoist_without_preheader.cpp new file mode 100644 index 000000000..fe60fc82d --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/loop_optimizations/hoist_without_preheader.cpp @@ -0,0 +1,123 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include + +#include "../pass_fixture.h" +#include "opt/licm_pass.h" + +namespace { + +using namespace spvtools; +using ::testing::UnorderedElementsAre; + +using PassClassTest = PassTest<::testing::Test>; + +/* + Tests that the LICM pass will generate a preheader when one is not present + + Generated from the following GLSL fragment shader +--eliminate-local-multi-store has also been run on the spv binary +#version 440 core +void main(){ + int a = 1; + int b = 2; + int hoist = 0; + for (int i = 0; i < 10; i++) { + if (i == 5) { + break; + } + } + for (int i = 0; i < 10; i++) { + hoist = a + b; + } +} +*/ +#ifdef SPIRV_EFFCEE +TEST_F(PassClassTest, HoistWithoutPreheader) { + const std::string text = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 440 +OpName %main "main" +%void = OpTypeVoid +%4 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%int_1 = OpConstant %int 1 +%int_2 = OpConstant %int 2 +%int_0 = OpConstant %int 0 +%int_10 = OpConstant %int 10 +%bool = OpTypeBool +%int_5 = OpConstant %int 5 +%main = OpFunction %void None %4 +%13 = OpLabel +OpBranch %14 +%14 = OpLabel +%15 = OpPhi %int %int_0 %13 %16 %17 +; CHECK: OpLoopMerge [[preheader:%\w+]] +OpLoopMerge %25 %17 None +OpBranch %19 +%19 = OpLabel +%20 = OpSLessThan %bool %15 %int_10 +OpBranchConditional %20 %21 %25 +%21 = OpLabel +%22 = OpIEqual %bool %15 %int_5 +OpSelectionMerge %23 None +OpBranchConditional %22 %24 %23 +%24 = OpLabel +OpBranch %25 +%23 = OpLabel +OpBranch %17 +%17 = OpLabel +%16 = OpIAdd %int %15 %int_1 +OpBranch %14 +; Check that we hoisted the code to the preheader +; CHECK: [[preheader]] = OpLabel +; CHECK-NEXT: OpPhi +; CHECK-NEXT: OpPhi +; CHECK-NEXT: OpIAdd +; CHECK-NEXT: OpBranch [[header:%\w+]] +; CHECK: [[header]] = OpLabel +; CHECK-NEXT: OpPhi +; CHECK-NEXT: OpPhi +; CHECK: OpLoopMerge +%25 = OpLabel +%26 = OpPhi %int %int_0 %24 %int_0 %19 %27 %28 +%29 = OpPhi %int %int_0 %24 %int_0 %19 %30 %28 +OpLoopMerge %31 %28 None +OpBranch %32 +%32 = OpLabel +%33 = OpSLessThan %bool %29 %int_10 +OpBranchConditional %33 %34 %31 +%34 = OpLabel +%27 = OpIAdd %int %int_1 %int_2 +OpBranch %28 +%28 = OpLabel +%30 = OpIAdd %int %29 %int_1 +OpBranch %25 +%31 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, false); +} +#endif + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/loop_optimizations/lcssa.cpp b/3rdparty/spirv-tools/test/opt/loop_optimizations/lcssa.cpp new file mode 100644 index 000000000..95d5a2a57 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/loop_optimizations/lcssa.cpp @@ -0,0 +1,614 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include +#include + +#ifdef SPIRV_EFFCEE +#include "effcee/effcee.h" +#endif + +#include "../assembly_builder.h" +#include "../function_utils.h" + +#include "opt/build_module.h" +#include "opt/loop_descriptor.h" +#include "opt/loop_utils.h" +#include "opt/pass.h" + +namespace { + +using namespace spvtools; + +#ifdef SPIRV_EFFCEE + +bool Validate(const std::vector& bin) { + spv_target_env target_env = SPV_ENV_UNIVERSAL_1_2; + spv_context spvContext = spvContextCreate(target_env); + spv_diagnostic diagnostic = nullptr; + spv_const_binary_t binary = {bin.data(), bin.size()}; + spv_result_t error = spvValidate(spvContext, &binary, &diagnostic); + if (error != 0) spvDiagnosticPrint(diagnostic); + spvDiagnosticDestroy(diagnostic); + spvContextDestroy(spvContext); + return error == 0; +} + +void Match(const std::string& original, ir::IRContext* context, + bool do_validation = true) { + std::vector bin; + context->module()->ToBinary(&bin, true); + if (do_validation) { + EXPECT_TRUE(Validate(bin)); + } + std::string assembly; + SpirvTools tools(SPV_ENV_UNIVERSAL_1_2); + EXPECT_TRUE( + tools.Disassemble(bin, &assembly, SPV_BINARY_TO_TEXT_OPTION_NO_HEADER)) + << "Disassembling failed for shader:\n" + << assembly << std::endl; + auto match_result = effcee::Match(assembly, original); + EXPECT_EQ(effcee::Result::Status::Ok, match_result.status()) + << match_result.message() << "\nChecking result:\n" + << assembly; +} + +using LCSSATest = ::testing::Test; + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 330 core +layout(location = 0) out vec4 c; +void main() { + int i = 0; + for (; i < 10; i++) { + } + if (i != 0) { + i = 1; + } +} +*/ +TEST_F(LCSSATest, SimpleLCSSA) { + const std::string text = R"( +; CHECK: OpLoopMerge [[merge:%\w+]] %19 None +; CHECK: [[merge]] = OpLabel +; CHECK-NEXT: [[phi:%\w+]] = OpPhi {{%\w+}} %30 %20 +; CHECK-NEXT: %27 = OpINotEqual {{%\w+}} [[phi]] %9 + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 330 + OpName %2 "main" + OpName %3 "c" + OpDecorate %3 Location 0 + %5 = OpTypeVoid + %6 = OpTypeFunction %5 + %7 = OpTypeInt 32 1 + %8 = OpTypePointer Function %7 + %9 = OpConstant %7 0 + %10 = OpConstant %7 10 + %11 = OpTypeBool + %12 = OpConstant %7 1 + %13 = OpTypeFloat 32 + %14 = OpTypeVector %13 4 + %15 = OpTypePointer Output %14 + %3 = OpVariable %15 Output + %2 = OpFunction %5 None %6 + %16 = OpLabel + OpBranch %17 + %17 = OpLabel + %30 = OpPhi %7 %9 %16 %25 %19 + OpLoopMerge %18 %19 None + OpBranch %20 + %20 = OpLabel + %22 = OpSLessThan %11 %30 %10 + OpBranchConditional %22 %23 %18 + %23 = OpLabel + OpBranch %19 + %19 = OpLabel + %25 = OpIAdd %7 %30 %12 + OpBranch %17 + %18 = OpLabel + %27 = OpINotEqual %11 %30 %9 + OpSelectionMerge %28 None + OpBranchConditional %27 %29 %28 + %29 = OpLabel + OpBranch %28 + %28 = OpLabel + %31 = OpPhi %7 %30 %18 %12 %29 + OpReturn + OpFunctionEnd + )"; + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + const ir::Function* f = spvtest::GetFunction(module, 2); + ir::LoopDescriptor ld{f}; + + ir::Loop* loop = ld[17]; + EXPECT_FALSE(loop->IsLCSSA()); + opt::LoopUtils Util(context.get(), loop); + Util.MakeLoopClosedSSA(); + EXPECT_TRUE(loop->IsLCSSA()); + Match(text, context.get()); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 330 core +layout(location = 0) out vec4 c; +void main() { + int i = 0; + for (; i < 10; i++) { + } + if (i != 0) { + i = 1; + } +} +*/ +// Same test as above, but should reuse an existing phi. +TEST_F(LCSSATest, PhiReuseLCSSA) { + const std::string text = R"( +; CHECK: OpLoopMerge [[merge:%\w+]] %19 None +; CHECK: [[merge]] = OpLabel +; CHECK-NEXT: [[phi:%\w+]] = OpPhi {{%\w+}} %30 %20 +; CHECK-NEXT: %27 = OpINotEqual {{%\w+}} [[phi]] %9 + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 330 + OpName %2 "main" + OpName %3 "c" + OpDecorate %3 Location 0 + %5 = OpTypeVoid + %6 = OpTypeFunction %5 + %7 = OpTypeInt 32 1 + %8 = OpTypePointer Function %7 + %9 = OpConstant %7 0 + %10 = OpConstant %7 10 + %11 = OpTypeBool + %12 = OpConstant %7 1 + %13 = OpTypeFloat 32 + %14 = OpTypeVector %13 4 + %15 = OpTypePointer Output %14 + %3 = OpVariable %15 Output + %2 = OpFunction %5 None %6 + %16 = OpLabel + OpBranch %17 + %17 = OpLabel + %30 = OpPhi %7 %9 %16 %25 %19 + OpLoopMerge %18 %19 None + OpBranch %20 + %20 = OpLabel + %22 = OpSLessThan %11 %30 %10 + OpBranchConditional %22 %23 %18 + %23 = OpLabel + OpBranch %19 + %19 = OpLabel + %25 = OpIAdd %7 %30 %12 + OpBranch %17 + %18 = OpLabel + %32 = OpPhi %7 %30 %20 + %27 = OpINotEqual %11 %30 %9 + OpSelectionMerge %28 None + OpBranchConditional %27 %29 %28 + %29 = OpLabel + OpBranch %28 + %28 = OpLabel + %31 = OpPhi %7 %30 %18 %12 %29 + OpReturn + OpFunctionEnd + )"; + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + const ir::Function* f = spvtest::GetFunction(module, 2); + ir::LoopDescriptor ld{f}; + + ir::Loop* loop = ld[17]; + EXPECT_FALSE(loop->IsLCSSA()); + opt::LoopUtils Util(context.get(), loop); + Util.MakeLoopClosedSSA(); + EXPECT_TRUE(loop->IsLCSSA()); + Match(text, context.get()); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 330 core +layout(location = 0) out vec4 c; +void main() { + int i = 0; + int j = 0; + for (; i < 10; i++) {} + for (; j < 10; j++) {} + if (j != 0) { + i = 1; + } +} +*/ +TEST_F(LCSSATest, DualLoopLCSSA) { + const std::string text = R"( +; CHECK: %20 = OpLabel +; CHECK-NEXT: [[phi:%\w+]] = OpPhi %6 %17 %21 +; CHECK: %33 = OpLabel +; CHECK-NEXT: {{%\w+}} = OpPhi {{%\w+}} [[phi]] %28 %11 %34 + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 330 + OpName %2 "main" + OpName %3 "c" + OpDecorate %3 Location 0 + %4 = OpTypeVoid + %5 = OpTypeFunction %4 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %8 = OpConstant %6 0 + %9 = OpConstant %6 10 + %10 = OpTypeBool + %11 = OpConstant %6 1 + %12 = OpTypeFloat 32 + %13 = OpTypeVector %12 4 + %14 = OpTypePointer Output %13 + %3 = OpVariable %14 Output + %2 = OpFunction %4 None %5 + %15 = OpLabel + OpBranch %16 + %16 = OpLabel + %17 = OpPhi %6 %8 %15 %18 %19 + OpLoopMerge %20 %19 None + OpBranch %21 + %21 = OpLabel + %22 = OpSLessThan %10 %17 %9 + OpBranchConditional %22 %23 %20 + %23 = OpLabel + OpBranch %19 + %19 = OpLabel + %18 = OpIAdd %6 %17 %11 + OpBranch %16 + %20 = OpLabel + OpBranch %24 + %24 = OpLabel + %25 = OpPhi %6 %8 %20 %26 %27 + OpLoopMerge %28 %27 None + OpBranch %29 + %29 = OpLabel + %30 = OpSLessThan %10 %25 %9 + OpBranchConditional %30 %31 %28 + %31 = OpLabel + OpBranch %27 + %27 = OpLabel + %26 = OpIAdd %6 %25 %11 + OpBranch %24 + %28 = OpLabel + %32 = OpINotEqual %10 %25 %8 + OpSelectionMerge %33 None + OpBranchConditional %32 %34 %33 + %34 = OpLabel + OpBranch %33 + %33 = OpLabel + %35 = OpPhi %6 %17 %28 %11 %34 + OpReturn + OpFunctionEnd + )"; + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + const ir::Function* f = spvtest::GetFunction(module, 2); + ir::LoopDescriptor ld{f}; + + ir::Loop* loop = ld[16]; + EXPECT_FALSE(loop->IsLCSSA()); + opt::LoopUtils Util(context.get(), loop); + Util.MakeLoopClosedSSA(); + EXPECT_TRUE(loop->IsLCSSA()); + Match(text, context.get()); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 330 core +layout(location = 0) out vec4 c; +void main() { + int i = 0; + if (i != 0) { + for (; i < 10; i++) {} + } + if (i != 0) { + i = 1; + } +} +*/ +TEST_F(LCSSATest, PhiUserLCSSA) { + const std::string text = R"( +; CHECK: OpLoopMerge [[merge:%\w+]] %22 None +; CHECK: [[merge]] = OpLabel +; CHECK-NEXT: [[phi:%\w+]] = OpPhi {{%\w+}} %20 %24 +; CHECK: %17 = OpLabel +; CHECK-NEXT: {{%\w+}} = OpPhi {{%\w+}} %8 %15 [[phi]] %23 + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 330 + OpName %2 "main" + OpName %3 "c" + OpDecorate %3 Location 0 + %4 = OpTypeVoid + %5 = OpTypeFunction %4 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %8 = OpConstant %6 0 + %9 = OpTypeBool + %10 = OpConstant %6 10 + %11 = OpConstant %6 1 + %12 = OpTypeFloat 32 + %13 = OpTypeVector %12 4 + %14 = OpTypePointer Output %13 + %3 = OpVariable %14 Output + %2 = OpFunction %4 None %5 + %15 = OpLabel + %16 = OpINotEqual %9 %8 %8 + OpSelectionMerge %17 None + OpBranchConditional %16 %18 %17 + %18 = OpLabel + OpBranch %19 + %19 = OpLabel + %20 = OpPhi %6 %8 %18 %21 %22 + OpLoopMerge %23 %22 None + OpBranch %24 + %24 = OpLabel + %25 = OpSLessThan %9 %20 %10 + OpBranchConditional %25 %26 %23 + %26 = OpLabel + OpBranch %22 + %22 = OpLabel + %21 = OpIAdd %6 %20 %11 + OpBranch %19 + %23 = OpLabel + OpBranch %17 + %17 = OpLabel + %27 = OpPhi %6 %8 %15 %20 %23 + %28 = OpINotEqual %9 %27 %8 + OpSelectionMerge %29 None + OpBranchConditional %28 %30 %29 + %30 = OpLabel + OpBranch %29 + %29 = OpLabel + %31 = OpPhi %6 %27 %17 %11 %30 + OpReturn + OpFunctionEnd + )"; + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + const ir::Function* f = spvtest::GetFunction(module, 2); + ir::LoopDescriptor ld{f}; + + ir::Loop* loop = ld[19]; + EXPECT_FALSE(loop->IsLCSSA()); + opt::LoopUtils Util(context.get(), loop); + Util.MakeLoopClosedSSA(); + EXPECT_TRUE(loop->IsLCSSA()); + Match(text, context.get()); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 330 core +void main() { + int i = 0; + if (i != 0) { + for (; i < 10; i++) { + if (i > 5) break; + } + } + if (i != 0) { + i = 1; + } +} +*/ +TEST_F(LCSSATest, LCSSAWithBreak) { + const std::string text = R"( +; CHECK: OpLoopMerge [[merge:%\w+]] %19 None +; CHECK: [[merge]] = OpLabel +; CHECK-NEXT: [[phi:%\w+]] = OpPhi {{%\w+}} %17 %21 %17 %26 +; CHECK: %14 = OpLabel +; CHECK-NEXT: {{%\w+}} = OpPhi {{%\w+}} %7 %12 [[phi]] [[merge]] + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 330 + OpName %2 "main" + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeInt 32 1 + %6 = OpTypePointer Function %5 + %7 = OpConstant %5 0 + %8 = OpTypeBool + %9 = OpConstant %5 10 + %10 = OpConstant %5 5 + %11 = OpConstant %5 1 + %2 = OpFunction %3 None %4 + %12 = OpLabel + %13 = OpINotEqual %8 %7 %7 + OpSelectionMerge %14 None + OpBranchConditional %13 %15 %14 + %15 = OpLabel + OpBranch %16 + %16 = OpLabel + %17 = OpPhi %5 %7 %15 %18 %19 + OpLoopMerge %20 %19 None + OpBranch %21 + %21 = OpLabel + %22 = OpSLessThan %8 %17 %9 + OpBranchConditional %22 %23 %20 + %23 = OpLabel + %24 = OpSGreaterThan %8 %17 %10 + OpSelectionMerge %25 None + OpBranchConditional %24 %26 %25 + %26 = OpLabel + OpBranch %20 + %25 = OpLabel + OpBranch %19 + %19 = OpLabel + %18 = OpIAdd %5 %17 %11 + OpBranch %16 + %20 = OpLabel + OpBranch %14 + %14 = OpLabel + %27 = OpPhi %5 %7 %12 %17 %20 + %28 = OpINotEqual %8 %27 %7 + OpSelectionMerge %29 None + OpBranchConditional %28 %30 %29 + %30 = OpLabel + OpBranch %29 + %29 = OpLabel + %31 = OpPhi %5 %27 %14 %11 %30 + OpReturn + OpFunctionEnd + )"; + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + const ir::Function* f = spvtest::GetFunction(module, 2); + ir::LoopDescriptor ld{f}; + + ir::Loop* loop = ld[19]; + EXPECT_FALSE(loop->IsLCSSA()); + opt::LoopUtils Util(context.get(), loop); + Util.MakeLoopClosedSSA(); + EXPECT_TRUE(loop->IsLCSSA()); + Match(text, context.get()); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 330 core +void main() { + int i = 0; + for (; i < 10; i++) {} + for (int j = i; j < 10;) { j = i + j; } +} +*/ +TEST_F(LCSSATest, LCSSAUseInNonEligiblePhi) { + const std::string text = R"( +; CHECK: %12 = OpLabel +; CHECK-NEXT: [[def_to_close:%\w+]] = OpPhi {{%\w+}} {{%\w+}} {{%\w+}} {{%\w+}} [[continue:%\w+]] +; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None +; CHECK: [[merge]] = OpLabel +; CHECK-NEXT: [[closing_phi:%\w+]] = OpPhi {{%\w+}} [[def_to_close]] %17 +; CHECK: %16 = OpLabel +; CHECK-NEXT: [[use_in_phi:%\w+]] = OpPhi {{%\w+}} %21 %22 [[closing_phi]] [[merge]] +; CHECK: OpIAdd {{%\w+}} [[closing_phi]] [[use_in_phi]] + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 330 + OpName %2 "main" + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeInt 32 1 + %6 = OpTypePointer Function %5 + %7 = OpConstant %5 0 + %8 = OpConstant %5 10 + %9 = OpTypeBool + %10 = OpConstant %5 1 + %2 = OpFunction %3 None %4 + %11 = OpLabel + OpBranch %12 + %12 = OpLabel + %13 = OpPhi %5 %7 %11 %14 %15 + OpLoopMerge %16 %15 None + OpBranch %17 + %17 = OpLabel + %18 = OpSLessThan %9 %13 %8 + OpBranchConditional %18 %19 %16 + %19 = OpLabel + OpBranch %15 + %15 = OpLabel + %14 = OpIAdd %5 %13 %10 + OpBranch %12 + %16 = OpLabel + %20 = OpPhi %5 %13 %17 %21 %22 + OpLoopMerge %23 %22 None + OpBranch %24 + %24 = OpLabel + %25 = OpSLessThan %9 %20 %8 + OpBranchConditional %25 %26 %23 + %26 = OpLabel + %21 = OpIAdd %5 %13 %20 + OpBranch %22 + %22 = OpLabel + OpBranch %16 + %23 = OpLabel + OpReturn + OpFunctionEnd + )"; + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + const ir::Function* f = spvtest::GetFunction(module, 2); + ir::LoopDescriptor ld{f}; + + ir::Loop* loop = ld[12]; + EXPECT_FALSE(loop->IsLCSSA()); + opt::LoopUtils Util(context.get(), loop); + Util.MakeLoopClosedSSA(); + EXPECT_TRUE(loop->IsLCSSA()); + Match(text, context.get()); +} + +#endif // SPIRV_EFFCEE + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/loop_optimizations/loop_descriptions.cpp b/3rdparty/spirv-tools/test/opt/loop_optimizations/loop_descriptions.cpp new file mode 100644 index 000000000..f53ad0508 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/loop_optimizations/loop_descriptions.cpp @@ -0,0 +1,300 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include +#include + +#include "../assembly_builder.h" +#include "../function_utils.h" +#include "../pass_fixture.h" +#include "../pass_utils.h" +#include "opt/loop_descriptor.h" +#include "opt/pass.h" + +namespace { + +using namespace spvtools; +using ::testing::UnorderedElementsAre; + +using PassClassTest = PassTest<::testing::Test>; + +/* +Generated from the following GLSL +#version 330 core +layout(location = 0) out vec4 c; +void main() { + int i = 0; + for(; i < 10; ++i) { + } +} +*/ +TEST_F(PassClassTest, BasicVisitFromEntryPoint) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 330 + OpName %2 "main" + OpName %5 "i" + OpName %3 "c" + OpDecorate %3 Location 0 + %6 = OpTypeVoid + %7 = OpTypeFunction %6 + %8 = OpTypeInt 32 1 + %9 = OpTypePointer Function %8 + %10 = OpConstant %8 0 + %11 = OpConstant %8 10 + %12 = OpTypeBool + %13 = OpConstant %8 1 + %14 = OpTypeFloat 32 + %15 = OpTypeVector %14 4 + %16 = OpTypePointer Output %15 + %3 = OpVariable %16 Output + %2 = OpFunction %6 None %7 + %17 = OpLabel + %5 = OpVariable %9 Function + OpStore %5 %10 + OpBranch %18 + %18 = OpLabel + OpLoopMerge %19 %20 None + OpBranch %21 + %21 = OpLabel + %22 = OpLoad %8 %5 + %23 = OpSLessThan %12 %22 %11 + OpBranchConditional %23 %24 %19 + %24 = OpLabel + OpBranch %20 + %20 = OpLabel + %25 = OpLoad %8 %5 + %26 = OpIAdd %8 %25 %13 + OpStore %5 %26 + OpBranch %18 + %19 = OpLabel + OpReturn + OpFunctionEnd + )"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + const ir::Function* f = spvtest::GetFunction(module, 2); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(f); + + EXPECT_EQ(ld.NumLoops(), 1u); + + ir::Loop& loop = ld.GetLoopByIndex(0); + EXPECT_EQ(loop.GetHeaderBlock(), spvtest::GetBasicBlock(f, 18)); + EXPECT_EQ(loop.GetLatchBlock(), spvtest::GetBasicBlock(f, 20)); + EXPECT_EQ(loop.GetMergeBlock(), spvtest::GetBasicBlock(f, 19)); + + EXPECT_FALSE(loop.HasNestedLoops()); + EXPECT_FALSE(loop.IsNested()); + EXPECT_EQ(loop.GetDepth(), 1u); +} + +/* +Generated from the following GLSL: +#version 330 core +layout(location = 0) out vec4 c; +void main() { + for(int i = 0; i < 10; ++i) {} + for(int i = 0; i < 10; ++i) {} +} + +But it was "hacked" to make the first loop merge block the second loop header. +*/ +TEST_F(PassClassTest, LoopWithNoPreHeader) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 330 + OpName %2 "main" + OpName %4 "i" + OpName %5 "i" + OpName %3 "c" + OpDecorate %3 Location 0 + %6 = OpTypeVoid + %7 = OpTypeFunction %6 + %8 = OpTypeInt 32 1 + %9 = OpTypePointer Function %8 + %10 = OpConstant %8 0 + %11 = OpConstant %8 10 + %12 = OpTypeBool + %13 = OpConstant %8 1 + %14 = OpTypeFloat 32 + %15 = OpTypeVector %14 4 + %16 = OpTypePointer Output %15 + %3 = OpVariable %16 Output + %2 = OpFunction %6 None %7 + %17 = OpLabel + %4 = OpVariable %9 Function + %5 = OpVariable %9 Function + OpStore %4 %10 + OpStore %5 %10 + OpBranch %18 + %18 = OpLabel + OpLoopMerge %27 %20 None + OpBranch %21 + %21 = OpLabel + %22 = OpLoad %8 %4 + %23 = OpSLessThan %12 %22 %11 + OpBranchConditional %23 %24 %27 + %24 = OpLabel + OpBranch %20 + %20 = OpLabel + %25 = OpLoad %8 %4 + %26 = OpIAdd %8 %25 %13 + OpStore %4 %26 + OpBranch %18 + %27 = OpLabel + OpLoopMerge %28 %29 None + OpBranch %30 + %30 = OpLabel + %31 = OpLoad %8 %5 + %32 = OpSLessThan %12 %31 %11 + OpBranchConditional %32 %33 %28 + %33 = OpLabel + OpBranch %29 + %29 = OpLabel + %34 = OpLoad %8 %5 + %35 = OpIAdd %8 %34 %13 + OpStore %5 %35 + OpBranch %27 + %28 = OpLabel + OpReturn + OpFunctionEnd + )"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + const ir::Function* f = spvtest::GetFunction(module, 2); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(f); + + EXPECT_EQ(ld.NumLoops(), 2u); + + ir::Loop* loop = ld[27]; + EXPECT_EQ(loop->GetPreHeaderBlock(), nullptr); + EXPECT_NE(loop->GetOrCreatePreHeaderBlock(), nullptr); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 330 core +in vec4 c; +void main() { + int i = 0; + bool cond = c[0] == 0; + for (; i < 10; i++) { + if (cond) { + return; + } + else { + return; + } + } + bool cond2 = i == 9; +} +*/ +TEST_F(PassClassTest, NoLoop) { + const std::string text = R"(; SPIR-V +; Version: 1.0 +; Generator: Khronos Glslang Reference Front End; 3 +; Bound: 47 +; Schema: 0 + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %16 + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 330 + OpName %4 "main" + OpName %16 "c" + OpDecorate %16 Location 0 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %10 = OpTypeBool + %11 = OpTypePointer Function %10 + %13 = OpTypeFloat 32 + %14 = OpTypeVector %13 4 + %15 = OpTypePointer Input %14 + %16 = OpVariable %15 Input + %17 = OpTypeInt 32 0 + %18 = OpConstant %17 0 + %19 = OpTypePointer Input %13 + %22 = OpConstant %13 0 + %30 = OpConstant %6 10 + %39 = OpConstant %6 1 + %46 = OpUndef %6 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %20 = OpAccessChain %19 %16 %18 + %21 = OpLoad %13 %20 + %23 = OpFOrdEqual %10 %21 %22 + OpBranch %24 + %24 = OpLabel + %45 = OpPhi %6 %9 %5 %40 %27 + OpLoopMerge %26 %27 None + OpBranch %28 + %28 = OpLabel + %31 = OpSLessThan %10 %45 %30 + OpBranchConditional %31 %25 %26 + %25 = OpLabel + OpSelectionMerge %34 None + OpBranchConditional %23 %33 %36 + %33 = OpLabel + OpReturn + %36 = OpLabel + OpReturn + %34 = OpLabel + OpBranch %27 + %27 = OpLabel + %40 = OpIAdd %6 %46 %39 + OpBranch %24 + %26 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + const ir::Function* f = spvtest::GetFunction(module, 4); + ir::LoopDescriptor ld{f}; + + EXPECT_EQ(ld.NumLoops(), 0u); +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/loop_optimizations/nested_loops.cpp b/3rdparty/spirv-tools/test/opt/loop_optimizations/nested_loops.cpp new file mode 100644 index 000000000..480a28040 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/loop_optimizations/nested_loops.cpp @@ -0,0 +1,796 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include +#include +#include + +#include "../assembly_builder.h" +#include "../function_utils.h" +#include "../pass_fixture.h" +#include "../pass_utils.h" + +#include "opt/iterator.h" +#include "opt/loop_descriptor.h" +#include "opt/pass.h" +#include "opt/tree_iterator.h" + +namespace { + +using namespace spvtools; +using ::testing::UnorderedElementsAre; + +bool Validate(const std::vector& bin) { + spv_target_env target_env = SPV_ENV_UNIVERSAL_1_2; + spv_context spvContext = spvContextCreate(target_env); + spv_diagnostic diagnostic = nullptr; + spv_const_binary_t binary = {bin.data(), bin.size()}; + spv_result_t error = spvValidate(spvContext, &binary, &diagnostic); + if (error != 0) spvDiagnosticPrint(diagnostic); + spvDiagnosticDestroy(diagnostic); + spvContextDestroy(spvContext); + return error == 0; +} + +using PassClassTest = PassTest<::testing::Test>; + +/* +Generated from the following GLSL +#version 330 core +layout(location = 0) out vec4 c; +void main() { + int i = 0; + for (; i < 10; ++i) { + int j = 0; + int k = 0; + for (; j < 11; ++j) {} + for (; k < 12; ++k) {} + } +} +*/ +TEST_F(PassClassTest, BasicVisitFromEntryPoint) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 330 + OpName %2 "main" + OpName %4 "i" + OpName %5 "j" + OpName %6 "k" + OpName %3 "c" + OpDecorate %3 Location 0 + %7 = OpTypeVoid + %8 = OpTypeFunction %7 + %9 = OpTypeInt 32 1 + %10 = OpTypePointer Function %9 + %11 = OpConstant %9 0 + %12 = OpConstant %9 10 + %13 = OpTypeBool + %14 = OpConstant %9 11 + %15 = OpConstant %9 1 + %16 = OpConstant %9 12 + %17 = OpTypeFloat 32 + %18 = OpTypeVector %17 4 + %19 = OpTypePointer Output %18 + %3 = OpVariable %19 Output + %2 = OpFunction %7 None %8 + %20 = OpLabel + %4 = OpVariable %10 Function + %5 = OpVariable %10 Function + %6 = OpVariable %10 Function + OpStore %4 %11 + OpBranch %21 + %21 = OpLabel + OpLoopMerge %22 %23 None + OpBranch %24 + %24 = OpLabel + %25 = OpLoad %9 %4 + %26 = OpSLessThan %13 %25 %12 + OpBranchConditional %26 %27 %22 + %27 = OpLabel + OpStore %5 %11 + OpStore %6 %11 + OpBranch %28 + %28 = OpLabel + OpLoopMerge %29 %30 None + OpBranch %31 + %31 = OpLabel + %32 = OpLoad %9 %5 + %33 = OpSLessThan %13 %32 %14 + OpBranchConditional %33 %34 %29 + %34 = OpLabel + OpBranch %30 + %30 = OpLabel + %35 = OpLoad %9 %5 + %36 = OpIAdd %9 %35 %15 + OpStore %5 %36 + OpBranch %28 + %29 = OpLabel + OpBranch %37 + %37 = OpLabel + OpLoopMerge %38 %39 None + OpBranch %40 + %40 = OpLabel + %41 = OpLoad %9 %6 + %42 = OpSLessThan %13 %41 %16 + OpBranchConditional %42 %43 %38 + %43 = OpLabel + OpBranch %39 + %39 = OpLabel + %44 = OpLoad %9 %6 + %45 = OpIAdd %9 %44 %15 + OpStore %6 %45 + OpBranch %37 + %38 = OpLabel + OpBranch %23 + %23 = OpLabel + %46 = OpLoad %9 %4 + %47 = OpIAdd %9 %46 %15 + OpStore %4 %47 + OpBranch %21 + %22 = OpLabel + OpReturn + OpFunctionEnd + )"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + const ir::Function* f = spvtest::GetFunction(module, 2); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(f); + + EXPECT_EQ(ld.NumLoops(), 3u); + + // Invalid basic block id. + EXPECT_EQ(ld[0u], nullptr); + // Not a loop header. + EXPECT_EQ(ld[20], nullptr); + + ir::Loop& parent_loop = *ld[21]; + EXPECT_TRUE(parent_loop.HasNestedLoops()); + EXPECT_FALSE(parent_loop.IsNested()); + EXPECT_EQ(parent_loop.GetDepth(), 1u); + EXPECT_EQ(std::distance(parent_loop.begin(), parent_loop.end()), 2u); + EXPECT_EQ(parent_loop.GetHeaderBlock(), spvtest::GetBasicBlock(f, 21)); + EXPECT_EQ(parent_loop.GetLatchBlock(), spvtest::GetBasicBlock(f, 23)); + EXPECT_EQ(parent_loop.GetMergeBlock(), spvtest::GetBasicBlock(f, 22)); + + ir::Loop& child_loop_1 = *ld[28]; + EXPECT_FALSE(child_loop_1.HasNestedLoops()); + EXPECT_TRUE(child_loop_1.IsNested()); + EXPECT_EQ(child_loop_1.GetDepth(), 2u); + EXPECT_EQ(std::distance(child_loop_1.begin(), child_loop_1.end()), 0u); + EXPECT_EQ(child_loop_1.GetHeaderBlock(), spvtest::GetBasicBlock(f, 28)); + EXPECT_EQ(child_loop_1.GetLatchBlock(), spvtest::GetBasicBlock(f, 30)); + EXPECT_EQ(child_loop_1.GetMergeBlock(), spvtest::GetBasicBlock(f, 29)); + + ir::Loop& child_loop_2 = *ld[37]; + EXPECT_FALSE(child_loop_2.HasNestedLoops()); + EXPECT_TRUE(child_loop_2.IsNested()); + EXPECT_EQ(child_loop_2.GetDepth(), 2u); + EXPECT_EQ(std::distance(child_loop_2.begin(), child_loop_2.end()), 0u); + EXPECT_EQ(child_loop_2.GetHeaderBlock(), spvtest::GetBasicBlock(f, 37)); + EXPECT_EQ(child_loop_2.GetLatchBlock(), spvtest::GetBasicBlock(f, 39)); + EXPECT_EQ(child_loop_2.GetMergeBlock(), spvtest::GetBasicBlock(f, 38)); +} + +static void CheckLoopBlocks(ir::Loop* loop, + std::unordered_set* expected_ids) { + SCOPED_TRACE("Check loop " + std::to_string(loop->GetHeaderBlock()->id())); + for (uint32_t bb_id : loop->GetBlocks()) { + EXPECT_EQ(expected_ids->count(bb_id), 1u); + expected_ids->erase(bb_id); + } + EXPECT_FALSE(loop->IsInsideLoop(loop->GetMergeBlock())); + EXPECT_EQ(expected_ids->size(), 0u); +} + +/* +Generated from the following GLSL +#version 330 core +layout(location = 0) out vec4 c; +void main() { + int i = 0; + for (; i < 10; ++i) { + for (int j = 0; j < 11; ++j) { + if (j < 5) { + for (int k = 0; k < 12; ++k) {} + } + else {} + for (int k = 0; k < 12; ++k) {} + } + } +}*/ +TEST_F(PassClassTest, TripleNestedLoop) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 330 + OpName %2 "main" + OpName %4 "i" + OpName %5 "j" + OpName %6 "k" + OpName %7 "k" + OpName %3 "c" + OpDecorate %3 Location 0 + %8 = OpTypeVoid + %9 = OpTypeFunction %8 + %10 = OpTypeInt 32 1 + %11 = OpTypePointer Function %10 + %12 = OpConstant %10 0 + %13 = OpConstant %10 10 + %14 = OpTypeBool + %15 = OpConstant %10 11 + %16 = OpConstant %10 5 + %17 = OpConstant %10 12 + %18 = OpConstant %10 1 + %19 = OpTypeFloat 32 + %20 = OpTypeVector %19 4 + %21 = OpTypePointer Output %20 + %3 = OpVariable %21 Output + %2 = OpFunction %8 None %9 + %22 = OpLabel + %4 = OpVariable %11 Function + %5 = OpVariable %11 Function + %6 = OpVariable %11 Function + %7 = OpVariable %11 Function + OpStore %4 %12 + OpBranch %23 + %23 = OpLabel + OpLoopMerge %24 %25 None + OpBranch %26 + %26 = OpLabel + %27 = OpLoad %10 %4 + %28 = OpSLessThan %14 %27 %13 + OpBranchConditional %28 %29 %24 + %29 = OpLabel + OpStore %5 %12 + OpBranch %30 + %30 = OpLabel + OpLoopMerge %31 %32 None + OpBranch %33 + %33 = OpLabel + %34 = OpLoad %10 %5 + %35 = OpSLessThan %14 %34 %15 + OpBranchConditional %35 %36 %31 + %36 = OpLabel + %37 = OpLoad %10 %5 + %38 = OpSLessThan %14 %37 %16 + OpSelectionMerge %39 None + OpBranchConditional %38 %40 %39 + %40 = OpLabel + OpStore %6 %12 + OpBranch %41 + %41 = OpLabel + OpLoopMerge %42 %43 None + OpBranch %44 + %44 = OpLabel + %45 = OpLoad %10 %6 + %46 = OpSLessThan %14 %45 %17 + OpBranchConditional %46 %47 %42 + %47 = OpLabel + OpBranch %43 + %43 = OpLabel + %48 = OpLoad %10 %6 + %49 = OpIAdd %10 %48 %18 + OpStore %6 %49 + OpBranch %41 + %42 = OpLabel + OpBranch %39 + %39 = OpLabel + OpStore %7 %12 + OpBranch %50 + %50 = OpLabel + OpLoopMerge %51 %52 None + OpBranch %53 + %53 = OpLabel + %54 = OpLoad %10 %7 + %55 = OpSLessThan %14 %54 %17 + OpBranchConditional %55 %56 %51 + %56 = OpLabel + OpBranch %52 + %52 = OpLabel + %57 = OpLoad %10 %7 + %58 = OpIAdd %10 %57 %18 + OpStore %7 %58 + OpBranch %50 + %51 = OpLabel + OpBranch %32 + %32 = OpLabel + %59 = OpLoad %10 %5 + %60 = OpIAdd %10 %59 %18 + OpStore %5 %60 + OpBranch %30 + %31 = OpLabel + OpBranch %25 + %25 = OpLabel + %61 = OpLoad %10 %4 + %62 = OpIAdd %10 %61 %18 + OpStore %4 %62 + OpBranch %23 + %24 = OpLabel + OpReturn + OpFunctionEnd + )"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + const ir::Function* f = spvtest::GetFunction(module, 2); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(f); + + EXPECT_EQ(ld.NumLoops(), 4u); + + // Invalid basic block id. + EXPECT_EQ(ld[0u], nullptr); + // Not in a loop. + EXPECT_EQ(ld[22], nullptr); + + // Check that we can map basic block to the correct loop. + // The following block ids do not belong to a loop. + for (uint32_t bb_id : {22, 24}) EXPECT_EQ(ld[bb_id], nullptr); + + { + std::unordered_set basic_block_in_loop = { + {23, 26, 29, 30, 33, 36, 40, 41, 44, 47, 43, + 42, 39, 50, 53, 56, 52, 51, 32, 31, 25}}; + ir::Loop* loop = ld[23]; + CheckLoopBlocks(loop, &basic_block_in_loop); + + EXPECT_TRUE(loop->HasNestedLoops()); + EXPECT_FALSE(loop->IsNested()); + EXPECT_EQ(loop->GetDepth(), 1u); + EXPECT_EQ(std::distance(loop->begin(), loop->end()), 1u); + EXPECT_EQ(loop->GetPreHeaderBlock(), spvtest::GetBasicBlock(f, 22)); + EXPECT_EQ(loop->GetHeaderBlock(), spvtest::GetBasicBlock(f, 23)); + EXPECT_EQ(loop->GetLatchBlock(), spvtest::GetBasicBlock(f, 25)); + EXPECT_EQ(loop->GetMergeBlock(), spvtest::GetBasicBlock(f, 24)); + EXPECT_FALSE(loop->IsInsideLoop(loop->GetMergeBlock())); + EXPECT_FALSE(loop->IsInsideLoop(loop->GetPreHeaderBlock())); + } + + { + std::unordered_set basic_block_in_loop = { + {30, 33, 36, 40, 41, 44, 47, 43, 42, 39, 50, 53, 56, 52, 51, 32}}; + ir::Loop* loop = ld[30]; + CheckLoopBlocks(loop, &basic_block_in_loop); + + EXPECT_TRUE(loop->HasNestedLoops()); + EXPECT_TRUE(loop->IsNested()); + EXPECT_EQ(loop->GetDepth(), 2u); + EXPECT_EQ(std::distance(loop->begin(), loop->end()), 2u); + EXPECT_EQ(loop->GetPreHeaderBlock(), spvtest::GetBasicBlock(f, 29)); + EXPECT_EQ(loop->GetHeaderBlock(), spvtest::GetBasicBlock(f, 30)); + EXPECT_EQ(loop->GetLatchBlock(), spvtest::GetBasicBlock(f, 32)); + EXPECT_EQ(loop->GetMergeBlock(), spvtest::GetBasicBlock(f, 31)); + EXPECT_FALSE(loop->IsInsideLoop(loop->GetMergeBlock())); + EXPECT_FALSE(loop->IsInsideLoop(loop->GetPreHeaderBlock())); + } + + { + std::unordered_set basic_block_in_loop = {{41, 44, 47, 43}}; + ir::Loop* loop = ld[41]; + CheckLoopBlocks(loop, &basic_block_in_loop); + + EXPECT_FALSE(loop->HasNestedLoops()); + EXPECT_TRUE(loop->IsNested()); + EXPECT_EQ(loop->GetDepth(), 3u); + EXPECT_EQ(std::distance(loop->begin(), loop->end()), 0u); + EXPECT_EQ(loop->GetPreHeaderBlock(), spvtest::GetBasicBlock(f, 40)); + EXPECT_EQ(loop->GetHeaderBlock(), spvtest::GetBasicBlock(f, 41)); + EXPECT_EQ(loop->GetLatchBlock(), spvtest::GetBasicBlock(f, 43)); + EXPECT_EQ(loop->GetMergeBlock(), spvtest::GetBasicBlock(f, 42)); + EXPECT_FALSE(loop->IsInsideLoop(loop->GetMergeBlock())); + EXPECT_FALSE(loop->IsInsideLoop(loop->GetPreHeaderBlock())); + } + + { + std::unordered_set basic_block_in_loop = {{50, 53, 56, 52}}; + ir::Loop* loop = ld[50]; + CheckLoopBlocks(loop, &basic_block_in_loop); + + EXPECT_FALSE(loop->HasNestedLoops()); + EXPECT_TRUE(loop->IsNested()); + EXPECT_EQ(loop->GetDepth(), 3u); + EXPECT_EQ(std::distance(loop->begin(), loop->end()), 0u); + EXPECT_EQ(loop->GetPreHeaderBlock(), spvtest::GetBasicBlock(f, 39)); + EXPECT_EQ(loop->GetHeaderBlock(), spvtest::GetBasicBlock(f, 50)); + EXPECT_EQ(loop->GetLatchBlock(), spvtest::GetBasicBlock(f, 52)); + EXPECT_EQ(loop->GetMergeBlock(), spvtest::GetBasicBlock(f, 51)); + EXPECT_FALSE(loop->IsInsideLoop(loop->GetMergeBlock())); + EXPECT_FALSE(loop->IsInsideLoop(loop->GetPreHeaderBlock())); + } + + // Make sure LoopDescriptor gives us the inner most loop when we query for + // loops. + for (const ir::BasicBlock& bb : *f) { + if (ir::Loop* loop = ld[&bb]) { + for (ir::Loop& sub_loop : + ir::make_range(++opt::TreeDFIterator(loop), + opt::TreeDFIterator())) { + EXPECT_FALSE(sub_loop.IsInsideLoop(bb.id())); + } + } + } +} + +/* +Generated from the following GLSL +#version 330 core +layout(location = 0) out vec4 c; +void main() { + for (int i = 0; i < 10; ++i) { + for (int j = 0; j < 11; ++j) { + for (int k = 0; k < 11; ++k) {} + } + for (int k = 0; k < 12; ++k) {} + } +} +*/ +TEST_F(PassClassTest, LoopParentTest) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 330 + OpName %2 "main" + OpName %4 "i" + OpName %5 "j" + OpName %6 "k" + OpName %7 "k" + OpName %3 "c" + OpDecorate %3 Location 0 + %8 = OpTypeVoid + %9 = OpTypeFunction %8 + %10 = OpTypeInt 32 1 + %11 = OpTypePointer Function %10 + %12 = OpConstant %10 0 + %13 = OpConstant %10 10 + %14 = OpTypeBool + %15 = OpConstant %10 11 + %16 = OpConstant %10 1 + %17 = OpConstant %10 12 + %18 = OpTypeFloat 32 + %19 = OpTypeVector %18 4 + %20 = OpTypePointer Output %19 + %3 = OpVariable %20 Output + %2 = OpFunction %8 None %9 + %21 = OpLabel + %4 = OpVariable %11 Function + %5 = OpVariable %11 Function + %6 = OpVariable %11 Function + %7 = OpVariable %11 Function + OpStore %4 %12 + OpBranch %22 + %22 = OpLabel + OpLoopMerge %23 %24 None + OpBranch %25 + %25 = OpLabel + %26 = OpLoad %10 %4 + %27 = OpSLessThan %14 %26 %13 + OpBranchConditional %27 %28 %23 + %28 = OpLabel + OpStore %5 %12 + OpBranch %29 + %29 = OpLabel + OpLoopMerge %30 %31 None + OpBranch %32 + %32 = OpLabel + %33 = OpLoad %10 %5 + %34 = OpSLessThan %14 %33 %15 + OpBranchConditional %34 %35 %30 + %35 = OpLabel + OpStore %6 %12 + OpBranch %36 + %36 = OpLabel + OpLoopMerge %37 %38 None + OpBranch %39 + %39 = OpLabel + %40 = OpLoad %10 %6 + %41 = OpSLessThan %14 %40 %15 + OpBranchConditional %41 %42 %37 + %42 = OpLabel + OpBranch %38 + %38 = OpLabel + %43 = OpLoad %10 %6 + %44 = OpIAdd %10 %43 %16 + OpStore %6 %44 + OpBranch %36 + %37 = OpLabel + OpBranch %31 + %31 = OpLabel + %45 = OpLoad %10 %5 + %46 = OpIAdd %10 %45 %16 + OpStore %5 %46 + OpBranch %29 + %30 = OpLabel + OpStore %7 %12 + OpBranch %47 + %47 = OpLabel + OpLoopMerge %48 %49 None + OpBranch %50 + %50 = OpLabel + %51 = OpLoad %10 %7 + %52 = OpSLessThan %14 %51 %17 + OpBranchConditional %52 %53 %48 + %53 = OpLabel + OpBranch %49 + %49 = OpLabel + %54 = OpLoad %10 %7 + %55 = OpIAdd %10 %54 %16 + OpStore %7 %55 + OpBranch %47 + %48 = OpLabel + OpBranch %24 + %24 = OpLabel + %56 = OpLoad %10 %4 + %57 = OpIAdd %10 %56 %16 + OpStore %4 %57 + OpBranch %22 + %23 = OpLabel + OpReturn + OpFunctionEnd + )"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + const ir::Function* f = spvtest::GetFunction(module, 2); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(f); + + EXPECT_EQ(ld.NumLoops(), 4u); + + { + ir::Loop& loop = *ld[22]; + EXPECT_TRUE(loop.HasNestedLoops()); + EXPECT_FALSE(loop.IsNested()); + EXPECT_EQ(loop.GetDepth(), 1u); + EXPECT_EQ(loop.GetParent(), nullptr); + } + + { + ir::Loop& loop = *ld[29]; + EXPECT_TRUE(loop.HasNestedLoops()); + EXPECT_TRUE(loop.IsNested()); + EXPECT_EQ(loop.GetDepth(), 2u); + EXPECT_EQ(loop.GetParent(), ld[22]); + } + + { + ir::Loop& loop = *ld[36]; + EXPECT_FALSE(loop.HasNestedLoops()); + EXPECT_TRUE(loop.IsNested()); + EXPECT_EQ(loop.GetDepth(), 3u); + EXPECT_EQ(loop.GetParent(), ld[29]); + } + + { + ir::Loop& loop = *ld[47]; + EXPECT_FALSE(loop.HasNestedLoops()); + EXPECT_TRUE(loop.IsNested()); + EXPECT_EQ(loop.GetDepth(), 2u); + EXPECT_EQ(loop.GetParent(), ld[22]); + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store +The preheader of loop %33 and %41 were removed as well. + +#version 330 core +void main() { + int a = 0; + for (int i = 0; i < 10; ++i) { + if (i == 0) { + a = 1; + } else { + a = 2; + } + for (int j = 0; j < 11; ++j) { + a++; + } + } + for (int k = 0; k < 12; ++k) {} +} +*/ +TEST_F(PassClassTest, CreatePreheaderTest) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 330 + OpName %2 "main" + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeInt 32 1 + %6 = OpTypePointer Function %5 + %7 = OpConstant %5 0 + %8 = OpConstant %5 10 + %9 = OpTypeBool + %10 = OpConstant %5 1 + %11 = OpConstant %5 2 + %12 = OpConstant %5 11 + %13 = OpConstant %5 12 + %14 = OpUndef %5 + %2 = OpFunction %3 None %4 + %15 = OpLabel + OpBranch %16 + %16 = OpLabel + %17 = OpPhi %5 %7 %15 %18 %19 + %20 = OpPhi %5 %7 %15 %21 %19 + %22 = OpPhi %5 %14 %15 %23 %19 + OpLoopMerge %41 %19 None + OpBranch %25 + %25 = OpLabel + %26 = OpSLessThan %9 %20 %8 + OpBranchConditional %26 %27 %41 + %27 = OpLabel + %28 = OpIEqual %9 %20 %7 + OpSelectionMerge %33 None + OpBranchConditional %28 %30 %31 + %30 = OpLabel + OpBranch %33 + %31 = OpLabel + OpBranch %33 + %33 = OpLabel + %18 = OpPhi %5 %10 %30 %11 %31 %34 %35 + %23 = OpPhi %5 %7 %30 %7 %31 %36 %35 + OpLoopMerge %37 %35 None + OpBranch %38 + %38 = OpLabel + %39 = OpSLessThan %9 %23 %12 + OpBranchConditional %39 %40 %37 + %40 = OpLabel + %34 = OpIAdd %5 %18 %10 + OpBranch %35 + %35 = OpLabel + %36 = OpIAdd %5 %23 %10 + OpBranch %33 + %37 = OpLabel + OpBranch %19 + %19 = OpLabel + %21 = OpIAdd %5 %20 %10 + OpBranch %16 + %41 = OpLabel + %42 = OpPhi %5 %7 %25 %43 %44 + OpLoopMerge %45 %44 None + OpBranch %46 + %46 = OpLabel + %47 = OpSLessThan %9 %42 %13 + OpBranchConditional %47 %48 %45 + %48 = OpLabel + OpBranch %44 + %44 = OpLabel + %43 = OpIAdd %5 %42 %10 + OpBranch %41 + %45 = OpLabel + OpReturn + OpFunctionEnd + )"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + const ir::Function* f = spvtest::GetFunction(module, 2); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(f); + // No invalidation of the cfg should occur during this test. + ir::CFG* cfg = context->cfg(); + + EXPECT_EQ(ld.NumLoops(), 3u); + + { + ir::Loop& loop = *ld[16]; + EXPECT_TRUE(loop.HasNestedLoops()); + EXPECT_FALSE(loop.IsNested()); + EXPECT_EQ(loop.GetDepth(), 1u); + EXPECT_EQ(loop.GetParent(), nullptr); + } + + { + ir::Loop& loop = *ld[33]; + EXPECT_EQ(loop.GetPreHeaderBlock(), nullptr); + EXPECT_NE(loop.GetOrCreatePreHeaderBlock(), nullptr); + // Make sure the loop descriptor was properly updated. + EXPECT_EQ(ld[loop.GetPreHeaderBlock()], ld[16]); + { + const std::vector& preds = + cfg->preds(loop.GetPreHeaderBlock()->id()); + std::unordered_set pred_set(preds.begin(), preds.end()); + EXPECT_EQ(pred_set.size(), 2u); + EXPECT_TRUE(pred_set.count(30)); + EXPECT_TRUE(pred_set.count(31)); + // Check the phi instructions. + loop.GetPreHeaderBlock()->ForEachPhiInst( + [&pred_set](ir::Instruction* phi) { + for (uint32_t i = 1; i < phi->NumInOperands(); i += 2) { + EXPECT_TRUE(pred_set.count(phi->GetSingleWordInOperand(i))); + } + }); + } + { + const std::vector& preds = + cfg->preds(loop.GetHeaderBlock()->id()); + std::unordered_set pred_set(preds.begin(), preds.end()); + EXPECT_EQ(pred_set.size(), 2u); + EXPECT_TRUE(pred_set.count(loop.GetPreHeaderBlock()->id())); + EXPECT_TRUE(pred_set.count(35)); + // Check the phi instructions. + loop.GetHeaderBlock()->ForEachPhiInst([&pred_set](ir::Instruction* phi) { + for (uint32_t i = 1; i < phi->NumInOperands(); i += 2) { + EXPECT_TRUE(pred_set.count(phi->GetSingleWordInOperand(i))); + } + }); + } + } + + { + ir::Loop& loop = *ld[41]; + EXPECT_EQ(loop.GetPreHeaderBlock(), nullptr); + EXPECT_NE(loop.GetOrCreatePreHeaderBlock(), nullptr); + EXPECT_EQ(ld[loop.GetPreHeaderBlock()], nullptr); + EXPECT_EQ(cfg->preds(loop.GetPreHeaderBlock()->id()).size(), 1u); + EXPECT_EQ(cfg->preds(loop.GetPreHeaderBlock()->id())[0], 25u); + // Check the phi instructions. + loop.GetPreHeaderBlock()->ForEachPhiInst([](ir::Instruction* phi) { + EXPECT_EQ(phi->NumInOperands(), 2u); + EXPECT_EQ(phi->GetSingleWordInOperand(1), 25u); + }); + { + const std::vector& preds = + cfg->preds(loop.GetHeaderBlock()->id()); + std::unordered_set pred_set(preds.begin(), preds.end()); + EXPECT_EQ(pred_set.size(), 2u); + EXPECT_TRUE(pred_set.count(loop.GetPreHeaderBlock()->id())); + EXPECT_TRUE(pred_set.count(44)); + // Check the phi instructions. + loop.GetHeaderBlock()->ForEachPhiInst([&pred_set](ir::Instruction* phi) { + for (uint32_t i = 1; i < phi->NumInOperands(); i += 2) { + EXPECT_TRUE(pred_set.count(phi->GetSingleWordInOperand(i))); + } + }); + } + } + + // Make sure pre-header insertion leaves the module valid. + std::vector bin; + context->module()->ToBinary(&bin, true); + EXPECT_TRUE(Validate(bin)); +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/loop_optimizations/peeling.cpp b/3rdparty/spirv-tools/test/opt/loop_optimizations/peeling.cpp new file mode 100644 index 000000000..a88e8dd35 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/loop_optimizations/peeling.cpp @@ -0,0 +1,1075 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#ifdef SPIRV_EFFCEE +#include "effcee/effcee.h" +#endif + +#include "../pass_fixture.h" +#include "opt/ir_builder.h" +#include "opt/loop_descriptor.h" +#include "opt/loop_peeling.h" + +namespace { + +using namespace spvtools; + +using PeelingTest = PassTest<::testing::Test>; + +bool Validate(const std::vector& bin) { + spv_target_env target_env = SPV_ENV_UNIVERSAL_1_2; + spv_context spvContext = spvContextCreate(target_env); + spv_diagnostic diagnostic = nullptr; + spv_const_binary_t binary = {bin.data(), bin.size()}; + spv_result_t error = spvValidate(spvContext, &binary, &diagnostic); + if (error != 0) spvDiagnosticPrint(diagnostic); + spvDiagnosticDestroy(diagnostic); + spvContextDestroy(spvContext); + return error == 0; +} + +void Match(const std::string& checks, ir::IRContext* context) { + std::vector bin; + context->module()->ToBinary(&bin, true); + EXPECT_TRUE(Validate(bin)); +#ifdef SPIRV_EFFCEE + std::string assembly; + SpirvTools tools(SPV_ENV_UNIVERSAL_1_2); + EXPECT_TRUE( + tools.Disassemble(bin, &assembly, SPV_BINARY_TO_TEXT_OPTION_NO_HEADER)) + << "Disassembling failed for shader:\n" + << assembly << std::endl; + auto match_result = effcee::Match(assembly, checks); + EXPECT_EQ(effcee::Result::Status::Ok, match_result.status()) + << match_result.message() << "\nChecking result:\n" + << assembly; +#endif // ! SPIRV_EFFCEE +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +First test: +#version 330 core +void main() { + for(int i = 0; i < 10; ++i) { + if (i < 4) + break; + } +} + +Second test (with a common sub-expression elimination): +#version 330 core +void main() { + for(int i = 0; i + 1 < 10; ++i) { + } +} + +Third test: +#version 330 core +void main() { + int a[10]; + for (int i = 0; a[i] != 0; i++) {} +} + +Forth test: +#version 330 core +void main() { + for (long i = 0; i < 10; i++) {} +} + +Fifth test: +#version 330 core +void main() { + for (float i = 0; i < 10; i++) {} +} + +Sixth test: +#version 450 +layout(location = 0)out float o; +void main() { + o = 0.0; + for( int i = 0; true; i++ ) { + o += 1.0; + if (i > 10) break; + } +} +*/ +TEST_F(PeelingTest, CannotPeel) { + // Build the given SPIR-V program in |text|, take the first loop in the first + // function and test that it is not peelable. |loop_count_id| is the id + // representing the loop count, if equals to 0, then the function build a 10 + // constant as loop count. + auto test_cannot_peel = [](const std::string& text, uint32_t loop_count_id) { + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + + EXPECT_EQ(ld.NumLoops(), 1u); + + ir::Instruction* loop_count = nullptr; + if (loop_count_id) { + loop_count = context->get_def_use_mgr()->GetDef(loop_count_id); + } else { + opt::InstructionBuilder builder(context.get(), &*f.begin()); + // Exit condition. + loop_count = builder.Add32BitSignedIntegerConstant(10); + } + + opt::LoopPeeling peel(context.get(), &*ld.begin(), loop_count); + EXPECT_FALSE(peel.CanPeelLoop()); + }; + { + SCOPED_TRACE("loop with break"); + + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" + OpExecutionMode %main OriginLowerLeft + OpSource GLSL 330 + OpName %main "main" + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int + %int_0 = OpConstant %int 0 + %int_10 = OpConstant %int 10 + %bool = OpTypeBool + %int_4 = OpConstant %int 4 + %int_1 = OpConstant %int 1 + %main = OpFunction %void None %3 + %5 = OpLabel + OpBranch %10 + %10 = OpLabel + %28 = OpPhi %int %int_0 %5 %27 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %bool %28 %int_10 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %21 = OpSLessThan %bool %28 %int_4 + OpSelectionMerge %23 None + OpBranchConditional %21 %22 %23 + %22 = OpLabel + OpBranch %12 + %23 = OpLabel + OpBranch %13 + %13 = OpLabel + %27 = OpIAdd %int %28 %int_1 + OpBranch %10 + %12 = OpLabel + OpReturn + OpFunctionEnd + )"; + test_cannot_peel(text, 0); + } + + { + SCOPED_TRACE("Ambiguous iterator update"); + + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" + OpExecutionMode %main OriginLowerLeft + OpSource GLSL 330 + OpName %main "main" + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int + %int_0 = OpConstant %int 0 + %int_1 = OpConstant %int 1 + %int_10 = OpConstant %int 10 + %bool = OpTypeBool + %main = OpFunction %void None %3 + %5 = OpLabel + OpBranch %10 + %10 = OpLabel + %23 = OpPhi %int %int_0 %5 %17 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %17 = OpIAdd %int %23 %int_1 + %20 = OpSLessThan %bool %17 %int_10 + OpBranchConditional %20 %11 %12 + %11 = OpLabel + OpBranch %13 + %13 = OpLabel + OpBranch %10 + %12 = OpLabel + OpReturn + OpFunctionEnd + )"; + + test_cannot_peel(text, 0); + } + + { + SCOPED_TRACE("No loop static bounds"); + + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" + OpExecutionMode %main OriginLowerLeft + OpSource GLSL 330 + OpName %main "main" + OpName %i "i" + OpName %a "a" + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int + %int_0 = OpConstant %int 0 + %uint = OpTypeInt 32 0 + %uint_10 = OpConstant %uint 10 +%_arr_int_uint_10 = OpTypeArray %int %uint_10 +%_ptr_Function__arr_int_uint_10 = OpTypePointer Function %_arr_int_uint_10 + %bool = OpTypeBool + %int_1 = OpConstant %int 1 + %main = OpFunction %void None %3 + %5 = OpLabel + %i = OpVariable %_ptr_Function_int Function + %a = OpVariable %_ptr_Function__arr_int_uint_10 Function + OpStore %i %int_0 + OpBranch %10 + %10 = OpLabel + %28 = OpPhi %int %int_0 %5 %27 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %21 = OpAccessChain %_ptr_Function_int %a %28 + %22 = OpLoad %int %21 + %24 = OpINotEqual %bool %22 %int_0 + OpBranchConditional %24 %11 %12 + %11 = OpLabel + OpBranch %13 + %13 = OpLabel + %27 = OpIAdd %int %28 %int_1 + OpStore %i %27 + OpBranch %10 + %12 = OpLabel + OpReturn + OpFunctionEnd + )"; + + test_cannot_peel(text, 22); + } + { + SCOPED_TRACE("Int 64 type for conditions"); + + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginLowerLeft + OpSource GLSL 330 + OpName %2 "main" + OpName %4 "i" + %6 = OpTypeVoid + %3 = OpTypeFunction %6 + %7 = OpTypeInt 64 1 + %8 = OpTypePointer Function %7 + %9 = OpConstant %7 0 + %15 = OpConstant %7 10 + %16 = OpTypeBool + %17 = OpConstant %7 1 + %2 = OpFunction %6 None %3 + %5 = OpLabel + %4 = OpVariable %8 Function + OpStore %4 %9 + OpBranch %10 + %10 = OpLabel + %22 = OpPhi %7 %9 %5 %21 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %16 %22 %15 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpBranch %13 + %13 = OpLabel + %21 = OpIAdd %7 %22 %17 + OpStore %4 %21 + OpBranch %10 + %12 = OpLabel + OpReturn + OpFunctionEnd + )"; + // %15 is a constant for a 64 int. Currently rejected. + test_cannot_peel(text, 15); + } + { + SCOPED_TRACE("Float type for conditions"); + + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginLowerLeft + OpSource GLSL 330 + OpName %2 "main" + OpName %4 "i" + %6 = OpTypeVoid + %3 = OpTypeFunction %6 + %7 = OpTypeFloat 32 + %8 = OpTypePointer Function %7 + %9 = OpConstant %7 0 + %15 = OpConstant %7 10 + %16 = OpTypeBool + %17 = OpConstant %7 1 + %2 = OpFunction %6 None %3 + %5 = OpLabel + %4 = OpVariable %8 Function + OpStore %4 %9 + OpBranch %10 + %10 = OpLabel + %22 = OpPhi %7 %9 %5 %21 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpFOrdLessThan %16 %22 %15 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpBranch %13 + %13 = OpLabel + %21 = OpFAdd %7 %22 %17 + OpStore %4 %21 + OpBranch %10 + %12 = OpLabel + OpReturn + OpFunctionEnd + )"; + // %15 is a constant for a float. Currently rejected. + test_cannot_peel(text, 15); + } + { + SCOPED_TRACE("Side effect before exit"); + + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %o + OpExecutionMode %main OriginLowerLeft + OpSource GLSL 450 + OpName %main "main" + OpName %o "o" + OpName %i "i" + OpDecorate %o Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 +%_ptr_Output_float = OpTypePointer Output %float + %o = OpVariable %_ptr_Output_float Output + %float_0 = OpConstant %float 0 + %int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int + %int_0 = OpConstant %int 0 + %bool = OpTypeBool + %true = OpConstantTrue %bool + %float_1 = OpConstant %float 1 + %int_10 = OpConstant %int 10 + %int_1 = OpConstant %int 1 + %main = OpFunction %void None %3 + %5 = OpLabel + %i = OpVariable %_ptr_Function_int Function + OpStore %o %float_0 + OpStore %i %int_0 + OpBranch %14 + %14 = OpLabel + %33 = OpPhi %int %int_0 %5 %32 %17 + OpLoopMerge %16 %17 None + OpBranch %15 + %15 = OpLabel + %22 = OpLoad %float %o + %23 = OpFAdd %float %22 %float_1 + OpStore %o %23 + %26 = OpSGreaterThan %bool %33 %int_10 + OpSelectionMerge %28 None + OpBranchConditional %26 %27 %28 + %27 = OpLabel + OpBranch %16 + %28 = OpLabel + OpBranch %17 + %17 = OpLabel + %32 = OpIAdd %int %33 %int_1 + OpStore %i %32 + OpBranch %14 + %16 = OpLabel + OpReturn + OpFunctionEnd + )"; + test_cannot_peel(text, 0); + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 330 core +void main() { + int i = 0; + for (; i < 10; i++) {} +} +*/ +TEST_F(PeelingTest, SimplePeeling) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" + OpExecutionMode %main OriginLowerLeft + OpSource GLSL 330 + OpName %main "main" + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int + %int_0 = OpConstant %int 0 + %int_10 = OpConstant %int 10 + %bool = OpTypeBool + %int_1 = OpConstant %int 1 + %main = OpFunction %void None %3 + %5 = OpLabel + OpBranch %10 + %10 = OpLabel + %22 = OpPhi %int %int_0 %5 %21 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %bool %22 %int_10 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpBranch %13 + %13 = OpLabel + %21 = OpIAdd %int %22 %int_1 + OpBranch %10 + %12 = OpLabel + OpReturn + OpFunctionEnd + )"; + + // Peel before. + { + SCOPED_TRACE("Peel before"); + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + + EXPECT_EQ(ld.NumLoops(), 1u); + + opt::InstructionBuilder builder(context.get(), &*f.begin()); + // Exit condition. + ir::Instruction* ten_cst = builder.Add32BitSignedIntegerConstant(10); + + opt::LoopPeeling peel(context.get(), &*ld.begin(), ten_cst); + EXPECT_TRUE(peel.CanPeelLoop()); + peel.PeelBefore(2); + + const std::string check = R"( +CHECK: [[CST_TEN:%\w+]] = OpConstant {{%\w+}} 10 +CHECK: [[CST_TWO:%\w+]] = OpConstant {{%\w+}} 2 +CHECK: OpFunction +CHECK-NEXT: [[ENTRY:%\w+]] = OpLabel +CHECK: [[MIN_LOOP_COUNT:%\w+]] = OpSLessThan {{%\w+}} [[CST_TWO]] [[CST_TEN]] +CHECK-NEXT: [[LOOP_COUNT:%\w+]] = OpSelect {{%\w+}} [[MIN_LOOP_COUNT]] [[CST_TWO]] [[CST_TEN]] +CHECK: [[BEFORE_LOOP:%\w+]] = OpLabel +CHECK-NEXT: [[DUMMY_IT:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[DUMMY_IT_1:%\w+]] [[BE:%\w+]] +CHECK-NEXT: [[i:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[I_1:%\w+]] [[BE]] +CHECK-NEXT: OpLoopMerge [[AFTER_LOOP_PREHEADER:%\w+]] [[BE]] None +CHECK: [[COND_BLOCK:%\w+]] = OpLabel +CHECK-NEXT: OpSLessThan +CHECK-NEXT: [[EXIT_COND:%\w+]] = OpSLessThan {{%\w+}} [[DUMMY_IT]] +CHECK-NEXT: OpBranchConditional [[EXIT_COND]] {{%\w+}} [[AFTER_LOOP_PREHEADER]] +CHECK: [[I_1]] = OpIAdd {{%\w+}} [[i]] +CHECK-NEXT: [[DUMMY_IT_1]] = OpIAdd {{%\w+}} [[DUMMY_IT]] +CHECK-NEXT: OpBranch [[BEFORE_LOOP]] + +CHECK: [[AFTER_LOOP_PREHEADER]] = OpLabel +CHECK-NEXT: OpSelectionMerge [[IF_MERGE:%\w+]] +CHECK-NEXT: OpBranchConditional [[MIN_LOOP_COUNT]] [[AFTER_LOOP:%\w+]] [[IF_MERGE]] + +CHECK: [[AFTER_LOOP]] = OpLabel +CHECK-NEXT: OpPhi {{%\w+}} {{%\w+}} {{%\w+}} [[i]] [[AFTER_LOOP_PREHEADER]] +CHECK-NEXT: OpLoopMerge +)"; + + Match(check, context.get()); + } + + // Peel after. + { + SCOPED_TRACE("Peel after"); + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + + EXPECT_EQ(ld.NumLoops(), 1u); + + opt::InstructionBuilder builder(context.get(), &*f.begin()); + // Exit condition. + ir::Instruction* ten_cst = builder.Add32BitSignedIntegerConstant(10); + + opt::LoopPeeling peel(context.get(), &*ld.begin(), ten_cst); + EXPECT_TRUE(peel.CanPeelLoop()); + peel.PeelAfter(2); + + const std::string check = R"( +CHECK: OpFunction +CHECK-NEXT: [[ENTRY:%\w+]] = OpLabel +CHECK: [[MIN_LOOP_COUNT:%\w+]] = OpSLessThan {{%\w+}} +CHECK-NEXT: OpSelectionMerge [[IF_MERGE:%\w+]] +CHECK-NEXT: OpBranchConditional [[MIN_LOOP_COUNT]] [[BEFORE_LOOP:%\w+]] [[IF_MERGE]] +CHECK: [[BEFORE_LOOP]] = OpLabel +CHECK-NEXT: [[DUMMY_IT:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[DUMMY_IT_1:%\w+]] [[BE:%\w+]] +CHECK-NEXT: [[I:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[I_1:%\w+]] [[BE]] +CHECK-NEXT: OpLoopMerge [[BEFORE_LOOP_MERGE:%\w+]] [[BE]] None +CHECK: [[COND_BLOCK:%\w+]] = OpLabel +CHECK-NEXT: OpSLessThan +CHECK-NEXT: [[TMP:%\w+]] = OpIAdd {{%\w+}} [[DUMMY_IT]] {{%\w+}} +CHECK-NEXT: [[EXIT_COND:%\w+]] = OpSLessThan {{%\w+}} [[TMP]] +CHECK-NEXT: OpBranchConditional [[EXIT_COND]] {{%\w+}} [[BEFORE_LOOP_MERGE]] +CHECK: [[I_1]] = OpIAdd {{%\w+}} [[I]] +CHECK-NEXT: [[DUMMY_IT_1]] = OpIAdd {{%\w+}} [[DUMMY_IT]] +CHECK-NEXT: OpBranch [[BEFORE_LOOP]] + +CHECK: [[IF_MERGE]] = OpLabel +CHECK-NEXT: [[TMP:%\w+]] = OpPhi {{%\w+}} [[I]] [[BEFORE_LOOP_MERGE]] +CHECK-NEXT: OpBranch [[AFTER_LOOP:%\w+]] + +CHECK: [[AFTER_LOOP]] = OpLabel +CHECK-NEXT: OpPhi {{%\w+}} {{%\w+}} {{%\w+}} [[TMP]] [[IF_MERGE]] +CHECK-NEXT: OpLoopMerge + +)"; + + Match(check, context.get()); + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 330 core +void main() { + int a[10]; + int n = a[0]; + for(int i = 0; i < n; ++i) {} +} +*/ +TEST_F(PeelingTest, PeelingUncountable) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" + OpExecutionMode %main OriginLowerLeft + OpSource GLSL 330 + OpName %main "main" + OpName %a "a" + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int + %uint = OpTypeInt 32 0 + %uint_10 = OpConstant %uint 10 +%_arr_int_uint_10 = OpTypeArray %int %uint_10 +%_ptr_Function__arr_int_uint_10 = OpTypePointer Function %_arr_int_uint_10 + %int_0 = OpConstant %int 0 + %bool = OpTypeBool + %int_1 = OpConstant %int 1 + %main = OpFunction %void None %3 + %5 = OpLabel + %a = OpVariable %_ptr_Function__arr_int_uint_10 Function + %15 = OpAccessChain %_ptr_Function_int %a %int_0 + %16 = OpLoad %int %15 + OpBranch %18 + %18 = OpLabel + %30 = OpPhi %int %int_0 %5 %29 %21 + OpLoopMerge %20 %21 None + OpBranch %22 + %22 = OpLabel + %26 = OpSLessThan %bool %30 %16 + OpBranchConditional %26 %19 %20 + %19 = OpLabel + OpBranch %21 + %21 = OpLabel + %29 = OpIAdd %int %30 %int_1 + OpBranch %18 + %20 = OpLabel + OpReturn + OpFunctionEnd + )"; + + // Peel before. + { + SCOPED_TRACE("Peel before"); + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + + EXPECT_EQ(ld.NumLoops(), 1u); + + ir::Instruction* loop_count = context->get_def_use_mgr()->GetDef(16); + EXPECT_EQ(loop_count->opcode(), SpvOpLoad); + + opt::LoopPeeling peel(context.get(), &*ld.begin(), loop_count); + EXPECT_TRUE(peel.CanPeelLoop()); + peel.PeelBefore(1); + + const std::string check = R"( +CHECK: OpFunction +CHECK-NEXT: [[ENTRY:%\w+]] = OpLabel +CHECK: [[LOOP_COUNT:%\w+]] = OpLoad +CHECK: [[MIN_LOOP_COUNT:%\w+]] = OpSLessThan {{%\w+}} {{%\w+}} [[LOOP_COUNT]] +CHECK-NEXT: [[LOOP_COUNT:%\w+]] = OpSelect {{%\w+}} [[MIN_LOOP_COUNT]] {{%\w+}} [[LOOP_COUNT]] +CHECK: [[BEFORE_LOOP:%\w+]] = OpLabel +CHECK-NEXT: [[DUMMY_IT:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[DUMMY_IT_1:%\w+]] [[BE:%\w+]] +CHECK-NEXT: [[i:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[I_1:%\w+]] [[BE]] +CHECK-NEXT: OpLoopMerge [[AFTER_LOOP_PREHEADER:%\w+]] [[BE]] None +CHECK: [[COND_BLOCK:%\w+]] = OpLabel +CHECK-NEXT: OpSLessThan +CHECK-NEXT: [[EXIT_COND:%\w+]] = OpSLessThan {{%\w+}} [[DUMMY_IT]] +CHECK-NEXT: OpBranchConditional [[EXIT_COND]] {{%\w+}} [[AFTER_LOOP_PREHEADER]] +CHECK: [[I_1]] = OpIAdd {{%\w+}} [[i]] +CHECK-NEXT: [[DUMMY_IT_1]] = OpIAdd {{%\w+}} [[DUMMY_IT]] +CHECK-NEXT: OpBranch [[BEFORE_LOOP]] + +CHECK: [[AFTER_LOOP_PREHEADER]] = OpLabel +CHECK-NEXT: OpSelectionMerge [[IF_MERGE:%\w+]] +CHECK-NEXT: OpBranchConditional [[MIN_LOOP_COUNT]] [[AFTER_LOOP:%\w+]] [[IF_MERGE]] + +CHECK: [[AFTER_LOOP]] = OpLabel +CHECK-NEXT: OpPhi {{%\w+}} {{%\w+}} {{%\w+}} [[i]] [[AFTER_LOOP_PREHEADER]] +CHECK-NEXT: OpLoopMerge +)"; + + Match(check, context.get()); + } + + // Peel after. + { + SCOPED_TRACE("Peel after"); + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + + EXPECT_EQ(ld.NumLoops(), 1u); + + ir::Instruction* loop_count = context->get_def_use_mgr()->GetDef(16); + EXPECT_EQ(loop_count->opcode(), SpvOpLoad); + + opt::LoopPeeling peel(context.get(), &*ld.begin(), loop_count); + EXPECT_TRUE(peel.CanPeelLoop()); + peel.PeelAfter(1); + + const std::string check = R"( +CHECK: OpFunction +CHECK-NEXT: [[ENTRY:%\w+]] = OpLabel +CHECK: [[MIN_LOOP_COUNT:%\w+]] = OpSLessThan {{%\w+}} +CHECK-NEXT: OpSelectionMerge [[IF_MERGE:%\w+]] +CHECK-NEXT: OpBranchConditional [[MIN_LOOP_COUNT]] [[BEFORE_LOOP:%\w+]] [[IF_MERGE]] +CHECK: [[BEFORE_LOOP]] = OpLabel +CHECK-NEXT: [[DUMMY_IT:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[DUMMY_IT_1:%\w+]] [[BE:%\w+]] +CHECK-NEXT: [[I:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[I_1:%\w+]] [[BE]] +CHECK-NEXT: OpLoopMerge [[BEFORE_LOOP_MERGE:%\w+]] [[BE]] None +CHECK: [[COND_BLOCK:%\w+]] = OpLabel +CHECK-NEXT: OpSLessThan +CHECK-NEXT: [[TMP:%\w+]] = OpIAdd {{%\w+}} [[DUMMY_IT]] {{%\w+}} +CHECK-NEXT: [[EXIT_COND:%\w+]] = OpSLessThan {{%\w+}} [[TMP]] +CHECK-NEXT: OpBranchConditional [[EXIT_COND]] {{%\w+}} [[BEFORE_LOOP_MERGE]] +CHECK: [[I_1]] = OpIAdd {{%\w+}} [[I]] +CHECK-NEXT: [[DUMMY_IT_1]] = OpIAdd {{%\w+}} [[DUMMY_IT]] +CHECK-NEXT: OpBranch [[BEFORE_LOOP]] + +CHECK: [[IF_MERGE]] = OpLabel +CHECK-NEXT: [[TMP:%\w+]] = OpPhi {{%\w+}} [[I]] [[BEFORE_LOOP_MERGE]] +CHECK-NEXT: OpBranch [[AFTER_LOOP:%\w+]] + +CHECK: [[AFTER_LOOP]] = OpLabel +CHECK-NEXT: OpPhi {{%\w+}} {{%\w+}} {{%\w+}} [[TMP]] [[IF_MERGE]] +CHECK-NEXT: OpLoopMerge + +)"; + + Match(check, context.get()); + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 330 core +void main() { + int i = 0; + do { + i++; + } while (i < 10); +} +*/ +TEST_F(PeelingTest, DoWhilePeeling) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" + OpExecutionMode %main OriginLowerLeft + OpSource GLSL 330 + OpName %main "main" + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int + %int_0 = OpConstant %int 0 + %int_1 = OpConstant %int 1 + %int_10 = OpConstant %int 10 + %bool = OpTypeBool + %main = OpFunction %void None %3 + %5 = OpLabel + OpBranch %10 + %10 = OpLabel + %21 = OpPhi %int %int_0 %5 %16 %13 + OpLoopMerge %12 %13 None + OpBranch %11 + %11 = OpLabel + %16 = OpIAdd %int %21 %int_1 + OpBranch %13 + %13 = OpLabel + %20 = OpSLessThan %bool %16 %int_10 + OpBranchConditional %20 %10 %12 + %12 = OpLabel + OpReturn + OpFunctionEnd + )"; + + // Peel before. + { + SCOPED_TRACE("Peel before"); + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + + EXPECT_EQ(ld.NumLoops(), 1u); + opt::InstructionBuilder builder(context.get(), &*f.begin()); + // Exit condition. + ir::Instruction* ten_cst = builder.Add32BitUnsignedIntegerConstant(10); + + opt::LoopPeeling peel(context.get(), &*ld.begin(), ten_cst); + EXPECT_TRUE(peel.CanPeelLoop()); + peel.PeelBefore(2); + + const std::string check = R"( +CHECK: OpFunction +CHECK-NEXT: [[ENTRY:%\w+]] = OpLabel +CHECK: [[MIN_LOOP_COUNT:%\w+]] = OpULessThan {{%\w+}} +CHECK-NEXT: [[LOOP_COUNT:%\w+]] = OpSelect {{%\w+}} [[MIN_LOOP_COUNT]] +CHECK: [[BEFORE_LOOP:%\w+]] = OpLabel +CHECK-NEXT: [[DUMMY_IT:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[DUMMY_IT_1:%\w+]] [[BE:%\w+]] +CHECK-NEXT: [[i:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[I_1:%\w+]] [[BE]] +CHECK-NEXT: OpLoopMerge [[AFTER_LOOP_PREHEADER:%\w+]] [[BE]] None +CHECK: [[I_1]] = OpIAdd {{%\w+}} [[i]] +CHECK: [[BE]] = OpLabel +CHECK: [[DUMMY_IT_1]] = OpIAdd {{%\w+}} [[DUMMY_IT]] +CHECK-NEXT: [[EXIT_COND:%\w+]] = OpULessThan {{%\w+}} [[DUMMY_IT_1]] +CHECK-NEXT: OpBranchConditional [[EXIT_COND]] [[BEFORE_LOOP]] [[AFTER_LOOP_PREHEADER]] + +CHECK: [[AFTER_LOOP_PREHEADER]] = OpLabel +CHECK-NEXT: OpSelectionMerge [[IF_MERGE:%\w+]] +CHECK-NEXT: OpBranchConditional [[MIN_LOOP_COUNT]] [[AFTER_LOOP:%\w+]] [[IF_MERGE]] + +CHECK: [[AFTER_LOOP]] = OpLabel +CHECK-NEXT: OpPhi {{%\w+}} {{%\w+}} {{%\w+}} [[I_1]] [[AFTER_LOOP_PREHEADER]] +CHECK-NEXT: OpLoopMerge +)"; + + Match(check, context.get()); + } + + // Peel after. + { + SCOPED_TRACE("Peel after"); + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + + EXPECT_EQ(ld.NumLoops(), 1u); + + opt::InstructionBuilder builder(context.get(), &*f.begin()); + // Exit condition. + ir::Instruction* ten_cst = builder.Add32BitUnsignedIntegerConstant(10); + + opt::LoopPeeling peel(context.get(), &*ld.begin(), ten_cst); + EXPECT_TRUE(peel.CanPeelLoop()); + peel.PeelAfter(2); + + const std::string check = R"( +CHECK: OpFunction +CHECK-NEXT: [[ENTRY:%\w+]] = OpLabel +CHECK: [[MIN_LOOP_COUNT:%\w+]] = OpULessThan {{%\w+}} +CHECK-NEXT: OpSelectionMerge [[IF_MERGE:%\w+]] +CHECK-NEXT: OpBranchConditional [[MIN_LOOP_COUNT]] [[BEFORE_LOOP:%\w+]] [[IF_MERGE]] +CHECK: [[BEFORE_LOOP]] = OpLabel +CHECK-NEXT: [[DUMMY_IT:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[DUMMY_IT_1:%\w+]] [[BE:%\w+]] +CHECK-NEXT: [[I:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[I_1:%\w+]] [[BE]] +CHECK-NEXT: OpLoopMerge [[BEFORE_LOOP_MERGE:%\w+]] [[BE]] None +CHECK: [[I_1]] = OpIAdd {{%\w+}} [[I]] +CHECK: [[BE]] = OpLabel +CHECK: [[DUMMY_IT_1]] = OpIAdd {{%\w+}} [[DUMMY_IT]] +CHECK-NEXT: [[EXIT_VAL:%\w+]] = OpIAdd {{%\w+}} [[DUMMY_IT_1]] +CHECK-NEXT: [[EXIT_COND:%\w+]] = OpULessThan {{%\w+}} [[EXIT_VAL]] +CHECK-NEXT: OpBranchConditional [[EXIT_COND]] [[BEFORE_LOOP]] [[BEFORE_LOOP_MERGE]] + +CHECK: [[IF_MERGE]] = OpLabel +CHECK-NEXT: [[TMP:%\w+]] = OpPhi {{%\w+}} [[I_1]] [[BEFORE_LOOP_MERGE]] +CHECK-NEXT: OpBranch [[AFTER_LOOP:%\w+]] + +CHECK: [[AFTER_LOOP]] = OpLabel +CHECK-NEXT: OpPhi {{%\w+}} {{%\w+}} {{%\w+}} [[TMP]] [[IF_MERGE]] +CHECK-NEXT: OpLoopMerge +)"; + + Match(check, context.get()); + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 330 core +void main() { + int a[10]; + int n = a[0]; + for(int i = 0; i < n; ++i) {} +} +*/ +TEST_F(PeelingTest, PeelingLoopWithStore) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %o %n + OpExecutionMode %main OriginLowerLeft + OpSource GLSL 450 + OpName %main "main" + OpName %o "o" + OpName %end "end" + OpName %n "n" + OpName %i "i" + OpDecorate %o Location 0 + OpDecorate %n Flat + OpDecorate %n Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 +%_ptr_Output_float = OpTypePointer Output %float + %o = OpVariable %_ptr_Output_float Output + %float_0 = OpConstant %float 0 + %int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int +%_ptr_Input_int = OpTypePointer Input %int + %n = OpVariable %_ptr_Input_int Input + %int_0 = OpConstant %int 0 + %bool = OpTypeBool + %float_1 = OpConstant %float 1 + %int_1 = OpConstant %int 1 + %main = OpFunction %void None %3 + %5 = OpLabel + %end = OpVariable %_ptr_Function_int Function + %i = OpVariable %_ptr_Function_int Function + OpStore %o %float_0 + %15 = OpLoad %int %n + OpStore %end %15 + OpStore %i %int_0 + OpBranch %18 + %18 = OpLabel + %33 = OpPhi %int %int_0 %5 %32 %21 + OpLoopMerge %20 %21 None + OpBranch %22 + %22 = OpLabel + %26 = OpSLessThan %bool %33 %15 + OpBranchConditional %26 %19 %20 + %19 = OpLabel + %28 = OpLoad %float %o + %29 = OpFAdd %float %28 %float_1 + OpStore %o %29 + OpBranch %21 + %21 = OpLabel + %32 = OpIAdd %int %33 %int_1 + OpStore %i %32 + OpBranch %18 + %20 = OpLabel + OpReturn + OpFunctionEnd + )"; + + // Peel before. + { + SCOPED_TRACE("Peel before"); + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + + EXPECT_EQ(ld.NumLoops(), 1u); + + ir::Instruction* loop_count = context->get_def_use_mgr()->GetDef(15); + EXPECT_EQ(loop_count->opcode(), SpvOpLoad); + + opt::LoopPeeling peel(context.get(), &*ld.begin(), loop_count); + EXPECT_TRUE(peel.CanPeelLoop()); + peel.PeelBefore(1); + + const std::string check = R"( +CHECK: OpFunction +CHECK-NEXT: [[ENTRY:%\w+]] = OpLabel +CHECK: [[LOOP_COUNT:%\w+]] = OpLoad +CHECK: [[MIN_LOOP_COUNT:%\w+]] = OpSLessThan {{%\w+}} {{%\w+}} [[LOOP_COUNT]] +CHECK-NEXT: [[LOOP_COUNT:%\w+]] = OpSelect {{%\w+}} [[MIN_LOOP_COUNT]] {{%\w+}} [[LOOP_COUNT]] +CHECK: [[BEFORE_LOOP:%\w+]] = OpLabel +CHECK-NEXT: [[DUMMY_IT:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[DUMMY_IT_1:%\w+]] [[BE:%\w+]] +CHECK-NEXT: [[i:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[I_1:%\w+]] [[BE]] +CHECK-NEXT: OpLoopMerge [[AFTER_LOOP_PREHEADER:%\w+]] [[BE]] None +CHECK: [[COND_BLOCK:%\w+]] = OpLabel +CHECK-NEXT: OpSLessThan +CHECK-NEXT: [[EXIT_COND:%\w+]] = OpSLessThan {{%\w+}} [[DUMMY_IT]] +CHECK-NEXT: OpBranchConditional [[EXIT_COND]] {{%\w+}} [[AFTER_LOOP_PREHEADER]] +CHECK: [[I_1]] = OpIAdd {{%\w+}} [[i]] +CHECK: [[DUMMY_IT_1]] = OpIAdd {{%\w+}} [[DUMMY_IT]] +CHECK-NEXT: OpBranch [[BEFORE_LOOP]] + +CHECK: [[AFTER_LOOP_PREHEADER]] = OpLabel +CHECK-NEXT: OpSelectionMerge [[IF_MERGE:%\w+]] +CHECK-NEXT: OpBranchConditional [[MIN_LOOP_COUNT]] [[AFTER_LOOP:%\w+]] [[IF_MERGE]] + +CHECK: [[AFTER_LOOP]] = OpLabel +CHECK-NEXT: OpPhi {{%\w+}} {{%\w+}} {{%\w+}} [[i]] [[AFTER_LOOP_PREHEADER]] +CHECK-NEXT: OpLoopMerge +)"; + + Match(check, context.get()); + } + + // Peel after. + { + SCOPED_TRACE("Peel after"); + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + + EXPECT_EQ(ld.NumLoops(), 1u); + + ir::Instruction* loop_count = context->get_def_use_mgr()->GetDef(15); + EXPECT_EQ(loop_count->opcode(), SpvOpLoad); + + opt::LoopPeeling peel(context.get(), &*ld.begin(), loop_count); + EXPECT_TRUE(peel.CanPeelLoop()); + peel.PeelAfter(1); + + const std::string check = R"( +CHECK: OpFunction +CHECK-NEXT: [[ENTRY:%\w+]] = OpLabel +CHECK: [[MIN_LOOP_COUNT:%\w+]] = OpSLessThan {{%\w+}} +CHECK-NEXT: OpSelectionMerge [[IF_MERGE:%\w+]] +CHECK-NEXT: OpBranchConditional [[MIN_LOOP_COUNT]] [[BEFORE_LOOP:%\w+]] [[IF_MERGE]] +CHECK: [[BEFORE_LOOP]] = OpLabel +CHECK-NEXT: [[DUMMY_IT:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[DUMMY_IT_1:%\w+]] [[BE:%\w+]] +CHECK-NEXT: [[I:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[I_1:%\w+]] [[BE]] +CHECK-NEXT: OpLoopMerge [[BEFORE_LOOP_MERGE:%\w+]] [[BE]] None +CHECK: [[COND_BLOCK:%\w+]] = OpLabel +CHECK-NEXT: OpSLessThan +CHECK-NEXT: [[TMP:%\w+]] = OpIAdd {{%\w+}} [[DUMMY_IT]] {{%\w+}} +CHECK-NEXT: [[EXIT_COND:%\w+]] = OpSLessThan {{%\w+}} [[TMP]] +CHECK-NEXT: OpBranchConditional [[EXIT_COND]] {{%\w+}} [[BEFORE_LOOP_MERGE]] +CHECK: [[I_1]] = OpIAdd {{%\w+}} [[I]] +CHECK: [[DUMMY_IT_1]] = OpIAdd {{%\w+}} [[DUMMY_IT]] +CHECK-NEXT: OpBranch [[BEFORE_LOOP]] + +CHECK: [[IF_MERGE]] = OpLabel +CHECK-NEXT: [[TMP:%\w+]] = OpPhi {{%\w+}} [[I]] [[BEFORE_LOOP_MERGE]] +CHECK-NEXT: OpBranch [[AFTER_LOOP:%\w+]] + +CHECK: [[AFTER_LOOP]] = OpLabel +CHECK-NEXT: OpPhi {{%\w+}} {{%\w+}} {{%\w+}} [[TMP]] [[IF_MERGE]] +CHECK-NEXT: OpLoopMerge + +)"; + + Match(check, context.get()); + } +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/loop_optimizations/unroll_assumptions.cpp b/3rdparty/spirv-tools/test/opt/loop_optimizations/unroll_assumptions.cpp new file mode 100644 index 000000000..3a991c7bb --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/loop_optimizations/unroll_assumptions.cpp @@ -0,0 +1,1446 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include + +#include "../assembly_builder.h" +#include "../function_utils.h" +#include "../pass_fixture.h" +#include "../pass_utils.h" +#include "opt/loop_unroller.h" +#include "opt/loop_utils.h" +#include "opt/pass.h" + +namespace { + +using namespace spvtools; +using ::testing::UnorderedElementsAre; + +template +class PartialUnrollerTestPass : public opt::Pass { + public: + PartialUnrollerTestPass() : Pass() {} + + const char* name() const override { return "Loop unroller"; } + + Status Process(ir::IRContext* context) override { + bool changed = false; + for (ir::Function& f : *context->module()) { + ir::LoopDescriptor& loop_descriptor = *context->GetLoopDescriptor(&f); + for (auto& loop : loop_descriptor) { + opt::LoopUtils loop_utils{context, &loop}; + if (loop_utils.PartiallyUnroll(factor)) { + changed = true; + } + } + } + + if (changed) return Pass::Status::SuccessWithChange; + return Pass::Status::SuccessWithoutChange; + } +}; + +using PassClassTest = PassTest<::testing::Test>; + +/* +Generated from the following GLSL +#version 410 core +layout(location = 0) flat in int in_upper_bound; +void main() { + for (int i = 0; i < in_upper_bound; ++i) { + x[i] = 1.0f; + } +} +*/ +TEST_F(PassClassTest, CheckUpperBound) { + // clang-format off + // With opt::LocalMultiStoreElimPass + const std::string text = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" %3 +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 410 +OpName %2 "main" +OpName %3 "in_upper_bound" +OpName %4 "x" +OpDecorate %3 Flat +OpDecorate %3 Location 0 +%5 = OpTypeVoid +%6 = OpTypeFunction %5 +%7 = OpTypeInt 32 1 +%8 = OpTypePointer Function %7 +%9 = OpConstant %7 0 +%10 = OpTypePointer Input %7 +%3 = OpVariable %10 Input +%11 = OpTypeBool +%12 = OpTypeFloat 32 +%13 = OpTypeInt 32 0 +%14 = OpConstant %13 10 +%15 = OpTypeArray %12 %14 +%16 = OpTypePointer Function %15 +%17 = OpConstant %12 1 +%18 = OpTypePointer Function %12 +%19 = OpConstant %7 1 +%2 = OpFunction %5 None %6 +%20 = OpLabel +%4 = OpVariable %16 Function +OpBranch %21 +%21 = OpLabel +%22 = OpPhi %7 %9 %20 %23 %24 +OpLoopMerge %25 %24 Unroll +OpBranch %26 +%26 = OpLabel +%27 = OpLoad %7 %3 +%28 = OpSLessThan %11 %22 %27 +OpBranchConditional %28 %29 %25 +%29 = OpLabel +%30 = OpAccessChain %18 %4 %22 +OpStore %30 %17 +OpBranch %24 +%24 = OpLabel +%23 = OpIAdd %7 %22 %19 +OpBranch %21 +%25 = OpLabel +OpReturn +OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + + // Make sure the pass doesn't run + SinglePassRunAndCheck(text, text, false); + SinglePassRunAndCheck>(text, text, false); + SinglePassRunAndCheck>(text, text, false); +} + +/* +Generated from the following GLSL +#version 410 core +void main() { + float out_array[10]; + for (uint i = 0; i < 2; i++) { + for (float x = 0; x < 5; ++x) { + out_array[x + i*5] = i; + } + } +} +*/ +TEST_F(PassClassTest, UnrollNestedLoopsInvalid) { + // clang-format off + // With opt::LocalMultiStoreElimPass +const std::string text = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 410 +OpName %2 "main" +OpName %3 "out_array" +%4 = OpTypeVoid +%5 = OpTypeFunction %4 +%6 = OpTypeInt 32 0 +%7 = OpTypePointer Function %6 +%8 = OpConstant %6 0 +%9 = OpConstant %6 2 +%10 = OpTypeBool +%11 = OpTypeInt 32 1 +%12 = OpTypePointer Function %11 +%13 = OpConstant %11 0 +%14 = OpConstant %11 5 +%15 = OpTypeFloat 32 +%16 = OpConstant %6 10 +%17 = OpTypeArray %15 %16 +%18 = OpTypePointer Function %17 +%19 = OpConstant %6 5 +%20 = OpTypePointer Function %15 +%21 = OpConstant %11 1 +%22 = OpUndef %11 +%2 = OpFunction %4 None %5 +%23 = OpLabel +%3 = OpVariable %18 Function +OpBranch %24 +%24 = OpLabel +%25 = OpPhi %6 %8 %23 %26 %27 +%28 = OpPhi %11 %22 %23 %29 %27 +OpLoopMerge %30 %27 Unroll +OpBranch %31 +%31 = OpLabel +%32 = OpULessThan %10 %25 %9 +OpBranchConditional %32 %33 %30 +%33 = OpLabel +OpBranch %34 +%34 = OpLabel +%29 = OpPhi %11 %13 %33 %35 %36 +OpLoopMerge %37 %36 None +OpBranch %38 +%38 = OpLabel +%39 = OpSLessThan %10 %29 %14 +OpBranchConditional %39 %40 %37 +%40 = OpLabel +%41 = OpBitcast %6 %29 +%42 = OpIMul %6 %25 %19 +%43 = OpIAdd %6 %41 %42 +%44 = OpConvertUToF %15 %25 +%45 = OpAccessChain %20 %3 %43 +OpStore %45 %44 +OpBranch %36 +%36 = OpLabel +%35 = OpIAdd %11 %29 %21 +OpBranch %34 +%37 = OpLabel +OpBranch %27 +%27 = OpLabel +%26 = OpIAdd %6 %25 %21 +OpBranch %24 +%30 = OpLabel +OpReturn +OpFunctionEnd +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndCheck(text, text, false); +} + +/* +Generated from the following GLSL +#version 440 core +void main(){ + float x[10]; + for (int i = 0; i < 10; i++) { + if (i == 5) { + break; + } + x[i] = i; + } +} +*/ +TEST_F(PassClassTest, BreakInBody) { + // clang-format off + // With opt::LocalMultiStoreElimPass +const std::string text = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 440 +OpName %2 "main" +OpName %3 "x" +%4 = OpTypeVoid +%5 = OpTypeFunction %4 +%6 = OpTypeInt 32 1 +%7 = OpTypePointer Function %6 +%8 = OpConstant %6 0 +%9 = OpConstant %6 10 +%10 = OpTypeBool +%11 = OpConstant %6 5 +%12 = OpTypeFloat 32 +%13 = OpTypeInt 32 0 +%14 = OpConstant %13 10 +%15 = OpTypeArray %12 %14 +%16 = OpTypePointer Function %15 +%17 = OpTypePointer Function %12 +%18 = OpConstant %6 1 +%2 = OpFunction %4 None %5 +%19 = OpLabel +%3 = OpVariable %16 Function +OpBranch %20 +%20 = OpLabel +%21 = OpPhi %6 %8 %19 %22 %23 +OpLoopMerge %24 %23 Unroll +OpBranch %25 +%25 = OpLabel +%26 = OpSLessThan %10 %21 %9 +OpBranchConditional %26 %27 %24 +%27 = OpLabel +%28 = OpIEqual %10 %21 %11 +OpSelectionMerge %29 None +OpBranchConditional %28 %30 %29 +%30 = OpLabel +OpBranch %24 +%29 = OpLabel +%31 = OpConvertSToF %12 %21 +%32 = OpAccessChain %17 %3 %21 +OpStore %32 %31 +OpBranch %23 +%23 = OpLabel +%22 = OpIAdd %6 %21 %18 +OpBranch %20 +%24 = OpLabel +OpReturn +OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndCheck(text, text, false); +} + +/* +Generated from the following GLSL +#version 440 core +void main(){ + float x[10]; + for (int i = 0; i < 10; i++) { + if (i == 5) { + continue; + } + x[i] = i; + } +} +*/ +TEST_F(PassClassTest, ContinueInBody) { + // clang-format off + // With opt::LocalMultiStoreElimPass +const std::string text = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 440 +OpName %2 "main" +OpName %3 "x" +%4 = OpTypeVoid +%5 = OpTypeFunction %4 +%6 = OpTypeInt 32 1 +%7 = OpTypePointer Function %6 +%8 = OpConstant %6 0 +%9 = OpConstant %6 10 +%10 = OpTypeBool +%11 = OpConstant %6 5 +%12 = OpTypeFloat 32 +%13 = OpTypeInt 32 0 +%14 = OpConstant %13 10 +%15 = OpTypeArray %12 %14 +%16 = OpTypePointer Function %15 +%17 = OpTypePointer Function %12 +%18 = OpConstant %6 1 +%2 = OpFunction %4 None %5 +%19 = OpLabel +%3 = OpVariable %16 Function +OpBranch %20 +%20 = OpLabel +%21 = OpPhi %6 %8 %19 %22 %23 +OpLoopMerge %24 %23 Unroll +OpBranch %25 +%25 = OpLabel +%26 = OpSLessThan %10 %21 %9 +OpBranchConditional %26 %27 %24 +%27 = OpLabel +%28 = OpIEqual %10 %21 %11 +OpSelectionMerge %29 None +OpBranchConditional %28 %30 %29 +%30 = OpLabel +OpBranch %23 +%29 = OpLabel +%31 = OpConvertSToF %12 %21 +%32 = OpAccessChain %17 %3 %21 +OpStore %32 %31 +OpBranch %23 +%23 = OpLabel +%22 = OpIAdd %6 %21 %18 +OpBranch %20 +%24 = OpLabel +OpReturn +OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndCheck(text, text, false); +} + +/* +Generated from the following GLSL +#version 440 core +void main(){ + float x[10]; + for (int i = 0; i < 10; i++) { + if (i == 5) { + return; + } + x[i] = i; + } +} +*/ +TEST_F(PassClassTest, ReturnInBody) { + // clang-format off + // With opt::LocalMultiStoreElimPass +const std::string text = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 440 +OpName %2 "main" +OpName %3 "x" +%4 = OpTypeVoid +%5 = OpTypeFunction %4 +%6 = OpTypeInt 32 1 +%7 = OpTypePointer Function %6 +%8 = OpConstant %6 0 +%9 = OpConstant %6 10 +%10 = OpTypeBool +%11 = OpConstant %6 5 +%12 = OpTypeFloat 32 +%13 = OpTypeInt 32 0 +%14 = OpConstant %13 10 +%15 = OpTypeArray %12 %14 +%16 = OpTypePointer Function %15 +%17 = OpTypePointer Function %12 +%18 = OpConstant %6 1 +%2 = OpFunction %4 None %5 +%19 = OpLabel +%3 = OpVariable %16 Function +OpBranch %20 +%20 = OpLabel +%21 = OpPhi %6 %8 %19 %22 %23 +OpLoopMerge %24 %23 Unroll +OpBranch %25 +%25 = OpLabel +%26 = OpSLessThan %10 %21 %9 +OpBranchConditional %26 %27 %24 +%27 = OpLabel +%28 = OpIEqual %10 %21 %11 +OpSelectionMerge %29 None +OpBranchConditional %28 %30 %29 +%30 = OpLabel +OpReturn +%29 = OpLabel +%31 = OpConvertSToF %12 %21 +%32 = OpAccessChain %17 %3 %21 +OpStore %32 %31 +OpBranch %23 +%23 = OpLabel +%22 = OpIAdd %6 %21 %18 +OpBranch %20 +%24 = OpLabel +OpReturn +OpFunctionEnd +)"; + // clang-format on + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndCheck(text, text, false); +} + +/* +Generated from the following GLSL +#version 440 core +void main() { + int j = 0; + for (int i = 0; i < 10 && i > 0; i++) { + j++; + } +} +*/ +TEST_F(PassClassTest, MultipleConditionsSingleVariable) { + // clang-format off + // With opt::LocalMultiStoreElimPass + const std::string text = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 440 +OpName %2 "main" +%3 = OpTypeVoid +%4 = OpTypeFunction %3 +%5 = OpTypeInt 32 1 +%6 = OpTypePointer Function %5 +%7 = OpConstant %5 0 +%8 = OpConstant %5 10 +%9 = OpTypeBool +%10 = OpConstant %5 1 +%2 = OpFunction %3 None %4 +%11 = OpLabel +OpBranch %12 +%12 = OpLabel +%13 = OpPhi %5 %7 %11 %14 %15 +%16 = OpPhi %5 %7 %11 %17 %15 +OpLoopMerge %18 %15 Unroll +OpBranch %19 +%19 = OpLabel +%20 = OpSLessThan %9 %16 %8 +%21 = OpSGreaterThan %9 %16 %7 +%22 = OpLogicalAnd %9 %20 %21 +OpBranchConditional %22 %23 %18 +%23 = OpLabel +%14 = OpIAdd %5 %13 %10 +OpBranch %15 +%15 = OpLabel +%17 = OpIAdd %5 %16 %10 +OpBranch %12 +%18 = OpLabel +OpReturn +OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + + // Make sure the pass doesn't run + SinglePassRunAndCheck(text, text, false); + SinglePassRunAndCheck>(text, text, false); + SinglePassRunAndCheck>(text, text, false); +} + +/* +Generated from the following GLSL +#version 440 core +void main() { + int i = 0; + int j = 0; + int k = 0; + for (; i < 10 && j > 0; i++, j++) { + k++; + } +} +*/ +TEST_F(PassClassTest, MultipleConditionsMultipleVariables) { + // clang-format off + // With opt::LocalMultiStoreElimPass + const std::string text = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 440 +OpName %2 "main" +%3 = OpTypeVoid +%4 = OpTypeFunction %3 +%5 = OpTypeInt 32 1 +%6 = OpTypePointer Function %5 +%7 = OpConstant %5 0 +%8 = OpConstant %5 10 +%9 = OpTypeBool +%10 = OpConstant %5 1 +%2 = OpFunction %3 None %4 +%11 = OpLabel +OpBranch %12 +%12 = OpLabel +%13 = OpPhi %5 %7 %11 %14 %15 +%16 = OpPhi %5 %7 %11 %17 %15 +%18 = OpPhi %5 %7 %11 %19 %15 +OpLoopMerge %20 %15 Unroll +OpBranch %21 +%21 = OpLabel +%22 = OpSLessThan %9 %13 %8 +%23 = OpSGreaterThan %9 %16 %7 +%24 = OpLogicalAnd %9 %22 %23 +OpBranchConditional %24 %25 %20 +%25 = OpLabel +%19 = OpIAdd %5 %18 %10 +OpBranch %15 +%15 = OpLabel +%14 = OpIAdd %5 %13 %10 +%17 = OpIAdd %5 %16 %10 +OpBranch %12 +%20 = OpLabel +OpReturn +OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + + // Make sure the pass doesn't run + SinglePassRunAndCheck(text, text, false); + SinglePassRunAndCheck>(text, text, false); + SinglePassRunAndCheck>(text, text, false); +} + +/* +Generated from the following GLSL +#version 440 core +void main() { + float i = 0.0; + int j = 0; + for (; i < 10; i++) { + j++; + } +} +*/ +TEST_F(PassClassTest, FloatingPointLoop) { + // clang-format off + // With opt::LocalMultiStoreElimPass + const std::string text = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 440 +OpName %2 "main" +%3 = OpTypeVoid +%4 = OpTypeFunction %3 +%5 = OpTypeFloat 32 +%6 = OpTypePointer Function %5 +%7 = OpConstant %5 0 +%8 = OpTypeInt 32 1 +%9 = OpTypePointer Function %8 +%10 = OpConstant %8 0 +%11 = OpConstant %5 10 +%12 = OpTypeBool +%13 = OpConstant %8 1 +%14 = OpConstant %5 1 +%2 = OpFunction %3 None %4 +%15 = OpLabel +OpBranch %16 +%16 = OpLabel +%17 = OpPhi %5 %7 %15 %18 %19 +%20 = OpPhi %8 %10 %15 %21 %19 +OpLoopMerge %22 %19 Unroll +OpBranch %23 +%23 = OpLabel +%24 = OpFOrdLessThan %12 %17 %11 +OpBranchConditional %24 %25 %22 +%25 = OpLabel +%21 = OpIAdd %8 %20 %13 +OpBranch %19 +%19 = OpLabel +%18 = OpFAdd %5 %17 %14 +OpBranch %16 +%22 = OpLabel +OpReturn +OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + + // Make sure the pass doesn't run + SinglePassRunAndCheck(text, text, false); + SinglePassRunAndCheck>(text, text, false); + SinglePassRunAndCheck>(text, text, false); +} + +/* +Generated from the following GLSL +#version 440 core +void main() { + int i = 2; + int j = 0; + if (j == 0) { i = 5; } + for (; i < 3; ++i) { + j++; + } +} +*/ +TEST_F(PassClassTest, InductionPhiOutsideLoop) { + // clang-format off + // With opt::LocalMultiStoreElimPass + const std::string text = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 440 +OpName %2 "main" +%3 = OpTypeVoid +%4 = OpTypeFunction %3 +%5 = OpTypeInt 32 1 +%6 = OpTypePointer Function %5 +%7 = OpConstant %5 2 +%8 = OpConstant %5 0 +%9 = OpTypeBool +%10 = OpConstant %5 5 +%11 = OpConstant %5 3 +%12 = OpConstant %5 1 +%2 = OpFunction %3 None %4 +%13 = OpLabel +%14 = OpIEqual %9 %8 %8 +OpSelectionMerge %15 None +OpBranchConditional %14 %16 %15 +%16 = OpLabel +OpBranch %15 +%15 = OpLabel +%17 = OpPhi %5 %7 %13 %10 %16 +OpBranch %18 +%18 = OpLabel +%19 = OpPhi %5 %17 %15 %20 %21 +%22 = OpPhi %5 %8 %15 %23 %21 +OpLoopMerge %24 %21 Unroll +OpBranch %25 +%25 = OpLabel +%26 = OpSLessThan %9 %19 %11 +OpBranchConditional %26 %27 %24 +%27 = OpLabel +%23 = OpIAdd %5 %22 %12 +OpBranch %21 +%21 = OpLabel +%20 = OpIAdd %5 %19 %12 +OpBranch %18 +%24 = OpLabel +OpReturn +OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + + // Make sure the pass doesn't run + SinglePassRunAndCheck(text, text, false); + SinglePassRunAndCheck>(text, text, false); + SinglePassRunAndCheck>(text, text, false); +} + +/* +Generated from the following GLSL +#version 440 core +void main() { + int j = 0; + for (int i = 0; i == 0; ++i) { + ++j; + } + for (int i = 0; i != 3; ++i) { + ++j; + } + for (int i = 0; i < 3; i *= 2) { + ++j; + } + for (int i = 10; i > 3; i /= 2) { + ++j; + } + for (int i = 10; i > 3; i |= 2) { + ++j; + } + for (int i = 10; i > 3; i &= 2) { + ++j; + } + for (int i = 10; i > 3; i ^= 2) { + ++j; + } + for (int i = 0; i < 3; i << 2) { + ++j; + } + for (int i = 10; i > 3; i >> 2) { + ++j; + } +} +*/ +TEST_F(PassClassTest, UnsupportedLoopTypes) { + // clang-format off + // With opt::LocalMultiStoreElimPass + const std::string text = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 440 +OpName %2 "main" +%3 = OpTypeVoid +%4 = OpTypeFunction %3 +%5 = OpTypeInt 32 1 +%6 = OpTypePointer Function %5 +%7 = OpConstant %5 0 +%8 = OpTypeBool +%9 = OpConstant %5 1 +%10 = OpConstant %5 3 +%11 = OpConstant %5 2 +%12 = OpConstant %5 10 +%2 = OpFunction %3 None %4 +%13 = OpLabel +OpBranch %14 +%14 = OpLabel +%15 = OpPhi %5 %7 %13 %16 %17 +%18 = OpPhi %5 %7 %13 %19 %17 +OpLoopMerge %20 %17 Unroll +OpBranch %21 +%21 = OpLabel +%22 = OpIEqual %8 %18 %7 +OpBranchConditional %22 %23 %20 +%23 = OpLabel +%16 = OpIAdd %5 %15 %9 +OpBranch %17 +%17 = OpLabel +%19 = OpIAdd %5 %18 %9 +OpBranch %14 +%20 = OpLabel +OpBranch %24 +%24 = OpLabel +%25 = OpPhi %5 %15 %20 %26 %27 +%28 = OpPhi %5 %7 %20 %29 %27 +OpLoopMerge %30 %27 Unroll +OpBranch %31 +%31 = OpLabel +%32 = OpINotEqual %8 %28 %10 +OpBranchConditional %32 %33 %30 +%33 = OpLabel +%26 = OpIAdd %5 %25 %9 +OpBranch %27 +%27 = OpLabel +%29 = OpIAdd %5 %28 %9 +OpBranch %24 +%30 = OpLabel +OpBranch %34 +%34 = OpLabel +%35 = OpPhi %5 %25 %30 %36 %37 +%38 = OpPhi %5 %7 %30 %39 %37 +OpLoopMerge %40 %37 Unroll +OpBranch %41 +%41 = OpLabel +%42 = OpSLessThan %8 %38 %10 +OpBranchConditional %42 %43 %40 +%43 = OpLabel +%36 = OpIAdd %5 %35 %9 +OpBranch %37 +%37 = OpLabel +%39 = OpIMul %5 %38 %11 +OpBranch %34 +%40 = OpLabel +OpBranch %44 +%44 = OpLabel +%45 = OpPhi %5 %35 %40 %46 %47 +%48 = OpPhi %5 %12 %40 %49 %47 +OpLoopMerge %50 %47 Unroll +OpBranch %51 +%51 = OpLabel +%52 = OpSGreaterThan %8 %48 %10 +OpBranchConditional %52 %53 %50 +%53 = OpLabel +%46 = OpIAdd %5 %45 %9 +OpBranch %47 +%47 = OpLabel +%49 = OpSDiv %5 %48 %11 +OpBranch %44 +%50 = OpLabel +OpBranch %54 +%54 = OpLabel +%55 = OpPhi %5 %45 %50 %56 %57 +%58 = OpPhi %5 %12 %50 %59 %57 +OpLoopMerge %60 %57 Unroll +OpBranch %61 +%61 = OpLabel +%62 = OpSGreaterThan %8 %58 %10 +OpBranchConditional %62 %63 %60 +%63 = OpLabel +%56 = OpIAdd %5 %55 %9 +OpBranch %57 +%57 = OpLabel +%59 = OpBitwiseOr %5 %58 %11 +OpBranch %54 +%60 = OpLabel +OpBranch %64 +%64 = OpLabel +%65 = OpPhi %5 %55 %60 %66 %67 +%68 = OpPhi %5 %12 %60 %69 %67 +OpLoopMerge %70 %67 Unroll +OpBranch %71 +%71 = OpLabel +%72 = OpSGreaterThan %8 %68 %10 +OpBranchConditional %72 %73 %70 +%73 = OpLabel +%66 = OpIAdd %5 %65 %9 +OpBranch %67 +%67 = OpLabel +%69 = OpBitwiseAnd %5 %68 %11 +OpBranch %64 +%70 = OpLabel +OpBranch %74 +%74 = OpLabel +%75 = OpPhi %5 %65 %70 %76 %77 +%78 = OpPhi %5 %12 %70 %79 %77 +OpLoopMerge %80 %77 Unroll +OpBranch %81 +%81 = OpLabel +%82 = OpSGreaterThan %8 %78 %10 +OpBranchConditional %82 %83 %80 +%83 = OpLabel +%76 = OpIAdd %5 %75 %9 +OpBranch %77 +%77 = OpLabel +%79 = OpBitwiseXor %5 %78 %11 +OpBranch %74 +%80 = OpLabel +OpBranch %84 +%84 = OpLabel +%85 = OpPhi %5 %75 %80 %86 %87 +OpLoopMerge %88 %87 Unroll +OpBranch %89 +%89 = OpLabel +%90 = OpSLessThan %8 %7 %10 +OpBranchConditional %90 %91 %88 +%91 = OpLabel +%86 = OpIAdd %5 %85 %9 +OpBranch %87 +%87 = OpLabel +%92 = OpShiftLeftLogical %5 %7 %11 +OpBranch %84 +%88 = OpLabel +OpBranch %93 +%93 = OpLabel +%94 = OpPhi %5 %85 %88 %95 %96 +OpLoopMerge %97 %96 Unroll +OpBranch %98 +%98 = OpLabel +%99 = OpSGreaterThan %8 %12 %10 +OpBranchConditional %99 %100 %97 +%100 = OpLabel +%95 = OpIAdd %5 %94 %9 +OpBranch %96 +%96 = OpLabel +%101 = OpShiftRightArithmetic %5 %12 %11 +OpBranch %93 +%97 = OpLabel +OpReturn +OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + + // Make sure the pass doesn't run + SinglePassRunAndCheck(text, text, false); + SinglePassRunAndCheck>(text, text, false); + SinglePassRunAndCheck>(text, text, false); +} + +/* +#version 430 + +layout(location = 0) out float o; + +void main(void) { + for (int j = 2; j < 0; j += 1) { + o += 1.0; + } +} +*/ +TEST_F(PassClassTest, NegativeNumberOfIterations) { + // clang-format off + // With opt::LocalMultiStoreElimPass + const std::string text = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" %3 +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 430 +OpName %2 "main" +OpName %3 "o" +OpDecorate %3 Location 0 +%4 = OpTypeVoid +%5 = OpTypeFunction %4 +%6 = OpTypeInt 32 1 +%7 = OpTypePointer Function %6 +%8 = OpConstant %6 2 +%9 = OpConstant %6 0 +%10 = OpTypeBool +%11 = OpTypeFloat 32 +%12 = OpTypePointer Output %11 +%3 = OpVariable %12 Output +%13 = OpConstant %11 1 +%14 = OpConstant %6 1 +%2 = OpFunction %4 None %5 +%15 = OpLabel +OpBranch %16 +%16 = OpLabel +%17 = OpPhi %6 %8 %15 %18 %19 +OpLoopMerge %20 %19 None +OpBranch %21 +%21 = OpLabel +%22 = OpSLessThan %10 %17 %9 +OpBranchConditional %22 %23 %20 +%23 = OpLabel +%24 = OpLoad %11 %3 +%25 = OpFAdd %11 %24 %13 +OpStore %3 %25 +OpBranch %19 +%19 = OpLabel +%18 = OpIAdd %6 %17 %14 +OpBranch %16 +%20 = OpLabel +OpReturn +OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + + // Make sure the pass doesn't run + SinglePassRunAndCheck(text, text, false); + SinglePassRunAndCheck>(text, text, false); + SinglePassRunAndCheck>(text, text, false); +} + +/* +#version 430 + +layout(location = 0) out float o; + +void main(void) { + float s = 0.0; + for (int j = 0; j < 3; j += 1) { + s += 1.0; + j += 1; + } + o = s; +} +*/ +TEST_F(PassClassTest, MultipleStepOperations) { + // clang-format off + // With opt::LocalMultiStoreElimPass +const std::string text = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" %3 +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 430 +OpName %2 "main" +OpName %3 "o" +OpDecorate %3 Location 0 +%4 = OpTypeVoid +%5 = OpTypeFunction %4 +%6 = OpTypeFloat 32 +%7 = OpTypePointer Function %6 +%8 = OpConstant %6 0 +%9 = OpTypeInt 32 1 +%10 = OpTypePointer Function %9 +%11 = OpConstant %9 0 +%12 = OpConstant %9 3 +%13 = OpTypeBool +%14 = OpConstant %6 1 +%15 = OpConstant %9 1 +%16 = OpTypePointer Output %6 +%3 = OpVariable %16 Output +%2 = OpFunction %4 None %5 +%17 = OpLabel +OpBranch %18 +%18 = OpLabel +%19 = OpPhi %6 %8 %17 %20 %21 +%22 = OpPhi %9 %11 %17 %23 %21 +OpLoopMerge %24 %21 Unroll +OpBranch %25 +%25 = OpLabel +%26 = OpSLessThan %13 %22 %12 +OpBranchConditional %26 %27 %24 +%27 = OpLabel +%20 = OpFAdd %6 %19 %14 +%28 = OpIAdd %9 %22 %15 +OpBranch %21 +%21 = OpLabel +%23 = OpIAdd %9 %28 %15 +OpBranch %18 +%24 = OpLabel +OpStore %3 %19 +OpReturn +OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + + // Make sure the pass doesn't run + SinglePassRunAndCheck(text, text, false); + SinglePassRunAndCheck>(text, text, false); + SinglePassRunAndCheck>(text, text, false); +} + +/* +#version 430 + +layout(location = 0) out float o; + +void main(void) { + float s = 0.0; + for (int j = 10; j > 20; j -= 1) { + s += 1.0; + } + o = s; +} +*/ + +TEST_F(PassClassTest, ConditionFalseFromStartGreaterThan) { + // clang-format off + // With opt::LocalMultiStoreElimPass +const std::string text = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" %3 +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 430 +OpName %2 "main" +OpName %3 "o" +OpDecorate %3 Location 0 +%4 = OpTypeVoid +%5 = OpTypeFunction %4 +%6 = OpTypeFloat 32 +%7 = OpTypePointer Function %6 +%8 = OpConstant %6 0 +%9 = OpTypeInt 32 1 +%10 = OpTypePointer Function %9 +%11 = OpConstant %9 10 +%12 = OpConstant %9 20 +%13 = OpTypeBool +%14 = OpConstant %6 1 +%15 = OpConstant %9 1 +%16 = OpTypePointer Output %6 +%3 = OpVariable %16 Output +%2 = OpFunction %4 None %5 +%17 = OpLabel +OpBranch %18 +%18 = OpLabel +%19 = OpPhi %6 %8 %17 %20 %21 +%22 = OpPhi %9 %11 %17 %23 %21 +OpLoopMerge %24 %21 Unroll +OpBranch %25 +%25 = OpLabel +%26 = OpSGreaterThan %13 %22 %12 +OpBranchConditional %26 %27 %24 +%27 = OpLabel +%20 = OpFAdd %6 %19 %14 +OpBranch %21 +%21 = OpLabel +%23 = OpISub %9 %22 %15 +OpBranch %18 +%24 = OpLabel +OpStore %3 %19 +OpReturn +OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + + // Make sure the pass doesn't run + SinglePassRunAndCheck(text, text, false); + SinglePassRunAndCheck>(text, text, false); + SinglePassRunAndCheck>(text, text, false); +} + +/* +#version 430 + +layout(location = 0) out float o; + +void main(void) { + float s = 0.0; + for (int j = 10; j >= 20; j -= 1) { + s += 1.0; + } + o = s; +} +*/ +TEST_F(PassClassTest, ConditionFalseFromStartGreaterThanOrEqual) { + // clang-format off + // With opt::LocalMultiStoreElimPass +const std::string text = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" %3 +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 430 +OpName %2 "main" +OpName %3 "o" +OpDecorate %3 Location 0 +%4 = OpTypeVoid +%5 = OpTypeFunction %4 +%6 = OpTypeFloat 32 +%7 = OpTypePointer Function %6 +%8 = OpConstant %6 0 +%9 = OpTypeInt 32 1 +%10 = OpTypePointer Function %9 +%11 = OpConstant %9 10 +%12 = OpConstant %9 20 +%13 = OpTypeBool +%14 = OpConstant %6 1 +%15 = OpConstant %9 1 +%16 = OpTypePointer Output %6 +%3 = OpVariable %16 Output +%2 = OpFunction %4 None %5 +%17 = OpLabel +OpBranch %18 +%18 = OpLabel +%19 = OpPhi %6 %8 %17 %20 %21 +%22 = OpPhi %9 %11 %17 %23 %21 +OpLoopMerge %24 %21 Unroll +OpBranch %25 +%25 = OpLabel +%26 = OpSGreaterThanEqual %13 %22 %12 +OpBranchConditional %26 %27 %24 +%27 = OpLabel +%20 = OpFAdd %6 %19 %14 +OpBranch %21 +%21 = OpLabel +%23 = OpISub %9 %22 %15 +OpBranch %18 +%24 = OpLabel +OpStore %3 %19 +OpReturn +OpFunctionEnd +)"; + + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + + // Make sure the pass doesn't run + SinglePassRunAndCheck(text, text, false); + SinglePassRunAndCheck>(text, text, false); + SinglePassRunAndCheck>(text, text, false); +} + +/* +#version 430 + +layout(location = 0) out float o; + +void main(void) { + float s = 0.0; + for (int j = 20; j < 10; j -= 1) { + s += 1.0; + } + o = s; +} +*/ +TEST_F(PassClassTest, ConditionFalseFromStartLessThan) { + // clang-format off + // With opt::LocalMultiStoreElimPass +const std::string text = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" %3 +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 430 +OpName %2 "main" +OpName %3 "o" +OpDecorate %3 Location 0 +%4 = OpTypeVoid +%5 = OpTypeFunction %4 +%6 = OpTypeFloat 32 +%7 = OpTypePointer Function %6 +%8 = OpConstant %6 0 +%9 = OpTypeInt 32 1 +%10 = OpTypePointer Function %9 +%11 = OpConstant %9 20 +%12 = OpConstant %9 10 +%13 = OpTypeBool +%14 = OpConstant %6 1 +%15 = OpConstant %9 1 +%16 = OpTypePointer Output %6 +%3 = OpVariable %16 Output +%2 = OpFunction %4 None %5 +%17 = OpLabel +OpBranch %18 +%18 = OpLabel +%19 = OpPhi %6 %8 %17 %20 %21 +%22 = OpPhi %9 %11 %17 %23 %21 +OpLoopMerge %24 %21 Unroll +OpBranch %25 +%25 = OpLabel +%26 = OpSLessThan %13 %22 %12 +OpBranchConditional %26 %27 %24 +%27 = OpLabel +%20 = OpFAdd %6 %19 %14 +OpBranch %21 +%21 = OpLabel +%23 = OpISub %9 %22 %15 +OpBranch %18 +%24 = OpLabel +OpStore %3 %19 +OpReturn +OpFunctionEnd +)"; + + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + + // Make sure the pass doesn't run + SinglePassRunAndCheck(text, text, false); + SinglePassRunAndCheck>(text, text, false); + SinglePassRunAndCheck>(text, text, false); +} + +/* +#version 430 + +layout(location = 0) out float o; + +void main(void) { + float s = 0.0; + for (int j = 20; j <= 10; j -= 1) { + s += 1.0; + } + o = s; +} +*/ +TEST_F(PassClassTest, ConditionFalseFromStartLessThanEqual) { + // clang-format off + // With opt::LocalMultiStoreElimPass +const std::string text = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" %3 +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 430 +OpName %2 "main" +OpName %3 "o" +OpDecorate %3 Location 0 +%4 = OpTypeVoid +%5 = OpTypeFunction %4 +%6 = OpTypeFloat 32 +%7 = OpTypePointer Function %6 +%8 = OpConstant %6 0 +%9 = OpTypeInt 32 1 +%10 = OpTypePointer Function %9 +%11 = OpConstant %9 20 +%12 = OpConstant %9 10 +%13 = OpTypeBool +%14 = OpConstant %6 1 +%15 = OpConstant %9 1 +%16 = OpTypePointer Output %6 +%3 = OpVariable %16 Output +%2 = OpFunction %4 None %5 +%17 = OpLabel +OpBranch %18 +%18 = OpLabel +%19 = OpPhi %6 %8 %17 %20 %21 +%22 = OpPhi %9 %11 %17 %23 %21 +OpLoopMerge %24 %21 Unroll +OpBranch %25 +%25 = OpLabel +%26 = OpSLessThanEqual %13 %22 %12 +OpBranchConditional %26 %27 %24 +%27 = OpLabel +%20 = OpFAdd %6 %19 %14 +OpBranch %21 +%21 = OpLabel +%23 = OpISub %9 %22 %15 +OpBranch %18 +%24 = OpLabel +OpStore %3 %19 +OpReturn +OpFunctionEnd +)"; + + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + + // Make sure the pass doesn't run + SinglePassRunAndCheck(text, text, false); + SinglePassRunAndCheck>(text, text, false); + SinglePassRunAndCheck>(text, text, false); +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/loop_optimizations/unroll_simple.cpp b/3rdparty/spirv-tools/test/opt/loop_optimizations/unroll_simple.cpp new file mode 100644 index 000000000..3d8fb67f3 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/loop_optimizations/unroll_simple.cpp @@ -0,0 +1,2799 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include + +#include "../assembly_builder.h" +#include "../function_utils.h" +#include "../pass_fixture.h" +#include "../pass_utils.h" +#include "opt/loop_unroller.h" +#include "opt/loop_utils.h" +#include "opt/pass.h" + +namespace { + +using namespace spvtools; +using ::testing::UnorderedElementsAre; + +using PassClassTest = PassTest<::testing::Test>; + +/* +Generated from the following GLSL +#version 330 core +layout(location = 0) out vec4 c; +void main() { + float x[4]; + for (int i = 0; i < 4; ++i) { + x[i] = 1.0f; + } +} +*/ +TEST_F(PassClassTest, SimpleFullyUnrollTest) { + // clang-format off + // With opt::LocalMultiStoreElimPass + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 330 + OpName %2 "main" + OpName %5 "x" + OpName %3 "c" + OpDecorate %3 Location 0 + %6 = OpTypeVoid + %7 = OpTypeFunction %6 + %8 = OpTypeInt 32 1 + %9 = OpTypePointer Function %8 + %10 = OpConstant %8 0 + %11 = OpConstant %8 4 + %12 = OpTypeBool + %13 = OpTypeFloat 32 + %14 = OpTypeInt 32 0 + %15 = OpConstant %14 4 + %16 = OpTypeArray %13 %15 + %17 = OpTypePointer Function %16 + %18 = OpConstant %13 1 + %19 = OpTypePointer Function %13 + %20 = OpConstant %8 1 + %21 = OpTypeVector %13 4 + %22 = OpTypePointer Output %21 + %3 = OpVariable %22 Output + %2 = OpFunction %6 None %7 + %23 = OpLabel + %5 = OpVariable %17 Function + OpBranch %24 + %24 = OpLabel + %35 = OpPhi %8 %10 %23 %34 %26 + OpLoopMerge %25 %26 Unroll + OpBranch %27 + %27 = OpLabel + %29 = OpSLessThan %12 %35 %11 + OpBranchConditional %29 %30 %25 + %30 = OpLabel + %32 = OpAccessChain %19 %5 %35 + OpStore %32 %18 + OpBranch %26 + %26 = OpLabel + %34 = OpIAdd %8 %35 %20 + OpBranch %24 + %25 = OpLabel + OpReturn + OpFunctionEnd + )"; + +const std::string output = +R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" %3 +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 330 +OpName %2 "main" +OpName %4 "x" +OpName %3 "c" +OpDecorate %3 Location 0 +%5 = OpTypeVoid +%6 = OpTypeFunction %5 +%7 = OpTypeInt 32 1 +%8 = OpTypePointer Function %7 +%9 = OpConstant %7 0 +%10 = OpConstant %7 4 +%11 = OpTypeBool +%12 = OpTypeFloat 32 +%13 = OpTypeInt 32 0 +%14 = OpConstant %13 4 +%15 = OpTypeArray %12 %14 +%16 = OpTypePointer Function %15 +%17 = OpConstant %12 1 +%18 = OpTypePointer Function %12 +%19 = OpConstant %7 1 +%20 = OpTypeVector %12 4 +%21 = OpTypePointer Output %20 +%3 = OpVariable %21 Output +%2 = OpFunction %5 None %6 +%22 = OpLabel +%4 = OpVariable %16 Function +OpBranch %23 +%23 = OpLabel +OpBranch %28 +%28 = OpLabel +%29 = OpSLessThan %11 %9 %10 +OpBranch %30 +%30 = OpLabel +%31 = OpAccessChain %18 %4 %9 +OpStore %31 %17 +OpBranch %26 +%26 = OpLabel +%25 = OpIAdd %7 %9 %19 +OpBranch %32 +%32 = OpLabel +OpBranch %34 +%34 = OpLabel +%35 = OpSLessThan %11 %25 %10 +OpBranch %36 +%36 = OpLabel +%37 = OpAccessChain %18 %4 %25 +OpStore %37 %17 +OpBranch %38 +%38 = OpLabel +%39 = OpIAdd %7 %25 %19 +OpBranch %40 +%40 = OpLabel +OpBranch %42 +%42 = OpLabel +%43 = OpSLessThan %11 %39 %10 +OpBranch %44 +%44 = OpLabel +%45 = OpAccessChain %18 %4 %39 +OpStore %45 %17 +OpBranch %46 +%46 = OpLabel +%47 = OpIAdd %7 %39 %19 +OpBranch %48 +%48 = OpLabel +OpBranch %50 +%50 = OpLabel +%51 = OpSLessThan %11 %47 %10 +OpBranch %52 +%52 = OpLabel +%53 = OpAccessChain %18 %4 %47 +OpStore %53 %17 +OpBranch %54 +%54 = OpLabel +%55 = OpIAdd %7 %47 %19 +OpBranch %27 +%27 = OpLabel +OpReturn +OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" + << text << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndCheck(text, output, false); +} + +template +class PartialUnrollerTestPass : public opt::Pass { + public: + PartialUnrollerTestPass() : Pass() {} + + const char* name() const override { return "Loop unroller"; } + + Status Process(ir::IRContext* context) override { + for (ir::Function& f : *context->module()) { + ir::LoopDescriptor& loop_descriptor = *context->GetLoopDescriptor(&f); + for (auto& loop : loop_descriptor) { + opt::LoopUtils loop_utils{context, &loop}; + loop_utils.PartiallyUnroll(factor); + } + } + + return Pass::Status::SuccessWithChange; + } +}; + +/* +Generated from the following GLSL +#version 330 core +layout(location = 0) out vec4 c; +void main() { + float x[10]; + for (int i = 0; i < 10; ++i) { + x[i] = 1.0f; + } +} +*/ +TEST_F(PassClassTest, SimplePartialUnroll) { + // clang-format off + // With opt::LocalMultiStoreElimPass + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 330 + OpName %2 "main" + OpName %5 "x" + OpName %3 "c" + OpDecorate %3 Location 0 + %6 = OpTypeVoid + %7 = OpTypeFunction %6 + %8 = OpTypeInt 32 1 + %9 = OpTypePointer Function %8 + %10 = OpConstant %8 0 + %11 = OpConstant %8 10 + %12 = OpTypeBool + %13 = OpTypeFloat 32 + %14 = OpTypeInt 32 0 + %15 = OpConstant %14 10 + %16 = OpTypeArray %13 %15 + %17 = OpTypePointer Function %16 + %18 = OpConstant %13 1 + %19 = OpTypePointer Function %13 + %20 = OpConstant %8 1 + %21 = OpTypeVector %13 4 + %22 = OpTypePointer Output %21 + %3 = OpVariable %22 Output + %2 = OpFunction %6 None %7 + %23 = OpLabel + %5 = OpVariable %17 Function + OpBranch %24 + %24 = OpLabel + %35 = OpPhi %8 %10 %23 %34 %26 + OpLoopMerge %25 %26 Unroll + OpBranch %27 + %27 = OpLabel + %29 = OpSLessThan %12 %35 %11 + OpBranchConditional %29 %30 %25 + %30 = OpLabel + %32 = OpAccessChain %19 %5 %35 + OpStore %32 %18 + OpBranch %26 + %26 = OpLabel + %34 = OpIAdd %8 %35 %20 + OpBranch %24 + %25 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const std::string output = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" %3 +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 330 +OpName %2 "main" +OpName %4 "x" +OpName %3 "c" +OpDecorate %3 Location 0 +%5 = OpTypeVoid +%6 = OpTypeFunction %5 +%7 = OpTypeInt 32 1 +%8 = OpTypePointer Function %7 +%9 = OpConstant %7 0 +%10 = OpConstant %7 10 +%11 = OpTypeBool +%12 = OpTypeFloat 32 +%13 = OpTypeInt 32 0 +%14 = OpConstant %13 10 +%15 = OpTypeArray %12 %14 +%16 = OpTypePointer Function %15 +%17 = OpConstant %12 1 +%18 = OpTypePointer Function %12 +%19 = OpConstant %7 1 +%20 = OpTypeVector %12 4 +%21 = OpTypePointer Output %20 +%3 = OpVariable %21 Output +%2 = OpFunction %5 None %6 +%22 = OpLabel +%4 = OpVariable %16 Function +OpBranch %23 +%23 = OpLabel +%24 = OpPhi %7 %9 %22 %39 %38 +OpLoopMerge %27 %38 DontUnroll +OpBranch %28 +%28 = OpLabel +%29 = OpSLessThan %11 %24 %10 +OpBranchConditional %29 %30 %27 +%30 = OpLabel +%31 = OpAccessChain %18 %4 %24 +OpStore %31 %17 +OpBranch %26 +%26 = OpLabel +%25 = OpIAdd %7 %24 %19 +OpBranch %32 +%32 = OpLabel +OpBranch %34 +%34 = OpLabel +%35 = OpSLessThan %11 %25 %10 +OpBranch %36 +%36 = OpLabel +%37 = OpAccessChain %18 %4 %25 +OpStore %37 %17 +OpBranch %38 +%38 = OpLabel +%39 = OpIAdd %7 %25 %19 +OpBranch %23 +%27 = OpLabel +OpReturn +OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" + << text << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndCheck>(text, output, false); +} + +/* +Generated from the following GLSL +#version 330 core +layout(location = 0) out vec4 c; +void main() { + float x[10]; + for (int i = 0; i < 10; ++i) { + x[i] = 1.0f; + } +} +*/ +TEST_F(PassClassTest, SimpleUnevenPartialUnroll) { + // clang-format off + // With opt::LocalMultiStoreElimPass + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 330 + OpName %2 "main" + OpName %5 "x" + OpName %3 "c" + OpDecorate %3 Location 0 + %6 = OpTypeVoid + %7 = OpTypeFunction %6 + %8 = OpTypeInt 32 1 + %9 = OpTypePointer Function %8 + %10 = OpConstant %8 0 + %11 = OpConstant %8 10 + %12 = OpTypeBool + %13 = OpTypeFloat 32 + %14 = OpTypeInt 32 0 + %15 = OpConstant %14 10 + %16 = OpTypeArray %13 %15 + %17 = OpTypePointer Function %16 + %18 = OpConstant %13 1 + %19 = OpTypePointer Function %13 + %20 = OpConstant %8 1 + %21 = OpTypeVector %13 4 + %22 = OpTypePointer Output %21 + %3 = OpVariable %22 Output + %2 = OpFunction %6 None %7 + %23 = OpLabel + %5 = OpVariable %17 Function + OpBranch %24 + %24 = OpLabel + %35 = OpPhi %8 %10 %23 %34 %26 + OpLoopMerge %25 %26 Unroll + OpBranch %27 + %27 = OpLabel + %29 = OpSLessThan %12 %35 %11 + OpBranchConditional %29 %30 %25 + %30 = OpLabel + %32 = OpAccessChain %19 %5 %35 + OpStore %32 %18 + OpBranch %26 + %26 = OpLabel + %34 = OpIAdd %8 %35 %20 + OpBranch %24 + %25 = OpLabel + OpReturn + OpFunctionEnd + )"; + +const std::string output = +R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" %3 +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 330 +OpName %2 "main" +OpName %4 "x" +OpName %3 "c" +OpDecorate %3 Location 0 +%5 = OpTypeVoid +%6 = OpTypeFunction %5 +%7 = OpTypeInt 32 1 +%8 = OpTypePointer Function %7 +%9 = OpConstant %7 0 +%10 = OpConstant %7 10 +%11 = OpTypeBool +%12 = OpTypeFloat 32 +%13 = OpTypeInt 32 0 +%14 = OpConstant %13 10 +%15 = OpTypeArray %12 %14 +%16 = OpTypePointer Function %15 +%17 = OpConstant %12 1 +%18 = OpTypePointer Function %12 +%19 = OpConstant %7 1 +%20 = OpTypeVector %12 4 +%21 = OpTypePointer Output %20 +%3 = OpVariable %21 Output +%58 = OpConstant %13 1 +%2 = OpFunction %5 None %6 +%22 = OpLabel +%4 = OpVariable %16 Function +OpBranch %23 +%23 = OpLabel +%24 = OpPhi %7 %9 %22 %25 %26 +OpLoopMerge %32 %26 Unroll +OpBranch %28 +%28 = OpLabel +%29 = OpSLessThan %11 %24 %58 +OpBranchConditional %29 %30 %32 +%30 = OpLabel +%31 = OpAccessChain %18 %4 %24 +OpStore %31 %17 +OpBranch %26 +%26 = OpLabel +%25 = OpIAdd %7 %24 %19 +OpBranch %23 +%32 = OpLabel +OpBranch %33 +%33 = OpLabel +%34 = OpPhi %7 %24 %32 %57 %56 +OpLoopMerge %41 %56 DontUnroll +OpBranch %35 +%35 = OpLabel +%36 = OpSLessThan %11 %34 %10 +OpBranchConditional %36 %37 %41 +%37 = OpLabel +%38 = OpAccessChain %18 %4 %34 +OpStore %38 %17 +OpBranch %39 +%39 = OpLabel +%40 = OpIAdd %7 %34 %19 +OpBranch %42 +%42 = OpLabel +OpBranch %44 +%44 = OpLabel +%45 = OpSLessThan %11 %40 %10 +OpBranch %46 +%46 = OpLabel +%47 = OpAccessChain %18 %4 %40 +OpStore %47 %17 +OpBranch %48 +%48 = OpLabel +%49 = OpIAdd %7 %40 %19 +OpBranch %50 +%50 = OpLabel +OpBranch %52 +%52 = OpLabel +%53 = OpSLessThan %11 %49 %10 +OpBranch %54 +%54 = OpLabel +%55 = OpAccessChain %18 %4 %49 +OpStore %55 %17 +OpBranch %56 +%56 = OpLabel +%57 = OpIAdd %7 %49 %19 +OpBranch %33 +%41 = OpLabel +OpReturn +%27 = OpLabel +OpReturn +OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" + << text << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + // By unrolling by a factor that doesn't divide evenly into the number of loop + // iterations we perfom an additional transform when partially unrolling to + // account for the remainder. + SinglePassRunAndCheck>(text, output, false); +} + +/* Generated from +#version 410 core +layout(location=0) flat in int upper_bound; +void main() { + float x[10]; + for (int i = 2; i < 8; i+=2) { + x[i] = i; + } +} +*/ +TEST_F(PassClassTest, SimpleLoopIterationsCheck) { + // clang-format off + // With opt::LocalMultiStoreElimPass + const std::string text = R"( +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" %3 +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 410 +OpName %2 "main" +OpName %5 "x" +OpName %3 "upper_bound" +OpDecorate %3 Flat +OpDecorate %3 Location 0 +%6 = OpTypeVoid +%7 = OpTypeFunction %6 +%8 = OpTypeInt 32 1 +%9 = OpTypePointer Function %8 +%10 = OpConstant %8 2 +%11 = OpConstant %8 8 +%12 = OpTypeBool +%13 = OpTypeFloat 32 +%14 = OpTypeInt 32 0 +%15 = OpConstant %14 10 +%16 = OpTypeArray %13 %15 +%17 = OpTypePointer Function %16 +%18 = OpTypePointer Function %13 +%19 = OpTypePointer Input %8 +%3 = OpVariable %19 Input +%2 = OpFunction %6 None %7 +%20 = OpLabel +%5 = OpVariable %17 Function +OpBranch %21 +%21 = OpLabel +%34 = OpPhi %8 %10 %20 %33 %23 +OpLoopMerge %22 %23 Unroll +OpBranch %24 +%24 = OpLabel +%26 = OpSLessThan %12 %34 %11 +OpBranchConditional %26 %27 %22 +%27 = OpLabel +%30 = OpConvertSToF %13 %34 +%31 = OpAccessChain %18 %5 %34 +OpStore %31 %30 +OpBranch %23 +%23 = OpLabel +%33 = OpIAdd %8 %34 %10 +OpBranch %21 +%22 = OpLabel +OpReturn +OpFunctionEnd +)"; + // clang-format on + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" + << text << std::endl; + + ir::Function* f = spvtest::GetFunction(module, 2); + + ir::LoopDescriptor& loop_descriptor = *context->GetLoopDescriptor(f); + EXPECT_EQ(loop_descriptor.NumLoops(), 1u); + + ir::Loop& loop = loop_descriptor.GetLoopByIndex(0); + + EXPECT_TRUE(loop.HasUnrollLoopControl()); + + ir::BasicBlock* condition = loop.FindConditionBlock(); + EXPECT_EQ(condition->id(), 24u); + + ir::Instruction* induction = loop.FindConditionVariable(condition); + EXPECT_EQ(induction->result_id(), 34u); + + opt::LoopUtils loop_utils{context.get(), &loop}; + EXPECT_TRUE(loop_utils.CanPerformUnroll()); + + size_t iterations = 0; + EXPECT_TRUE(loop.FindNumberOfIterations(induction, &*condition->ctail(), + &iterations)); + EXPECT_EQ(iterations, 3u); +} + +/* Generated from +#version 410 core +void main() { + float x[10]; + for (int i = -1; i < 6; i+=3) { + x[i] = i; + } +} +*/ +TEST_F(PassClassTest, SimpleLoopIterationsCheckSignedInit) { + // clang-format off + // With opt::LocalMultiStoreElimPass + const std::string text = R"( +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" %3 +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 410 +OpName %2 "main" +OpName %5 "x" +OpName %3 "upper_bound" +OpDecorate %3 Flat +OpDecorate %3 Location 0 +%6 = OpTypeVoid +%7 = OpTypeFunction %6 +%8 = OpTypeInt 32 1 +%9 = OpTypePointer Function %8 +%10 = OpConstant %8 -1 +%11 = OpConstant %8 6 +%12 = OpTypeBool +%13 = OpTypeFloat 32 +%14 = OpTypeInt 32 0 +%15 = OpConstant %14 10 +%16 = OpTypeArray %13 %15 +%17 = OpTypePointer Function %16 +%18 = OpTypePointer Function %13 +%19 = OpConstant %8 3 +%20 = OpTypePointer Input %8 +%3 = OpVariable %20 Input +%2 = OpFunction %6 None %7 +%21 = OpLabel +%5 = OpVariable %17 Function +OpBranch %22 +%22 = OpLabel +%35 = OpPhi %8 %10 %21 %34 %24 +OpLoopMerge %23 %24 None +OpBranch %25 +%25 = OpLabel +%27 = OpSLessThan %12 %35 %11 +OpBranchConditional %27 %28 %23 +%28 = OpLabel +%31 = OpConvertSToF %13 %35 +%32 = OpAccessChain %18 %5 %35 +OpStore %32 %31 +OpBranch %24 +%24 = OpLabel +%34 = OpIAdd %8 %35 %19 +OpBranch %22 +%23 = OpLabel +OpReturn +OpFunctionEnd +)"; + // clang-format on + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" + << text << std::endl; + + ir::Function* f = spvtest::GetFunction(module, 2); + + ir::LoopDescriptor& loop_descriptor = *context->GetLoopDescriptor(f); + + EXPECT_EQ(loop_descriptor.NumLoops(), 1u); + + ir::Loop& loop = loop_descriptor.GetLoopByIndex(0); + + EXPECT_FALSE(loop.HasUnrollLoopControl()); + + ir::BasicBlock* condition = loop.FindConditionBlock(); + EXPECT_EQ(condition->id(), 25u); + + ir::Instruction* induction = loop.FindConditionVariable(condition); + EXPECT_EQ(induction->result_id(), 35u); + + opt::LoopUtils loop_utils{context.get(), &loop}; + EXPECT_TRUE(loop_utils.CanPerformUnroll()); + + size_t iterations = 0; + EXPECT_TRUE(loop.FindNumberOfIterations(induction, &*condition->ctail(), + &iterations)); + EXPECT_EQ(iterations, 3u); +} + +/* +Generated from the following GLSL +#version 410 core +void main() { + float out_array[6]; + for (uint i = 0; i < 2; i++) { + for (int x = 0; x < 3; ++x) { + out_array[x + i*3] = i; + } + } +} +*/ +TEST_F(PassClassTest, UnrollNestedLoops) { + // clang-format off + // With opt::LocalMultiStoreElimPass + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 410 + OpName %4 "main" + OpName %35 "out_array" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 0 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 2 + %17 = OpTypeBool + %19 = OpTypeInt 32 1 + %20 = OpTypePointer Function %19 + %22 = OpConstant %19 0 + %29 = OpConstant %19 3 + %31 = OpTypeFloat 32 + %32 = OpConstant %6 6 + %33 = OpTypeArray %31 %32 + %34 = OpTypePointer Function %33 + %39 = OpConstant %6 3 + %44 = OpTypePointer Function %31 + %47 = OpConstant %19 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %35 = OpVariable %34 Function + OpBranch %10 + %10 = OpLabel + %51 = OpPhi %6 %9 %5 %50 %13 + OpLoopMerge %12 %13 Unroll + OpBranch %14 + %14 = OpLabel + %18 = OpULessThan %17 %51 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpBranch %23 + %23 = OpLabel + %54 = OpPhi %19 %22 %11 %48 %26 + OpLoopMerge %25 %26 Unroll + OpBranch %27 + %27 = OpLabel + %30 = OpSLessThan %17 %54 %29 + OpBranchConditional %30 %24 %25 + %24 = OpLabel + %37 = OpBitcast %6 %54 + %40 = OpIMul %6 %51 %39 + %41 = OpIAdd %6 %37 %40 + %43 = OpConvertUToF %31 %51 + %45 = OpAccessChain %44 %35 %41 + OpStore %45 %43 + OpBranch %26 + %26 = OpLabel + %48 = OpIAdd %19 %54 %47 + OpBranch %23 + %25 = OpLabel + OpBranch %13 + %13 = OpLabel + %50 = OpIAdd %6 %51 %47 + OpBranch %10 + %12 = OpLabel + OpReturn + OpFunctionEnd + )"; + +const std::string output = +R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 410 +OpName %2 "main" +OpName %3 "out_array" +%4 = OpTypeVoid +%5 = OpTypeFunction %4 +%6 = OpTypeInt 32 0 +%7 = OpTypePointer Function %6 +%8 = OpConstant %6 0 +%9 = OpConstant %6 2 +%10 = OpTypeBool +%11 = OpTypeInt 32 1 +%12 = OpTypePointer Function %11 +%13 = OpConstant %11 0 +%14 = OpConstant %11 3 +%15 = OpTypeFloat 32 +%16 = OpConstant %6 6 +%17 = OpTypeArray %15 %16 +%18 = OpTypePointer Function %17 +%19 = OpConstant %6 3 +%20 = OpTypePointer Function %15 +%21 = OpConstant %11 1 +%2 = OpFunction %4 None %5 +%22 = OpLabel +%3 = OpVariable %18 Function +OpBranch %23 +%23 = OpLabel +OpBranch %28 +%28 = OpLabel +%29 = OpULessThan %10 %8 %9 +OpBranch %30 +%30 = OpLabel +OpBranch %31 +%31 = OpLabel +OpBranch %36 +%36 = OpLabel +%37 = OpSLessThan %10 %13 %14 +OpBranch %38 +%38 = OpLabel +%39 = OpBitcast %6 %13 +%40 = OpIMul %6 %8 %19 +%41 = OpIAdd %6 %39 %40 +%42 = OpConvertUToF %15 %8 +%43 = OpAccessChain %20 %3 %41 +OpStore %43 %42 +OpBranch %34 +%34 = OpLabel +%33 = OpIAdd %11 %13 %21 +OpBranch %44 +%44 = OpLabel +OpBranch %46 +%46 = OpLabel +%47 = OpSLessThan %10 %33 %14 +OpBranch %48 +%48 = OpLabel +%49 = OpBitcast %6 %33 +%50 = OpIMul %6 %8 %19 +%51 = OpIAdd %6 %49 %50 +%52 = OpConvertUToF %15 %8 +%53 = OpAccessChain %20 %3 %51 +OpStore %53 %52 +OpBranch %54 +%54 = OpLabel +%55 = OpIAdd %11 %33 %21 +OpBranch %56 +%56 = OpLabel +OpBranch %58 +%58 = OpLabel +%59 = OpSLessThan %10 %55 %14 +OpBranch %60 +%60 = OpLabel +%61 = OpBitcast %6 %55 +%62 = OpIMul %6 %8 %19 +%63 = OpIAdd %6 %61 %62 +%64 = OpConvertUToF %15 %8 +%65 = OpAccessChain %20 %3 %63 +OpStore %65 %64 +OpBranch %66 +%66 = OpLabel +%67 = OpIAdd %11 %55 %21 +OpBranch %35 +%35 = OpLabel +OpBranch %26 +%26 = OpLabel +%25 = OpIAdd %6 %8 %21 +OpBranch %68 +%68 = OpLabel +OpBranch %70 +%70 = OpLabel +%71 = OpULessThan %10 %25 %9 +OpBranch %72 +%72 = OpLabel +OpBranch %73 +%73 = OpLabel +OpBranch %74 +%74 = OpLabel +%75 = OpSLessThan %10 %13 %14 +OpBranch %76 +%76 = OpLabel +%77 = OpBitcast %6 %13 +%78 = OpIMul %6 %25 %19 +%79 = OpIAdd %6 %77 %78 +%80 = OpConvertUToF %15 %25 +%81 = OpAccessChain %20 %3 %79 +OpStore %81 %80 +OpBranch %82 +%82 = OpLabel +%83 = OpIAdd %11 %13 %21 +OpBranch %84 +%84 = OpLabel +OpBranch %85 +%85 = OpLabel +%86 = OpSLessThan %10 %83 %14 +OpBranch %87 +%87 = OpLabel +%88 = OpBitcast %6 %83 +%89 = OpIMul %6 %25 %19 +%90 = OpIAdd %6 %88 %89 +%91 = OpConvertUToF %15 %25 +%92 = OpAccessChain %20 %3 %90 +OpStore %92 %91 +OpBranch %93 +%93 = OpLabel +%94 = OpIAdd %11 %83 %21 +OpBranch %95 +%95 = OpLabel +OpBranch %96 +%96 = OpLabel +%97 = OpSLessThan %10 %94 %14 +OpBranch %98 +%98 = OpLabel +%99 = OpBitcast %6 %94 +%100 = OpIMul %6 %25 %19 +%101 = OpIAdd %6 %99 %100 +%102 = OpConvertUToF %15 %25 +%103 = OpAccessChain %20 %3 %101 +OpStore %103 %102 +OpBranch %104 +%104 = OpLabel +%105 = OpIAdd %11 %94 %21 +OpBranch %106 +%106 = OpLabel +OpBranch %107 +%107 = OpLabel +%108 = OpIAdd %6 %25 %21 +OpBranch %27 +%27 = OpLabel +OpReturn +OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" + << text << std::endl; + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndCheck(text, output, false); +} + +/* +Generated from the following GLSL +#version 410 core +void main() { + float out_array[2]; + for (int i = -3; i < -1; i++) { + out_array[3 + i] = i; + } +} +*/ +TEST_F(PassClassTest, NegativeConditionAndInit) { + // clang-format off + // With opt::LocalMultiStoreElimPass +const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 410 + OpName %4 "main" + OpName %23 "out_array" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 -3 + %16 = OpConstant %6 -1 + %17 = OpTypeBool + %19 = OpTypeInt 32 0 + %20 = OpConstant %19 2 + %21 = OpTypeArray %6 %20 + %22 = OpTypePointer Function %21 + %25 = OpConstant %6 3 + %30 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %23 = OpVariable %22 Function + OpBranch %10 + %10 = OpLabel + %32 = OpPhi %6 %9 %5 %31 %13 + OpLoopMerge %12 %13 Unroll + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %32 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %26 = OpIAdd %6 %32 %25 + %28 = OpAccessChain %7 %23 %26 + OpStore %28 %32 + OpBranch %13 + %13 = OpLabel + %31 = OpIAdd %6 %32 %30 + OpBranch %10 + %12 = OpLabel + OpReturn + OpFunctionEnd +)"; + +const std::string expected = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 410 +OpName %2 "main" +OpName %3 "out_array" +%4 = OpTypeVoid +%5 = OpTypeFunction %4 +%6 = OpTypeInt 32 1 +%7 = OpTypePointer Function %6 +%8 = OpConstant %6 -3 +%9 = OpConstant %6 -1 +%10 = OpTypeBool +%11 = OpTypeInt 32 0 +%12 = OpConstant %11 2 +%13 = OpTypeArray %6 %12 +%14 = OpTypePointer Function %13 +%15 = OpConstant %6 3 +%16 = OpConstant %6 1 +%2 = OpFunction %4 None %5 +%17 = OpLabel +%3 = OpVariable %14 Function +OpBranch %18 +%18 = OpLabel +OpBranch %23 +%23 = OpLabel +%24 = OpSLessThan %10 %8 %9 +OpBranch %25 +%25 = OpLabel +%26 = OpIAdd %6 %8 %15 +%27 = OpAccessChain %7 %3 %26 +OpStore %27 %8 +OpBranch %21 +%21 = OpLabel +%20 = OpIAdd %6 %8 %16 +OpBranch %28 +%28 = OpLabel +OpBranch %30 +%30 = OpLabel +%31 = OpSLessThan %10 %20 %9 +OpBranch %32 +%32 = OpLabel +%33 = OpIAdd %6 %20 %15 +%34 = OpAccessChain %7 %3 %33 +OpStore %34 %20 +OpBranch %35 +%35 = OpLabel +%36 = OpIAdd %6 %20 %16 +OpBranch %22 +%22 = OpLabel +OpReturn +OpFunctionEnd +)"; + // clang-format on + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" + << text << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + // SinglePassRunAndCheck(text, expected, false); + + ir::Function* f = spvtest::GetFunction(module, 4); + + ir::LoopDescriptor& loop_descriptor = *context->GetLoopDescriptor(f); + EXPECT_EQ(loop_descriptor.NumLoops(), 1u); + + ir::Loop& loop = loop_descriptor.GetLoopByIndex(0); + + EXPECT_TRUE(loop.HasUnrollLoopControl()); + + ir::BasicBlock* condition = loop.FindConditionBlock(); + EXPECT_EQ(condition->id(), 14u); + + ir::Instruction* induction = loop.FindConditionVariable(condition); + EXPECT_EQ(induction->result_id(), 32u); + + opt::LoopUtils loop_utils{context.get(), &loop}; + EXPECT_TRUE(loop_utils.CanPerformUnroll()); + + size_t iterations = 0; + EXPECT_TRUE(loop.FindNumberOfIterations(induction, &*condition->ctail(), + &iterations)); + EXPECT_EQ(iterations, 2u); + SinglePassRunAndCheck(text, expected, false); +} + +/* +Generated from the following GLSL +#version 410 core +void main() { + float out_array[9]; + for (int i = -10; i < -1; i++) { + out_array[i] = i; + } +} +*/ +TEST_F(PassClassTest, NegativeConditionAndInitResidualUnroll) { + // clang-format off + // With opt::LocalMultiStoreElimPass +const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 410 + OpName %4 "main" + OpName %23 "out_array" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 -10 + %16 = OpConstant %6 -1 + %17 = OpTypeBool + %19 = OpTypeInt 32 0 + %20 = OpConstant %19 9 + %21 = OpTypeArray %6 %20 + %22 = OpTypePointer Function %21 + %25 = OpConstant %6 10 + %30 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %23 = OpVariable %22 Function + OpBranch %10 + %10 = OpLabel + %32 = OpPhi %6 %9 %5 %31 %13 + OpLoopMerge %12 %13 Unroll + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %32 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %26 = OpIAdd %6 %32 %25 + %28 = OpAccessChain %7 %23 %26 + OpStore %28 %32 + OpBranch %13 + %13 = OpLabel + %31 = OpIAdd %6 %32 %30 + OpBranch %10 + %12 = OpLabel + OpReturn + OpFunctionEnd +)"; + +const std::string expected = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 410 +OpName %2 "main" +OpName %3 "out_array" +%4 = OpTypeVoid +%5 = OpTypeFunction %4 +%6 = OpTypeInt 32 1 +%7 = OpTypePointer Function %6 +%8 = OpConstant %6 -10 +%9 = OpConstant %6 -1 +%10 = OpTypeBool +%11 = OpTypeInt 32 0 +%12 = OpConstant %11 9 +%13 = OpTypeArray %6 %12 +%14 = OpTypePointer Function %13 +%15 = OpConstant %6 10 +%16 = OpConstant %6 1 +%48 = OpConstant %6 -9 +%2 = OpFunction %4 None %5 +%17 = OpLabel +%3 = OpVariable %14 Function +OpBranch %18 +%18 = OpLabel +%19 = OpPhi %6 %8 %17 %20 %21 +OpLoopMerge %28 %21 Unroll +OpBranch %23 +%23 = OpLabel +%24 = OpSLessThan %10 %19 %48 +OpBranchConditional %24 %25 %28 +%25 = OpLabel +%26 = OpIAdd %6 %19 %15 +%27 = OpAccessChain %7 %3 %26 +OpStore %27 %19 +OpBranch %21 +%21 = OpLabel +%20 = OpIAdd %6 %19 %16 +OpBranch %18 +%28 = OpLabel +OpBranch %29 +%29 = OpLabel +%30 = OpPhi %6 %19 %28 %47 %46 +OpLoopMerge %38 %46 DontUnroll +OpBranch %31 +%31 = OpLabel +%32 = OpSLessThan %10 %30 %9 +OpBranchConditional %32 %33 %38 +%33 = OpLabel +%34 = OpIAdd %6 %30 %15 +%35 = OpAccessChain %7 %3 %34 +OpStore %35 %30 +OpBranch %36 +%36 = OpLabel +%37 = OpIAdd %6 %30 %16 +OpBranch %39 +%39 = OpLabel +OpBranch %41 +%41 = OpLabel +%42 = OpSLessThan %10 %37 %9 +OpBranch %43 +%43 = OpLabel +%44 = OpIAdd %6 %37 %15 +%45 = OpAccessChain %7 %3 %44 +OpStore %45 %37 +OpBranch %46 +%46 = OpLabel +%47 = OpIAdd %6 %37 %16 +OpBranch %29 +%38 = OpLabel +OpReturn +%22 = OpLabel +OpReturn +OpFunctionEnd +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" + << text << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + + ir::Function* f = spvtest::GetFunction(module, 4); + + ir::LoopDescriptor& loop_descriptor = *context->GetLoopDescriptor(f); + EXPECT_EQ(loop_descriptor.NumLoops(), 1u); + + ir::Loop& loop = loop_descriptor.GetLoopByIndex(0); + + EXPECT_TRUE(loop.HasUnrollLoopControl()); + + ir::BasicBlock* condition = loop.FindConditionBlock(); + EXPECT_EQ(condition->id(), 14u); + + ir::Instruction* induction = loop.FindConditionVariable(condition); + EXPECT_EQ(induction->result_id(), 32u); + + opt::LoopUtils loop_utils{context.get(), &loop}; + EXPECT_TRUE(loop_utils.CanPerformUnroll()); + + size_t iterations = 0; + EXPECT_TRUE(loop.FindNumberOfIterations(induction, &*condition->ctail(), + &iterations)); + EXPECT_EQ(iterations, 9u); + SinglePassRunAndCheck>(text, expected, false); +} + +/* +Generated from the following GLSL +#version 410 core +void main() { + float out_array[10]; + for (uint i = 0; i < 2; i++) { + for (int x = 0; x < 5; ++x) { + out_array[x + i*5] = i; + } + } +} +*/ +TEST_F(PassClassTest, UnrollNestedLoopsValidateDescriptor) { + // clang-format off + // With opt::LocalMultiStoreElimPass + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 410 + OpName %4 "main" + OpName %35 "out_array" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 0 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 2 + %17 = OpTypeBool + %19 = OpTypeInt 32 1 + %20 = OpTypePointer Function %19 + %22 = OpConstant %19 0 + %29 = OpConstant %19 5 + %31 = OpTypeFloat 32 + %32 = OpConstant %6 10 + %33 = OpTypeArray %31 %32 + %34 = OpTypePointer Function %33 + %39 = OpConstant %6 5 + %44 = OpTypePointer Function %31 + %47 = OpConstant %19 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %35 = OpVariable %34 Function + OpBranch %10 + %10 = OpLabel + %51 = OpPhi %6 %9 %5 %50 %13 + OpLoopMerge %12 %13 Unroll + OpBranch %14 + %14 = OpLabel + %18 = OpULessThan %17 %51 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpBranch %23 + %23 = OpLabel + %54 = OpPhi %19 %22 %11 %48 %26 + OpLoopMerge %25 %26 Unroll + OpBranch %27 + %27 = OpLabel + %30 = OpSLessThan %17 %54 %29 + OpBranchConditional %30 %24 %25 + %24 = OpLabel + %37 = OpBitcast %6 %54 + %40 = OpIMul %6 %51 %39 + %41 = OpIAdd %6 %37 %40 + %43 = OpConvertUToF %31 %51 + %45 = OpAccessChain %44 %35 %41 + OpStore %45 %43 + OpBranch %26 + %26 = OpLabel + %48 = OpIAdd %19 %54 %47 + OpBranch %23 + %25 = OpLabel + OpBranch %13 + %13 = OpLabel + %50 = OpIAdd %6 %51 %47 + OpBranch %10 + %12 = OpLabel + OpReturn + OpFunctionEnd + )"; + + // clang-format on + + { // Test fully unroll + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" + << text << std::endl; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + + ir::Function* f = spvtest::GetFunction(module, 4); + ir::LoopDescriptor& loop_descriptor = *context->GetLoopDescriptor(f); + EXPECT_EQ(loop_descriptor.NumLoops(), 2u); + + ir::Loop& outer_loop = loop_descriptor.GetLoopByIndex(1); + + EXPECT_TRUE(outer_loop.HasUnrollLoopControl()); + + ir::Loop& inner_loop = loop_descriptor.GetLoopByIndex(0); + + EXPECT_TRUE(inner_loop.HasUnrollLoopControl()); + + EXPECT_EQ(outer_loop.GetBlocks().size(), 9u); + + EXPECT_EQ(inner_loop.GetBlocks().size(), 4u); + EXPECT_EQ(outer_loop.NumImmediateChildren(), 1u); + EXPECT_EQ(inner_loop.NumImmediateChildren(), 0u); + + { + opt::LoopUtils loop_utils{context.get(), &inner_loop}; + loop_utils.FullyUnroll(); + loop_utils.Finalize(); + } + + EXPECT_EQ(loop_descriptor.NumLoops(), 1u); + EXPECT_EQ(outer_loop.GetBlocks().size(), 25u); + EXPECT_EQ(outer_loop.NumImmediateChildren(), 0u); + { + opt::LoopUtils loop_utils{context.get(), &outer_loop}; + loop_utils.FullyUnroll(); + loop_utils.Finalize(); + } + EXPECT_EQ(loop_descriptor.NumLoops(), 0u); + } + + { // Test partially unroll + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" + << text << std::endl; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + + ir::Function* f = spvtest::GetFunction(module, 4); + ir::LoopDescriptor& loop_descriptor = *context->GetLoopDescriptor(f); + EXPECT_EQ(loop_descriptor.NumLoops(), 2u); + + ir::Loop& outer_loop = loop_descriptor.GetLoopByIndex(1); + + EXPECT_TRUE(outer_loop.HasUnrollLoopControl()); + + ir::Loop& inner_loop = loop_descriptor.GetLoopByIndex(0); + + EXPECT_TRUE(inner_loop.HasUnrollLoopControl()); + + EXPECT_EQ(outer_loop.GetBlocks().size(), 9u); + + EXPECT_EQ(inner_loop.GetBlocks().size(), 4u); + + EXPECT_EQ(outer_loop.NumImmediateChildren(), 1u); + EXPECT_EQ(inner_loop.NumImmediateChildren(), 0u); + + opt::LoopUtils loop_utils{context.get(), &inner_loop}; + loop_utils.PartiallyUnroll(2); + loop_utils.Finalize(); + + // The number of loops should actually grow. + EXPECT_EQ(loop_descriptor.NumLoops(), 3u); + EXPECT_EQ(outer_loop.GetBlocks().size(), 18u); + EXPECT_EQ(outer_loop.NumImmediateChildren(), 2u); + } +} + +/* +Generated from the following GLSL +#version 410 core +void main() { + float out_array[3]; + for (int i = 3; i > 0; --i) { + out_array[i] = i; + } +} +*/ +TEST_F(PassClassTest, FullyUnrollNegativeStepLoopTest) { + // clang-format off + // With opt::LocalMultiStoreElimPass + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 410 + OpName %4 "main" + OpName %24 "out_array" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 3 + %16 = OpConstant %6 0 + %17 = OpTypeBool + %19 = OpTypeFloat 32 + %20 = OpTypeInt 32 0 + %21 = OpConstant %20 3 + %22 = OpTypeArray %19 %21 + %23 = OpTypePointer Function %22 + %28 = OpTypePointer Function %19 + %31 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %24 = OpVariable %23 Function + OpBranch %10 + %10 = OpLabel + %33 = OpPhi %6 %9 %5 %32 %13 + OpLoopMerge %12 %13 Unroll + OpBranch %14 + %14 = OpLabel + %18 = OpSGreaterThan %17 %33 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %27 = OpConvertSToF %19 %33 + %29 = OpAccessChain %28 %24 %33 + OpStore %29 %27 + OpBranch %13 + %13 = OpLabel + %32 = OpISub %6 %33 %31 + OpBranch %10 + %12 = OpLabel + OpReturn + OpFunctionEnd + )"; + +const std::string output = +R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 410 +OpName %2 "main" +OpName %3 "out_array" +%4 = OpTypeVoid +%5 = OpTypeFunction %4 +%6 = OpTypeInt 32 1 +%7 = OpTypePointer Function %6 +%8 = OpConstant %6 3 +%9 = OpConstant %6 0 +%10 = OpTypeBool +%11 = OpTypeFloat 32 +%12 = OpTypeInt 32 0 +%13 = OpConstant %12 3 +%14 = OpTypeArray %11 %13 +%15 = OpTypePointer Function %14 +%16 = OpTypePointer Function %11 +%17 = OpConstant %6 1 +%2 = OpFunction %4 None %5 +%18 = OpLabel +%3 = OpVariable %15 Function +OpBranch %19 +%19 = OpLabel +OpBranch %24 +%24 = OpLabel +%25 = OpSGreaterThan %10 %8 %9 +OpBranch %26 +%26 = OpLabel +%27 = OpConvertSToF %11 %8 +%28 = OpAccessChain %16 %3 %8 +OpStore %28 %27 +OpBranch %22 +%22 = OpLabel +%21 = OpISub %6 %8 %17 +OpBranch %29 +%29 = OpLabel +OpBranch %31 +%31 = OpLabel +%32 = OpSGreaterThan %10 %21 %9 +OpBranch %33 +%33 = OpLabel +%34 = OpConvertSToF %11 %21 +%35 = OpAccessChain %16 %3 %21 +OpStore %35 %34 +OpBranch %36 +%36 = OpLabel +%37 = OpISub %6 %21 %17 +OpBranch %38 +%38 = OpLabel +OpBranch %40 +%40 = OpLabel +%41 = OpSGreaterThan %10 %37 %9 +OpBranch %42 +%42 = OpLabel +%43 = OpConvertSToF %11 %37 +%44 = OpAccessChain %16 %3 %37 +OpStore %44 %43 +OpBranch %45 +%45 = OpLabel +%46 = OpISub %6 %37 %17 +OpBranch %23 +%23 = OpLabel +OpReturn +OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" + << text << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndCheck(text, output, false); +} + +/* +Generated from the following GLSL +#version 410 core +void main() { + float out_array[3]; + for (int i = 9; i > 0; i-=3) { + out_array[i] = i; + } +} +*/ +TEST_F(PassClassTest, FullyUnrollNegativeNonOneStepLoop) { + // clang-format off + // With opt::LocalMultiStoreElimPass + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 410 + OpName %4 "main" + OpName %24 "out_array" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 9 + %16 = OpConstant %6 0 + %17 = OpTypeBool + %19 = OpTypeFloat 32 + %20 = OpTypeInt 32 0 + %21 = OpConstant %20 3 + %22 = OpTypeArray %19 %21 + %23 = OpTypePointer Function %22 + %28 = OpTypePointer Function %19 + %30 = OpConstant %6 3 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %24 = OpVariable %23 Function + OpBranch %10 + %10 = OpLabel + %33 = OpPhi %6 %9 %5 %32 %13 + OpLoopMerge %12 %13 Unroll + OpBranch %14 + %14 = OpLabel + %18 = OpSGreaterThan %17 %33 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %27 = OpConvertSToF %19 %33 + %29 = OpAccessChain %28 %24 %33 + OpStore %29 %27 + OpBranch %13 + %13 = OpLabel + %32 = OpISub %6 %33 %30 + OpBranch %10 + %12 = OpLabel + OpReturn + OpFunctionEnd + )"; + +const std::string output = +R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 410 +OpName %2 "main" +OpName %3 "out_array" +%4 = OpTypeVoid +%5 = OpTypeFunction %4 +%6 = OpTypeInt 32 1 +%7 = OpTypePointer Function %6 +%8 = OpConstant %6 9 +%9 = OpConstant %6 0 +%10 = OpTypeBool +%11 = OpTypeFloat 32 +%12 = OpTypeInt 32 0 +%13 = OpConstant %12 3 +%14 = OpTypeArray %11 %13 +%15 = OpTypePointer Function %14 +%16 = OpTypePointer Function %11 +%17 = OpConstant %6 3 +%2 = OpFunction %4 None %5 +%18 = OpLabel +%3 = OpVariable %15 Function +OpBranch %19 +%19 = OpLabel +OpBranch %24 +%24 = OpLabel +%25 = OpSGreaterThan %10 %8 %9 +OpBranch %26 +%26 = OpLabel +%27 = OpConvertSToF %11 %8 +%28 = OpAccessChain %16 %3 %8 +OpStore %28 %27 +OpBranch %22 +%22 = OpLabel +%21 = OpISub %6 %8 %17 +OpBranch %29 +%29 = OpLabel +OpBranch %31 +%31 = OpLabel +%32 = OpSGreaterThan %10 %21 %9 +OpBranch %33 +%33 = OpLabel +%34 = OpConvertSToF %11 %21 +%35 = OpAccessChain %16 %3 %21 +OpStore %35 %34 +OpBranch %36 +%36 = OpLabel +%37 = OpISub %6 %21 %17 +OpBranch %38 +%38 = OpLabel +OpBranch %40 +%40 = OpLabel +%41 = OpSGreaterThan %10 %37 %9 +OpBranch %42 +%42 = OpLabel +%43 = OpConvertSToF %11 %37 +%44 = OpAccessChain %16 %3 %37 +OpStore %44 %43 +OpBranch %45 +%45 = OpLabel +%46 = OpISub %6 %37 %17 +OpBranch %23 +%23 = OpLabel +OpReturn +OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" + << text << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndCheck(text, output, false); +} + +/* +Generated from the following GLSL +#version 410 core +void main() { + float out_array[3]; + for (int i = 0; i < 7; i+=3) { + out_array[i] = i; + } +} +*/ +TEST_F(PassClassTest, FullyUnrollNonDivisibleStepLoop) { + // clang-format off + // With opt::LocalMultiStoreElimPass + const std::string text = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %4 "main" +OpExecutionMode %4 OriginUpperLeft +OpSource GLSL 410 +OpName %4 "main" +OpName %24 "out_array" +%2 = OpTypeVoid +%3 = OpTypeFunction %2 +%6 = OpTypeInt 32 1 +%7 = OpTypePointer Function %6 +%9 = OpConstant %6 0 +%16 = OpConstant %6 7 +%17 = OpTypeBool +%19 = OpTypeFloat 32 +%20 = OpTypeInt 32 0 +%21 = OpConstant %20 3 +%22 = OpTypeArray %19 %21 +%23 = OpTypePointer Function %22 +%28 = OpTypePointer Function %19 +%30 = OpConstant %6 3 +%4 = OpFunction %2 None %3 +%5 = OpLabel +%24 = OpVariable %23 Function +OpBranch %10 +%10 = OpLabel +%33 = OpPhi %6 %9 %5 %32 %13 +OpLoopMerge %12 %13 Unroll +OpBranch %14 +%14 = OpLabel +%18 = OpSLessThan %17 %33 %16 +OpBranchConditional %18 %11 %12 +%11 = OpLabel +%27 = OpConvertSToF %19 %33 +%29 = OpAccessChain %28 %24 %33 +OpStore %29 %27 +OpBranch %13 +%13 = OpLabel +%32 = OpIAdd %6 %33 %30 +OpBranch %10 +%12 = OpLabel +OpReturn +OpFunctionEnd +)"; + +const std::string output = +R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 410 +OpName %2 "main" +OpName %3 "out_array" +%4 = OpTypeVoid +%5 = OpTypeFunction %4 +%6 = OpTypeInt 32 1 +%7 = OpTypePointer Function %6 +%8 = OpConstant %6 0 +%9 = OpConstant %6 7 +%10 = OpTypeBool +%11 = OpTypeFloat 32 +%12 = OpTypeInt 32 0 +%13 = OpConstant %12 3 +%14 = OpTypeArray %11 %13 +%15 = OpTypePointer Function %14 +%16 = OpTypePointer Function %11 +%17 = OpConstant %6 3 +%2 = OpFunction %4 None %5 +%18 = OpLabel +%3 = OpVariable %15 Function +OpBranch %19 +%19 = OpLabel +OpBranch %24 +%24 = OpLabel +%25 = OpSLessThan %10 %8 %9 +OpBranch %26 +%26 = OpLabel +%27 = OpConvertSToF %11 %8 +%28 = OpAccessChain %16 %3 %8 +OpStore %28 %27 +OpBranch %22 +%22 = OpLabel +%21 = OpIAdd %6 %8 %17 +OpBranch %29 +%29 = OpLabel +OpBranch %31 +%31 = OpLabel +%32 = OpSLessThan %10 %21 %9 +OpBranch %33 +%33 = OpLabel +%34 = OpConvertSToF %11 %21 +%35 = OpAccessChain %16 %3 %21 +OpStore %35 %34 +OpBranch %36 +%36 = OpLabel +%37 = OpIAdd %6 %21 %17 +OpBranch %38 +%38 = OpLabel +OpBranch %40 +%40 = OpLabel +%41 = OpSLessThan %10 %37 %9 +OpBranch %42 +%42 = OpLabel +%43 = OpConvertSToF %11 %37 +%44 = OpAccessChain %16 %3 %37 +OpStore %44 %43 +OpBranch %45 +%45 = OpLabel +%46 = OpIAdd %6 %37 %17 +OpBranch %23 +%23 = OpLabel +OpReturn +OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" + << text << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndCheck(text, output, false); +} + +/* +Generated from the following GLSL +#version 410 core +void main() { + float out_array[4]; + for (int i = 11; i > 0; i-=3) { + out_array[i] = i; + } +} +*/ +TEST_F(PassClassTest, FullyUnrollNegativeNonDivisibleStepLoop) { + // clang-format off + // With opt::LocalMultiStoreElimPass + const std::string text = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %4 "main" +OpExecutionMode %4 OriginUpperLeft +OpSource GLSL 410 +OpName %4 "main" +OpName %24 "out_array" +%2 = OpTypeVoid +%3 = OpTypeFunction %2 +%6 = OpTypeInt 32 1 +%7 = OpTypePointer Function %6 +%9 = OpConstant %6 11 +%16 = OpConstant %6 0 +%17 = OpTypeBool +%19 = OpTypeFloat 32 +%20 = OpTypeInt 32 0 +%21 = OpConstant %20 4 +%22 = OpTypeArray %19 %21 +%23 = OpTypePointer Function %22 +%28 = OpTypePointer Function %19 +%30 = OpConstant %6 3 +%4 = OpFunction %2 None %3 +%5 = OpLabel +%24 = OpVariable %23 Function +OpBranch %10 +%10 = OpLabel +%33 = OpPhi %6 %9 %5 %32 %13 +OpLoopMerge %12 %13 Unroll +OpBranch %14 +%14 = OpLabel +%18 = OpSGreaterThan %17 %33 %16 +OpBranchConditional %18 %11 %12 +%11 = OpLabel +%27 = OpConvertSToF %19 %33 +%29 = OpAccessChain %28 %24 %33 +OpStore %29 %27 +OpBranch %13 +%13 = OpLabel +%32 = OpISub %6 %33 %30 +OpBranch %10 +%12 = OpLabel +OpReturn +OpFunctionEnd +)"; + +const std::string output = +R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 410 +OpName %2 "main" +OpName %3 "out_array" +%4 = OpTypeVoid +%5 = OpTypeFunction %4 +%6 = OpTypeInt 32 1 +%7 = OpTypePointer Function %6 +%8 = OpConstant %6 11 +%9 = OpConstant %6 0 +%10 = OpTypeBool +%11 = OpTypeFloat 32 +%12 = OpTypeInt 32 0 +%13 = OpConstant %12 4 +%14 = OpTypeArray %11 %13 +%15 = OpTypePointer Function %14 +%16 = OpTypePointer Function %11 +%17 = OpConstant %6 3 +%2 = OpFunction %4 None %5 +%18 = OpLabel +%3 = OpVariable %15 Function +OpBranch %19 +%19 = OpLabel +OpBranch %24 +%24 = OpLabel +%25 = OpSGreaterThan %10 %8 %9 +OpBranch %26 +%26 = OpLabel +%27 = OpConvertSToF %11 %8 +%28 = OpAccessChain %16 %3 %8 +OpStore %28 %27 +OpBranch %22 +%22 = OpLabel +%21 = OpISub %6 %8 %17 +OpBranch %29 +%29 = OpLabel +OpBranch %31 +%31 = OpLabel +%32 = OpSGreaterThan %10 %21 %9 +OpBranch %33 +%33 = OpLabel +%34 = OpConvertSToF %11 %21 +%35 = OpAccessChain %16 %3 %21 +OpStore %35 %34 +OpBranch %36 +%36 = OpLabel +%37 = OpISub %6 %21 %17 +OpBranch %38 +%38 = OpLabel +OpBranch %40 +%40 = OpLabel +%41 = OpSGreaterThan %10 %37 %9 +OpBranch %42 +%42 = OpLabel +%43 = OpConvertSToF %11 %37 +%44 = OpAccessChain %16 %3 %37 +OpStore %44 %43 +OpBranch %45 +%45 = OpLabel +%46 = OpISub %6 %37 %17 +OpBranch %47 +%47 = OpLabel +OpBranch %49 +%49 = OpLabel +%50 = OpSGreaterThan %10 %46 %9 +OpBranch %51 +%51 = OpLabel +%52 = OpConvertSToF %11 %46 +%53 = OpAccessChain %16 %3 %46 +OpStore %53 %52 +OpBranch %54 +%54 = OpLabel +%55 = OpISub %6 %46 %17 +OpBranch %23 +%23 = OpLabel +OpReturn +OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" + << text << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndCheck(text, output, false); +} + +// clang-format off +// With opt::LocalMultiStoreElimPass +static const std::string multiple_phi_shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 410 + OpName %4 "main" + OpName %8 "foo(" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypeFunction %6 + %10 = OpTypePointer Function %6 + %12 = OpConstant %6 0 + %14 = OpConstant %6 3 + %22 = OpConstant %6 6 + %23 = OpTypeBool + %31 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %40 = OpFunctionCall %6 %8 + OpReturn + OpFunctionEnd + %8 = OpFunction %6 None %7 + %9 = OpLabel + OpBranch %16 + %16 = OpLabel + %41 = OpPhi %6 %12 %9 %34 %19 + %42 = OpPhi %6 %14 %9 %29 %19 + %43 = OpPhi %6 %12 %9 %32 %19 + OpLoopMerge %18 %19 Unroll + OpBranch %20 + %20 = OpLabel + %24 = OpSLessThan %23 %43 %22 + OpBranchConditional %24 %17 %18 + %17 = OpLabel + %27 = OpIMul %6 %43 %41 + %29 = OpIAdd %6 %42 %27 + OpBranch %19 + %19 = OpLabel + %32 = OpIAdd %6 %43 %31 + %34 = OpISub %6 %41 %31 + OpBranch %16 + %18 = OpLabel + %37 = OpIAdd %6 %42 %41 + OpReturnValue %37 + OpFunctionEnd + )"; +// clang-format on + +TEST_F(PassClassTest, PartiallyUnrollResidualMultipleInductionVariables) { + // clang-format off +const std::string output = +R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 410 +OpName %2 "main" +OpName %3 "foo(" +%4 = OpTypeVoid +%5 = OpTypeFunction %4 +%6 = OpTypeInt 32 1 +%7 = OpTypeFunction %6 +%8 = OpTypePointer Function %6 +%9 = OpConstant %6 0 +%10 = OpConstant %6 3 +%11 = OpConstant %6 6 +%12 = OpTypeBool +%13 = OpConstant %6 1 +%82 = OpTypeInt 32 0 +%83 = OpConstant %82 2 +%2 = OpFunction %4 None %5 +%14 = OpLabel +%15 = OpFunctionCall %6 %3 +OpReturn +OpFunctionEnd +%3 = OpFunction %6 None %7 +%16 = OpLabel +OpBranch %17 +%17 = OpLabel +%18 = OpPhi %6 %9 %16 %19 %20 +%21 = OpPhi %6 %10 %16 %22 %20 +%23 = OpPhi %6 %9 %16 %24 %20 +OpLoopMerge %31 %20 Unroll +OpBranch %26 +%26 = OpLabel +%27 = OpSLessThan %12 %23 %83 +OpBranchConditional %27 %28 %31 +%28 = OpLabel +%29 = OpIMul %6 %23 %18 +%22 = OpIAdd %6 %21 %29 +OpBranch %20 +%20 = OpLabel +%24 = OpIAdd %6 %23 %13 +%19 = OpISub %6 %18 %13 +OpBranch %17 +%31 = OpLabel +OpBranch %32 +%32 = OpLabel +%33 = OpPhi %6 %18 %31 %81 %79 +%34 = OpPhi %6 %21 %31 %78 %79 +%35 = OpPhi %6 %23 %31 %80 %79 +OpLoopMerge %44 %79 DontUnroll +OpBranch %36 +%36 = OpLabel +%37 = OpSLessThan %12 %35 %11 +OpBranchConditional %37 %38 %44 +%38 = OpLabel +%39 = OpIMul %6 %35 %33 +%40 = OpIAdd %6 %34 %39 +OpBranch %41 +%41 = OpLabel +%42 = OpIAdd %6 %35 %13 +%43 = OpISub %6 %33 %13 +OpBranch %46 +%46 = OpLabel +OpBranch %50 +%50 = OpLabel +%51 = OpSLessThan %12 %42 %11 +OpBranch %52 +%52 = OpLabel +%53 = OpIMul %6 %42 %43 +%54 = OpIAdd %6 %40 %53 +OpBranch %55 +%55 = OpLabel +%56 = OpIAdd %6 %42 %13 +%57 = OpISub %6 %43 %13 +OpBranch %58 +%58 = OpLabel +OpBranch %62 +%62 = OpLabel +%63 = OpSLessThan %12 %56 %11 +OpBranch %64 +%64 = OpLabel +%65 = OpIMul %6 %56 %57 +%66 = OpIAdd %6 %54 %65 +OpBranch %67 +%67 = OpLabel +%68 = OpIAdd %6 %56 %13 +%69 = OpISub %6 %57 %13 +OpBranch %70 +%70 = OpLabel +OpBranch %74 +%74 = OpLabel +%75 = OpSLessThan %12 %68 %11 +OpBranch %76 +%76 = OpLabel +%77 = OpIMul %6 %68 %69 +%78 = OpIAdd %6 %66 %77 +OpBranch %79 +%79 = OpLabel +%80 = OpIAdd %6 %68 %13 +%81 = OpISub %6 %69 %13 +OpBranch %32 +%44 = OpLabel +%45 = OpIAdd %6 %34 %33 +OpReturnValue %45 +%25 = OpLabel +%30 = OpIAdd %6 %34 %33 +OpReturnValue %30 +OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, multiple_phi_shader, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" + << multiple_phi_shader << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndCheck>(multiple_phi_shader, output, + false); +} + +TEST_F(PassClassTest, PartiallyUnrollMultipleInductionVariables) { + // clang-format off +const std::string output = +R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 410 +OpName %2 "main" +OpName %3 "foo(" +%4 = OpTypeVoid +%5 = OpTypeFunction %4 +%6 = OpTypeInt 32 1 +%7 = OpTypeFunction %6 +%8 = OpTypePointer Function %6 +%9 = OpConstant %6 0 +%10 = OpConstant %6 3 +%11 = OpConstant %6 6 +%12 = OpTypeBool +%13 = OpConstant %6 1 +%2 = OpFunction %4 None %5 +%14 = OpLabel +%15 = OpFunctionCall %6 %3 +OpReturn +OpFunctionEnd +%3 = OpFunction %6 None %7 +%16 = OpLabel +OpBranch %17 +%17 = OpLabel +%18 = OpPhi %6 %9 %16 %42 %40 +%21 = OpPhi %6 %10 %16 %39 %40 +%23 = OpPhi %6 %9 %16 %41 %40 +OpLoopMerge %25 %40 DontUnroll +OpBranch %26 +%26 = OpLabel +%27 = OpSLessThan %12 %23 %11 +OpBranchConditional %27 %28 %25 +%28 = OpLabel +%29 = OpIMul %6 %23 %18 +%22 = OpIAdd %6 %21 %29 +OpBranch %20 +%20 = OpLabel +%24 = OpIAdd %6 %23 %13 +%19 = OpISub %6 %18 %13 +OpBranch %31 +%31 = OpLabel +OpBranch %35 +%35 = OpLabel +%36 = OpSLessThan %12 %24 %11 +OpBranch %37 +%37 = OpLabel +%38 = OpIMul %6 %24 %19 +%39 = OpIAdd %6 %22 %38 +OpBranch %40 +%40 = OpLabel +%41 = OpIAdd %6 %24 %13 +%42 = OpISub %6 %19 %13 +OpBranch %17 +%25 = OpLabel +%30 = OpIAdd %6 %21 %18 +OpReturnValue %30 +OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, multiple_phi_shader, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" + << multiple_phi_shader << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndCheck>(multiple_phi_shader, output, + false); +} + +TEST_F(PassClassTest, FullyUnrollMultipleInductionVariables) { + // clang-format off +const std::string output = +R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 410 +OpName %2 "main" +OpName %3 "foo(" +%4 = OpTypeVoid +%5 = OpTypeFunction %4 +%6 = OpTypeInt 32 1 +%7 = OpTypeFunction %6 +%8 = OpTypePointer Function %6 +%9 = OpConstant %6 0 +%10 = OpConstant %6 3 +%11 = OpConstant %6 6 +%12 = OpTypeBool +%13 = OpConstant %6 1 +%2 = OpFunction %4 None %5 +%14 = OpLabel +%15 = OpFunctionCall %6 %3 +OpReturn +OpFunctionEnd +%3 = OpFunction %6 None %7 +%16 = OpLabel +OpBranch %17 +%17 = OpLabel +OpBranch %26 +%26 = OpLabel +%27 = OpSLessThan %12 %9 %11 +OpBranch %28 +%28 = OpLabel +%29 = OpIMul %6 %9 %9 +%22 = OpIAdd %6 %10 %29 +OpBranch %20 +%20 = OpLabel +%24 = OpIAdd %6 %9 %13 +%19 = OpISub %6 %9 %13 +OpBranch %31 +%31 = OpLabel +OpBranch %35 +%35 = OpLabel +%36 = OpSLessThan %12 %24 %11 +OpBranch %37 +%37 = OpLabel +%38 = OpIMul %6 %24 %19 +%39 = OpIAdd %6 %22 %38 +OpBranch %40 +%40 = OpLabel +%41 = OpIAdd %6 %24 %13 +%42 = OpISub %6 %19 %13 +OpBranch %43 +%43 = OpLabel +OpBranch %47 +%47 = OpLabel +%48 = OpSLessThan %12 %41 %11 +OpBranch %49 +%49 = OpLabel +%50 = OpIMul %6 %41 %42 +%51 = OpIAdd %6 %39 %50 +OpBranch %52 +%52 = OpLabel +%53 = OpIAdd %6 %41 %13 +%54 = OpISub %6 %42 %13 +OpBranch %55 +%55 = OpLabel +OpBranch %59 +%59 = OpLabel +%60 = OpSLessThan %12 %53 %11 +OpBranch %61 +%61 = OpLabel +%62 = OpIMul %6 %53 %54 +%63 = OpIAdd %6 %51 %62 +OpBranch %64 +%64 = OpLabel +%65 = OpIAdd %6 %53 %13 +%66 = OpISub %6 %54 %13 +OpBranch %67 +%67 = OpLabel +OpBranch %71 +%71 = OpLabel +%72 = OpSLessThan %12 %65 %11 +OpBranch %73 +%73 = OpLabel +%74 = OpIMul %6 %65 %66 +%75 = OpIAdd %6 %63 %74 +OpBranch %76 +%76 = OpLabel +%77 = OpIAdd %6 %65 %13 +%78 = OpISub %6 %66 %13 +OpBranch %79 +%79 = OpLabel +OpBranch %83 +%83 = OpLabel +%84 = OpSLessThan %12 %77 %11 +OpBranch %85 +%85 = OpLabel +%86 = OpIMul %6 %77 %78 +%87 = OpIAdd %6 %75 %86 +OpBranch %88 +%88 = OpLabel +%89 = OpIAdd %6 %77 %13 +%90 = OpISub %6 %78 %13 +OpBranch %25 +%25 = OpLabel +%30 = OpIAdd %6 %87 %90 +OpReturnValue %30 +OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, multiple_phi_shader, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" + << multiple_phi_shader << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndCheck(multiple_phi_shader, output, false); +} + +/* +Generated from the following GLSL +#version 440 core +void main() +{ + int j = 0; + for (int i = 0; i <= 2; ++i) + ++j; + + for (int i = 1; i >= 0; --i) + ++j; +} +*/ +TEST_F(PassClassTest, FullyUnrollEqualToOperations) { + // clang-format off + // With opt::LocalMultiStoreElimPass + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %17 = OpConstant %6 2 + %18 = OpTypeBool + %21 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpBranch %11 + %11 = OpLabel + %37 = OpPhi %6 %9 %5 %22 %14 + %38 = OpPhi %6 %9 %5 %24 %14 + OpLoopMerge %13 %14 Unroll + OpBranch %15 + %15 = OpLabel + %19 = OpSLessThanEqual %18 %38 %17 + OpBranchConditional %19 %12 %13 + %12 = OpLabel + %22 = OpIAdd %6 %37 %21 + OpBranch %14 + %14 = OpLabel + %24 = OpIAdd %6 %38 %21 + OpBranch %11 + %13 = OpLabel + OpBranch %26 + %26 = OpLabel + %39 = OpPhi %6 %37 %13 %34 %29 + %40 = OpPhi %6 %21 %13 %36 %29 + OpLoopMerge %28 %29 Unroll + OpBranch %30 + %30 = OpLabel + %32 = OpSGreaterThanEqual %18 %40 %9 + OpBranchConditional %32 %27 %28 + %27 = OpLabel + %34 = OpIAdd %6 %39 %21 + OpBranch %29 + %29 = OpLabel + %36 = OpISub %6 %40 %21 + OpBranch %26 + %28 = OpLabel + OpReturn + OpFunctionEnd + )"; + +const std::string output = +R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 440 +OpName %2 "main" +%3 = OpTypeVoid +%4 = OpTypeFunction %3 +%5 = OpTypeInt 32 1 +%6 = OpTypePointer Function %5 +%7 = OpConstant %5 0 +%8 = OpConstant %5 2 +%9 = OpTypeBool +%10 = OpConstant %5 1 +%2 = OpFunction %3 None %4 +%11 = OpLabel +OpBranch %12 +%12 = OpLabel +OpBranch %19 +%19 = OpLabel +%20 = OpSLessThanEqual %9 %7 %8 +OpBranch %21 +%21 = OpLabel +%14 = OpIAdd %5 %7 %10 +OpBranch %15 +%15 = OpLabel +%17 = OpIAdd %5 %7 %10 +OpBranch %41 +%41 = OpLabel +OpBranch %44 +%44 = OpLabel +%45 = OpSLessThanEqual %9 %17 %8 +OpBranch %46 +%46 = OpLabel +%47 = OpIAdd %5 %14 %10 +OpBranch %48 +%48 = OpLabel +%49 = OpIAdd %5 %17 %10 +OpBranch %50 +%50 = OpLabel +OpBranch %53 +%53 = OpLabel +%54 = OpSLessThanEqual %9 %49 %8 +OpBranch %55 +%55 = OpLabel +%56 = OpIAdd %5 %47 %10 +OpBranch %57 +%57 = OpLabel +%58 = OpIAdd %5 %49 %10 +OpBranch %18 +%18 = OpLabel +OpBranch %22 +%22 = OpLabel +OpBranch %29 +%29 = OpLabel +%30 = OpSGreaterThanEqual %9 %10 %7 +OpBranch %31 +%31 = OpLabel +%24 = OpIAdd %5 %56 %10 +OpBranch %25 +%25 = OpLabel +%27 = OpISub %5 %10 %10 +OpBranch %32 +%32 = OpLabel +OpBranch %35 +%35 = OpLabel +%36 = OpSGreaterThanEqual %9 %27 %7 +OpBranch %37 +%37 = OpLabel +%38 = OpIAdd %5 %24 %10 +OpBranch %39 +%39 = OpLabel +%40 = OpISub %5 %27 %10 +OpBranch %28 +%28 = OpLabel +OpReturn +OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" + << text << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndCheck(text, output, false); +} + +// clang-format off + // With opt::LocalMultiStoreElimPass + const std::string condition_in_header = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %o + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 430 + OpDecorate %o Location 0 + %void = OpTypeVoid + %6 = OpTypeFunction %void + %int = OpTypeInt 32 1 + %int_n2 = OpConstant %int -2 + %int_2 = OpConstant %int 2 + %bool = OpTypeBool + %float = OpTypeFloat 32 +%_ptr_Output_float = OpTypePointer Output %float + %o = OpVariable %_ptr_Output_float Output + %float_1 = OpConstant %float 1 + %main = OpFunction %void None %6 + %15 = OpLabel + OpBranch %16 + %16 = OpLabel + %27 = OpPhi %int %int_n2 %15 %26 %18 + %21 = OpSLessThanEqual %bool %27 %int_2 + OpLoopMerge %17 %18 Unroll + OpBranchConditional %21 %22 %17 + %22 = OpLabel + %23 = OpLoad %float %o + %24 = OpFAdd %float %23 %float_1 + OpStore %o %24 + OpBranch %18 + %18 = OpLabel + %26 = OpIAdd %int %27 %int_2 + OpBranch %16 + %17 = OpLabel + OpReturn + OpFunctionEnd + )"; +//clang-format on + + +TEST_F(PassClassTest, FullyUnrollConditionIsInHeaderBlock) { + +// clang-format off +const std::string output = +R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %1 "main" %2 +OpExecutionMode %1 OriginUpperLeft +OpSource GLSL 430 +OpDecorate %2 Location 0 +%3 = OpTypeVoid +%4 = OpTypeFunction %3 +%5 = OpTypeInt 32 1 +%6 = OpConstant %5 -2 +%7 = OpConstant %5 2 +%8 = OpTypeBool +%9 = OpTypeFloat 32 +%10 = OpTypePointer Output %9 +%2 = OpVariable %10 Output +%11 = OpConstant %9 1 +%1 = OpFunction %3 None %4 +%12 = OpLabel +OpBranch %13 +%13 = OpLabel +%17 = OpSLessThanEqual %8 %6 %7 +OpBranch %19 +%19 = OpLabel +%20 = OpLoad %9 %2 +%21 = OpFAdd %9 %20 %11 +OpStore %2 %21 +OpBranch %16 +%16 = OpLabel +%15 = OpIAdd %5 %6 %7 +OpBranch %22 +%22 = OpLabel +%24 = OpSLessThanEqual %8 %15 %7 +OpBranch %25 +%25 = OpLabel +%26 = OpLoad %9 %2 +%27 = OpFAdd %9 %26 %11 +OpStore %2 %27 +OpBranch %28 +%28 = OpLabel +%29 = OpIAdd %5 %15 %7 +OpBranch %30 +%30 = OpLabel +%32 = OpSLessThanEqual %8 %29 %7 +OpBranch %33 +%33 = OpLabel +%34 = OpLoad %9 %2 +%35 = OpFAdd %9 %34 %11 +OpStore %2 %35 +OpBranch %36 +%36 = OpLabel +%37 = OpIAdd %5 %29 %7 +OpBranch %18 +%18 = OpLabel +OpReturn +OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, condition_in_header, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" + << condition_in_header << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndCheck(condition_in_header, output, false); +} + +TEST_F(PassClassTest, PartiallyUnrollResidualConditionIsInHeaderBlock) { + // clang-format off +const std::string output = +R"(OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %1 "main" %2 +OpExecutionMode %1 OriginUpperLeft +OpSource GLSL 430 +OpDecorate %2 Location 0 +%3 = OpTypeVoid +%4 = OpTypeFunction %3 +%5 = OpTypeInt 32 1 +%6 = OpConstant %5 -2 +%7 = OpConstant %5 2 +%8 = OpTypeBool +%9 = OpTypeFloat 32 +%10 = OpTypePointer Output %9 +%2 = OpVariable %10 Output +%11 = OpConstant %9 1 +%40 = OpTypeInt 32 0 +%41 = OpConstant %40 1 +%1 = OpFunction %3 None %4 +%12 = OpLabel +OpBranch %13 +%13 = OpLabel +%14 = OpPhi %5 %6 %12 %15 %16 +%17 = OpSLessThanEqual %8 %14 %41 +OpLoopMerge %22 %16 Unroll +OpBranchConditional %17 %19 %22 +%19 = OpLabel +%20 = OpLoad %9 %2 +%21 = OpFAdd %9 %20 %11 +OpStore %2 %21 +OpBranch %16 +%16 = OpLabel +%15 = OpIAdd %5 %14 %7 +OpBranch %13 +%22 = OpLabel +OpBranch %23 +%23 = OpLabel +%24 = OpPhi %5 %14 %22 %39 %38 +%25 = OpSLessThanEqual %8 %24 %7 +OpLoopMerge %31 %38 DontUnroll +OpBranchConditional %25 %26 %31 +%26 = OpLabel +%27 = OpLoad %9 %2 +%28 = OpFAdd %9 %27 %11 +OpStore %2 %28 +OpBranch %29 +%29 = OpLabel +%30 = OpIAdd %5 %24 %7 +OpBranch %32 +%32 = OpLabel +%34 = OpSLessThanEqual %8 %30 %7 +OpBranch %35 +%35 = OpLabel +%36 = OpLoad %9 %2 +%37 = OpFAdd %9 %36 %11 +OpStore %2 %37 +OpBranch %38 +%38 = OpLabel +%39 = OpIAdd %5 %30 %7 +OpBranch %23 +%31 = OpLabel +OpReturn +%18 = OpLabel +OpReturn +OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, condition_in_header, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" + << condition_in_header << std::endl; + + opt::LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndCheck>(condition_in_header, output, + false); +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/loop_optimizations/unswitch.cpp b/3rdparty/spirv-tools/test/opt/loop_optimizations/unswitch.cpp new file mode 100644 index 000000000..d5c5209aa --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/loop_optimizations/unswitch.cpp @@ -0,0 +1,914 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#ifdef SPIRV_EFFCEE +#include "effcee/effcee.h" +#endif + +#include "../pass_fixture.h" + +namespace { + +using namespace spvtools; + +using UnswitchTest = PassTest<::testing::Test>; + +#ifdef SPIRV_EFFCEE + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 450 core +uniform vec4 c; +void main() { + int i = 0; + int j = 0; + bool cond = c[0] == 0; + for (; i < 10; i++, j++) { + if (cond) { + i++; + } + else { + j++; + } + } +} +*/ +TEST_F(UnswitchTest, SimpleUnswitch) { + const std::string text = R"( +; CHECK: [[cst_cond:%\w+]] = OpFOrdEqual +; CHECK-NEXT: OpSelectionMerge [[if_merge:%\w+]] None +; CHECK-NEXT: OpBranchConditional [[cst_cond]] [[loop_t:%\w+]] [[loop_f:%\w+]] + +; Loop specialized for false. +; CHECK: [[loop_f]] = OpLabel +; CHECK-NEXT: OpBranch [[loop:%\w+]] +; CHECK: [[loop]] = OpLabel +; CHECK-NEXT: [[phi_i:%\w+]] = OpPhi %int %int_0 [[loop_f]] [[iv_i:%\w+]] [[continue:%\w+]] +; CHECK-NEXT: [[phi_j:%\w+]] = OpPhi %int %int_0 [[loop_f]] [[iv_j:%\w+]] [[continue]] +; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None +; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_i]] {{%\w+}} +; CHECK-NEXT: OpBranchConditional [[loop_exit]] {{%\w+}} [[merge]] +; Check that we have i+=1 and j+=2. +; CHECK: [[phi_j:%\w+]] = OpIAdd %int [[phi_j]] %int_1 +; CHECK: [[iv_i]] = OpIAdd %int [[phi_i]] %int_1 +; CHECK: [[iv_j]] = OpIAdd %int [[phi_j]] %int_1 +; CHECK: [[merge]] = OpLabel +; CHECK-NEXT: OpBranch [[if_merge]] + +; Loop specialized for true. +; CHECK: [[loop_t]] = OpLabel +; CHECK-NEXT: OpBranch [[loop:%\w+]] +; CHECK: [[loop]] = OpLabel +; CHECK-NEXT: [[phi_i:%\w+]] = OpPhi %int %int_0 [[loop_t]] [[iv_i:%\w+]] [[continue:%\w+]] +; CHECK-NEXT: [[phi_j:%\w+]] = OpPhi %int %int_0 [[loop_t]] [[iv_j:%\w+]] [[continue]] +; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None +; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_i]] {{%\w+}} +; CHECK-NEXT: OpBranchConditional [[loop_exit]] {{%\w+}} [[merge]] +; Check that we have i+=2 and j+=1. +; CHECK: [[phi_i:%\w+]] = OpIAdd %int [[phi_i]] %int_1 +; CHECK: [[iv_i]] = OpIAdd %int [[phi_i]] %int_1 +; CHECK: [[iv_j]] = OpIAdd %int [[phi_j]] %int_1 +; CHECK: [[merge]] = OpLabel +; CHECK-NEXT: OpBranch [[if_merge]] + +; CHECK: [[if_merge]] = OpLabel +; CHECK-NEXT: OpReturn + + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" + OpExecutionMode %main OriginLowerLeft + OpSource GLSL 450 + OpName %main "main" + OpName %c "c" + OpDecorate %c Location 0 + OpDecorate %c DescriptorSet 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int + %int_0 = OpConstant %int 0 + %bool = OpTypeBool +%_ptr_Function_bool = OpTypePointer Function %bool + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 +%_ptr_UniformConstant_v4float = OpTypePointer UniformConstant %v4float + %c = OpVariable %_ptr_UniformConstant_v4float UniformConstant + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 +%_ptr_UniformConstant_float = OpTypePointer UniformConstant %float + %float_0 = OpConstant %float 0 + %int_10 = OpConstant %int 10 + %int_1 = OpConstant %int 1 + %main = OpFunction %void None %3 + %5 = OpLabel + %21 = OpAccessChain %_ptr_UniformConstant_float %c %uint_0 + %22 = OpLoad %float %21 + %24 = OpFOrdEqual %bool %22 %float_0 + OpBranch %25 + %25 = OpLabel + %46 = OpPhi %int %int_0 %5 %43 %28 + %47 = OpPhi %int %int_0 %5 %45 %28 + OpLoopMerge %27 %28 None + OpBranch %29 + %29 = OpLabel + %32 = OpSLessThan %bool %46 %int_10 + OpBranchConditional %32 %26 %27 + %26 = OpLabel + OpSelectionMerge %35 None + OpBranchConditional %24 %34 %39 + %34 = OpLabel + %38 = OpIAdd %int %46 %int_1 + OpBranch %35 + %39 = OpLabel + %41 = OpIAdd %int %47 %int_1 + OpBranch %35 + %35 = OpLabel + %48 = OpPhi %int %38 %34 %46 %39 + %49 = OpPhi %int %47 %34 %41 %39 + OpBranch %28 + %28 = OpLabel + %43 = OpIAdd %int %48 %int_1 + %45 = OpIAdd %int %49 %int_1 + OpBranch %25 + %27 = OpLabel + OpReturn + OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 330 core +in vec4 c; +void main() { + int i = 0; + bool cond = c[0] == 0; + for (; i < 10; i++) { + if (cond) { + i++; + } + else { + return; + } + } +} +*/ +TEST_F(UnswitchTest, UnswitchExit) { + const std::string text = R"( +; CHECK: [[cst_cond:%\w+]] = OpFOrdEqual +; CHECK-NEXT: OpSelectionMerge [[if_merge:%\w+]] None +; CHECK-NEXT: OpBranchConditional [[cst_cond]] [[loop_t:%\w+]] [[loop_f:%\w+]] + +; Loop specialized for false. +; CHECK: [[loop_f]] = OpLabel +; CHECK: OpReturn + +; Loop specialized for true. +; CHECK: [[loop_t]] = OpLabel +; CHECK-NEXT: OpBranch [[loop:%\w+]] +; CHECK: [[loop]] = OpLabel +; CHECK-NEXT: [[phi_i:%\w+]] = OpPhi %int %int_0 [[loop_t]] [[iv_i:%\w+]] [[continue:%\w+]] +; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None +; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_i]] {{%\w+}} +; CHECK-NEXT: OpBranchConditional [[loop_exit]] {{%\w+}} [[merge]] +; Check that we have i+=2. +; CHECK: [[phi_i:%\w+]] = OpIAdd %int [[phi_i]] %int_1 +; CHECK: [[iv_i]] = OpIAdd %int [[phi_i]] %int_1 +; CHECK: [[merge]] = OpLabel +; CHECK-NEXT: OpBranch [[if_merge]] + +; CHECK: [[if_merge]] = OpLabel +; CHECK-NEXT: OpReturn + + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %c + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 330 + OpName %main "main" + OpName %c "c" + OpDecorate %c Location 0 + OpDecorate %23 Uniform + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int + %int_0 = OpConstant %int 0 + %bool = OpTypeBool +%_ptr_Function_bool = OpTypePointer Function %bool + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float + %c = OpVariable %_ptr_Input_v4float Input + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 +%_ptr_Input_float = OpTypePointer Input %float + %float_0 = OpConstant %float 0 + %int_10 = OpConstant %int 10 + %int_1 = OpConstant %int 1 + %main = OpFunction %void None %3 + %5 = OpLabel + %20 = OpAccessChain %_ptr_Input_float %c %uint_0 + %21 = OpLoad %float %20 + %23 = OpFOrdEqual %bool %21 %float_0 + OpBranch %24 + %24 = OpLabel + %42 = OpPhi %int %int_0 %5 %41 %27 + OpLoopMerge %26 %27 None + OpBranch %28 + %28 = OpLabel + %31 = OpSLessThan %bool %42 %int_10 + OpBranchConditional %31 %25 %26 + %25 = OpLabel + OpSelectionMerge %34 None + OpBranchConditional %23 %33 %38 + %33 = OpLabel + %37 = OpIAdd %int %42 %int_1 + OpBranch %34 + %38 = OpLabel + OpReturn + %34 = OpLabel + OpBranch %27 + %27 = OpLabel + %41 = OpIAdd %int %37 %int_1 + OpBranch %24 + %26 = OpLabel + OpReturn + OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 330 core +in vec4 c; +void main() { + int i = 0; + bool cond = c[0] == 0; + for (; i < 10; i++) { + if (cond) { + continue; + } + else { + i++; + } + } +} +*/ +TEST_F(UnswitchTest, UnswitchContinue) { + const std::string text = R"( +; CHECK: [[cst_cond:%\w+]] = OpFOrdEqual +; CHECK-NEXT: OpSelectionMerge [[if_merge:%\w+]] None +; CHECK-NEXT: OpBranchConditional [[cst_cond]] [[loop_t:%\w+]] [[loop_f:%\w+]] + +; Loop specialized for false. +; CHECK: [[loop_f]] = OpLabel +; CHECK-NEXT: OpBranch [[loop:%\w+]] +; CHECK: [[loop]] = OpLabel +; CHECK-NEXT: [[phi_i:%\w+]] = OpPhi %int %int_0 [[loop_f]] [[iv_i:%\w+]] [[continue:%\w+]] +; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None +; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_i]] {{%\w+}} +; CHECK-NEXT: OpBranchConditional [[loop_exit]] {{%\w+}} [[merge]] +; Check that we have i+=2. +; CHECK: [[phi_i:%\w+]] = OpIAdd %int [[phi_i]] %int_1 +; CHECK: [[iv_i]] = OpIAdd %int [[phi_i]] %int_1 +; CHECK: [[merge]] = OpLabel +; CHECK-NEXT: OpBranch [[if_merge]] + +; Loop specialized for true. +; CHECK: [[loop_t]] = OpLabel +; CHECK-NEXT: OpBranch [[loop:%\w+]] +; CHECK: [[loop]] = OpLabel +; CHECK-NEXT: [[phi_i:%\w+]] = OpPhi %int %int_0 [[loop_t]] [[iv_i:%\w+]] [[continue:%\w+]] +; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None +; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_i]] {{%\w+}} +; CHECK-NEXT: OpBranchConditional [[loop_exit]] {{%\w+}} [[merge]] +; Check that we have i+=1. +; CHECK: [[iv_i]] = OpIAdd %int [[phi_i]] %int_1 +; CHECK: [[merge]] = OpLabel +; CHECK-NEXT: OpBranch [[if_merge]] + +; CHECK: [[if_merge]] = OpLabel +; CHECK-NEXT: OpReturn + + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %c + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 330 + OpName %main "main" + OpName %c "c" + OpDecorate %c Location 0 + OpDecorate %23 Uniform + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int + %int_0 = OpConstant %int 0 + %bool = OpTypeBool +%_ptr_Function_bool = OpTypePointer Function %bool + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float + %c = OpVariable %_ptr_Input_v4float Input + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 +%_ptr_Input_float = OpTypePointer Input %float + %float_0 = OpConstant %float 0 + %int_10 = OpConstant %int 10 + %int_1 = OpConstant %int 1 + %main = OpFunction %void None %3 + %5 = OpLabel + %20 = OpAccessChain %_ptr_Input_float %c %uint_0 + %21 = OpLoad %float %20 + %23 = OpFOrdEqual %bool %21 %float_0 + OpBranch %24 + %24 = OpLabel + %42 = OpPhi %int %int_0 %5 %41 %27 + OpLoopMerge %26 %27 None + OpBranch %28 + %28 = OpLabel + %31 = OpSLessThan %bool %42 %int_10 + OpBranchConditional %31 %25 %26 + %25 = OpLabel + OpSelectionMerge %34 None + OpBranchConditional %23 %33 %36 + %33 = OpLabel + OpBranch %27 + %36 = OpLabel + %39 = OpIAdd %int %42 %int_1 + OpBranch %34 + %34 = OpLabel + OpBranch %27 + %27 = OpLabel + %43 = OpPhi %int %42 %33 %39 %34 + %41 = OpIAdd %int %43 %int_1 + OpBranch %24 + %26 = OpLabel + OpReturn + OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 330 core +in vec4 c; +void main() { + int i = 0; + bool cond = c[0] == 0; + for (; i < 10; i++) { + if (cond) { + i++; + } + else { + break; + } + } +} +*/ +TEST_F(UnswitchTest, UnswitchKillLoop) { + const std::string text = R"( +; CHECK: [[cst_cond:%\w+]] = OpFOrdEqual +; CHECK-NEXT: OpSelectionMerge [[if_merge:%\w+]] None +; CHECK-NEXT: OpBranchConditional [[cst_cond]] [[loop_t:%\w+]] [[loop_f:%\w+]] + +; Loop specialized for false. +; CHECK: [[loop_f]] = OpLabel +; CHECK: OpBranch [[if_merge]] + +; Loop specialized for true. +; CHECK: [[loop_t]] = OpLabel +; CHECK-NEXT: OpBranch [[loop:%\w+]] +; CHECK: [[loop]] = OpLabel +; CHECK-NEXT: [[phi_i:%\w+]] = OpPhi %int %int_0 [[loop_t]] [[iv_i:%\w+]] [[continue:%\w+]] +; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None +; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_i]] {{%\w+}} +; CHECK-NEXT: OpBranchConditional [[loop_exit]] {{%\w+}} [[merge]] +; Check that we have i+=2. +; CHECK: [[phi_i:%\w+]] = OpIAdd %int [[phi_i]] %int_1 +; CHECK: [[iv_i]] = OpIAdd %int [[phi_i]] %int_1 +; CHECK: [[merge]] = OpLabel +; CHECK-NEXT: OpBranch [[if_merge]] + +; CHECK: [[if_merge]] = OpLabel +; CHECK-NEXT: OpReturn + + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %c + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 330 + OpName %main "main" + OpName %c "c" + OpDecorate %c Location 0 + OpDecorate %23 Uniform + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int + %int_0 = OpConstant %int 0 + %bool = OpTypeBool +%_ptr_Function_bool = OpTypePointer Function %bool + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float + %c = OpVariable %_ptr_Input_v4float Input + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 +%_ptr_Input_float = OpTypePointer Input %float + %float_0 = OpConstant %float 0 + %int_10 = OpConstant %int 10 + %int_1 = OpConstant %int 1 + %main = OpFunction %void None %3 + %5 = OpLabel + %20 = OpAccessChain %_ptr_Input_float %c %uint_0 + %21 = OpLoad %float %20 + %23 = OpFOrdEqual %bool %21 %float_0 + OpBranch %24 + %24 = OpLabel + %42 = OpPhi %int %int_0 %5 %41 %27 + OpLoopMerge %26 %27 None + OpBranch %28 + %28 = OpLabel + %31 = OpSLessThan %bool %42 %int_10 + OpBranchConditional %31 %25 %26 + %25 = OpLabel + OpSelectionMerge %34 None + OpBranchConditional %23 %33 %38 + %33 = OpLabel + %37 = OpIAdd %int %42 %int_1 + OpBranch %34 + %38 = OpLabel + OpBranch %26 + %34 = OpLabel + OpBranch %27 + %27 = OpLabel + %41 = OpIAdd %int %37 %int_1 + OpBranch %24 + %26 = OpLabel + OpReturn + OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 330 core +in vec4 c; +void main() { + int i = 0; + int cond = int(c[0]); + for (; i < 10; i++) { + switch (cond) { + case 0: + return; + case 1: + discard; + case 2: + break; + default: + break; + } + } + bool cond2 = i == 9; +} +*/ +TEST_F(UnswitchTest, UnswitchSwitch) { + const std::string text = R"( +; CHECK: [[cst_cond:%\w+]] = OpConvertFToS +; CHECK-NEXT: OpSelectionMerge [[if_merge:%\w+]] None +; CHECK-NEXT: OpSwitch [[cst_cond]] [[default:%\w+]] 0 [[loop_0:%\w+]] 1 [[loop_1:%\w+]] 2 [[loop_2:%\w+]] + +; Loop specialized for 2. +; CHECK: [[loop_2]] = OpLabel +; CHECK-NEXT: OpBranch [[loop:%\w+]] +; CHECK: [[loop]] = OpLabel +; CHECK-NEXT: [[phi_i:%\w+]] = OpPhi %int %int_0 [[loop_2]] [[iv_i:%\w+]] [[continue:%\w+]] +; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None +; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_i]] {{%\w+}} +; CHECK-NEXT: OpBranchConditional [[loop_exit]] {{%\w+}} [[merge]] +; Check that we have i+=1. +; CHECK: [[iv_i]] = OpIAdd %int [[phi_i]] %int_1 +; CHECK: OpBranch [[loop]] + +; Loop specialized for 1. +; CHECK: [[loop_1]] = OpLabel +; CHECK: OpKill + +; Loop specialized for 0. +; CHECK: [[loop_0]] = OpLabel +; CHECK: OpReturn + +; Loop specialized for the default case. +; CHECK: [[default]] = OpLabel +; CHECK-NEXT: OpBranch [[loop:%\w+]] +; CHECK: [[loop]] = OpLabel +; CHECK-NEXT: [[phi_i:%\w+]] = OpPhi %int %int_0 [[default]] [[iv_i:%\w+]] [[continue:%\w+]] +; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None +; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_i]] {{%\w+}} +; CHECK-NEXT: OpBranchConditional [[loop_exit]] {{%\w+}} [[merge]] +; Check that we have i+=1. +; CHECK: [[phi_i:%\w+]] = OpIAdd %int [[phi_i]] %int_1 +; CHECK: OpBranch [[loop]] + +; CHECK: [[if_merge]] = OpLabel +; CHECK-NEXT: OpReturn + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %c + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 330 + OpName %main "main" + OpName %c "c" + OpDecorate %c Location 0 + OpDecorate %20 Uniform + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int + %int_0 = OpConstant %int 0 + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float + %c = OpVariable %_ptr_Input_v4float Input + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 +%_ptr_Input_float = OpTypePointer Input %float + %int_10 = OpConstant %int 10 + %bool = OpTypeBool + %int_1 = OpConstant %int 1 +%_ptr_Function_bool = OpTypePointer Function %bool + %main = OpFunction %void None %3 + %5 = OpLabel + %18 = OpAccessChain %_ptr_Input_float %c %uint_0 + %19 = OpLoad %float %18 + %20 = OpConvertFToS %int %19 + OpBranch %21 + %21 = OpLabel + %49 = OpPhi %int %int_0 %5 %43 %24 + OpLoopMerge %23 %24 None + OpBranch %25 + %25 = OpLabel + %29 = OpSLessThan %bool %49 %int_10 + OpBranchConditional %29 %22 %23 + %22 = OpLabel + OpSelectionMerge %35 None + OpSwitch %20 %34 0 %31 1 %32 2 %33 + %34 = OpLabel + OpBranch %35 + %31 = OpLabel + OpReturn + %32 = OpLabel + OpKill + %33 = OpLabel + OpBranch %35 + %35 = OpLabel + OpBranch %24 + %24 = OpLabel + %43 = OpIAdd %int %49 %int_1 + OpBranch %21 + %23 = OpLabel + OpReturn + OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +layout(location = 0)in vec4 c; +void main() { + int i = 0; + int j = 0; + int k = 0; + bool cond = c[0] == 0; + for (; i < 10; i++) { + for (; j < 10; j++) { + if (cond) { + i++; + } else { + j++; + } + } + } + for (; k < 10; k++) { + if (cond) { + k++; + } + } +} +*/ +TEST_F(UnswitchTest, UnSwitchNested) { + const std::string text = R"( +; CHECK: [[cst_cond:%\w+]] = OpFOrdEqual +; CHECK-NEXT: OpSelectionMerge [[if_merge:%\w+]] None +; CHECK-NEXT: OpBranchConditional [[cst_cond]] [[loop_t:%\w+]] [[loop_f:%\w+]] + +; Loop specialized for false, one loop is killed, j won't change anymore. +; CHECK: [[loop_f]] = OpLabel +; CHECK-NEXT: OpBranch [[loop:%\w+]] +; CHECK: [[loop]] = OpLabel +; CHECK-NEXT: [[phi_i:%\w+]] = OpPhi %int %int_0 [[loop_f]] [[iv_i:%\w+]] [[continue:%\w+]] +; CHECK-NEXT: [[phi_j:%\w+]] = OpPhi %int %int_0 [[loop_f]] [[iv_j:%\w+]] [[continue]] +; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None +; CHECK: [[iv_i]] = OpIAdd %int [[phi_i]] %int_1 +; CHECK-NEXT: OpBranch [[loop]] +; CHECK: OpReturn + +; Loop specialized for true. +; CHECK: [[loop_t]] = OpLabel +; CHECK-NEXT: OpBranch [[loop:%\w+]] +; CHECK: [[loop]] = OpLabel +; CHECK-NEXT: [[phi_i:%\w+]] = OpPhi %int %int_0 [[loop_t]] [[iv_i:%\w+]] [[continue:%\w+]] +; CHECK-NEXT: [[phi_j:%\w+]] = OpPhi %int %int_0 [[loop_t]] [[iv_j:%\w+]] [[continue]] +; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None +; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_i]] {{%\w+}} +; CHECK-NEXT: OpBranchConditional [[loop_exit]] [[pre_loop_inner:%\w+]] [[merge]] + +; CHECK: [[pre_loop_inner]] = OpLabel +; CHECK-NEXT: OpBranch [[loop_inner:%\w+]] +; CHECK-NEXT: [[loop_inner]] = OpLabel +; CHECK-NEXT: [[phi2_i:%\w+]] = OpPhi %int [[phi_i]] [[pre_loop_inner]] [[iv2_i:%\w+]] [[continue2:%\w+]] +; CHECK-NEXT: [[phi2_j:%\w+]] = OpPhi %int [[phi_j]] [[pre_loop_inner]] [[iv2_j:%\w+]] [[continue2]] +; CHECK-NEXT: OpLoopMerge [[merge2:%\w+]] [[continue2]] None + +; CHECK: OpBranch [[continue2]] +; CHECK: [[merge2]] = OpLabel +; CHECK: OpBranch [[continue]] +; CHECK: [[merge]] = OpLabel + +; Unswitched double nested loop is done. Test the single remaining one. + +; CHECK: [[if_merge]] = OpLabel +; CHECK-NEXT: OpSelectionMerge [[if_merge:%\w+]] None +; CHECK-NEXT: OpBranchConditional [[cst_cond]] [[loop_t:%\w+]] [[loop_f:%\w+]] + +; Loop specialized for false. +; CHECK: [[loop_f]] = OpLabel +; CHECK-NEXT: OpBranch [[loop:%\w+]] +; CHECK: [[loop]] = OpLabel +; CHECK-NEXT: [[phi_k:%\w+]] = OpPhi %int %int_0 [[loop_f]] [[iv_k:%\w+]] [[continue:%\w+]] +; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None +; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_k]] {{%\w+}} +; CHECK-NEXT: OpBranchConditional [[loop_exit]] {{%\w+}} [[merge]] +; Check that we have k+=1 +; CHECK: [[iv_k]] = OpIAdd %int [[phi_k]] %int_1 +; CHECK: OpBranch [[loop]] +; CHECK: [[merge]] = OpLabel +; CHECK-NEXT: OpBranch [[if_merge]] + +; Loop specialized for true. +; CHECK: [[loop_t]] = OpLabel +; CHECK-NEXT: OpBranch [[loop:%\w+]] +; CHECK: [[loop]] = OpLabel +; CHECK-NEXT: [[phi_k:%\w+]] = OpPhi %int %int_0 [[loop_t]] [[iv_k:%\w+]] [[continue:%\w+]] +; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None +; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_k]] {{%\w+}} +; CHECK-NEXT: OpBranchConditional [[loop_exit]] {{%\w+}} [[merge]] +; Check that we have k+=2. +; CHECK: [[tmp_k:%\w+]] = OpIAdd %int [[phi_k]] %int_1 +; CHECK: [[iv_k]] = OpIAdd %int [[tmp_k]] %int_1 +; CHECK: OpBranch [[loop]] +; CHECK: [[merge]] = OpLabel +; CHECK-NEXT: OpBranch [[if_merge]] + +; CHECK: [[if_merge]] = OpLabel +; CHECK-NEXT: OpReturn + + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %c + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 440 + OpName %main "main" + OpName %c "c" + OpDecorate %c Location 0 + OpDecorate %25 Uniform + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int + %int_0 = OpConstant %int 0 + %bool = OpTypeBool +%_ptr_Function_bool = OpTypePointer Function %bool + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float + %c = OpVariable %_ptr_Input_v4float Input + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 +%_ptr_Input_float = OpTypePointer Input %float + %float_0 = OpConstant %float 0 + %int_10 = OpConstant %int 10 + %int_1 = OpConstant %int 1 + %main = OpFunction %void None %3 + %5 = OpLabel + %22 = OpAccessChain %_ptr_Input_float %c %uint_0 + %23 = OpLoad %float %22 + %25 = OpFOrdEqual %bool %23 %float_0 + OpBranch %26 + %26 = OpLabel + %67 = OpPhi %int %int_0 %5 %52 %29 + %68 = OpPhi %int %int_0 %5 %70 %29 + OpLoopMerge %28 %29 None + OpBranch %30 + %30 = OpLabel + %33 = OpSLessThan %bool %67 %int_10 + OpBranchConditional %33 %27 %28 + %27 = OpLabel + OpBranch %34 + %34 = OpLabel + %69 = OpPhi %int %67 %27 %46 %37 + %70 = OpPhi %int %68 %27 %50 %37 + OpLoopMerge %36 %37 None + OpBranch %38 + %38 = OpLabel + %40 = OpSLessThan %bool %70 %int_10 + OpBranchConditional %40 %35 %36 + %35 = OpLabel + OpSelectionMerge %43 None + OpBranchConditional %25 %42 %47 + %42 = OpLabel + %46 = OpIAdd %int %69 %int_1 + OpBranch %43 + %47 = OpLabel + OpReturn + %43 = OpLabel + OpBranch %37 + %37 = OpLabel + %50 = OpIAdd %int %70 %int_1 + OpBranch %34 + %36 = OpLabel + OpBranch %29 + %29 = OpLabel + %52 = OpIAdd %int %69 %int_1 + OpBranch %26 + %28 = OpLabel + OpBranch %53 + %53 = OpLabel + %71 = OpPhi %int %int_0 %28 %66 %56 + OpLoopMerge %55 %56 None + OpBranch %57 + %57 = OpLabel + %59 = OpSLessThan %bool %71 %int_10 + OpBranchConditional %59 %54 %55 + %54 = OpLabel + OpSelectionMerge %62 None + OpBranchConditional %25 %61 %62 + %61 = OpLabel + %64 = OpIAdd %int %71 %int_1 + OpBranch %62 + %62 = OpLabel + %72 = OpPhi %int %71 %54 %64 %61 + OpBranch %56 + %56 = OpLabel + %66 = OpIAdd %int %72 %int_1 + OpBranch %53 + %55 = OpLabel + OpReturn + OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} +#endif // SPIRV_EFFCEE + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 330 core +in vec4 c; +void main() { + bool cond = false; + if (c[0] == 0) { + cond = c[1] == 0; + } else { + cond = c[2] == 0; + } + for (int i = 0; i < 10; i++) { + if (cond) { + i++; + } + } +} +*/ +TEST_F(UnswitchTest, UnswitchNotUniform) { + // Check that the unswitch is not triggered (condition loop invariant but not + // uniform) + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %c + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 330 + OpName %main "main" + OpName %c "c" + OpDecorate %c Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %bool = OpTypeBool +%_ptr_Function_bool = OpTypePointer Function %bool + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float + %c = OpVariable %_ptr_Input_v4float Input + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 +%_ptr_Input_float = OpTypePointer Input %float + %float_0 = OpConstant %float 0 + %uint_1 = OpConstant %uint 1 + %uint_2 = OpConstant %uint 2 + %int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int + %int_0 = OpConstant %int 0 + %int_10 = OpConstant %int 10 + %int_1 = OpConstant %int 1 + %main = OpFunction %void None %3 + %5 = OpLabel + %17 = OpAccessChain %_ptr_Input_float %c %uint_0 + %18 = OpLoad %float %17 + %20 = OpFOrdEqual %bool %18 %float_0 + OpSelectionMerge %22 None + OpBranchConditional %20 %21 %27 + %21 = OpLabel + %24 = OpAccessChain %_ptr_Input_float %c %uint_1 + %25 = OpLoad %float %24 + %26 = OpFOrdEqual %bool %25 %float_0 + OpBranch %22 + %27 = OpLabel + %29 = OpAccessChain %_ptr_Input_float %c %uint_2 + %30 = OpLoad %float %29 + %31 = OpFOrdEqual %bool %30 %float_0 + OpBranch %22 + %22 = OpLabel + %52 = OpPhi %bool %26 %21 %31 %27 + OpBranch %36 + %36 = OpLabel + %53 = OpPhi %int %int_0 %22 %51 %39 + OpLoopMerge %38 %39 None + OpBranch %40 + %40 = OpLabel + %43 = OpSLessThan %bool %53 %int_10 + OpBranchConditional %43 %37 %38 + %37 = OpLabel + OpSelectionMerge %46 None + OpBranchConditional %52 %45 %46 + %45 = OpLabel + %49 = OpIAdd %int %53 %int_1 + OpBranch %46 + %46 = OpLabel + %54 = OpPhi %int %53 %37 %49 %45 + OpBranch %39 + %39 = OpLabel + %51 = OpIAdd %int %54 %int_1 + OpBranch %36 + %38 = OpLabel + OpReturn + OpFunctionEnd + )"; + + auto result = + SinglePassRunAndDisassemble(text, true, false); + + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/module_test.cpp b/3rdparty/spirv-tools/test/opt/module_test.cpp new file mode 100644 index 000000000..177b45b96 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/module_test.cpp @@ -0,0 +1,143 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "message.h" +#include "opt/build_module.h" +#include "opt/module.h" +#include "spirv-tools/libspirv.hpp" + +#include "module_utils.h" + +namespace { + +using ::testing::Eq; +using spvtest::GetIdBound; +using spvtools::ir::IRContext; +using spvtools::ir::Module; + +TEST(ModuleTest, SetIdBound) { + Module m; + // It's initialized to 0. + EXPECT_EQ(0u, GetIdBound(m)); + + m.SetIdBound(19); + EXPECT_EQ(19u, GetIdBound(m)); + + m.SetIdBound(102); + EXPECT_EQ(102u, GetIdBound(m)); +} + +// Returns an IRContext owning the module formed by assembling the given text, +// then loading the result. +inline std::unique_ptr BuildModule(std::string text) { + return spvtools::BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); +} + +TEST(ModuleTest, ComputeIdBound) { + // Emtpy module case. + EXPECT_EQ(1u, BuildModule("")->module()->ComputeIdBound()); + // Sensitive to result id + EXPECT_EQ(2u, BuildModule("%void = OpTypeVoid")->module()->ComputeIdBound()); + // Sensitive to type id + EXPECT_EQ(1000u, + BuildModule("%a = OpTypeArray !999 3")->module()->ComputeIdBound()); + // Sensitive to a regular Id parameter + EXPECT_EQ(2000u, + BuildModule("OpDecorate !1999 0")->module()->ComputeIdBound()); + // Sensitive to a scope Id parameter. + EXPECT_EQ(3000u, + BuildModule("%f = OpFunction %void None %fntype %a = OpLabel " + "OpMemoryBarrier !2999 %b\n") + ->module() + ->ComputeIdBound()); + // Sensitive to a semantics Id parameter + EXPECT_EQ(4000u, + BuildModule("%f = OpFunction %void None %fntype %a = OpLabel " + "OpMemoryBarrier %b !3999\n") + ->module() + ->ComputeIdBound()); +} + +TEST(ModuleTest, OstreamOperator) { + const std::string text = R"(OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpName %7 "restrict" +OpDecorate %8 Restrict +%9 = OpTypeVoid +%10 = OpTypeInt 32 0 +%11 = OpTypeStruct %10 %10 +%12 = OpTypePointer Function %10 +%13 = OpTypePointer Function %11 +%14 = OpConstant %10 0 +%15 = OpConstant %10 1 +%7 = OpTypeFunction %9 +%1 = OpFunction %9 None %7 +%2 = OpLabel +%8 = OpVariable %13 Function +%3 = OpAccessChain %12 %8 %14 +%4 = OpLoad %10 %3 +%5 = OpAccessChain %12 %8 %15 +%6 = OpLoad %10 %5 +OpReturn +OpFunctionEnd)"; + + std::string s; + std::ostringstream str(s); + str << *BuildModule(text)->module(); + EXPECT_EQ(text, str.str()); +} + +TEST(ModuleTest, OstreamOperatorInt64) { + const std::string text = R"(OpCapability Shader +OpCapability Linkage +OpCapability Int64 +OpMemoryModel Logical GLSL450 +OpName %7 "restrict" +OpDecorate %5 Restrict +%9 = OpTypeVoid +%10 = OpTypeInt 64 0 +%11 = OpTypeStruct %10 %10 +%12 = OpTypePointer Function %10 +%13 = OpTypePointer Function %11 +%14 = OpConstant %10 0 +%15 = OpConstant %10 1 +%16 = OpConstant %10 4294967297 +%7 = OpTypeFunction %9 +%1 = OpFunction %9 None %7 +%2 = OpLabel +%5 = OpVariable %12 Function +%6 = OpLoad %10 %5 +OpSelectionMerge %3 None +OpSwitch %6 %3 4294967297 %4 +%4 = OpLabel +OpBranch %3 +%3 = OpLabel +OpReturn +OpFunctionEnd)"; + + std::string s; + std::ostringstream str(s); + str << *BuildModule(text)->module(); + EXPECT_EQ(text, str.str()); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/module_utils.h b/3rdparty/spirv-tools/test/opt/module_utils.h new file mode 100644 index 000000000..ad59058d1 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/module_utils.h @@ -0,0 +1,34 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_TEST_OPT_MODULE_UTILS_H_ +#define LIBSPIRV_TEST_OPT_MODULE_UTILS_H_ + +#include +#include "opt/module.h" + +namespace spvtest { + +inline uint32_t GetIdBound(const spvtools::ir::Module& m) { + std::vector binary; + m.ToBinary(&binary, false); + // The 5-word header must always exist. + EXPECT_LE(5u, binary.size()); + // The bound is the fourth word. + return binary[3]; +} + +} // namespace spvtest + +#endif // LIBSPIRV_TEST_OPT_MODULE_UTILS_H_ diff --git a/3rdparty/spirv-tools/test/opt/optimizer_test.cpp b/3rdparty/spirv-tools/test/opt/optimizer_test.cpp new file mode 100644 index 000000000..ef0e99213 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/optimizer_test.cpp @@ -0,0 +1,109 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "spirv-tools/libspirv.hpp" +#include "spirv-tools/optimizer.hpp" + +#include "pass_fixture.h" + +namespace { + +using spvtools::CreateNullPass; +using spvtools::CreateStripDebugInfoPass; +using spvtools::Optimizer; +using spvtools::SpirvTools; +using ::testing::Eq; + +TEST(Optimizer, CanRunNullPassWithDistinctInputOutputVectors) { + SpirvTools tools(SPV_ENV_UNIVERSAL_1_0); + std::vector binary_in; + tools.Assemble("OpName %foo \"foo\"\n%foo = OpTypeVoid", &binary_in); + + Optimizer opt(SPV_ENV_UNIVERSAL_1_0); + opt.RegisterPass(CreateNullPass()); + std::vector binary_out; + opt.Run(binary_in.data(), binary_in.size(), &binary_out); + + std::string disassembly; + tools.Disassemble(binary_out.data(), binary_out.size(), &disassembly); + EXPECT_THAT(disassembly, Eq("OpName %foo \"foo\"\n%foo = OpTypeVoid\n")); +} + +TEST(Optimizer, CanRunTransformingPassWithDistinctInputOutputVectors) { + SpirvTools tools(SPV_ENV_UNIVERSAL_1_0); + std::vector binary_in; + tools.Assemble("OpName %foo \"foo\"\n%foo = OpTypeVoid", &binary_in); + + Optimizer opt(SPV_ENV_UNIVERSAL_1_0); + opt.RegisterPass(CreateStripDebugInfoPass()); + std::vector binary_out; + opt.Run(binary_in.data(), binary_in.size(), &binary_out); + + std::string disassembly; + tools.Disassemble(binary_out.data(), binary_out.size(), &disassembly); + EXPECT_THAT(disassembly, Eq("%void = OpTypeVoid\n")); +} + +TEST(Optimizer, CanRunNullPassWithAliasedVectors) { + SpirvTools tools(SPV_ENV_UNIVERSAL_1_0); + std::vector binary; + tools.Assemble("OpName %foo \"foo\"\n%foo = OpTypeVoid", &binary); + + Optimizer opt(SPV_ENV_UNIVERSAL_1_0); + opt.RegisterPass(CreateNullPass()); + opt.Run(binary.data(), binary.size(), &binary); // This is the key. + + std::string disassembly; + tools.Disassemble(binary.data(), binary.size(), &disassembly); + EXPECT_THAT(disassembly, Eq("OpName %foo \"foo\"\n%foo = OpTypeVoid\n")); +} + +TEST(Optimizer, CanRunNullPassWithAliasedVectorDataButDifferentSize) { + SpirvTools tools(SPV_ENV_UNIVERSAL_1_0); + std::vector binary; + tools.Assemble("OpName %foo \"foo\"\n%foo = OpTypeVoid", &binary); + + Optimizer opt(SPV_ENV_UNIVERSAL_1_0); + opt.RegisterPass(CreateNullPass()); + auto orig_size = binary.size(); + // Now change the size. Add a word that will be ignored + // by the optimizer. + binary.push_back(42); + EXPECT_THAT(orig_size + 1, Eq(binary.size())); + opt.Run(binary.data(), orig_size, &binary); // This is the key. + // The binary vector should have been rewritten. + EXPECT_THAT(binary.size(), Eq(orig_size)); + + std::string disassembly; + tools.Disassemble(binary.data(), binary.size(), &disassembly); + EXPECT_THAT(disassembly, Eq("OpName %foo \"foo\"\n%foo = OpTypeVoid\n")); +} + +TEST(Optimizer, CanRunTransformingPassWithAliasedVectors) { + SpirvTools tools(SPV_ENV_UNIVERSAL_1_0); + std::vector binary; + tools.Assemble("OpName %foo \"foo\"\n%foo = OpTypeVoid", &binary); + + Optimizer opt(SPV_ENV_UNIVERSAL_1_0); + opt.RegisterPass(CreateStripDebugInfoPass()); + opt.Run(binary.data(), binary.size(), &binary); // This is the key + + std::string disassembly; + tools.Disassemble(binary.data(), binary.size(), &disassembly); + EXPECT_THAT(disassembly, Eq("%void = OpTypeVoid\n")); +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/pass_fixture.h b/3rdparty/spirv-tools/test/opt/pass_fixture.h new file mode 100644 index 000000000..d935231c9 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/pass_fixture.h @@ -0,0 +1,244 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_TEST_OPT_PASS_FIXTURE_H_ +#define LIBSPIRV_TEST_OPT_PASS_FIXTURE_H_ + +#include +#include +#include +#include + +#include + +#ifdef SPIRV_EFFCEE +#include "effcee/effcee.h" +#endif + +#include "opt/build_module.h" +#include "opt/make_unique.h" +#include "opt/pass_manager.h" +#include "opt/passes.h" +#include "spirv-tools/libspirv.hpp" + +namespace spvtools { + +// Template class for testing passes. It contains some handy utility methods for +// running passes and checking results. +// +// To write value-Parameterized tests: +// using ValueParamTest = PassTest<::testing::TestWithParam>; +// To use as normal fixture: +// using FixtureTest = PassTest<::testing::Test>; +template +class PassTest : public TestT { + public: + PassTest() + : consumer_(nullptr), + context_(nullptr), + tools_(SPV_ENV_UNIVERSAL_1_1), + manager_(new opt::PassManager()), + assemble_options_(SpirvTools::kDefaultAssembleOption), + disassemble_options_(SpirvTools::kDefaultDisassembleOption) {} + + // Runs the given |pass| on the binary assembled from the |original|. + // Returns a tuple of the optimized binary and the boolean value returned + // from pass Process() function. + std::tuple, opt::Pass::Status> OptimizeToBinary( + opt::Pass* pass, const std::string& original, bool skip_nop) { + context_ = std::move(BuildModule(SPV_ENV_UNIVERSAL_1_1, consumer_, original, + assemble_options_)); + EXPECT_NE(nullptr, context()) << "Assembling failed for shader:\n" + << original << std::endl; + if (!context()) { + return std::make_tuple(std::vector(), + opt::Pass::Status::Failure); + } + + const auto status = pass->Run(context()); + + std::vector binary; + context()->module()->ToBinary(&binary, skip_nop); + return std::make_tuple(binary, status); + } + + // Runs a single pass of class |PassT| on the binary assembled from the + // |assembly|. Returns a tuple of the optimized binary and the boolean value + // from the pass Process() function. + template + std::tuple, opt::Pass::Status> SinglePassRunToBinary( + const std::string& assembly, bool skip_nop, Args&&... args) { + auto pass = MakeUnique(std::forward(args)...); + pass->SetMessageConsumer(consumer_); + return OptimizeToBinary(pass.get(), assembly, skip_nop); + } + + // Runs a single pass of class |PassT| on the binary assembled from the + // |assembly|, disassembles the optimized binary. Returns a tuple of + // disassembly string and the boolean value from the pass Process() function. + template + std::tuple SinglePassRunAndDisassemble( + const std::string& assembly, bool skip_nop, bool do_validation, + Args&&... args) { + std::vector optimized_bin; + auto status = opt::Pass::Status::SuccessWithoutChange; + std::tie(optimized_bin, status) = SinglePassRunToBinary( + assembly, skip_nop, std::forward(args)...); + if (do_validation) { + spv_target_env target_env = SPV_ENV_UNIVERSAL_1_1; + spv_context spvContext = spvContextCreate(target_env); + spv_diagnostic diagnostic = nullptr; + spv_const_binary_t binary = {optimized_bin.data(), optimized_bin.size()}; + spv_result_t error = spvValidate(spvContext, &binary, &diagnostic); + EXPECT_EQ(error, 0); + if (error != 0) spvDiagnosticPrint(diagnostic); + spvDiagnosticDestroy(diagnostic); + spvContextDestroy(spvContext); + } + std::string optimized_asm; + EXPECT_TRUE( + tools_.Disassemble(optimized_bin, &optimized_asm, disassemble_options_)) + << "Disassembling failed for shader:\n" + << assembly << std::endl; + return std::make_tuple(optimized_asm, status); + } + + // Runs a single pass of class |PassT| on the binary assembled from the + // |original| assembly, and checks whether the optimized binary can be + // disassembled to the |expected| assembly. Optionally will also validate + // the optimized binary. This does *not* involve pass manager. Callers + // are suggested to use SCOPED_TRACE() for better messages. + template + void SinglePassRunAndCheck(const std::string& original, + const std::string& expected, bool skip_nop, + bool do_validation, Args&&... args) { + std::vector optimized_bin; + auto status = opt::Pass::Status::SuccessWithoutChange; + std::tie(optimized_bin, status) = SinglePassRunToBinary( + original, skip_nop, std::forward(args)...); + // Check whether the pass returns the correct modification indication. + EXPECT_NE(opt::Pass::Status::Failure, status); + EXPECT_EQ(original == expected, + status == opt::Pass::Status::SuccessWithoutChange); + if (do_validation) { + spv_target_env target_env = SPV_ENV_UNIVERSAL_1_1; + spv_context spvContext = spvContextCreate(target_env); + spv_diagnostic diagnostic = nullptr; + spv_const_binary_t binary = {optimized_bin.data(), optimized_bin.size()}; + spv_result_t error = spvValidate(spvContext, &binary, &diagnostic); + EXPECT_EQ(error, 0); + if (error != 0) spvDiagnosticPrint(diagnostic); + spvDiagnosticDestroy(diagnostic); + spvContextDestroy(spvContext); + } + std::string optimized_asm; + EXPECT_TRUE( + tools_.Disassemble(optimized_bin, &optimized_asm, disassemble_options_)) + << "Disassembling failed for shader:\n" + << original << std::endl; + EXPECT_EQ(expected, optimized_asm); + } + + // Runs a single pass of class |PassT| on the binary assembled from the + // |original| assembly, and checks whether the optimized binary can be + // disassembled to the |expected| assembly. This does *not* involve pass + // manager. Callers are suggested to use SCOPED_TRACE() for better messages. + template + void SinglePassRunAndCheck(const std::string& original, + const std::string& expected, bool skip_nop, + Args&&... args) { + SinglePassRunAndCheck(original, expected, skip_nop, false, + std::forward(args)...); + } + +#ifdef SPIRV_EFFCEE + // Runs a single pass of class |PassT| on the binary assembled from the + // |original| assembly, then runs an Effcee matcher over the disassembled + // result, using checks parsed from |original|. Always skips OpNop. + // This does *not* involve pass manager. Callers are suggested to use + // SCOPED_TRACE() for better messages. + template + void SinglePassRunAndMatch(const std::string& original, bool do_validation, + Args&&... args) { + const bool skip_nop = true; + auto pass_result = SinglePassRunAndDisassemble( + original, skip_nop, do_validation, std::forward(args)...); + auto disassembly = std::get<0>(pass_result); + auto match_result = effcee::Match(disassembly, original); + EXPECT_EQ(effcee::Result::Status::Ok, match_result.status()) + << match_result.message() << "\nChecking result:\n" + << disassembly; + } +#endif + + // Adds a pass to be run. + template + void AddPass(Args&&... args) { + manager_->AddPass(std::forward(args)...); + } + + // Renews the pass manager, including clearing all previously added passes. + void RenewPassManger() { + manager_.reset(new opt::PassManager()); + manager_->SetMessageConsumer(consumer_); + } + + // Runs the passes added thus far using a pass manager on the binary assembled + // from the |original| assembly, and checks whether the optimized binary can + // be disassembled to the |expected| assembly. Callers are suggested to use + // SCOPED_TRACE() for better messages. + void RunAndCheck(const std::string& original, const std::string& expected) { + assert(manager_->NumPasses()); + + context_ = std::move(BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, original, + assemble_options_)); + ASSERT_NE(nullptr, context()); + + manager_->Run(context()); + + std::vector binary; + context()->module()->ToBinary(&binary, /* skip_nop = */ false); + + std::string optimized; + EXPECT_TRUE(tools_.Disassemble(binary, &optimized, disassemble_options_)); + EXPECT_EQ(expected, optimized); + } + + void SetAssembleOptions(uint32_t assemble_options) { + assemble_options_ = assemble_options; + } + + void SetDisassembleOptions(uint32_t disassemble_options) { + disassemble_options_ = disassemble_options; + } + + MessageConsumer consumer() { return consumer_; } + ir::IRContext* context() { return context_.get(); } + + void SetMessageConsumer(MessageConsumer msg_consumer) { + consumer_ = msg_consumer; + } + + private: + MessageConsumer consumer_; // Message consumer. + std::unique_ptr context_; // IR context + SpirvTools tools_; // An instance for calling SPIRV-Tools functionalities. + std::unique_ptr manager_; // The pass manager. + uint32_t assemble_options_; + uint32_t disassemble_options_; +}; + +} // namespace spvtools + +#endif // LIBSPIRV_TEST_OPT_PASS_FIXTURE_H_ diff --git a/3rdparty/spirv-tools/test/opt/pass_manager_test.cpp b/3rdparty/spirv-tools/test/opt/pass_manager_test.cpp new file mode 100644 index 000000000..2066eff97 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/pass_manager_test.cpp @@ -0,0 +1,186 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "gmock/gmock.h" + +#include + +#include "module_utils.h" +#include "opt/make_unique.h" +#include "pass_fixture.h" + +namespace { + +using namespace spvtools; +using spvtest::GetIdBound; +using ::testing::Eq; + +// A null pass whose construtors accept arguments +class NullPassWithArgs : public opt::NullPass { + public: + NullPassWithArgs(uint32_t) {} + NullPassWithArgs(std::string) {} + NullPassWithArgs(const std::vector&) {} + NullPassWithArgs(const std::vector&, uint32_t) {} + + const char* name() const override { return "null-with-args"; } +}; + +TEST(PassManager, Interface) { + opt::PassManager manager; + EXPECT_EQ(0u, manager.NumPasses()); + + manager.AddPass(); + EXPECT_EQ(1u, manager.NumPasses()); + EXPECT_STREQ("strip-debug", manager.GetPass(0)->name()); + + manager.AddPass(MakeUnique()); + EXPECT_EQ(2u, manager.NumPasses()); + EXPECT_STREQ("strip-debug", manager.GetPass(0)->name()); + EXPECT_STREQ("null", manager.GetPass(1)->name()); + + manager.AddPass(); + EXPECT_EQ(3u, manager.NumPasses()); + EXPECT_STREQ("strip-debug", manager.GetPass(0)->name()); + EXPECT_STREQ("null", manager.GetPass(1)->name()); + EXPECT_STREQ("strip-debug", manager.GetPass(2)->name()); + + manager.AddPass(1u); + manager.AddPass("null pass args"); + manager.AddPass(std::initializer_list{1, 2}); + manager.AddPass(std::initializer_list{1, 2}, 3); + EXPECT_EQ(7u, manager.NumPasses()); + EXPECT_STREQ("strip-debug", manager.GetPass(0)->name()); + EXPECT_STREQ("null", manager.GetPass(1)->name()); + EXPECT_STREQ("strip-debug", manager.GetPass(2)->name()); + EXPECT_STREQ("null-with-args", manager.GetPass(3)->name()); + EXPECT_STREQ("null-with-args", manager.GetPass(4)->name()); + EXPECT_STREQ("null-with-args", manager.GetPass(5)->name()); + EXPECT_STREQ("null-with-args", manager.GetPass(6)->name()); +} + +// A pass that appends an OpNop instruction to the debug1 section. +class AppendOpNopPass : public opt::Pass { + public: + const char* name() const override { return "AppendOpNop"; } + Status Process(ir::IRContext* irContext) override { + irContext->AddDebug1Inst(MakeUnique(irContext)); + return Status::SuccessWithChange; + } +}; + +// A pass that appends specified number of OpNop instructions to the debug1 +// section. +class AppendMultipleOpNopPass : public opt::Pass { + public: + explicit AppendMultipleOpNopPass(uint32_t num_nop) : num_nop_(num_nop) {} + + const char* name() const override { return "AppendOpNop"; } + Status Process(ir::IRContext* irContext) override { + for (uint32_t i = 0; i < num_nop_; i++) { + irContext->AddDebug1Inst(MakeUnique(irContext)); + } + return Status::SuccessWithChange; + } + + private: + uint32_t num_nop_; +}; + +// A pass that duplicates the last instruction in the debug1 section. +class DuplicateInstPass : public opt::Pass { + public: + const char* name() const override { return "DuplicateInst"; } + Status Process(ir::IRContext* irContext) override { + auto inst = MakeUnique( + *(--irContext->debug1_end())->Clone(irContext)); + irContext->AddDebug1Inst(std::move(inst)); + return Status::SuccessWithChange; + } +}; + +using PassManagerTest = PassTest<::testing::Test>; + +TEST_F(PassManagerTest, Run) { + const std::string text = "OpMemoryModel Logical GLSL450\nOpSource ESSL 310\n"; + + AddPass(); + AddPass(); + RunAndCheck(text.c_str(), (text + "OpNop\nOpNop\n").c_str()); + + RenewPassManger(); + AddPass(); + AddPass(); + RunAndCheck(text.c_str(), (text + "OpNop\nOpNop\n").c_str()); + + RenewPassManger(); + AddPass(); + AddPass(); + RunAndCheck(text.c_str(), (text + "OpSource ESSL 310\nOpNop\n").c_str()); + + RenewPassManger(); + AddPass(3); + RunAndCheck(text.c_str(), (text + "OpNop\nOpNop\nOpNop\n").c_str()); +} + +// A pass that appends an OpTypeVoid instruction that uses a given id. +class AppendTypeVoidInstPass : public opt::Pass { + public: + explicit AppendTypeVoidInstPass(uint32_t result_id) : result_id_(result_id) {} + + const char* name() const override { return "AppendTypeVoidInstPass"; } + Status Process(ir::IRContext* irContext) override { + auto inst = MakeUnique( + irContext, SpvOpTypeVoid, 0, result_id_, std::vector{}); + irContext->AddType(std::move(inst)); + return Status::SuccessWithChange; + } + + private: + uint32_t result_id_; +}; + +TEST(PassManager, RecomputeIdBoundAutomatically) { + opt::PassManager manager; + std::unique_ptr module(new ir::Module()); + ir::IRContext context(SPV_ENV_UNIVERSAL_1_2, std::move(module), + manager.consumer()); + EXPECT_THAT(GetIdBound(*context.module()), Eq(0u)); + + manager.Run(&context); + manager.AddPass(); + // With no ID changes, the ID bound does not change. + EXPECT_THAT(GetIdBound(*context.module()), Eq(0u)); + + // Now we force an Id of 100 to be used. + manager.AddPass(MakeUnique(100)); + EXPECT_THAT(GetIdBound(*context.module()), Eq(0u)); + manager.Run(&context); + // The Id has been updated automatically, even though the pass + // did not update it. + EXPECT_THAT(GetIdBound(*context.module()), Eq(101u)); + + // Try one more time! + manager.AddPass(MakeUnique(200)); + manager.Run(&context); + EXPECT_THAT(GetIdBound(*context.module()), Eq(201u)); + + // Add another pass, but which uses a lower Id. + manager.AddPass(MakeUnique(10)); + manager.Run(&context); + // The Id stays high. + EXPECT_THAT(GetIdBound(*context.module()), Eq(201u)); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/pass_merge_return_test.cpp b/3rdparty/spirv-tools/test/opt/pass_merge_return_test.cpp new file mode 100644 index 000000000..a4f482f85 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/pass_merge_return_test.cpp @@ -0,0 +1,706 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "spirv-tools/libspirv.hpp" +#include "spirv-tools/optimizer.hpp" + +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; + +using MergeReturnPassTest = PassTest<::testing::Test>; + +TEST_F(MergeReturnPassTest, OneReturn) { + const std::string before = + R"(OpCapability Addresses +OpCapability Kernel +OpCapability GenericPointer +OpCapability Linkage +OpMemoryModel Physical32 OpenCL +OpEntryPoint Kernel %1 "simple_kernel" +%2 = OpTypeVoid +%3 = OpTypeFunction %2 +%1 = OpFunction %2 None %3 +%4 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string after = before; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndCheck(before, after, false, true); +} + +TEST_F(MergeReturnPassTest, TwoReturnsNoValue) { + const std::string before = + R"(OpCapability Addresses +OpCapability Kernel +OpCapability GenericPointer +OpCapability Linkage +OpMemoryModel Physical32 OpenCL +OpEntryPoint Kernel %6 "simple_kernel" +%2 = OpTypeVoid +%3 = OpTypeBool +%4 = OpConstantFalse %3 +%1 = OpTypeFunction %2 +%6 = OpFunction %2 None %1 +%7 = OpLabel +OpBranchConditional %4 %8 %9 +%8 = OpLabel +OpReturn +%9 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(OpCapability Addresses +OpCapability Kernel +OpCapability GenericPointer +OpCapability Linkage +OpMemoryModel Physical32 OpenCL +OpEntryPoint Kernel %6 "simple_kernel" +%2 = OpTypeVoid +%3 = OpTypeBool +%4 = OpConstantFalse %3 +%1 = OpTypeFunction %2 +%6 = OpFunction %2 None %1 +%7 = OpLabel +OpBranchConditional %4 %8 %9 +%8 = OpLabel +OpBranch %10 +%9 = OpLabel +OpBranch %10 +%10 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndCheck(before, after, false, true); +} + +TEST_F(MergeReturnPassTest, TwoReturnsWithValues) { + const std::string before = + R"(OpCapability Linkage +OpCapability Kernel +OpMemoryModel Logical OpenCL +%1 = OpTypeInt 32 0 +%2 = OpTypeBool +%3 = OpConstantFalse %2 +%4 = OpConstant %1 0 +%5 = OpConstant %1 1 +%6 = OpTypeFunction %1 +%7 = OpFunction %1 None %6 +%8 = OpLabel +OpBranchConditional %3 %9 %10 +%9 = OpLabel +OpReturnValue %4 +%10 = OpLabel +OpReturnValue %5 +OpFunctionEnd +)"; + + const std::string after = + R"(OpCapability Linkage +OpCapability Kernel +OpMemoryModel Logical OpenCL +%1 = OpTypeInt 32 0 +%2 = OpTypeBool +%3 = OpConstantFalse %2 +%4 = OpConstant %1 0 +%5 = OpConstant %1 1 +%6 = OpTypeFunction %1 +%7 = OpFunction %1 None %6 +%8 = OpLabel +OpBranchConditional %3 %9 %10 +%9 = OpLabel +OpBranch %11 +%10 = OpLabel +OpBranch %11 +%11 = OpLabel +%12 = OpPhi %1 %4 %9 %5 %10 +OpReturnValue %12 +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndCheck(before, after, false, true); +} + +TEST_F(MergeReturnPassTest, UnreachableReturnsNoValue) { + const std::string before = + R"(OpCapability Addresses +OpCapability Kernel +OpCapability GenericPointer +OpCapability Linkage +OpMemoryModel Physical32 OpenCL +OpEntryPoint Kernel %6 "simple_kernel" +%2 = OpTypeVoid +%3 = OpTypeBool +%4 = OpConstantFalse %3 +%1 = OpTypeFunction %2 +%6 = OpFunction %2 None %1 +%7 = OpLabel +OpReturn +%8 = OpLabel +OpBranchConditional %4 %9 %10 +%9 = OpLabel +OpReturn +%10 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(OpCapability Addresses +OpCapability Kernel +OpCapability GenericPointer +OpCapability Linkage +OpMemoryModel Physical32 OpenCL +OpEntryPoint Kernel %6 "simple_kernel" +%2 = OpTypeVoid +%3 = OpTypeBool +%4 = OpConstantFalse %3 +%1 = OpTypeFunction %2 +%6 = OpFunction %2 None %1 +%7 = OpLabel +OpBranch %11 +%8 = OpLabel +OpBranchConditional %4 %9 %10 +%9 = OpLabel +OpBranch %11 +%10 = OpLabel +OpBranch %11 +%11 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndCheck(before, after, false, true); +} + +TEST_F(MergeReturnPassTest, UnreachableReturnsWithValues) { + const std::string before = + R"(OpCapability Linkage +OpCapability Kernel +OpMemoryModel Logical OpenCL +%1 = OpTypeInt 32 0 +%2 = OpTypeBool +%3 = OpConstantFalse %2 +%4 = OpConstant %1 0 +%5 = OpConstant %1 1 +%6 = OpTypeFunction %1 +%7 = OpFunction %1 None %6 +%8 = OpLabel +%9 = OpIAdd %1 %4 %5 +OpReturnValue %9 +%10 = OpLabel +OpBranchConditional %3 %11 %12 +%11 = OpLabel +OpReturnValue %4 +%12 = OpLabel +OpReturnValue %5 +OpFunctionEnd +)"; + + const std::string after = + R"(OpCapability Linkage +OpCapability Kernel +OpMemoryModel Logical OpenCL +%1 = OpTypeInt 32 0 +%2 = OpTypeBool +%3 = OpConstantFalse %2 +%4 = OpConstant %1 0 +%5 = OpConstant %1 1 +%6 = OpTypeFunction %1 +%7 = OpFunction %1 None %6 +%8 = OpLabel +%9 = OpIAdd %1 %4 %5 +OpBranch %13 +%10 = OpLabel +OpBranchConditional %3 %11 %12 +%11 = OpLabel +OpBranch %13 +%12 = OpLabel +OpBranch %13 +%13 = OpLabel +%14 = OpPhi %1 %9 %8 %4 %11 %5 %12 +OpReturnValue %14 +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndCheck(before, after, false, true); +} + +#ifdef SPIRV_EFFCEE +TEST_F(MergeReturnPassTest, StructuredControlFlowWithUnreachableMerge) { + const std::string before = + R"( +; CHECK: [[false:%\w+]] = OpConstantFalse +; CHECK: [[true:%\w+]] = OpConstantTrue +; CHECK: OpFunction +; CHECK: [[var:%\w+]] = OpVariable [[:%\w+]] Function [[false]] +; CHECK: OpSelectionMerge [[merge_lab:%\w+]] +; CHECK: OpBranchConditional [[cond:%\w+]] [[if_lab:%\w+]] [[then_lab:%\w+]] +; CHECK: [[if_lab]] = OpLabel +; CHECK-Next: OpStore [[var]] [[true]] +; CHECK-Next: OpBranch +; CHECK: [[then_lab]] = OpLabel +; CHECK-Next: OpStore [[var]] [[true]] +; CHECK-Next: OpBranch [[merge_lab]] +; CHECK: OpReturn +OpCapability Addresses +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %6 "simple_shader" +%2 = OpTypeVoid +%3 = OpTypeBool +%4 = OpConstantFalse %3 +%1 = OpTypeFunction %2 +%6 = OpFunction %2 None %1 +%7 = OpLabel +OpSelectionMerge %10 None +OpBranchConditional %4 %8 %9 +%8 = OpLabel +OpReturn +%9 = OpLabel +OpReturn +%10 = OpLabel +OpUnreachable +OpFunctionEnd +)"; + + SinglePassRunAndMatch(before, false); +} + +TEST_F(MergeReturnPassTest, StructuredControlFlowAddPhi) { + const std::string before = + R"( +; CHECK: [[false:%\w+]] = OpConstantFalse +; CHECK: [[true:%\w+]] = OpConstantTrue +; CHECK: OpFunction +; CHECK: [[var:%\w+]] = OpVariable [[:%\w+]] Function [[false]] +; CHECK: OpSelectionMerge [[merge_lab:%\w+]] +; CHECK: OpBranchConditional [[cond:%\w+]] [[if_lab:%\w+]] [[then_lab:%\w+]] +; CHECK: [[if_lab]] = OpLabel +; CHECK-NEXT: [[add:%\w+]] = OpIAdd [[type:%\w+]] +; CHECK-Next: OpStore [[var]] [[true]] +; CHECK-Next: OpBranch +; CHECK: [[then_lab]] = OpLabel +; CHECK-Next: OpStore [[var]] [[true]] +; CHECK-Next: OpBranch [[merge_lab]] +; CHECK: [[merge_lab]] = OpLabel +; CHECK-NEXT: [[phi:%\w+]] = OpPhi [[type]] [[add]] [[if_lab]] [[undef:%\w+]] [[then_lab]] +; CHECK: OpIAdd [[type]] [[phi]] [[phi]] +; CHECK: OpReturn +OpCapability Addresses +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %6 "simple_shader" +%2 = OpTypeVoid +%3 = OpTypeBool +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%4 = OpConstantFalse %3 +%1 = OpTypeFunction %2 +%6 = OpFunction %2 None %1 +%7 = OpLabel +OpSelectionMerge %10 None +OpBranchConditional %4 %8 %9 +%8 = OpLabel +%11 = OpIAdd %int %int_0 %int_0 +OpBranch %10 +%9 = OpLabel +OpReturn +%10 = OpLabel +%12 = OpIAdd %int %11 %11 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(before, false); +} +#endif + +TEST_F(MergeReturnPassTest, StructuredControlFlowBothMergeAndHeader) { + const std::string before = + R"(OpCapability Addresses + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %1 "simple_shader" + %2 = OpTypeVoid + %3 = OpTypeBool + %4 = OpTypeInt 32 0 + %5 = OpConstant %4 0 + %6 = OpConstantFalse %3 + %7 = OpTypeFunction %2 + %1 = OpFunction %2 None %7 + %8 = OpLabel + OpSelectionMerge %9 None + OpBranchConditional %6 %10 %11 + %10 = OpLabel + OpReturn + %11 = OpLabel + OpBranch %9 + %9 = OpLabel + OpLoopMerge %12 %13 None + OpBranch %13 + %13 = OpLabel + %14 = OpIAdd %4 %5 %5 + OpBranchConditional %6 %9 %12 + %12 = OpLabel + %15 = OpIAdd %4 %14 %14 + OpReturn + OpFunctionEnd +)"; + + const std::string after = + R"(OpCapability Addresses +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %1 "simple_shader" +%void = OpTypeVoid +%bool = OpTypeBool +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%false = OpConstantFalse %bool +%7 = OpTypeFunction %void +%_ptr_Function_bool = OpTypePointer Function %bool +%true = OpConstantTrue %bool +%1 = OpFunction %void None %7 +%8 = OpLabel +%18 = OpVariable %_ptr_Function_bool Function %false +OpSelectionMerge %9 None +OpBranchConditional %false %10 %11 +%10 = OpLabel +OpStore %18 %true +OpBranch %9 +%11 = OpLabel +OpBranch %9 +%9 = OpLabel +%23 = OpLoad %bool %18 +OpSelectionMerge %22 None +OpBranchConditional %23 %22 %21 +%21 = OpLabel +OpBranch %20 +%20 = OpLabel +OpLoopMerge %12 %13 None +OpBranch %13 +%13 = OpLabel +%14 = OpIAdd %uint %uint_0 %uint_0 +OpBranchConditional %false %20 %12 +%12 = OpLabel +%15 = OpIAdd %uint %14 %14 +OpStore %18 %true +OpBranch %22 +%22 = OpLabel +OpBranch %16 +%16 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(before, after, false, true); +} + +TEST_F(MergeReturnPassTest, NestedSelectionMerge) { + const std::string before = + R"( + OpCapability Addresses + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %1 "simple_shader" + %void = OpTypeVoid + %bool = OpTypeBool + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %false = OpConstantFalse %bool + %7 = OpTypeFunction %void + %1 = OpFunction %void None %7 + %8 = OpLabel + OpSelectionMerge %9 None + OpBranchConditional %false %10 %11 + %10 = OpLabel + OpReturn + %11 = OpLabel + OpSelectionMerge %12 None + OpBranchConditional %false %14 %15 + %14 = OpLabel + %16 = OpIAdd %uint %uint_0 %uint_0 + OpBranch %12 + %15 = OpLabel + OpReturn + %12 = OpLabel + OpBranch %9 + %9 = OpLabel + %17 = OpIAdd %uint %16 %16 + OpReturn + OpFunctionEnd +)"; + + const std::string after = + R"(OpCapability Addresses +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %1 "simple_shader" +%void = OpTypeVoid +%bool = OpTypeBool +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%false = OpConstantFalse %bool +%7 = OpTypeFunction %void +%_ptr_Function_bool = OpTypePointer Function %bool +%true = OpConstantTrue %bool +%24 = OpUndef %uint +%1 = OpFunction %void None %7 +%8 = OpLabel +%19 = OpVariable %_ptr_Function_bool Function %false +OpSelectionMerge %9 None +OpBranchConditional %false %10 %11 +%10 = OpLabel +OpStore %19 %true +OpBranch %9 +%11 = OpLabel +OpSelectionMerge %12 None +OpBranchConditional %false %13 %14 +%13 = OpLabel +%15 = OpIAdd %uint %uint_0 %uint_0 +OpBranch %12 +%14 = OpLabel +OpStore %19 %true +OpBranch %12 +%12 = OpLabel +%25 = OpPhi %uint %15 %13 %24 %14 +OpBranch %9 +%9 = OpLabel +%26 = OpPhi %uint %25 %12 %24 %10 +%23 = OpLoad %bool %19 +OpSelectionMerge %22 None +OpBranchConditional %23 %22 %21 +%21 = OpLabel +%16 = OpIAdd %uint %26 %26 +OpStore %19 %true +OpBranch %22 +%22 = OpLabel +OpBranch %17 +%17 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(before, after, false, true); +} + +// This is essentially the same as NestedSelectionMerge, except +// the order of the first branch is changed. This is to make sure things +// work even if the order of the traversals change. +TEST_F(MergeReturnPassTest, NestedSelectionMerge2) { + const std::string before = + R"( + OpCapability Addresses + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %1 "simple_shader" + %void = OpTypeVoid + %bool = OpTypeBool + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %false = OpConstantFalse %bool + %7 = OpTypeFunction %void + %1 = OpFunction %void None %7 + %8 = OpLabel + OpSelectionMerge %9 None + OpBranchConditional %false %10 %11 + %11 = OpLabel + OpReturn + %10 = OpLabel + OpSelectionMerge %12 None + OpBranchConditional %false %14 %15 + %14 = OpLabel + %16 = OpIAdd %uint %uint_0 %uint_0 + OpBranch %12 + %15 = OpLabel + OpReturn + %12 = OpLabel + OpBranch %9 + %9 = OpLabel + %17 = OpIAdd %uint %16 %16 + OpReturn + OpFunctionEnd +)"; + + const std::string after = + R"(OpCapability Addresses +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %1 "simple_shader" +%void = OpTypeVoid +%bool = OpTypeBool +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%false = OpConstantFalse %bool +%7 = OpTypeFunction %void +%_ptr_Function_bool = OpTypePointer Function %bool +%true = OpConstantTrue %bool +%24 = OpUndef %uint +%1 = OpFunction %void None %7 +%8 = OpLabel +%19 = OpVariable %_ptr_Function_bool Function %false +OpSelectionMerge %9 None +OpBranchConditional %false %10 %11 +%11 = OpLabel +OpStore %19 %true +OpBranch %9 +%10 = OpLabel +OpSelectionMerge %12 None +OpBranchConditional %false %13 %14 +%13 = OpLabel +%15 = OpIAdd %uint %uint_0 %uint_0 +OpBranch %12 +%14 = OpLabel +OpStore %19 %true +OpBranch %12 +%12 = OpLabel +%25 = OpPhi %uint %15 %13 %24 %14 +OpBranch %9 +%9 = OpLabel +%26 = OpPhi %uint %25 %12 %24 %11 +%23 = OpLoad %bool %19 +OpSelectionMerge %22 None +OpBranchConditional %23 %22 %21 +%21 = OpLabel +%16 = OpIAdd %uint %26 %26 +OpStore %19 %true +OpBranch %22 +%22 = OpLabel +OpBranch %17 +%17 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(before, after, false, true); +} + +TEST_F(MergeReturnPassTest, NestedSelectionMerge3) { + const std::string before = + R"( + OpCapability Addresses + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %1 "simple_shader" + %void = OpTypeVoid + %bool = OpTypeBool + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %false = OpConstantFalse %bool + %7 = OpTypeFunction %void + %1 = OpFunction %void None %7 + %8 = OpLabel + OpSelectionMerge %9 None + OpBranchConditional %false %10 %11 + %11 = OpLabel + OpReturn + %10 = OpLabel + %16 = OpIAdd %uint %uint_0 %uint_0 + OpSelectionMerge %12 None + OpBranchConditional %false %14 %15 + %14 = OpLabel + OpBranch %12 + %15 = OpLabel + OpReturn + %12 = OpLabel + OpBranch %9 + %9 = OpLabel + %17 = OpIAdd %uint %16 %16 + OpReturn + OpFunctionEnd +)"; + + const std::string after = + R"(OpCapability Addresses +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %1 "simple_shader" +%void = OpTypeVoid +%bool = OpTypeBool +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%false = OpConstantFalse %bool +%7 = OpTypeFunction %void +%_ptr_Function_bool = OpTypePointer Function %bool +%true = OpConstantTrue %bool +%24 = OpUndef %uint +%1 = OpFunction %void None %7 +%8 = OpLabel +%19 = OpVariable %_ptr_Function_bool Function %false +OpSelectionMerge %9 None +OpBranchConditional %false %10 %11 +%11 = OpLabel +OpStore %19 %true +OpBranch %9 +%10 = OpLabel +%12 = OpIAdd %uint %uint_0 %uint_0 +OpSelectionMerge %13 None +OpBranchConditional %false %14 %15 +%14 = OpLabel +OpBranch %13 +%15 = OpLabel +OpStore %19 %true +OpBranch %13 +%13 = OpLabel +OpBranch %9 +%9 = OpLabel +%25 = OpPhi %uint %12 %13 %24 %11 +%23 = OpLoad %bool %19 +OpSelectionMerge %22 None +OpBranchConditional %23 %22 %21 +%21 = OpLabel +%16 = OpIAdd %uint %25 %25 +OpStore %19 %true +OpBranch %22 +%22 = OpLabel +OpBranch %17 +%17 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(before, after, false, true); +} +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/pass_remove_duplicates_test.cpp b/3rdparty/spirv-tools/test/opt/pass_remove_duplicates_test.cpp new file mode 100644 index 000000000..d269daa41 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/pass_remove_duplicates_test.cpp @@ -0,0 +1,310 @@ +// Copyright (c) 2017 Pierre Moreau +// +// 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/opt/build_module.h" +#include "source/opt/ir_context.h" +#include "source/opt/pass_manager.h" +#include "source/opt/remove_duplicates_pass.h" +#include "source/spirv_constant.h" +#include "unit_spirv.h" + +namespace { + +using spvtools::ir::IRContext; +using spvtools::ir::Instruction; +using spvtools::opt::PassManager; +using spvtools::opt::RemoveDuplicatesPass; + +class RemoveDuplicatesTest : public ::testing::Test { + public: + RemoveDuplicatesTest() + : tools_(SPV_ENV_UNIVERSAL_1_2), + context_(), + consumer_([this](spv_message_level_t level, const char*, + const spv_position_t& position, const char* message) { + if (!error_message_.empty()) error_message_ += "\n"; + switch (level) { + case SPV_MSG_FATAL: + case SPV_MSG_INTERNAL_ERROR: + case SPV_MSG_ERROR: + error_message_ += "ERROR"; + break; + case SPV_MSG_WARNING: + error_message_ += "WARNING"; + break; + case SPV_MSG_INFO: + error_message_ += "INFO"; + break; + case SPV_MSG_DEBUG: + error_message_ += "DEBUG"; + break; + } + error_message_ += + ": " + std::to_string(position.index) + ": " + message; + }), + disassemble_options_(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER), + error_message_() { + tools_.SetMessageConsumer(consumer_); + } + + virtual void TearDown() override { error_message_.clear(); } + + std::string RunPass(const std::string& text) { + context_ = spvtools::BuildModule(SPV_ENV_UNIVERSAL_1_2, consumer_, text); + if (!context_.get()) return std::string(); + + PassManager manager; + manager.SetMessageConsumer(consumer_); + manager.AddPass(); + + spvtools::opt::Pass::Status pass_res = manager.Run(context_.get()); + if (pass_res == spvtools::opt::Pass::Status::Failure) return std::string(); + + return ModuleToText(); + } + + // Disassembles |binary| and outputs the result in |text|. If |text| is a + // null pointer, SPV_ERROR_INVALID_POINTER is returned. + spv_result_t Disassemble(const std::vector& binary, + std::string* text) { + if (!text) return SPV_ERROR_INVALID_POINTER; + return tools_.Disassemble(binary, text, disassemble_options_) + ? SPV_SUCCESS + : SPV_ERROR_INVALID_BINARY; + } + + // Returns the accumulated error messages for the test. + std::string GetErrorMessage() const { return error_message_; } + + std::string ToText(const std::vector& inst) { + std::vector binary = {SpvMagicNumber, 0x10200, 0u, 2u, 0u}; + for (const Instruction* i : inst) + i->ToBinaryWithoutAttachedDebugInsts(&binary); + std::string text; + Disassemble(binary, &text); + return text; + } + + std::string ModuleToText() { + std::vector binary; + context_->module()->ToBinary(&binary, false); + std::string text; + Disassemble(binary, &text); + return text; + } + + private: + spvtools::SpirvTools + tools_; // An instance for calling SPIRV-Tools functionalities. + std::unique_ptr context_; + spvtools::MessageConsumer consumer_; + uint32_t disassemble_options_; + std::string error_message_; +}; + +TEST_F(RemoveDuplicatesTest, DuplicateCapabilities) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpCapability Shader +OpMemoryModel Logical GLSL450 +)"; + const std::string after = R"(OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +)"; + + EXPECT_THAT(RunPass(spirv), after); + EXPECT_THAT(GetErrorMessage(), ""); +} + +TEST_F(RemoveDuplicatesTest, DuplicateExtInstImports) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +%1 = OpExtInstImport "OpenCL.std" +%2 = OpExtInstImport "OpenCL.std" +%3 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +)"; + const std::string after = R"(OpCapability Shader +OpCapability Linkage +%1 = OpExtInstImport "OpenCL.std" +%3 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +)"; + + EXPECT_THAT(RunPass(spirv), after); + EXPECT_THAT(GetErrorMessage(), ""); +} + +TEST_F(RemoveDuplicatesTest, DuplicateTypes) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +%1 = OpTypeInt 32 0 +%2 = OpTypeInt 32 0 +%3 = OpTypeStruct %1 %2 +)"; + const std::string after = R"(OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +%1 = OpTypeInt 32 0 +%3 = OpTypeStruct %1 %1 +)"; + + EXPECT_THAT(RunPass(spirv), after); + EXPECT_THAT(GetErrorMessage(), ""); +} + +TEST_F(RemoveDuplicatesTest, SameTypeDifferentMemberDecoration) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 GLSLPacked +%2 = OpTypeInt 32 0 +%1 = OpTypeStruct %2 %2 +%3 = OpTypeStruct %2 %2 +)"; + const std::string after = R"(OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 GLSLPacked +%2 = OpTypeInt 32 0 +%1 = OpTypeStruct %2 %2 +%3 = OpTypeStruct %2 %2 +)"; + + EXPECT_THAT(RunPass(spirv), after); + EXPECT_THAT(GetErrorMessage(), ""); +} + +TEST_F(RemoveDuplicatesTest, SameTypeAndMemberDecoration) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 GLSLPacked +OpDecorate %2 GLSLPacked +%3 = OpTypeInt 32 0 +%1 = OpTypeStruct %3 %3 +%2 = OpTypeStruct %3 %3 +)"; + const std::string after = R"(OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 GLSLPacked +%3 = OpTypeInt 32 0 +%1 = OpTypeStruct %3 %3 +)"; + + EXPECT_THAT(RunPass(spirv), after); + EXPECT_THAT(GetErrorMessage(), ""); +} + +TEST_F(RemoveDuplicatesTest, SameTypeAndDifferentName) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpName %1 "Type1" +OpName %2 "Type2" +%3 = OpTypeInt 32 0 +%1 = OpTypeStruct %3 %3 +%2 = OpTypeStruct %3 %3 +)"; + const std::string after = R"(OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpName %1 "Type1" +%3 = OpTypeInt 32 0 +%1 = OpTypeStruct %3 %3 +)"; + + EXPECT_THAT(RunPass(spirv), after); + EXPECT_THAT(GetErrorMessage(), ""); +} + +// Check that #1033 has been fixed. +TEST_F(RemoveDuplicatesTest, DoNotRemoveDifferentOpDecorationGroup) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 Constant +%1 = OpDecorationGroup +OpDecorate %2 Restrict +%2 = OpDecorationGroup +OpGroupDecorate %3 %1 %2 +%4 = OpTypeInt 32 0 +%3 = OpVariable %4 Uniform +)"; + const std::string after = R"(OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 Constant +%1 = OpDecorationGroup +OpDecorate %2 Restrict +%2 = OpDecorationGroup +OpGroupDecorate %3 %1 %2 +%4 = OpTypeInt 32 0 +%3 = OpVariable %4 Uniform +)"; + + EXPECT_THAT(RunPass(spirv), after); + EXPECT_THAT(GetErrorMessage(), ""); +} + +TEST_F(RemoveDuplicatesTest, DifferentDecorationGroup) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 Constant +OpDecorate %1 Restrict +%1 = OpDecorationGroup +OpDecorate %2 Constant +%2 = OpDecorationGroup +OpGroupDecorate %1 %3 +OpGroupDecorate %2 %4 +%5 = OpTypeInt 32 0 +%3 = OpVariable %5 Uniform +%4 = OpVariable %5 Uniform +)"; + const std::string after = R"(OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 Constant +OpDecorate %1 Restrict +%1 = OpDecorationGroup +OpDecorate %2 Constant +%2 = OpDecorationGroup +OpGroupDecorate %1 %3 +OpGroupDecorate %2 %4 +%5 = OpTypeInt 32 0 +%3 = OpVariable %5 Uniform +%4 = OpVariable %5 Uniform +)"; + + EXPECT_THAT(RunPass(spirv), after); + EXPECT_THAT(GetErrorMessage(), ""); +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/pass_test.cpp b/3rdparty/spirv-tools/test/opt/pass_test.cpp new file mode 100644 index 000000000..5ff1a121d --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/pass_test.cpp @@ -0,0 +1,244 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include + +#include "assembly_builder.h" +#include "opt/pass.h" +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { +using namespace spvtools; +class DummyPass : public opt::Pass { + public: + const char* name() const override { return "dummy-pass"; } + Status Process(ir::IRContext* irContext) override { + return irContext ? Status::SuccessWithoutChange : Status::Failure; + } +}; +} // namespace + +namespace { + +using namespace spvtools; +using ::testing::UnorderedElementsAre; + +using PassClassTest = PassTest<::testing::Test>; + +TEST_F(PassClassTest, BasicVisitFromEntryPoint) { + // Make sure we visit the entry point, and the function it calls. + // Do not visit Dead or Exported. + const std::string text = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %10 "main" + OpName %10 "main" + OpName %Dead "Dead" + OpName %11 "Constant" + OpName %ExportedFunc "ExportedFunc" + OpDecorate %ExportedFunc LinkageAttributes "ExportedFunc" Export + %void = OpTypeVoid + %6 = OpTypeFunction %void + %10 = OpFunction %void None %6 + %14 = OpLabel + %15 = OpFunctionCall %void %11 + %16 = OpFunctionCall %void %11 + OpReturn + OpFunctionEnd + %11 = OpFunction %void None %6 + %18 = OpLabel + OpReturn + OpFunctionEnd + %Dead = OpFunction %void None %6 + %19 = OpLabel + OpReturn + OpFunctionEnd +%ExportedFunc = OpFunction %void None %7 + %20 = OpLabel + %21 = OpFunctionCall %void %11 + OpReturn + OpFunctionEnd +)"; + // clang-format on + + std::unique_ptr localContext = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + EXPECT_NE(nullptr, localContext) << "Assembling failed for shader:\n" + << text << std::endl; + DummyPass testPass; + std::vector processed; + opt::Pass::ProcessFunction mark_visited = [&processed](ir::Function* fp) { + processed.push_back(fp->result_id()); + return false; + }; + testPass.ProcessEntryPointCallTree(mark_visited, localContext->module()); + EXPECT_THAT(processed, UnorderedElementsAre(10, 11)); +} + +TEST_F(PassClassTest, BasicVisitReachable) { + // Make sure we visit the entry point, exported function, and the function + // they call. Do not visit Dead. + const std::string text = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %10 "main" + OpName %10 "main" + OpName %Dead "Dead" + OpName %11 "Constant" + OpName %12 "ExportedFunc" + OpName %13 "Constant2" + OpDecorate %12 LinkageAttributes "ExportedFunc" Export + %void = OpTypeVoid + %6 = OpTypeFunction %void + %10 = OpFunction %void None %6 + %14 = OpLabel + %15 = OpFunctionCall %void %11 + %16 = OpFunctionCall %void %11 + OpReturn + OpFunctionEnd + %11 = OpFunction %void None %6 + %18 = OpLabel + OpReturn + OpFunctionEnd + %Dead = OpFunction %void None %6 + %19 = OpLabel + OpReturn + OpFunctionEnd + %12 = OpFunction %void None %9 + %20 = OpLabel + %21 = OpFunctionCall %void %13 + OpReturn + OpFunctionEnd + %13 = OpFunction %void None %6 + %22 = OpLabel + OpReturn + OpFunctionEnd +)"; + // clang-format on + + std::unique_ptr localContext = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + EXPECT_NE(nullptr, localContext) << "Assembling failed for shader:\n" + << text << std::endl; + + DummyPass testPass; + std::vector processed; + opt::Pass::ProcessFunction mark_visited = [&processed](ir::Function* fp) { + processed.push_back(fp->result_id()); + return false; + }; + testPass.ProcessReachableCallTree(mark_visited, localContext.get()); + EXPECT_THAT(processed, UnorderedElementsAre(10, 11, 12, 13)); +} + +TEST_F(PassClassTest, BasicVisitOnlyOnce) { + // Make sure we visit %11 only once, even if it is called from two different + // functions. + const std::string text = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %10 "main" %gl_FragColor + OpName %10 "main" + OpName %Dead "Dead" + OpName %11 "Constant" + OpName %12 "ExportedFunc" + OpDecorate %12 LinkageAttributes "ExportedFunc" Export + %void = OpTypeVoid + %6 = OpTypeFunction %void + %10 = OpFunction %void None %6 + %14 = OpLabel + %15 = OpFunctionCall %void %11 + %16 = OpFunctionCall %void %12 + OpReturn + OpFunctionEnd + %11 = OpFunction %void None %6 + %18 = OpLabel + %19 = OpFunctionCall %void %12 + OpReturn + OpFunctionEnd + %Dead = OpFunction %void None %6 + %20 = OpLabel + OpReturn + OpFunctionEnd + %12 = OpFunction %void None %9 + %21 = OpLabel + OpReturn + OpFunctionEnd +)"; + // clang-format on + + std::unique_ptr localContext = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + EXPECT_NE(nullptr, localContext) << "Assembling failed for shader:\n" + << text << std::endl; + + DummyPass testPass; + std::vector processed; + opt::Pass::ProcessFunction mark_visited = [&processed](ir::Function* fp) { + processed.push_back(fp->result_id()); + return false; + }; + testPass.ProcessReachableCallTree(mark_visited, localContext.get()); + EXPECT_THAT(processed, UnorderedElementsAre(10, 11, 12)); +} + +TEST_F(PassClassTest, BasicDontVisitExportedVariable) { + // Make sure we only visit functions and not exported variables. + const std::string text = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %10 "main" %gl_FragColor + OpExecutionMode %10 OriginUpperLeft + OpSource GLSL 150 + OpName %10 "main" + OpName %Dead "Dead" + OpName %11 "Constant" + OpName %12 "export_var" + OpDecorate %12 LinkageAttributes "export_var" Export + %void = OpTypeVoid + %6 = OpTypeFunction %void + %float = OpTypeFloat 32 + %float_1 = OpConstant %float 1 + %12 = OpVariable %float Output + %10 = OpFunction %void None %6 + %14 = OpLabel + OpStore %12 %float_1 + OpReturn + OpFunctionEnd +)"; + // clang-format on + + std::unique_ptr localContext = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + EXPECT_NE(nullptr, localContext) << "Assembling failed for shader:\n" + << text << std::endl; + + DummyPass testPass; + std::vector processed; + opt::Pass::ProcessFunction mark_visited = [&processed](ir::Function* fp) { + processed.push_back(fp->result_id()); + return false; + }; + testPass.ProcessReachableCallTree(mark_visited, localContext.get()); + EXPECT_THAT(processed, UnorderedElementsAre(10)); +} +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/pass_utils.cpp b/3rdparty/spirv-tools/test/opt/pass_utils.cpp new file mode 100644 index 000000000..95b5181de --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/pass_utils.cpp @@ -0,0 +1,81 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "pass_utils.h" + +#include +#include + +namespace { + +// Well, this is another place requiring the knowledge of the grammar and can be +// stale when SPIR-V is updated. It would be nice to automatically generate +// this, but the cost is just too high. + +const char* kDebugOpcodes[] = { + // clang-format off + "OpSourceContinued", "OpSource", "OpSourceExtension", + "OpName", "OpMemberName", "OpString", + "OpLine", "OpNoLine", "OpModuleProcessed" + // clang-format on +}; + +} // anonymous namespace + +namespace spvtools { + +bool FindAndReplace(std::string* process_str, const std::string find_str, + const std::string replace_str) { + if (process_str->empty() || find_str.empty()) { + return false; + } + bool replaced = false; + // Note this algorithm has quadratic time complexity. It is OK for test cases + // with short strings, but might not fit in other contexts. + for (size_t pos = process_str->find(find_str, 0); pos != std::string::npos; + pos = process_str->find(find_str, pos)) { + process_str->replace(pos, find_str.length(), replace_str); + pos += replace_str.length(); + replaced = true; + } + return replaced; +} + +bool ContainsDebugOpcode(const char* inst) { + return std::any_of(std::begin(kDebugOpcodes), std::end(kDebugOpcodes), + [inst](const char* op) { + return std::string(inst).find(op) != std::string::npos; + }); +} + +std::string SelectiveJoin(const std::vector& strings, + const std::function& skip_dictator, + char delimiter) { + std::ostringstream oss; + for (const auto* str : strings) { + if (!skip_dictator(str)) oss << str << delimiter; + } + return oss.str(); +} + +std::string JoinAllInsts(const std::vector& insts) { + return SelectiveJoin(insts, [](const char*) { return false; }); +} + +std::string JoinNonDebugInsts(const std::vector& insts) { + return SelectiveJoin( + insts, [](const char* inst) { return ContainsDebugOpcode(inst); }); +} + +} // namespace spvtools diff --git a/3rdparty/spirv-tools/test/opt/pass_utils.h b/3rdparty/spirv-tools/test/opt/pass_utils.h new file mode 100644 index 000000000..4f17700a0 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/pass_utils.h @@ -0,0 +1,65 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_TEST_OPT_PASS_UTILS_H_ +#define LIBSPIRV_TEST_OPT_PASS_UTILS_H_ + +#include +#include +#include +#include + +namespace spvtools { + +// In-place substring replacement. Finds the |find_str| in the |process_str| +// and replaces the found substring with |replace_str|. Returns true if at +// least one replacement is done successfully, returns false otherwise. The +// replaced substring won't be processed again, which means: If the +// |replace_str| has |find_str| as its substring, that newly replaced part of +// |process_str| won't be processed again. +bool FindAndReplace(std::string* process_str, const std::string find_str, + const std::string replace_str); + +// Returns true if the given string contains any debug opcode substring. +bool ContainsDebugOpcode(const char* inst); + +// Returns the concatenated string from a vector of |strings|, with postfixing +// each string with the given |delimiter|. if the |skip_dictator| returns true +// for an original string, that string will be omitted. +std::string SelectiveJoin(const std::vector& strings, + const std::function& skip_dictator, + char delimiter = '\n'); + +// Concatenates a vector of strings into one string. Each string is postfixed +// with '\n'. +std::string JoinAllInsts(const std::vector& insts); + +// Concatenates a vector of strings into one string. Each string is postfixed +// with '\n'. If a string contains opcode for debug instruction, that string +// will be ignored. +std::string JoinNonDebugInsts(const std::vector& insts); + +// Returns a vector that contains the contents of |a| followed by the contents +// of |b|. +template +std::vector Concat(const std::vector& a, const std::vector& b) { + std::vector ret; + std::copy(a.begin(), a.end(), back_inserter(ret)); + std::copy(b.begin(), b.end(), back_inserter(ret)); + return ret; +} + +} // namespace spvtools + +#endif // LIBSPIRV_TEST_OPT_PASS_UTILS_H_ diff --git a/3rdparty/spirv-tools/test/opt/private_to_local_test.cpp b/3rdparty/spirv-tools/test/opt/private_to_local_test.cpp new file mode 100644 index 000000000..d59711b61 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/private_to_local_test.cpp @@ -0,0 +1,242 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "opt/value_number_table.h" + +#include "assembly_builder.h" +#include "gmock/gmock.h" +#include "opt/build_module.h" +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; + +using ::testing::HasSubstr; +using ::testing::MatchesRegex; + +using PrivateToLocalTest = PassTest<::testing::Test>; + +#ifdef SPIRV_EFFCEE +TEST_F(PrivateToLocalTest, ChangeToLocal) { + // Change the private variable to a local, and change the types accordingly. + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 +; CHECK: [[float:%[a-zA-Z_\d]+]] = OpTypeFloat 32 + %5 = OpTypeFloat 32 +; CHECK: [[newtype:%[a-zA-Z_\d]+]] = OpTypePointer Function [[float]] + %6 = OpTypePointer Private %5 +; CHECK-NOT: OpVariable [[.+]] Private + %8 = OpVariable %6 Private +; CHECK: OpFunction + %2 = OpFunction %3 None %4 +; CHECK: OpLabel + %7 = OpLabel +; CHECK-NEXT: [[newvar:%[a-zA-Z_\d]+]] = OpVariable [[newtype]] Function +; CHECK: OpLoad [[float]] [[newvar]] + %9 = OpLoad %5 %8 + OpReturn + OpFunctionEnd + )"; + SinglePassRunAndMatch(text, false); +} + +TEST_F(PrivateToLocalTest, ReuseExistingType) { + // Change the private variable to a local, and change the types accordingly. + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 +; CHECK: [[float:%[a-zA-Z_\d]+]] = OpTypeFloat 32 + %5 = OpTypeFloat 32 + %func_ptr = OpTypePointer Function %5 +; CHECK: [[newtype:%[a-zA-Z_\d]+]] = OpTypePointer Function [[float]] +; CHECK-NOT: [[%[a-zA-Z_\d]+]] = OpTypePointer Function [[float]] + %6 = OpTypePointer Private %5 +; CHECK-NOT: OpVariable [[.+]] Private + %8 = OpVariable %6 Private +; CHECK: OpFunction + %2 = OpFunction %3 None %4 +; CHECK: OpLabel + %7 = OpLabel +; CHECK-NEXT: [[newvar:%[a-zA-Z_\d]+]] = OpVariable [[newtype]] Function +; CHECK: OpLoad [[float]] [[newvar]] + %9 = OpLoad %5 %8 + OpReturn + OpFunctionEnd + )"; + SinglePassRunAndMatch(text, false); +} + +TEST_F(PrivateToLocalTest, UpdateAccessChain) { + // Change the private variable to a local, and change the AccessChain. + 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 + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %void = OpTypeVoid + %6 = OpTypeFunction %void +; CHECK: [[float:%[a-zA-Z_\d]+]] = OpTypeFloat + %float = OpTypeFloat 32 +; CHECK: [[struct:%[a-zA-Z_\d]+]] = OpTypeStruct + %_struct_8 = OpTypeStruct %float +%_ptr_Private_float = OpTypePointer Private %float +; CHECK: [[new_struct_type:%[a-zA-Z_\d]+]] = OpTypePointer Function [[struct]] +; CHECK: [[new_float_type:%[a-zA-Z_\d]+]] = OpTypePointer Function [[float]] +%_ptr_Private__struct_8 = OpTypePointer Private %_struct_8 +; CHECK-NOT: OpVariable [[.+]] Private + %11 = OpVariable %_ptr_Private__struct_8 Private +; CHECK: OpFunction + %2 = OpFunction %void None %6 +; CHECK: OpLabel + %12 = OpLabel +; CHECK-NEXT: [[newvar:%[a-zA-Z_\d]+]] = OpVariable [[new_struct_type]] Function +; CHECK: [[member:%[a-zA-Z_\d]+]] = OpAccessChain [[new_float_type]] [[newvar]] + %13 = OpAccessChain %_ptr_Private_float %11 %uint_0 +; CHECK: OpLoad [[float]] [[member]] + %14 = OpLoad %float %13 + OpReturn + OpFunctionEnd + )"; + SinglePassRunAndMatch(text, false); +} + +TEST_F(PrivateToLocalTest, UseTexelPointer) { + // Change the private variable to a local, and change the OpImageTexelPointer. + const std::string text = R"( +OpCapability SampledBuffer + OpCapability StorageImageExtendedFormats + OpCapability ImageBuffer + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %2 "min" %gl_GlobalInvocationID + OpExecutionMode %2 LocalSize 64 1 1 + OpSource HLSL 600 + OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId + OpDecorate %4 DescriptorSet 4 + OpDecorate %4 Binding 70 + %uint = OpTypeInt 32 0 + %6 = OpTypeImage %uint Buffer 0 0 0 2 R32ui +%_ptr_UniformConstant_6 = OpTypePointer UniformConstant %6 +%_ptr_Private_6 = OpTypePointer Private %6 + %void = OpTypeVoid + %10 = OpTypeFunction %void + %uint_0 = OpConstant %uint 0 + %uint_1 = OpConstant %uint 1 + %v3uint = OpTypeVector %uint 3 +%_ptr_Input_v3uint = OpTypePointer Input %v3uint +%_ptr_Image_uint = OpTypePointer Image %uint + %4 = OpVariable %_ptr_UniformConstant_6 UniformConstant + %16 = OpVariable %_ptr_Private_6 Private +%gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input + %2 = OpFunction %void None %10 + %17 = OpLabel +; Make sure the variable was moved. +; CHECK: OpFunction +; CHECK-NEXT: OpLabel +; CHECK-NEXT: OpVariable %_ptr_Function_6 Function + %18 = OpLoad %6 %4 + OpStore %16 %18 + %19 = OpImageTexelPointer %_ptr_Image_uint %16 %uint_0 %uint_0 + %20 = OpAtomicIAdd %uint %19 %uint_1 %uint_0 %uint_1 + OpReturn + OpFunctionEnd + )"; + SinglePassRunAndMatch(text, false); +} + +TEST_F(PrivateToLocalTest, UsedInTwoFunctions) { + // Should not change because it is used in multiple functions. + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Private %5 + %8 = OpVariable %6 Private + %2 = OpFunction %3 None %4 + %7 = OpLabel + %9 = OpLoad %5 %8 + OpReturn + OpFunctionEnd + %10 = OpFunction %3 None %4 + %11 = OpLabel + %12 = OpLoad %5 %8 + OpReturn + OpFunctionEnd + )"; + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ false); + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +TEST_F(PrivateToLocalTest, UsedInFunctionCall) { + // Should not change because it is used in a function call. Changing the + // signature of the function would require cloning the function, which is not + // worth it. + 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 + %void = OpTypeVoid + %4 = OpTypeFunction %void + %float = OpTypeFloat 32 +%_ptr_Private_float = OpTypePointer Private %float + %7 = OpTypeFunction %void %_ptr_Private_float + %8 = OpVariable %_ptr_Private_float Private + %2 = OpFunction %void None %4 + %9 = OpLabel + %10 = OpFunctionCall %void %11 %8 + OpReturn + OpFunctionEnd + %11 = OpFunction %void None %7 + %12 = OpFunctionParameter %_ptr_Private_float + %13 = OpLabel + %14 = OpLoad %float %12 + OpReturn + OpFunctionEnd + )"; + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ false); + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} +#endif +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/propagator_test.cpp b/3rdparty/spirv-tools/test/opt/propagator_test.cpp new file mode 100644 index 000000000..b6cd2c4c0 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/propagator_test.cpp @@ -0,0 +1,214 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "opt/build_module.h" +#include "opt/cfg.h" +#include "opt/ir_context.h" +#include "opt/pass.h" +#include "opt/propagator.h" + +namespace { + +using namespace spvtools; + +using ::testing::UnorderedElementsAre; + +class PropagatorTest : public testing::Test { + protected: + virtual void TearDown() { + ctx_.reset(nullptr); + values_.clear(); + values_vec_.clear(); + } + + void Assemble(const std::string& input) { + ctx_ = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, input); + ASSERT_NE(nullptr, ctx_) << "Assembling failed for shader:\n" + << input << "\n"; + } + + bool Propagate(const opt::SSAPropagator::VisitFunction& visit_fn) { + opt::SSAPropagator propagator(ctx_.get(), visit_fn); + bool retval = false; + for (auto& fn : *ctx_->module()) { + retval |= propagator.Run(&fn); + } + return retval; + } + + const std::vector& GetValues() { + values_vec_.clear(); + for (const auto& it : values_) { + values_vec_.push_back(it.second); + } + return values_vec_; + } + + std::unique_ptr ctx_; + std::map values_; + std::vector values_vec_; +}; + +TEST_F(PropagatorTest, LocalPropagate) { + const std::string spv_asm = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %outparm + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 450 + OpName %main "main" + OpName %x "x" + OpName %y "y" + OpName %z "z" + OpName %outparm "outparm" + OpDecorate %outparm Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int + %int_4 = OpConstant %int 4 + %int_3 = OpConstant %int 3 + %int_1 = OpConstant %int 1 +%_ptr_Output_int = OpTypePointer Output %int + %outparm = OpVariable %_ptr_Output_int Output + %main = OpFunction %void None %3 + %5 = OpLabel + %x = OpVariable %_ptr_Function_int Function + %y = OpVariable %_ptr_Function_int Function + %z = OpVariable %_ptr_Function_int Function + OpStore %x %int_4 + OpStore %y %int_3 + OpStore %z %int_1 + %20 = OpLoad %int %z + OpStore %outparm %20 + OpReturn + OpFunctionEnd + )"; + Assemble(spv_asm); + + const auto visit_fn = [this](ir::Instruction* instr, + ir::BasicBlock** dest_bb) { + *dest_bb = nullptr; + if (instr->opcode() == SpvOpStore) { + uint32_t lhs_id = instr->GetSingleWordOperand(0); + uint32_t rhs_id = instr->GetSingleWordOperand(1); + ir::Instruction* rhs_def = ctx_->get_def_use_mgr()->GetDef(rhs_id); + if (rhs_def->opcode() == SpvOpConstant) { + uint32_t val = rhs_def->GetSingleWordOperand(2); + values_[lhs_id] = val; + return opt::SSAPropagator::kInteresting; + } + } + return opt::SSAPropagator::kVarying; + }; + + EXPECT_TRUE(Propagate(visit_fn)); + EXPECT_THAT(GetValues(), UnorderedElementsAre(4, 3, 1)); +} + +TEST_F(PropagatorTest, PropagateThroughPhis) { + const std::string spv_asm = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %x %outparm + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 450 + OpName %main "main" + OpName %x "x" + OpName %outparm "outparm" + OpDecorate %x Flat + OpDecorate %x Location 0 + OpDecorate %outparm Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 + %bool = OpTypeBool +%_ptr_Function_int = OpTypePointer Function %int + %int_4 = OpConstant %int 4 + %int_3 = OpConstant %int 3 + %int_1 = OpConstant %int 1 +%_ptr_Input_int = OpTypePointer Input %int + %x = OpVariable %_ptr_Input_int Input +%_ptr_Output_int = OpTypePointer Output %int + %outparm = OpVariable %_ptr_Output_int Output + %main = OpFunction %void None %3 + %4 = OpLabel + %5 = OpLoad %int %x + %6 = OpSGreaterThan %bool %5 %int_3 + OpSelectionMerge %25 None + OpBranchConditional %6 %22 %23 + %22 = OpLabel + %7 = OpLoad %int %int_4 + OpBranch %25 + %23 = OpLabel + %8 = OpLoad %int %int_4 + OpBranch %25 + %25 = OpLabel + %35 = OpPhi %int %7 %22 %8 %23 + OpStore %outparm %35 + OpReturn + OpFunctionEnd + )"; + + Assemble(spv_asm); + + ir::Instruction *phi_instr = nullptr; + const auto visit_fn = [this, &phi_instr](ir::Instruction* instr, + ir::BasicBlock** dest_bb) { + *dest_bb = nullptr; + if (instr->opcode() == SpvOpLoad) { + uint32_t rhs_id = instr->GetSingleWordOperand(2); + ir::Instruction* rhs_def = ctx_->get_def_use_mgr()->GetDef(rhs_id); + if (rhs_def->opcode() == SpvOpConstant) { + uint32_t val = rhs_def->GetSingleWordOperand(2); + values_[instr->result_id()] = val; + return opt::SSAPropagator::kInteresting; + } + } else if (instr->opcode() == SpvOpPhi) { + phi_instr = instr; + opt::SSAPropagator::PropStatus retval; + for (uint32_t i = 2; i < instr->NumOperands(); i += 2) { + uint32_t phi_arg_id = instr->GetSingleWordOperand(i); + auto it = values_.find(phi_arg_id); + if (it != values_.end()) { + EXPECT_EQ(it->second, 4u); + retval = opt::SSAPropagator::kInteresting; + values_[instr->result_id()] = it->second; + } else { + retval = opt::SSAPropagator::kNotInteresting; + break; + } + } + return retval; + } + + return opt::SSAPropagator::kVarying; + }; + + EXPECT_TRUE(Propagate(visit_fn)); + + // The propagator should've concluded that the Phi instruction has a constant + // value of 4. + EXPECT_NE(phi_instr, nullptr); + EXPECT_EQ(values_[phi_instr->result_id()], 4u); + + EXPECT_THAT(GetValues(), UnorderedElementsAre(4u, 4u, 4u)); +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/redundancy_elimination_test.cpp b/3rdparty/spirv-tools/test/opt/redundancy_elimination_test.cpp new file mode 100644 index 000000000..c5f386818 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/redundancy_elimination_test.cpp @@ -0,0 +1,276 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "opt/value_number_table.h" + +#include "assembly_builder.h" +#include "gmock/gmock.h" +#include "opt/build_module.h" +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; + +using ::testing::HasSubstr; +using ::testing::MatchesRegex; + +using RedundancyEliminationTest = PassTest<::testing::Test>; + +#ifdef SPIRV_EFFCEE +// Test that it can get a simple case of local redundancy elimination. +// The rest of the test check for extra functionality. +TEST_F(RedundancyEliminationTest, RemoveRedundantLocalAdd) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Function %5 + %2 = OpFunction %3 None %4 + %7 = OpLabel + %8 = OpVariable %6 Function + %9 = OpLoad %5 %8 + %10 = OpFAdd %5 %9 %9 +; CHECK: OpFAdd +; CHECK-NOT: OpFAdd + %11 = OpFAdd %5 %9 %9 + OpReturn + OpFunctionEnd + )"; + SinglePassRunAndMatch(text, false); +} + +// Remove a redundant add across basic blocks. +TEST_F(RedundancyEliminationTest, RemoveRedundantAdd) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Function %5 + %2 = OpFunction %3 None %4 + %7 = OpLabel + %8 = OpVariable %6 Function + %9 = OpLoad %5 %8 + %10 = OpFAdd %5 %9 %9 + OpBranch %11 + %11 = OpLabel +; CHECK: OpFAdd +; CHECK-NOT: OpFAdd + %12 = OpFAdd %5 %9 %9 + OpReturn + OpFunctionEnd + )"; + SinglePassRunAndMatch(text, false); +} + +// Remove a redundant add going through a multiple basic blocks. +TEST_F(RedundancyEliminationTest, RemoveRedundantAddDiamond) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Function %5 + %7 = OpTypeBool + %8 = OpConstantTrue %7 + %2 = OpFunction %3 None %4 + %9 = OpLabel + %10 = OpVariable %6 Function + %11 = OpLoad %5 %10 + %12 = OpFAdd %5 %11 %11 +; CHECK: OpFAdd +; CHECK-NOT: OpFAdd + OpBranchConditional %8 %13 %14 + %13 = OpLabel + OpBranch %15 + %14 = OpLabel + OpBranch %15 + %15 = OpLabel + %16 = OpFAdd %5 %11 %11 + OpReturn + OpFunctionEnd + + )"; + SinglePassRunAndMatch(text, false); +} + +// Remove a redundant add in a side node. +TEST_F(RedundancyEliminationTest, RemoveRedundantAddInSideNode) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Function %5 + %7 = OpTypeBool + %8 = OpConstantTrue %7 + %2 = OpFunction %3 None %4 + %9 = OpLabel + %10 = OpVariable %6 Function + %11 = OpLoad %5 %10 + %12 = OpFAdd %5 %11 %11 +; CHECK: OpFAdd +; CHECK-NOT: OpFAdd + OpBranchConditional %8 %13 %14 + %13 = OpLabel + OpBranch %15 + %14 = OpLabel + %16 = OpFAdd %5 %11 %11 + OpBranch %15 + %15 = OpLabel + OpReturn + OpFunctionEnd + + )"; + SinglePassRunAndMatch(text, false); +} + +// Remove a redundant add whose value is in the result of a phi node. +TEST_F(RedundancyEliminationTest, RemoveRedundantAddWithPhi) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Function %5 + %7 = OpTypeBool + %8 = OpConstantTrue %7 + %2 = OpFunction %3 None %4 + %9 = OpLabel + %10 = OpVariable %6 Function + %11 = OpLoad %5 %10 + OpBranchConditional %8 %13 %14 + %13 = OpLabel + %add1 = OpFAdd %5 %11 %11 +; CHECK: OpFAdd + OpBranch %15 + %14 = OpLabel + %add2 = OpFAdd %5 %11 %11 +; CHECK: OpFAdd + OpBranch %15 + %15 = OpLabel +; CHECK: OpPhi + %phi = OpPhi %5 %add1 %13 %add2 %14 +; CHECK-NOT: OpFAdd + %16 = OpFAdd %5 %11 %11 + OpReturn + OpFunctionEnd + + )"; + SinglePassRunAndMatch(text, false); +} + +// Keep the add because it is redundant on some paths, but not all paths. +TEST_F(RedundancyEliminationTest, KeepPartiallyRedundantAdd) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Function %5 + %7 = OpTypeBool + %8 = OpConstantTrue %7 + %2 = OpFunction %3 None %4 + %9 = OpLabel + %10 = OpVariable %6 Function + %11 = OpLoad %5 %10 + OpBranchConditional %8 %13 %14 + %13 = OpLabel + %add = OpFAdd %5 %11 %11 + OpBranch %15 + %14 = OpLabel + OpBranch %15 + %15 = OpLabel + %16 = OpFAdd %5 %11 %11 + OpReturn + OpFunctionEnd + + )"; + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ false); + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +// Keep the add. Even if it is redundant on all paths, there is no single id +// whose definition dominates the add and contains the same value. +TEST_F(RedundancyEliminationTest, KeepRedundantAddWithoutPhi) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Function %5 + %7 = OpTypeBool + %8 = OpConstantTrue %7 + %2 = OpFunction %3 None %4 + %9 = OpLabel + %10 = OpVariable %6 Function + %11 = OpLoad %5 %10 + OpBranchConditional %8 %13 %14 + %13 = OpLabel + %add1 = OpFAdd %5 %11 %11 + OpBranch %15 + %14 = OpLabel + %add2 = OpFAdd %5 %11 %11 + OpBranch %15 + %15 = OpLabel + %16 = OpFAdd %5 %11 %11 + OpReturn + OpFunctionEnd + + )"; + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ false); + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} +#endif +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/replace_invalid_opc_test.cpp b/3rdparty/spirv-tools/test/opt/replace_invalid_opc_test.cpp new file mode 100644 index 000000000..badff544f --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/replace_invalid_opc_test.cpp @@ -0,0 +1,590 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "assembly_builder.h" +#include "gmock/gmock.h" +#include "pass_fixture.h" + +#include + +namespace { + +using namespace spvtools; + +using ReplaceInvalidOpcodeTest = PassTest<::testing::Test>; + +#ifdef SPIRV_EFFCEE +TEST_F(ReplaceInvalidOpcodeTest, ReplaceInstruction) { + const std::string text = R"( +; CHECK: [[special_const:%\w+]] = OpConstant %float -6.2598534e+18 +; CHECK: [[constant:%\w+]] = OpConstantComposite %v4float [[special_const]] [[special_const]] [[special_const]] [[special_const]] +; CHECK-NOT: OpImageSampleImplicitLod +; CHECK: OpStore [[:%\w+]] [[constant]] + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" %3 %gl_VertexIndex %5 + OpSource GLSL 400 + OpSourceExtension "GL_ARB_separate_shader_objects" + OpSourceExtension "GL_ARB_shading_language_420pack" + OpName %main "main" + OpDecorate %3 Location 0 + OpDecorate %gl_VertexIndex BuiltIn VertexIndex + OpMemberDecorate %_struct_6 0 BuiltIn Position + OpDecorate %_struct_6 Block + %void = OpTypeVoid + %8 = OpTypeFunction %void + %float = OpTypeFloat 32 + %10 = OpTypeImage %float 2D 0 0 0 1 Unknown +%_ptr_UniformConstant_10 = OpTypePointer UniformConstant %10 + %12 = OpTypeSampler +%_ptr_UniformConstant_12 = OpTypePointer UniformConstant %12 + %14 = OpTypeSampledImage %10 + %v4float = OpTypeVector %float 4 + %v2float = OpTypeVector %float 2 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %3 = OpVariable %_ptr_Output_v4float Output + %int = OpTypeInt 32 1 +%_ptr_Input_int = OpTypePointer Input %int +%gl_VertexIndex = OpVariable %_ptr_Input_int Input + %_struct_6 = OpTypeStruct %v4float +%_ptr_Output__struct_6 = OpTypePointer Output %_struct_6 + %5 = OpVariable %_ptr_Output__struct_6 Output + %int_0 = OpConstant %int 0 + %float_0 = OpConstant %float 0 + %23 = OpConstantComposite %v2float %float_0 %float_0 + %24 = OpVariable %_ptr_UniformConstant_10 UniformConstant + %25 = OpVariable %_ptr_UniformConstant_12 UniformConstant + %main = OpFunction %void None %8 + %26 = OpLabel + %27 = OpLoad %12 %25 + %28 = OpLoad %10 %24 + %29 = OpSampledImage %14 %28 %27 + %30 = OpImageSampleImplicitLod %v4float %29 %23 + %31 = OpAccessChain %_ptr_Output_v4float %5 %int_0 + OpStore %31 %30 + OpReturn + OpFunctionEnd)"; + + SinglePassRunAndMatch(text, false); +} + +TEST_F(ReplaceInvalidOpcodeTest, ReplaceInstructionInNonEntryPoint) { + const std::string text = R"( +; CHECK: [[special_const:%\w+]] = OpConstant %float -6.2598534e+18 +; CHECK: [[constant:%\w+]] = OpConstantComposite %v4float [[special_const]] [[special_const]] [[special_const]] [[special_const]] +; CHECK-NOT: OpImageSampleImplicitLod +; CHECK: OpStore [[:%\w+]] [[constant]] + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" %3 %gl_VertexIndex %5 + OpSource GLSL 400 + OpSourceExtension "GL_ARB_separate_shader_objects" + OpSourceExtension "GL_ARB_shading_language_420pack" + OpName %main "main" + OpDecorate %3 Location 0 + OpDecorate %gl_VertexIndex BuiltIn VertexIndex + OpMemberDecorate %_struct_6 0 BuiltIn Position + OpDecorate %_struct_6 Block + %void = OpTypeVoid + %8 = OpTypeFunction %void + %float = OpTypeFloat 32 + %10 = OpTypeImage %float 2D 0 0 0 1 Unknown +%_ptr_UniformConstant_10 = OpTypePointer UniformConstant %10 + %12 = OpTypeSampler +%_ptr_UniformConstant_12 = OpTypePointer UniformConstant %12 + %14 = OpTypeSampledImage %10 + %v4float = OpTypeVector %float 4 + %v2float = OpTypeVector %float 2 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %3 = OpVariable %_ptr_Output_v4float Output + %int = OpTypeInt 32 1 +%_ptr_Input_int = OpTypePointer Input %int +%gl_VertexIndex = OpVariable %_ptr_Input_int Input + %_struct_6 = OpTypeStruct %v4float +%_ptr_Output__struct_6 = OpTypePointer Output %_struct_6 + %5 = OpVariable %_ptr_Output__struct_6 Output + %int_0 = OpConstant %int 0 + %float_0 = OpConstant %float 0 + %23 = OpConstantComposite %v2float %float_0 %float_0 + %24 = OpVariable %_ptr_UniformConstant_10 UniformConstant + %25 = OpVariable %_ptr_UniformConstant_12 UniformConstant + %main = OpFunction %void None %8 + %26 = OpLabel + %27 = OpFunctionCall %void %28 + OpReturn + OpFunctionEnd + %28 = OpFunction %void None %8 + %29 = OpLabel + %30 = OpLoad %12 %25 + %31 = OpLoad %10 %24 + %32 = OpSampledImage %14 %31 %30 + %33 = OpImageSampleImplicitLod %v4float %32 %23 + %34 = OpAccessChain %_ptr_Output_v4float %5 %int_0 + OpStore %34 %33 + OpReturn + OpFunctionEnd)"; + + SinglePassRunAndMatch(text, false); +} + +TEST_F(ReplaceInvalidOpcodeTest, ReplaceInstructionMultipleEntryPoints) { + const std::string text = R"( +; CHECK: [[special_const:%\w+]] = OpConstant %float -6.2598534e+18 +; CHECK: [[constant:%\w+]] = OpConstantComposite %v4float [[special_const]] [[special_const]] [[special_const]] [[special_const]] +; CHECK-NOT: OpImageSampleImplicitLod +; CHECK: OpStore [[:%\w+]] [[constant]] +; CHECK-NOT: OpImageSampleImplicitLod +; CHECK: OpStore [[:%\w+]] [[constant]] + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" %3 %gl_VertexIndex %5 + OpEntryPoint Vertex %main2 "main2" %3 %gl_VertexIndex %5 + OpSource GLSL 400 + OpSourceExtension "GL_ARB_separate_shader_objects" + OpSourceExtension "GL_ARB_shading_language_420pack" + OpName %main "main" + OpName %main2 "main2" + OpDecorate %3 Location 0 + OpDecorate %gl_VertexIndex BuiltIn VertexIndex + OpMemberDecorate %_struct_6 0 BuiltIn Position + OpDecorate %_struct_6 Block + %void = OpTypeVoid + %8 = OpTypeFunction %void + %float = OpTypeFloat 32 + %10 = OpTypeImage %float 2D 0 0 0 1 Unknown +%_ptr_UniformConstant_10 = OpTypePointer UniformConstant %10 + %12 = OpTypeSampler +%_ptr_UniformConstant_12 = OpTypePointer UniformConstant %12 + %14 = OpTypeSampledImage %10 + %v4float = OpTypeVector %float 4 + %v2float = OpTypeVector %float 2 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %3 = OpVariable %_ptr_Output_v4float Output + %int = OpTypeInt 32 1 +%_ptr_Input_int = OpTypePointer Input %int +%gl_VertexIndex = OpVariable %_ptr_Input_int Input + %_struct_6 = OpTypeStruct %v4float +%_ptr_Output__struct_6 = OpTypePointer Output %_struct_6 + %5 = OpVariable %_ptr_Output__struct_6 Output + %int_0 = OpConstant %int 0 + %float_0 = OpConstant %float 0 + %23 = OpConstantComposite %v2float %float_0 %float_0 + %24 = OpVariable %_ptr_UniformConstant_10 UniformConstant + %25 = OpVariable %_ptr_UniformConstant_12 UniformConstant + %main = OpFunction %void None %8 + %26 = OpLabel + %27 = OpLoad %12 %25 + %28 = OpLoad %10 %24 + %29 = OpSampledImage %14 %28 %27 + %30 = OpImageSampleImplicitLod %v4float %29 %23 + %31 = OpAccessChain %_ptr_Output_v4float %5 %int_0 + OpStore %31 %30 + OpReturn + OpFunctionEnd + %main2 = OpFunction %void None %8 + %46 = OpLabel + %47 = OpLoad %12 %25 + %48 = OpLoad %10 %24 + %49 = OpSampledImage %14 %48 %47 + %50 = OpImageSampleImplicitLod %v4float %49 %23 + %51 = OpAccessChain %_ptr_Output_v4float %5 %int_0 + OpStore %51 %50 + OpReturn + OpFunctionEnd)"; + + SinglePassRunAndMatch(text, false); +} +TEST_F(ReplaceInvalidOpcodeTest, DontReplaceInstruction) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %3 %gl_VertexIndex %5 + OpSource GLSL 400 + OpSourceExtension "GL_ARB_separate_shader_objects" + OpSourceExtension "GL_ARB_shading_language_420pack" + OpName %main "main" + OpDecorate %3 Location 0 + OpDecorate %gl_VertexIndex BuiltIn VertexIndex + OpMemberDecorate %_struct_6 0 BuiltIn Position + OpDecorate %_struct_6 Block + %void = OpTypeVoid + %8 = OpTypeFunction %void + %float = OpTypeFloat 32 + %10 = OpTypeImage %float 2D 0 0 0 1 Unknown +%_ptr_UniformConstant_10 = OpTypePointer UniformConstant %10 + %12 = OpTypeSampler +%_ptr_UniformConstant_12 = OpTypePointer UniformConstant %12 + %14 = OpTypeSampledImage %10 + %v4float = OpTypeVector %float 4 + %v2float = OpTypeVector %float 2 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %3 = OpVariable %_ptr_Output_v4float Output + %int = OpTypeInt 32 1 +%_ptr_Input_int = OpTypePointer Input %int +%gl_VertexIndex = OpVariable %_ptr_Input_int Input + %_struct_6 = OpTypeStruct %v4float +%_ptr_Output__struct_6 = OpTypePointer Output %_struct_6 + %5 = OpVariable %_ptr_Output__struct_6 Output + %int_0 = OpConstant %int 0 + %float_0 = OpConstant %float 0 + %23 = OpConstantComposite %v2float %float_0 %float_0 + %24 = OpVariable %_ptr_UniformConstant_10 UniformConstant + %25 = OpVariable %_ptr_UniformConstant_12 UniformConstant + %main = OpFunction %void None %8 + %26 = OpLabel + %27 = OpLoad %12 %25 + %28 = OpLoad %10 %24 + %29 = OpSampledImage %14 %28 %27 + %30 = OpImageSampleImplicitLod %v4float %29 %23 + %31 = OpAccessChain %_ptr_Output_v4float %5 %int_0 + OpStore %31 %30 + OpReturn + OpFunctionEnd)"; + + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ false); + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +TEST_F(ReplaceInvalidOpcodeTest, MultipleEntryPointsDifferentStage) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" %3 %gl_VertexIndex %5 + OpEntryPoint Fragment %main2 "main2" %3 %gl_VertexIndex %5 + OpSource GLSL 400 + OpSourceExtension "GL_ARB_separate_shader_objects" + OpSourceExtension "GL_ARB_shading_language_420pack" + OpName %main "main" + OpName %main2 "main2" + OpDecorate %3 Location 0 + OpDecorate %gl_VertexIndex BuiltIn VertexIndex + OpMemberDecorate %_struct_6 0 BuiltIn Position + OpDecorate %_struct_6 Block + %void = OpTypeVoid + %8 = OpTypeFunction %void + %float = OpTypeFloat 32 + %10 = OpTypeImage %float 2D 0 0 0 1 Unknown +%_ptr_UniformConstant_10 = OpTypePointer UniformConstant %10 + %12 = OpTypeSampler +%_ptr_UniformConstant_12 = OpTypePointer UniformConstant %12 + %14 = OpTypeSampledImage %10 + %v4float = OpTypeVector %float 4 + %v2float = OpTypeVector %float 2 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %3 = OpVariable %_ptr_Output_v4float Output + %int = OpTypeInt 32 1 +%_ptr_Input_int = OpTypePointer Input %int +%gl_VertexIndex = OpVariable %_ptr_Input_int Input + %_struct_6 = OpTypeStruct %v4float +%_ptr_Output__struct_6 = OpTypePointer Output %_struct_6 + %5 = OpVariable %_ptr_Output__struct_6 Output + %int_0 = OpConstant %int 0 + %float_0 = OpConstant %float 0 + %23 = OpConstantComposite %v2float %float_0 %float_0 + %24 = OpVariable %_ptr_UniformConstant_10 UniformConstant + %25 = OpVariable %_ptr_UniformConstant_12 UniformConstant + %main = OpFunction %void None %8 + %26 = OpLabel + %27 = OpLoad %12 %25 + %28 = OpLoad %10 %24 + %29 = OpSampledImage %14 %28 %27 + %30 = OpImageSampleImplicitLod %v4float %29 %23 + %31 = OpAccessChain %_ptr_Output_v4float %5 %int_0 + OpStore %31 %30 + OpReturn + OpFunctionEnd + %main2 = OpFunction %void None %8 + %46 = OpLabel + %47 = OpLoad %12 %25 + %48 = OpLoad %10 %24 + %49 = OpSampledImage %14 %48 %47 + %50 = OpImageSampleImplicitLod %v4float %49 %23 + %51 = OpAccessChain %_ptr_Output_v4float %5 %int_0 + OpStore %51 %50 + OpReturn + OpFunctionEnd)"; + + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ false); + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +TEST_F(ReplaceInvalidOpcodeTest, DontReplaceLinkage) { + const std::string text = R"( + OpCapability Shader + OpCapability Linkage + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" %3 %gl_VertexIndex %5 + OpSource GLSL 400 + OpSourceExtension "GL_ARB_separate_shader_objects" + OpSourceExtension "GL_ARB_shading_language_420pack" + OpName %main "main" + OpDecorate %3 Location 0 + OpDecorate %gl_VertexIndex BuiltIn VertexIndex + OpMemberDecorate %_struct_6 0 BuiltIn Position + OpDecorate %_struct_6 Block + %void = OpTypeVoid + %8 = OpTypeFunction %void + %float = OpTypeFloat 32 + %10 = OpTypeImage %float 2D 0 0 0 1 Unknown +%_ptr_UniformConstant_10 = OpTypePointer UniformConstant %10 + %12 = OpTypeSampler +%_ptr_UniformConstant_12 = OpTypePointer UniformConstant %12 + %14 = OpTypeSampledImage %10 + %v4float = OpTypeVector %float 4 + %v2float = OpTypeVector %float 2 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %3 = OpVariable %_ptr_Output_v4float Output + %int = OpTypeInt 32 1 +%_ptr_Input_int = OpTypePointer Input %int +%gl_VertexIndex = OpVariable %_ptr_Input_int Input + %_struct_6 = OpTypeStruct %v4float +%_ptr_Output__struct_6 = OpTypePointer Output %_struct_6 + %5 = OpVariable %_ptr_Output__struct_6 Output + %int_0 = OpConstant %int 0 + %float_0 = OpConstant %float 0 + %23 = OpConstantComposite %v2float %float_0 %float_0 + %24 = OpVariable %_ptr_UniformConstant_10 UniformConstant + %25 = OpVariable %_ptr_UniformConstant_12 UniformConstant + %main = OpFunction %void None %8 + %26 = OpLabel + %27 = OpLoad %12 %25 + %28 = OpLoad %10 %24 + %29 = OpSampledImage %14 %28 %27 + %30 = OpImageSampleImplicitLod %v4float %29 %23 + %31 = OpAccessChain %_ptr_Output_v4float %5 %int_0 + OpStore %31 %30 + OpReturn + OpFunctionEnd)"; + + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ false); + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +TEST_F(ReplaceInvalidOpcodeTest, BarrierDontReplace) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %main "main" + OpExecutionMode %main LocalSize 1 1 1 + OpSource GLSL 450 + OpSourceExtension "GL_GOOGLE_cpp_style_line_directive" + OpSourceExtension "GL_GOOGLE_include_directive" + OpName %main "main" + %void = OpTypeVoid + %3 = OpTypeFunction %void + %uint = OpTypeInt 32 0 + %uint_2 = OpConstant %uint 2 +%uint_264 = OpConstant %uint 264 + %main = OpFunction %void None %3 + %5 = OpLabel + OpControlBarrier %uint_2 %uint_2 %uint_264 + OpReturn + OpFunctionEnd)"; + + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ false); + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +TEST_F(ReplaceInvalidOpcodeTest, BarrierReplace) { + const std::string text = R"( +; CHECK-NOT: OpControlBarrier + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" + OpExecutionMode %main LocalSize 1 1 1 + OpSource GLSL 450 + OpSourceExtension "GL_GOOGLE_cpp_style_line_directive" + OpSourceExtension "GL_GOOGLE_include_directive" + OpName %main "main" + %void = OpTypeVoid + %3 = OpTypeFunction %void + %uint = OpTypeInt 32 0 + %uint_2 = OpConstant %uint 2 +%uint_264 = OpConstant %uint 264 + %main = OpFunction %void None %3 + %5 = OpLabel + OpControlBarrier %uint_2 %uint_2 %uint_264 + OpReturn + OpFunctionEnd)"; + + SinglePassRunAndMatch(text, false); +} + +struct Message { + spv_message_level_t level; + const char* source_file; + uint32_t line_number; + uint32_t column_number; + const char* message; +}; + +MessageConsumer GetTestMessageConsumer( + std::vector& expected_messages) { + return [&expected_messages](spv_message_level_t level, const char* source, + const spv_position_t& position, + const char* message) { + EXPECT_TRUE(!expected_messages.empty()); + if (expected_messages.empty()) { + return; + } + + EXPECT_EQ(expected_messages[0].level, level); + EXPECT_EQ(expected_messages[0].line_number, position.line); + EXPECT_EQ(expected_messages[0].column_number, position.column); + EXPECT_STREQ(expected_messages[0].source_file, source); + EXPECT_STREQ(expected_messages[0].message, message); + + expected_messages.erase(expected_messages.begin()); + }; +} + +TEST_F(ReplaceInvalidOpcodeTest, MessageTest) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" %3 %gl_VertexIndex %5 + OpSource GLSL 400 + %6 = OpString "test.hlsl" + OpSourceExtension "GL_ARB_separate_shader_objects" + OpSourceExtension "GL_ARB_shading_language_420pack" + OpName %main "main" + OpDecorate %3 Location 0 + OpDecorate %gl_VertexIndex BuiltIn VertexIndex + OpMemberDecorate %_struct_7 0 BuiltIn Position + OpDecorate %_struct_7 Block + %void = OpTypeVoid + %9 = OpTypeFunction %void + %float = OpTypeFloat 32 + %11 = OpTypeImage %float 2D 0 0 0 1 Unknown +%_ptr_UniformConstant_11 = OpTypePointer UniformConstant %11 + %13 = OpTypeSampler +%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13 + %15 = OpTypeSampledImage %11 + %v4float = OpTypeVector %float 4 + %v2float = OpTypeVector %float 2 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %3 = OpVariable %_ptr_Output_v4float Output + %int = OpTypeInt 32 1 +%_ptr_Input_int = OpTypePointer Input %int +%gl_VertexIndex = OpVariable %_ptr_Input_int Input + %_struct_7 = OpTypeStruct %v4float +%_ptr_Output__struct_7 = OpTypePointer Output %_struct_7 + %5 = OpVariable %_ptr_Output__struct_7 Output + %int_0 = OpConstant %int 0 + %float_0 = OpConstant %float 0 + %24 = OpConstantComposite %v2float %float_0 %float_0 + %25 = OpVariable %_ptr_UniformConstant_11 UniformConstant + %26 = OpVariable %_ptr_UniformConstant_13 UniformConstant + %main = OpFunction %void None %9 + %27 = OpLabel + OpLine %6 2 4 + %28 = OpLoad %13 %26 + %29 = OpLoad %11 %25 + %30 = OpSampledImage %15 %29 %28 + %31 = OpImageSampleImplicitLod %v4float %30 %24 + %32 = OpAccessChain %_ptr_Output_v4float %5 %int_0 + OpStore %32 %31 + OpReturn + OpFunctionEnd)"; + + std::vector messages = { + {SPV_MSG_WARNING, "test.hlsl", 2, 4, + "Removing ImageSampleImplicitLod instruction because of incompatible " + "execution model."}}; + SetMessageConsumer(GetTestMessageConsumer(messages)); + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ false); + EXPECT_EQ(opt::Pass::Status::SuccessWithChange, std::get<1>(result)); +} + +TEST_F(ReplaceInvalidOpcodeTest, MultipleMessageTest) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" %3 %gl_VertexIndex %5 + OpSource GLSL 400 + %6 = OpString "test.hlsl" + OpSourceExtension "GL_ARB_separate_shader_objects" + OpSourceExtension "GL_ARB_shading_language_420pack" + OpName %main "main" + OpDecorate %3 Location 0 + OpDecorate %gl_VertexIndex BuiltIn VertexIndex + OpMemberDecorate %_struct_7 0 BuiltIn Position + OpDecorate %_struct_7 Block + %void = OpTypeVoid + %9 = OpTypeFunction %void + %float = OpTypeFloat 32 + %11 = OpTypeImage %float 2D 0 0 0 1 Unknown +%_ptr_UniformConstant_11 = OpTypePointer UniformConstant %11 + %13 = OpTypeSampler +%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13 + %15 = OpTypeSampledImage %11 + %v4float = OpTypeVector %float 4 + %v2float = OpTypeVector %float 2 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %3 = OpVariable %_ptr_Output_v4float Output + %int = OpTypeInt 32 1 +%_ptr_Input_int = OpTypePointer Input %int +%gl_VertexIndex = OpVariable %_ptr_Input_int Input + %_struct_7 = OpTypeStruct %v4float +%_ptr_Output__struct_7 = OpTypePointer Output %_struct_7 + %5 = OpVariable %_ptr_Output__struct_7 Output + %int_0 = OpConstant %int 0 + %float_0 = OpConstant %float 0 + %24 = OpConstantComposite %v2float %float_0 %float_0 + %25 = OpVariable %_ptr_UniformConstant_11 UniformConstant + %26 = OpVariable %_ptr_UniformConstant_13 UniformConstant + %main = OpFunction %void None %9 + %27 = OpLabel + OpLine %6 2 4 + %28 = OpLoad %13 %26 + %29 = OpLoad %11 %25 + %30 = OpSampledImage %15 %29 %28 + %31 = OpImageSampleImplicitLod %v4float %30 %24 + OpLine %6 12 4 + %41 = OpImageSampleProjImplicitLod %v4float %30 %24 + %32 = OpAccessChain %_ptr_Output_v4float %5 %int_0 + OpStore %32 %31 + OpReturn + OpFunctionEnd)"; + + std::vector messages = { + {SPV_MSG_WARNING, "test.hlsl", 2, 4, + "Removing ImageSampleImplicitLod instruction because of incompatible " + "execution model."}, + {SPV_MSG_WARNING, "test.hlsl", 12, 4, + "Removing ImageSampleProjImplicitLod instruction because of " + "incompatible " + "execution model."}}; + SetMessageConsumer(GetTestMessageConsumer(messages)); + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ false); + EXPECT_EQ(opt::Pass::Status::SuccessWithChange, std::get<1>(result)); +} +#endif +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/scalar_analysis.cpp b/3rdparty/spirv-tools/test/opt/scalar_analysis.cpp new file mode 100644 index 000000000..a73953eb8 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/scalar_analysis.cpp @@ -0,0 +1,1228 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include +#include +#include + +#include "assembly_builder.h" +#include "function_utils.h" +#include "pass_fixture.h" +#include "pass_utils.h" + +#include "opt/iterator.h" +#include "opt/loop_descriptor.h" +#include "opt/pass.h" +#include "opt/scalar_analysis.h" +#include "opt/tree_iterator.h" + +namespace { + +using namespace spvtools; +using ::testing::UnorderedElementsAre; + +using ScalarAnalysisTest = PassTest<::testing::Test>; + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 410 core +layout (location = 1) out float array[10]; +void main() { + for (int i = 0; i < 10; ++i) { + array[i] = array[i+1]; + } +} +*/ +TEST_F(ScalarAnalysisTest, BasicEvolutionTest) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %24 + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 410 + OpName %4 "main" + OpName %24 "array" + OpDecorate %24 Location 1 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %19 = OpTypeFloat 32 + %20 = OpTypeInt 32 0 + %21 = OpConstant %20 10 + %22 = OpTypeArray %19 %21 + %23 = OpTypePointer Output %22 + %24 = OpVariable %23 Output + %27 = OpConstant %6 1 + %29 = OpTypePointer Output %19 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpBranch %10 + %10 = OpLabel + %35 = OpPhi %6 %9 %5 %34 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %35 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %28 = OpIAdd %6 %35 %27 + %30 = OpAccessChain %29 %24 %28 + %31 = OpLoad %19 %30 + %32 = OpAccessChain %29 %24 %35 + OpStore %32 %31 + OpBranch %13 + %13 = OpLabel + %34 = OpIAdd %6 %35 %27 + OpBranch %10 + %12 = OpLabel + OpReturn + OpFunctionEnd + )"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + const ir::Function* f = spvtest::GetFunction(module, 4); + opt::ScalarEvolutionAnalysis analysis{context.get()}; + + const ir::Instruction* store = nullptr; + const ir::Instruction* load = nullptr; + for (const ir::Instruction& inst : *spvtest::GetBasicBlock(f, 11)) { + if (inst.opcode() == SpvOp::SpvOpStore) { + store = &inst; + } + if (inst.opcode() == SpvOp::SpvOpLoad) { + load = &inst; + } + } + + EXPECT_NE(load, nullptr); + EXPECT_NE(store, nullptr); + + ir::Instruction* access_chain = + context->get_def_use_mgr()->GetDef(load->GetSingleWordInOperand(0)); + + ir::Instruction* child = context->get_def_use_mgr()->GetDef( + access_chain->GetSingleWordInOperand(1)); + const opt::SENode* node = analysis.AnalyzeInstruction(child); + + EXPECT_NE(node, nullptr); + + // Unsimplified node should have the form of ADD(REC(0,1), 1) + EXPECT_EQ(node->GetType(), opt::SENode::Add); + + const opt::SENode* child_1 = node->GetChild(0); + EXPECT_TRUE(child_1->GetType() == opt::SENode::Constant || + child_1->GetType() == opt::SENode::RecurrentAddExpr); + + const opt::SENode* child_2 = node->GetChild(1); + EXPECT_TRUE(child_2->GetType() == opt::SENode::Constant || + child_2->GetType() == opt::SENode::RecurrentAddExpr); + + opt::SENode* simplified = + analysis.SimplifyExpression(const_cast(node)); + // Simplified should be in the form of REC(1,1) + EXPECT_EQ(simplified->GetType(), opt::SENode::RecurrentAddExpr); + + EXPECT_EQ(simplified->GetChild(0)->GetType(), opt::SENode::Constant); + EXPECT_EQ(simplified->GetChild(0)->AsSEConstantNode()->FoldToSingleValue(), + 1); + + EXPECT_EQ(simplified->GetChild(1)->GetType(), opt::SENode::Constant); + EXPECT_EQ(simplified->GetChild(1)->AsSEConstantNode()->FoldToSingleValue(), + 1); + + EXPECT_EQ(simplified->GetChild(0), simplified->GetChild(1)); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 410 core +layout (location = 1) out float array[10]; +layout (location = 2) flat in int loop_invariant; +void main() { + for (int i = 0; i < 10; ++i) { + array[i] = array[i+loop_invariant]; + } +} + +*/ +TEST_F(ScalarAnalysisTest, LoadTest) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 %4 + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 430 + OpName %2 "main" + OpName %3 "array" + OpName %4 "loop_invariant" + OpDecorate %3 Location 1 + OpDecorate %4 Flat + OpDecorate %4 Location 2 + %5 = OpTypeVoid + %6 = OpTypeFunction %5 + %7 = OpTypeInt 32 1 + %8 = OpTypePointer Function %7 + %9 = OpConstant %7 0 + %10 = OpConstant %7 10 + %11 = OpTypeBool + %12 = OpTypeFloat 32 + %13 = OpTypeInt 32 0 + %14 = OpConstant %13 10 + %15 = OpTypeArray %12 %14 + %16 = OpTypePointer Output %15 + %3 = OpVariable %16 Output + %17 = OpTypePointer Input %7 + %4 = OpVariable %17 Input + %18 = OpTypePointer Output %12 + %19 = OpConstant %7 1 + %2 = OpFunction %5 None %6 + %20 = OpLabel + OpBranch %21 + %21 = OpLabel + %22 = OpPhi %7 %9 %20 %23 %24 + OpLoopMerge %25 %24 None + OpBranch %26 + %26 = OpLabel + %27 = OpSLessThan %11 %22 %10 + OpBranchConditional %27 %28 %25 + %28 = OpLabel + %29 = OpLoad %7 %4 + %30 = OpIAdd %7 %22 %29 + %31 = OpAccessChain %18 %3 %30 + %32 = OpLoad %12 %31 + %33 = OpAccessChain %18 %3 %22 + OpStore %33 %32 + OpBranch %24 + %24 = OpLabel + %23 = OpIAdd %7 %22 %19 + OpBranch %21 + %25 = OpLabel + OpReturn + OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + const ir::Function* f = spvtest::GetFunction(module, 2); + opt::ScalarEvolutionAnalysis analysis{context.get()}; + + const ir::Instruction* load = nullptr; + for (const ir::Instruction& inst : *spvtest::GetBasicBlock(f, 28)) { + if (inst.opcode() == SpvOp::SpvOpLoad) { + load = &inst; + } + } + + EXPECT_NE(load, nullptr); + + ir::Instruction* access_chain = + context->get_def_use_mgr()->GetDef(load->GetSingleWordInOperand(0)); + + ir::Instruction* child = context->get_def_use_mgr()->GetDef( + access_chain->GetSingleWordInOperand(1)); + // const opt::SENode* node = + // analysis.GetNodeFromInstruction(child->unique_id()); + + const opt::SENode* node = analysis.AnalyzeInstruction(child); + + EXPECT_NE(node, nullptr); + + // Unsimplified node should have the form of ADD(REC(0,1), X) + EXPECT_EQ(node->GetType(), opt::SENode::Add); + + const opt::SENode* child_1 = node->GetChild(0); + EXPECT_TRUE(child_1->GetType() == opt::SENode::ValueUnknown || + child_1->GetType() == opt::SENode::RecurrentAddExpr); + + const opt::SENode* child_2 = node->GetChild(1); + EXPECT_TRUE(child_2->GetType() == opt::SENode::ValueUnknown || + child_2->GetType() == opt::SENode::RecurrentAddExpr); + + opt::SENode* simplified = + analysis.SimplifyExpression(const_cast(node)); + EXPECT_EQ(simplified->GetType(), opt::SENode::RecurrentAddExpr); + + const opt::SERecurrentNode* rec = simplified->AsSERecurrentNode(); + + EXPECT_NE(rec->GetChild(0), rec->GetChild(1)); + + EXPECT_EQ(rec->GetOffset()->GetType(), opt::SENode::ValueUnknown); + + EXPECT_EQ(rec->GetCoefficient()->GetType(), opt::SENode::Constant); + EXPECT_EQ(rec->GetCoefficient()->AsSEConstantNode()->FoldToSingleValue(), 1u); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 410 core +layout (location = 1) out float array[10]; +layout (location = 2) flat in int loop_invariant; +void main() { + array[0] = array[loop_invariant * 2 + 4 + 5 - 24 - loop_invariant - +loop_invariant+ 16 * 3]; +} + +*/ +TEST_F(ScalarAnalysisTest, SimplifySimple) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 %4 + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 430 + OpName %2 "main" + OpName %3 "array" + OpName %4 "loop_invariant" + OpDecorate %3 Location 1 + OpDecorate %4 Flat + OpDecorate %4 Location 2 + %5 = OpTypeVoid + %6 = OpTypeFunction %5 + %7 = OpTypeFloat 32 + %8 = OpTypeInt 32 0 + %9 = OpConstant %8 10 + %10 = OpTypeArray %7 %9 + %11 = OpTypePointer Output %10 + %3 = OpVariable %11 Output + %12 = OpTypeInt 32 1 + %13 = OpConstant %12 0 + %14 = OpTypePointer Input %12 + %4 = OpVariable %14 Input + %15 = OpConstant %12 2 + %16 = OpConstant %12 4 + %17 = OpConstant %12 5 + %18 = OpConstant %12 24 + %19 = OpConstant %12 48 + %20 = OpTypePointer Output %7 + %2 = OpFunction %5 None %6 + %21 = OpLabel + %22 = OpLoad %12 %4 + %23 = OpIMul %12 %22 %15 + %24 = OpIAdd %12 %23 %16 + %25 = OpIAdd %12 %24 %17 + %26 = OpISub %12 %25 %18 + %28 = OpISub %12 %26 %22 + %30 = OpISub %12 %28 %22 + %31 = OpIAdd %12 %30 %19 + %32 = OpAccessChain %20 %3 %31 + %33 = OpLoad %7 %32 + %34 = OpAccessChain %20 %3 %13 + OpStore %34 %33 + OpReturn + OpFunctionEnd + )"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + const ir::Function* f = spvtest::GetFunction(module, 2); + opt::ScalarEvolutionAnalysis analysis{context.get()}; + + const ir::Instruction* load = nullptr; + for (const ir::Instruction& inst : *spvtest::GetBasicBlock(f, 21)) { + if (inst.opcode() == SpvOp::SpvOpLoad && inst.result_id() == 33) { + load = &inst; + } + } + + EXPECT_NE(load, nullptr); + + ir::Instruction* access_chain = + context->get_def_use_mgr()->GetDef(load->GetSingleWordInOperand(0)); + + ir::Instruction* child = context->get_def_use_mgr()->GetDef( + access_chain->GetSingleWordInOperand(1)); + + const opt::SENode* node = analysis.AnalyzeInstruction(child); + + // Unsimplified is a very large graph with an add at the top. + EXPECT_NE(node, nullptr); + EXPECT_EQ(node->GetType(), opt::SENode::Add); + + // Simplified node should resolve down to a constant expression as the loads + // will eliminate themselves. + opt::SENode* simplified = + analysis.SimplifyExpression(const_cast(node)); + + EXPECT_EQ(simplified->GetType(), opt::SENode::Constant); + EXPECT_EQ(simplified->AsSEConstantNode()->FoldToSingleValue(), 33u); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 410 core +layout(location = 0) in vec4 c; +layout (location = 1) out float array[10]; +void main() { + int N = int(c.x); + for (int i = 0; i < 10; ++i) { + array[i] = array[i]; + array[i] = array[i-1]; + array[i] = array[i+1]; + array[i+1] = array[i+1]; + array[i+N] = array[i+N]; + array[i] = array[i+N]; + } +} + +*/ +TEST_F(ScalarAnalysisTest, Simplify) { + const std::string text = R"( OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %12 %33 + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 410 + OpName %4 "main" + OpName %8 "N" + OpName %12 "c" + OpName %19 "i" + OpName %33 "array" + OpDecorate %12 Location 0 + OpDecorate %33 Location 1 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpTypeFloat 32 + %10 = OpTypeVector %9 4 + %11 = OpTypePointer Input %10 + %12 = OpVariable %11 Input + %13 = OpTypeInt 32 0 + %14 = OpConstant %13 0 + %15 = OpTypePointer Input %9 + %20 = OpConstant %6 0 + %27 = OpConstant %6 10 + %28 = OpTypeBool + %30 = OpConstant %13 10 + %31 = OpTypeArray %9 %30 + %32 = OpTypePointer Output %31 + %33 = OpVariable %32 Output + %36 = OpTypePointer Output %9 + %42 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %19 = OpVariable %7 Function + %16 = OpAccessChain %15 %12 %14 + %17 = OpLoad %9 %16 + %18 = OpConvertFToS %6 %17 + OpStore %8 %18 + OpStore %19 %20 + OpBranch %21 + %21 = OpLabel + %78 = OpPhi %6 %20 %5 %77 %24 + OpLoopMerge %23 %24 None + OpBranch %25 + %25 = OpLabel + %29 = OpSLessThan %28 %78 %27 + OpBranchConditional %29 %22 %23 + %22 = OpLabel + %37 = OpAccessChain %36 %33 %78 + %38 = OpLoad %9 %37 + %39 = OpAccessChain %36 %33 %78 + OpStore %39 %38 + %43 = OpISub %6 %78 %42 + %44 = OpAccessChain %36 %33 %43 + %45 = OpLoad %9 %44 + %46 = OpAccessChain %36 %33 %78 + OpStore %46 %45 + %49 = OpIAdd %6 %78 %42 + %50 = OpAccessChain %36 %33 %49 + %51 = OpLoad %9 %50 + %52 = OpAccessChain %36 %33 %78 + OpStore %52 %51 + %54 = OpIAdd %6 %78 %42 + %56 = OpIAdd %6 %78 %42 + %57 = OpAccessChain %36 %33 %56 + %58 = OpLoad %9 %57 + %59 = OpAccessChain %36 %33 %54 + OpStore %59 %58 + %62 = OpIAdd %6 %78 %18 + %65 = OpIAdd %6 %78 %18 + %66 = OpAccessChain %36 %33 %65 + %67 = OpLoad %9 %66 + %68 = OpAccessChain %36 %33 %62 + OpStore %68 %67 + %72 = OpIAdd %6 %78 %18 + %73 = OpAccessChain %36 %33 %72 + %74 = OpLoad %9 %73 + %75 = OpAccessChain %36 %33 %78 + OpStore %75 %74 + OpBranch %24 + %24 = OpLabel + %77 = OpIAdd %6 %78 %42 + OpStore %19 %77 + OpBranch %21 + %23 = OpLabel + OpReturn + OpFunctionEnd +)"; + // clang-format on + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + const ir::Function* f = spvtest::GetFunction(module, 4); + opt::ScalarEvolutionAnalysis analysis{context.get()}; + + const ir::Instruction* loads[6]; + const ir::Instruction* stores[6]; + int load_count = 0; + int store_count = 0; + + for (const ir::Instruction& inst : *spvtest::GetBasicBlock(f, 22)) { + if (inst.opcode() == SpvOp::SpvOpLoad) { + loads[load_count] = &inst; + ++load_count; + } + if (inst.opcode() == SpvOp::SpvOpStore) { + stores[store_count] = &inst; + ++store_count; + } + } + + EXPECT_EQ(load_count, 6); + EXPECT_EQ(store_count, 6); + + ir::Instruction* load_access_chain; + ir::Instruction* store_access_chain; + ir::Instruction* load_child; + ir::Instruction* store_child; + opt::SENode* load_node; + opt::SENode* store_node; + opt::SENode* subtract_node; + opt::SENode* simplified_node; + + // Testing [i] - [i] == 0 + load_access_chain = + context->get_def_use_mgr()->GetDef(loads[0]->GetSingleWordInOperand(0)); + store_access_chain = + context->get_def_use_mgr()->GetDef(stores[0]->GetSingleWordInOperand(0)); + + load_child = context->get_def_use_mgr()->GetDef( + load_access_chain->GetSingleWordInOperand(1)); + store_child = context->get_def_use_mgr()->GetDef( + store_access_chain->GetSingleWordInOperand(1)); + + load_node = analysis.AnalyzeInstruction(load_child); + store_node = analysis.AnalyzeInstruction(store_child); + + subtract_node = analysis.CreateSubtraction(store_node, load_node); + simplified_node = analysis.SimplifyExpression(subtract_node); + EXPECT_EQ(simplified_node->GetType(), opt::SENode::Constant); + EXPECT_EQ(simplified_node->AsSEConstantNode()->FoldToSingleValue(), 0u); + + // Testing [i] - [i-1] == 1 + load_access_chain = + context->get_def_use_mgr()->GetDef(loads[1]->GetSingleWordInOperand(0)); + store_access_chain = + context->get_def_use_mgr()->GetDef(stores[1]->GetSingleWordInOperand(0)); + + load_child = context->get_def_use_mgr()->GetDef( + load_access_chain->GetSingleWordInOperand(1)); + store_child = context->get_def_use_mgr()->GetDef( + store_access_chain->GetSingleWordInOperand(1)); + + load_node = analysis.AnalyzeInstruction(load_child); + store_node = analysis.AnalyzeInstruction(store_child); + + subtract_node = analysis.CreateSubtraction(store_node, load_node); + simplified_node = analysis.SimplifyExpression(subtract_node); + + EXPECT_EQ(simplified_node->GetType(), opt::SENode::Constant); + EXPECT_EQ(simplified_node->AsSEConstantNode()->FoldToSingleValue(), 1u); + + // Testing [i] - [i+1] == -1 + load_access_chain = + context->get_def_use_mgr()->GetDef(loads[2]->GetSingleWordInOperand(0)); + store_access_chain = + context->get_def_use_mgr()->GetDef(stores[2]->GetSingleWordInOperand(0)); + + load_child = context->get_def_use_mgr()->GetDef( + load_access_chain->GetSingleWordInOperand(1)); + store_child = context->get_def_use_mgr()->GetDef( + store_access_chain->GetSingleWordInOperand(1)); + + load_node = analysis.AnalyzeInstruction(load_child); + store_node = analysis.AnalyzeInstruction(store_child); + + subtract_node = analysis.CreateSubtraction(store_node, load_node); + simplified_node = analysis.SimplifyExpression(subtract_node); + EXPECT_EQ(simplified_node->GetType(), opt::SENode::Constant); + EXPECT_EQ(simplified_node->AsSEConstantNode()->FoldToSingleValue(), -1); + + // Testing [i+1] - [i+1] == 0 + load_access_chain = + context->get_def_use_mgr()->GetDef(loads[3]->GetSingleWordInOperand(0)); + store_access_chain = + context->get_def_use_mgr()->GetDef(stores[3]->GetSingleWordInOperand(0)); + + load_child = context->get_def_use_mgr()->GetDef( + load_access_chain->GetSingleWordInOperand(1)); + store_child = context->get_def_use_mgr()->GetDef( + store_access_chain->GetSingleWordInOperand(1)); + + load_node = analysis.AnalyzeInstruction(load_child); + store_node = analysis.AnalyzeInstruction(store_child); + + subtract_node = analysis.CreateSubtraction(store_node, load_node); + simplified_node = analysis.SimplifyExpression(subtract_node); + EXPECT_EQ(simplified_node->GetType(), opt::SENode::Constant); + EXPECT_EQ(simplified_node->AsSEConstantNode()->FoldToSingleValue(), 0u); + + // Testing [i+N] - [i+N] == 0 + load_access_chain = + context->get_def_use_mgr()->GetDef(loads[4]->GetSingleWordInOperand(0)); + store_access_chain = + context->get_def_use_mgr()->GetDef(stores[4]->GetSingleWordInOperand(0)); + + load_child = context->get_def_use_mgr()->GetDef( + load_access_chain->GetSingleWordInOperand(1)); + store_child = context->get_def_use_mgr()->GetDef( + store_access_chain->GetSingleWordInOperand(1)); + + load_node = analysis.AnalyzeInstruction(load_child); + store_node = analysis.AnalyzeInstruction(store_child); + + subtract_node = analysis.CreateSubtraction(store_node, load_node); + + simplified_node = analysis.SimplifyExpression(subtract_node); + EXPECT_EQ(simplified_node->GetType(), opt::SENode::Constant); + EXPECT_EQ(simplified_node->AsSEConstantNode()->FoldToSingleValue(), 0u); + + // Testing [i] - [i+N] == -N + load_access_chain = + context->get_def_use_mgr()->GetDef(loads[5]->GetSingleWordInOperand(0)); + store_access_chain = + context->get_def_use_mgr()->GetDef(stores[5]->GetSingleWordInOperand(0)); + + load_child = context->get_def_use_mgr()->GetDef( + load_access_chain->GetSingleWordInOperand(1)); + store_child = context->get_def_use_mgr()->GetDef( + store_access_chain->GetSingleWordInOperand(1)); + + load_node = analysis.AnalyzeInstruction(load_child); + store_node = analysis.AnalyzeInstruction(store_child); + + subtract_node = analysis.CreateSubtraction(store_node, load_node); + simplified_node = analysis.SimplifyExpression(subtract_node); + EXPECT_EQ(simplified_node->GetType(), opt::SENode::Negative); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 430 +layout(location = 1) out float array[10]; +layout(location = 2) flat in int loop_invariant; +void main(void) { + for (int i = 0; i < 10; ++i) { + array[i * 2 + i * 5] = array[i * i * 2]; + array[i * 2] = array[i * 5]; + } +} + +*/ + +TEST_F(ScalarAnalysisTest, SimplifyMultiplyInductions) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 %4 + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 430 + OpName %2 "main" + OpName %5 "i" + OpName %3 "array" + OpName %4 "loop_invariant" + OpDecorate %3 Location 1 + OpDecorate %4 Flat + OpDecorate %4 Location 2 + %6 = OpTypeVoid + %7 = OpTypeFunction %6 + %8 = OpTypeInt 32 1 + %9 = OpTypePointer Function %8 + %10 = OpConstant %8 0 + %11 = OpConstant %8 10 + %12 = OpTypeBool + %13 = OpTypeFloat 32 + %14 = OpTypeInt 32 0 + %15 = OpConstant %14 10 + %16 = OpTypeArray %13 %15 + %17 = OpTypePointer Output %16 + %3 = OpVariable %17 Output + %18 = OpConstant %8 2 + %19 = OpConstant %8 5 + %20 = OpTypePointer Output %13 + %21 = OpConstant %8 1 + %22 = OpTypePointer Input %8 + %4 = OpVariable %22 Input + %2 = OpFunction %6 None %7 + %23 = OpLabel + %5 = OpVariable %9 Function + OpStore %5 %10 + OpBranch %24 + %24 = OpLabel + %25 = OpPhi %8 %10 %23 %26 %27 + OpLoopMerge %28 %27 None + OpBranch %29 + %29 = OpLabel + %30 = OpSLessThan %12 %25 %11 + OpBranchConditional %30 %31 %28 + %31 = OpLabel + %32 = OpIMul %8 %25 %18 + %33 = OpIMul %8 %25 %19 + %34 = OpIAdd %8 %32 %33 + %35 = OpIMul %8 %25 %25 + %36 = OpIMul %8 %35 %18 + %37 = OpAccessChain %20 %3 %36 + %38 = OpLoad %13 %37 + %39 = OpAccessChain %20 %3 %34 + OpStore %39 %38 + %40 = OpIMul %8 %25 %18 + %41 = OpIMul %8 %25 %19 + %42 = OpAccessChain %20 %3 %41 + %43 = OpLoad %13 %42 + %44 = OpAccessChain %20 %3 %40 + OpStore %44 %43 + OpBranch %27 + %27 = OpLabel + %26 = OpIAdd %8 %25 %21 + OpStore %5 %26 + OpBranch %24 + %28 = OpLabel + OpReturn + OpFunctionEnd + )"; + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + const ir::Function* f = spvtest::GetFunction(module, 2); + opt::ScalarEvolutionAnalysis analysis{context.get()}; + + const ir::Instruction* loads[2] = {nullptr, nullptr}; + const ir::Instruction* stores[2] = {nullptr, nullptr}; + int load_count = 0; + int store_count = 0; + + for (const ir::Instruction& inst : *spvtest::GetBasicBlock(f, 31)) { + if (inst.opcode() == SpvOp::SpvOpLoad) { + loads[load_count] = &inst; + ++load_count; + } + if (inst.opcode() == SpvOp::SpvOpStore) { + stores[store_count] = &inst; + ++store_count; + } + } + + EXPECT_EQ(load_count, 2); + EXPECT_EQ(store_count, 2); + + ir::Instruction* load_access_chain = + context->get_def_use_mgr()->GetDef(loads[0]->GetSingleWordInOperand(0)); + ir::Instruction* store_access_chain = + context->get_def_use_mgr()->GetDef(stores[0]->GetSingleWordInOperand(0)); + + ir::Instruction* load_child = context->get_def_use_mgr()->GetDef( + load_access_chain->GetSingleWordInOperand(1)); + ir::Instruction* store_child = context->get_def_use_mgr()->GetDef( + store_access_chain->GetSingleWordInOperand(1)); + + opt::SENode* store_node = analysis.AnalyzeInstruction(store_child); + + opt::SENode* store_simplified = analysis.SimplifyExpression(store_node); + + load_access_chain = + context->get_def_use_mgr()->GetDef(loads[1]->GetSingleWordInOperand(0)); + store_access_chain = + context->get_def_use_mgr()->GetDef(stores[1]->GetSingleWordInOperand(0)); + load_child = context->get_def_use_mgr()->GetDef( + load_access_chain->GetSingleWordInOperand(1)); + store_child = context->get_def_use_mgr()->GetDef( + store_access_chain->GetSingleWordInOperand(1)); + + opt::SENode* second_store = + analysis.SimplifyExpression(analysis.AnalyzeInstruction(store_child)); + opt::SENode* second_load = + analysis.SimplifyExpression(analysis.AnalyzeInstruction(load_child)); + opt::SENode* combined_add = analysis.SimplifyExpression( + analysis.CreateAddNode(second_load, second_store)); + + // We're checking that the two recurrent expression have been correctly + // folded. In store_simplified they will have been folded as the entire + // expression was simplified as one. In combined_add the two expressions have + // been simplified one after the other which means the recurrent expressions + // aren't exactly the same but should still be folded as they are with respect + // to the same loop. + EXPECT_EQ(combined_add, store_simplified); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 430 +void main(void) { + for (int i = 0; i < 10; --i) { + array[i] = array[i]; + } +} + +*/ + +TEST_F(ScalarAnalysisTest, SimplifyNegativeSteps) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 %4 + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 430 + OpName %2 "main" + OpName %5 "i" + OpName %3 "array" + OpName %4 "loop_invariant" + OpDecorate %3 Location 1 + OpDecorate %4 Flat + OpDecorate %4 Location 2 + %6 = OpTypeVoid + %7 = OpTypeFunction %6 + %8 = OpTypeInt 32 1 + %9 = OpTypePointer Function %8 + %10 = OpConstant %8 0 + %11 = OpConstant %8 10 + %12 = OpTypeBool + %13 = OpTypeFloat 32 + %14 = OpTypeInt 32 0 + %15 = OpConstant %14 10 + %16 = OpTypeArray %13 %15 + %17 = OpTypePointer Output %16 + %3 = OpVariable %17 Output + %18 = OpTypePointer Output %13 + %19 = OpConstant %8 1 + %20 = OpTypePointer Input %8 + %4 = OpVariable %20 Input + %2 = OpFunction %6 None %7 + %21 = OpLabel + %5 = OpVariable %9 Function + OpStore %5 %10 + OpBranch %22 + %22 = OpLabel + %23 = OpPhi %8 %10 %21 %24 %25 + OpLoopMerge %26 %25 None + OpBranch %27 + %27 = OpLabel + %28 = OpSLessThan %12 %23 %11 + OpBranchConditional %28 %29 %26 + %29 = OpLabel + %30 = OpAccessChain %18 %3 %23 + %31 = OpLoad %13 %30 + %32 = OpAccessChain %18 %3 %23 + OpStore %32 %31 + OpBranch %25 + %25 = OpLabel + %24 = OpISub %8 %23 %19 + OpStore %5 %24 + OpBranch %22 + %26 = OpLabel + OpReturn + OpFunctionEnd + )"; + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + const ir::Function* f = spvtest::GetFunction(module, 2); + opt::ScalarEvolutionAnalysis analysis{context.get()}; + + const ir::Instruction* loads[1] = {nullptr}; + int load_count = 0; + + for (const ir::Instruction& inst : *spvtest::GetBasicBlock(f, 29)) { + if (inst.opcode() == SpvOp::SpvOpLoad) { + loads[load_count] = &inst; + ++load_count; + } + } + + EXPECT_EQ(load_count, 1); + + ir::Instruction* load_access_chain = + context->get_def_use_mgr()->GetDef(loads[0]->GetSingleWordInOperand(0)); + ir::Instruction* load_child = context->get_def_use_mgr()->GetDef( + load_access_chain->GetSingleWordInOperand(1)); + + opt::SENode* load_node = analysis.AnalyzeInstruction(load_child); + + EXPECT_TRUE(load_node); + EXPECT_EQ(load_node->GetType(), opt::SENode::RecurrentAddExpr); + EXPECT_TRUE(load_node->AsSERecurrentNode()); + + opt::SENode* child_1 = load_node->AsSERecurrentNode()->GetCoefficient(); + opt::SENode* child_2 = load_node->AsSERecurrentNode()->GetOffset(); + + EXPECT_EQ(child_1->GetType(), opt::SENode::Constant); + EXPECT_EQ(child_2->GetType(), opt::SENode::Constant); + + EXPECT_EQ(child_1->AsSEConstantNode()->FoldToSingleValue(), -1); + EXPECT_EQ(child_2->AsSEConstantNode()->FoldToSingleValue(), 0u); + + opt::SERecurrentNode* load_simplified = + analysis.SimplifyExpression(load_node)->AsSERecurrentNode(); + + EXPECT_TRUE(load_simplified); + EXPECT_EQ(load_node, load_simplified); + + EXPECT_EQ(load_simplified->GetType(), opt::SENode::RecurrentAddExpr); + EXPECT_TRUE(load_simplified->AsSERecurrentNode()); + + opt::SENode* simplified_child_1 = + load_simplified->AsSERecurrentNode()->GetCoefficient(); + opt::SENode* simplified_child_2 = + load_simplified->AsSERecurrentNode()->GetOffset(); + + EXPECT_EQ(child_1, simplified_child_1); + EXPECT_EQ(child_2, simplified_child_2); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 430 +void main(void) { + for (int i = 0; i < 10; --i) { + array[i] = array[i]; + } +} + +*/ + +TEST_F(ScalarAnalysisTest, SimplifyInductionsAndLoads) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 %4 + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 430 + OpName %2 "main" + OpName %5 "i" + OpName %3 "array" + OpName %4 "N" + OpDecorate %3 Location 1 + OpDecorate %4 Flat + OpDecorate %4 Location 2 + %6 = OpTypeVoid + %7 = OpTypeFunction %6 + %8 = OpTypeInt 32 1 + %9 = OpTypePointer Function %8 + %10 = OpConstant %8 0 + %11 = OpConstant %8 10 + %12 = OpTypeBool + %13 = OpTypeFloat 32 + %14 = OpTypeInt 32 0 + %15 = OpConstant %14 10 + %16 = OpTypeArray %13 %15 + %17 = OpTypePointer Output %16 + %3 = OpVariable %17 Output + %18 = OpConstant %8 2 + %19 = OpTypePointer Input %8 + %4 = OpVariable %19 Input + %20 = OpTypePointer Output %13 + %21 = OpConstant %8 1 + %2 = OpFunction %6 None %7 + %22 = OpLabel + %5 = OpVariable %9 Function + OpStore %5 %10 + OpBranch %23 + %23 = OpLabel + %24 = OpPhi %8 %10 %22 %25 %26 + OpLoopMerge %27 %26 None + OpBranch %28 + %28 = OpLabel + %29 = OpSLessThan %12 %24 %11 + OpBranchConditional %29 %30 %27 + %30 = OpLabel + %31 = OpLoad %8 %4 + %32 = OpIMul %8 %18 %31 + %33 = OpIAdd %8 %24 %32 + %35 = OpIAdd %8 %24 %31 + %36 = OpAccessChain %20 %3 %35 + %37 = OpLoad %13 %36 + %38 = OpAccessChain %20 %3 %33 + OpStore %38 %37 + %39 = OpIMul %8 %18 %24 + %41 = OpIMul %8 %18 %31 + %42 = OpIAdd %8 %39 %41 + %43 = OpIAdd %8 %42 %21 + %44 = OpIMul %8 %18 %24 + %46 = OpIAdd %8 %44 %31 + %47 = OpIAdd %8 %46 %21 + %48 = OpAccessChain %20 %3 %47 + %49 = OpLoad %13 %48 + %50 = OpAccessChain %20 %3 %43 + OpStore %50 %49 + OpBranch %26 + %26 = OpLabel + %25 = OpISub %8 %24 %21 + OpStore %5 %25 + OpBranch %23 + %27 = OpLabel + OpReturn + OpFunctionEnd + )"; + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + const ir::Function* f = spvtest::GetFunction(module, 2); + opt::ScalarEvolutionAnalysis analysis{context.get()}; + + std::vector loads{}; + std::vector stores{}; + + for (const ir::Instruction& inst : *spvtest::GetBasicBlock(f, 30)) { + if (inst.opcode() == SpvOp::SpvOpLoad) { + loads.push_back(&inst); + } + if (inst.opcode() == SpvOp::SpvOpStore) { + stores.push_back(&inst); + } + } + + EXPECT_EQ(loads.size(), 3u); + EXPECT_EQ(stores.size(), 2u); + { + ir::Instruction* store_access_chain = context->get_def_use_mgr()->GetDef( + stores[0]->GetSingleWordInOperand(0)); + + ir::Instruction* store_child = context->get_def_use_mgr()->GetDef( + store_access_chain->GetSingleWordInOperand(1)); + + opt::SENode* store_node = analysis.AnalyzeInstruction(store_child); + + opt::SENode* store_simplified = analysis.SimplifyExpression(store_node); + + ir::Instruction* load_access_chain = + context->get_def_use_mgr()->GetDef(loads[1]->GetSingleWordInOperand(0)); + + ir::Instruction* load_child = context->get_def_use_mgr()->GetDef( + load_access_chain->GetSingleWordInOperand(1)); + + opt::SENode* load_node = analysis.AnalyzeInstruction(load_child); + + opt::SENode* load_simplified = analysis.SimplifyExpression(load_node); + + opt::SENode* difference = + analysis.CreateSubtraction(store_simplified, load_simplified); + + opt::SENode* difference_simplified = + analysis.SimplifyExpression(difference); + + // Check that i+2*N - i*N, turns into just N when both sides have already + // been simplified into a single recurrent expression. + EXPECT_EQ(difference_simplified->GetType(), opt::SENode::ValueUnknown); + + // Check that the inverse, i*N - i+2*N turns into -N. + opt::SENode* difference_inverse = analysis.SimplifyExpression( + analysis.CreateSubtraction(load_simplified, store_simplified)); + + EXPECT_EQ(difference_inverse->GetType(), opt::SENode::Negative); + EXPECT_EQ(difference_inverse->GetChild(0)->GetType(), + opt::SENode::ValueUnknown); + EXPECT_EQ(difference_inverse->GetChild(0), difference_simplified); + } + + { + ir::Instruction* store_access_chain = context->get_def_use_mgr()->GetDef( + stores[1]->GetSingleWordInOperand(0)); + + ir::Instruction* store_child = context->get_def_use_mgr()->GetDef( + store_access_chain->GetSingleWordInOperand(1)); + opt::SENode* store_node = analysis.AnalyzeInstruction(store_child); + opt::SENode* store_simplified = analysis.SimplifyExpression(store_node); + + ir::Instruction* load_access_chain = + context->get_def_use_mgr()->GetDef(loads[2]->GetSingleWordInOperand(0)); + + ir::Instruction* load_child = context->get_def_use_mgr()->GetDef( + load_access_chain->GetSingleWordInOperand(1)); + + opt::SENode* load_node = analysis.AnalyzeInstruction(load_child); + + opt::SENode* load_simplified = analysis.SimplifyExpression(load_node); + + opt::SENode* difference = + analysis.CreateSubtraction(store_simplified, load_simplified); + opt::SENode* difference_simplified = + analysis.SimplifyExpression(difference); + + // Check that 2*i + 2*N + 1 - 2*i + N + 1, turns into just N when both + // sides have already been simplified into a single recurrent expression. + EXPECT_EQ(difference_simplified->GetType(), opt::SENode::ValueUnknown); + + // Check that the inverse, (2*i + N + 1) - (2*i + 2*N + 1) turns into -N. + opt::SENode* difference_inverse = analysis.SimplifyExpression( + analysis.CreateSubtraction(load_simplified, store_simplified)); + + EXPECT_EQ(difference_inverse->GetType(), opt::SENode::Negative); + EXPECT_EQ(difference_inverse->GetChild(0)->GetType(), + opt::SENode::ValueUnknown); + EXPECT_EQ(difference_inverse->GetChild(0), difference_simplified); + } +} + +/* Generated from the following GLSL + --eliminate-local-multi-store + + #version 430 + layout(location = 1) out float array[10]; + layout(location = 2) flat in int N; + void main(void) { + int step = 0; + for (int i = 0; i < N; i += step) { + step++; + } + } +*/ +TEST_F(ScalarAnalysisTest, InductionWithVariantStep) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 %4 + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 430 + OpName %2 "main" + OpName %5 "step" + OpName %6 "i" + OpName %3 "N" + OpName %4 "array" + OpDecorate %3 Flat + OpDecorate %3 Location 2 + OpDecorate %4 Location 1 + %7 = OpTypeVoid + %8 = OpTypeFunction %7 + %9 = OpTypeInt 32 1 + %10 = OpTypePointer Function %9 + %11 = OpConstant %9 0 + %12 = OpTypePointer Input %9 + %3 = OpVariable %12 Input + %13 = OpTypeBool + %14 = OpConstant %9 1 + %15 = OpTypeFloat 32 + %16 = OpTypeInt 32 0 + %17 = OpConstant %16 10 + %18 = OpTypeArray %15 %17 + %19 = OpTypePointer Output %18 + %4 = OpVariable %19 Output + %2 = OpFunction %7 None %8 + %20 = OpLabel + %5 = OpVariable %10 Function + %6 = OpVariable %10 Function + OpStore %5 %11 + OpStore %6 %11 + OpBranch %21 + %21 = OpLabel + %22 = OpPhi %9 %11 %20 %23 %24 + %25 = OpPhi %9 %11 %20 %26 %24 + OpLoopMerge %27 %24 None + OpBranch %28 + %28 = OpLabel + %29 = OpLoad %9 %3 + %30 = OpSLessThan %13 %25 %29 + OpBranchConditional %30 %31 %27 + %31 = OpLabel + %23 = OpIAdd %9 %22 %14 + OpStore %5 %23 + OpBranch %24 + %24 = OpLabel + %26 = OpIAdd %9 %25 %23 + OpStore %6 %26 + OpBranch %21 + %27 = OpLabel + OpReturn + OpFunctionEnd + )"; + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + const ir::Function* f = spvtest::GetFunction(module, 2); + opt::ScalarEvolutionAnalysis analysis{context.get()}; + + std::vector phis{}; + + for (const ir::Instruction& inst : *spvtest::GetBasicBlock(f, 21)) { + if (inst.opcode() == SpvOp::SpvOpPhi) { + phis.push_back(&inst); + } + } + + EXPECT_EQ(phis.size(), 2u); + opt::SENode* phi_node_1 = analysis.AnalyzeInstruction(phis[0]); + opt::SENode* phi_node_2 = analysis.AnalyzeInstruction(phis[1]); + phi_node_1->DumpDot(std::cout, true); + EXPECT_NE(phi_node_1, nullptr); + EXPECT_NE(phi_node_2, nullptr); + + EXPECT_EQ(phi_node_1->GetType(), opt::SENode::RecurrentAddExpr); + EXPECT_EQ(phi_node_2->GetType(), opt::SENode::CanNotCompute); + + opt::SENode* simplified_1 = analysis.SimplifyExpression(phi_node_1); + opt::SENode* simplified_2 = analysis.SimplifyExpression(phi_node_2); + + EXPECT_EQ(simplified_1->GetType(), opt::SENode::RecurrentAddExpr); + EXPECT_EQ(simplified_2->GetType(), opt::SENode::CanNotCompute); +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/scalar_replacement_test.cpp b/3rdparty/spirv-tools/test/opt/scalar_replacement_test.cpp new file mode 100644 index 000000000..9c7d631eb --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/scalar_replacement_test.cpp @@ -0,0 +1,1273 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "assembly_builder.h" +#include "gmock/gmock.h" +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; + +using ScalarReplacementTest = PassTest<::testing::Test>; + +// TODO(dneto): Add Effcee as required dependency, and make this unconditional. +#ifdef SPIRV_EFFCEE +TEST_F(ScalarReplacementTest, SimpleStruct) { + const std::string text = R"( +; +; CHECK: [[struct:%\w+]] = OpTypeStruct [[elem:%\w+]] +; CHECK: [[struct_ptr:%\w+]] = OpTypePointer Function [[struct]] +; CHECK: [[elem_ptr:%\w+]] = OpTypePointer Function [[elem]] +; CHECK: OpConstantNull [[struct]] +; CHECK: [[null:%\w+]] = OpConstantNull [[elem]] +; CHECK-NOT: OpVariable [[struct_ptr]] +; CHECK: [[one:%\w+]] = OpVariable [[elem_ptr]] Function [[null]] +; CHECK-NEXT: [[two:%\w+]] = OpVariable [[elem_ptr]] Function [[null]] +; CHECK-NOT: OpVariable [[elem_ptr]] Function [[null]] +; CHECK-NOT: OpVariable [[struct_ptr]] +; CHECK-NOT: OpInBoundsAccessChain +; CHECK: [[l1:%\w+]] = OpLoad [[elem]] [[two]] +; CHECK-NOT: OpAccessChain +; CHECK: [[l2:%\w+]] = OpLoad [[elem]] [[one]] +; CHECK: OpIAdd [[elem]] [[l1]] [[l2]] +; +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpName %6 "simple_struct" +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypeStruct %2 %2 %2 %2 +%4 = OpTypePointer Function %3 +%5 = OpTypePointer Function %2 +%6 = OpTypeFunction %2 +%7 = OpConstantNull %3 +%8 = OpConstant %2 0 +%9 = OpConstant %2 1 +%10 = OpConstant %2 2 +%11 = OpConstant %2 3 +%12 = OpFunction %2 None %6 +%13 = OpLabel +%14 = OpVariable %4 Function %7 +%15 = OpInBoundsAccessChain %5 %14 %8 +%16 = OpLoad %2 %15 +%17 = OpAccessChain %5 %14 %10 +%18 = OpLoad %2 %17 +%19 = OpIAdd %2 %16 %18 +OpReturnValue %19 +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(ScalarReplacementTest, StructInitialization) { + const std::string text = R"( +; +; CHECK: [[elem:%\w+]] = OpTypeInt 32 0 +; CHECK: [[struct:%\w+]] = OpTypeStruct [[elem]] [[elem]] [[elem]] [[elem]] +; CHECK: [[struct_ptr:%\w+]] = OpTypePointer Function [[struct]] +; CHECK: [[elem_ptr:%\w+]] = OpTypePointer Function [[elem]] +; CHECK: [[zero:%\w+]] = OpConstant [[elem]] 0 +; CHECK: [[undef:%\w+]] = OpUndef [[elem]] +; CHECK: [[two:%\w+]] = OpConstant [[elem]] 2 +; CHECK: [[null:%\w+]] = OpConstantNull [[elem]] +; CHECK-NOT: OpVariable [[struct_ptr]] +; CHECK: OpVariable [[elem_ptr]] Function [[null]] +; CHECK-NEXT: OpVariable [[elem_ptr]] Function [[two]] +; CHECK-NOT: OpVariable [[elem_ptr]] Function [[undef]] +; CHECK-NEXT: OpVariable [[elem_ptr]] Function +; CHECK-NEXT: OpVariable [[elem_ptr]] Function [[zero]] +; CHECK-NOT: OpVariable [[elem_ptr]] Function [[undef]] +; +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpName %6 "struct_init" +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypeStruct %2 %2 %2 %2 +%4 = OpTypePointer Function %3 +%20 = OpTypePointer Function %2 +%6 = OpTypeFunction %1 +%7 = OpConstant %2 0 +%8 = OpUndef %2 +%9 = OpConstant %2 2 +%30 = OpConstant %2 1 +%31 = OpConstant %2 3 +%10 = OpConstantNull %2 +%11 = OpConstantComposite %3 %7 %8 %9 %10 +%12 = OpFunction %1 None %6 +%13 = OpLabel +%14 = OpVariable %4 Function %11 +%15 = OpAccessChain %20 %14 %7 +OpStore %15 %10 +%16 = OpAccessChain %20 %14 %9 +OpStore %16 %10 +%17 = OpAccessChain %20 %14 %30 +OpStore %17 %10 +%18 = OpAccessChain %20 %14 %31 +OpStore %18 %10 +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(ScalarReplacementTest, SpecConstantInitialization) { + const std::string text = R"( +; +; CHECK: [[int:%\w+]] = OpTypeInt 32 0 +; CHECK: [[struct:%\w+]] = OpTypeStruct [[int]] [[int]] +; CHECK: [[struct_ptr:%\w+]] = OpTypePointer Function [[struct]] +; CHECK: [[int_ptr:%\w+]] = OpTypePointer Function [[int]] +; CHECK: [[spec_comp:%\w+]] = OpSpecConstantComposite [[struct]] +; CHECK: [[ex0:%\w+]] = OpSpecConstantOp [[int]] CompositeExtract [[spec_comp]] 0 +; CHECK: [[ex1:%\w+]] = OpSpecConstantOp [[int]] CompositeExtract [[spec_comp]] 1 +; CHECK-NOT: OpVariable [[struct]] +; CHECK: OpVariable [[int_ptr]] Function [[ex1]] +; CHECK-NEXT: OpVariable [[int_ptr]] Function [[ex0]] +; CHECK-NOT: OpVariable [[struct]] +; +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpName %6 "spec_const" +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypeStruct %2 %2 +%4 = OpTypePointer Function %3 +%20 = OpTypePointer Function %2 +%5 = OpTypeFunction %1 +%6 = OpConstant %2 0 +%30 = OpConstant %2 1 +%7 = OpSpecConstant %2 0 +%8 = OpSpecConstantOp %2 IAdd %7 %7 +%9 = OpSpecConstantComposite %3 %7 %8 +%10 = OpFunction %1 None %5 +%11 = OpLabel +%12 = OpVariable %4 Function %9 +%13 = OpAccessChain %20 %12 %6 +%14 = OpLoad %2 %13 +%15 = OpAccessChain %20 %12 %30 +%16 = OpLoad %2 %15 +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +// TODO(alanbaker): Re-enable when vector and matrix scalarization is supported. +// TEST_F(ScalarReplacementTest, VectorInitialization) { +// const std::string text = R"( +//; +//; CHECK: [[elem:%\w+]] = OpTypeInt 32 0 +//; CHECK: [[vector:%\w+]] = OpTypeVector [[elem]] 4 +//; CHECK: [[vector_ptr:%\w+]] = OpTypePointer Function [[vector]] +//; CHECK: [[elem_ptr:%\w+]] = OpTypePointer Function [[elem]] +//; CHECK: [[zero:%\w+]] = OpConstant [[elem]] 0 +//; CHECK: [[undef:%\w+]] = OpUndef [[elem]] +//; CHECK: [[two:%\w+]] = OpConstant [[elem]] 2 +//; CHECK: [[null:%\w+]] = OpConstantNull [[elem]] +//; CHECK-NOT: OpVariable [[vector_ptr]] +//; CHECK: OpVariable [[elem_ptr]] Function [[zero]] +//; CHECK-NOT: OpVariable [[elem_ptr]] Function [[undef]] +//; CHECK-NEXT: OpVariable [[elem_ptr]] Function +//; CHECK-NEXT: OpVariable [[elem_ptr]] Function [[two]] +//; CHECK-NEXT: OpVariable [[elem_ptr]] Function [[null]] +//; CHECK-NOT: OpVariable [[elem_ptr]] Function [[undef]] +//; +// OpCapability Shader +// OpCapability Linkage +// OpMemoryModel Logical GLSL450 +// OpName %6 "vector_init" +//%1 = OpTypeVoid +//%2 = OpTypeInt 32 0 +//%3 = OpTypeVector %2 4 +//%4 = OpTypePointer Function %3 +//%20 = OpTypePointer Function %2 +//%6 = OpTypeFunction %1 +//%7 = OpConstant %2 0 +//%8 = OpUndef %2 +//%9 = OpConstant %2 2 +//%30 = OpConstant %2 1 +//%31 = OpConstant %2 3 +//%10 = OpConstantNull %2 +//%11 = OpConstantComposite %3 %10 %9 %8 %7 +//%12 = OpFunction %1 None %6 +//%13 = OpLabel +//%14 = OpVariable %4 Function %11 +//%15 = OpAccessChain %20 %14 %7 +// OpStore %15 %10 +//%16 = OpAccessChain %20 %14 %9 +// OpStore %16 %10 +//%17 = OpAccessChain %20 %14 %30 +// OpStore %17 %10 +//%18 = OpAccessChain %20 %14 %31 +// OpStore %18 %10 +// OpReturn +// OpFunctionEnd +// )"; +// +// SinglePassRunAndMatch(text, true); +//} +// +// TEST_F(ScalarReplacementTest, MatrixInitialization) { +// const std::string text = R"( +//; +//; CHECK: [[float:%\w+]] = OpTypeFloat 32 +//; CHECK: [[vector:%\w+]] = OpTypeVector [[float]] 2 +//; CHECK: [[matrix:%\w+]] = OpTypeMatrix [[vector]] 2 +//; CHECK: [[matrix_ptr:%\w+]] = OpTypePointer Function [[matrix]] +//; CHECK: [[float_ptr:%\w+]] = OpTypePointer Function [[float]] +//; CHECK: [[vec_ptr:%\w+]] = OpTypePointer Function [[vector]] +//; CHECK: [[zerof:%\w+]] = OpConstant [[float]] 0 +//; CHECK: [[onef:%\w+]] = OpConstant [[float]] 1 +//; CHECK: [[one_zero:%\w+]] = OpConstantComposite [[vector]] [[onef]] [[zerof]] +//; CHECK: [[zero_one:%\w+]] = OpConstantComposite [[vector]] [[zerof]] [[onef]] +//; CHECK: [[const_mat:%\w+]] = OpConstantComposite [[matrix]] [[one_zero]] +//[[zero_one]] ; CHECK-NOT: OpVariable [[matrix]] ; CHECK-NOT: OpVariable +//[[vector]] Function [[one_zero]] ; CHECK: [[f1:%\w+]] = OpVariable +//[[float_ptr]] Function [[zerof]] ; CHECK-NEXT: [[f2:%\w+]] = OpVariable +//[[float_ptr]] Function [[onef]] ; CHECK-NEXT: [[vec_var:%\w+]] = OpVariable +//[[vec_ptr]] Function [[zero_one]] ; CHECK-NOT: OpVariable [[matrix]] ; +// CHECK-NOT: OpVariable [[vector]] Function [[one_zero]] +//; +// OpCapability Shader +// OpCapability Linkage +// OpMemoryModel Logical GLSL450 +// OpName %7 "matrix_init" +//%1 = OpTypeVoid +//%2 = OpTypeFloat 32 +//%3 = OpTypeVector %2 2 +//%4 = OpTypeMatrix %3 2 +//%5 = OpTypePointer Function %4 +//%6 = OpTypePointer Function %2 +//%30 = OpTypePointer Function %3 +//%10 = OpTypeInt 32 0 +//%7 = OpTypeFunction %1 %10 +//%8 = OpConstant %2 0.0 +//%9 = OpConstant %2 1.0 +//%11 = OpConstant %10 0 +//%12 = OpConstant %10 1 +//%13 = OpConstantComposite %3 %9 %8 +//%14 = OpConstantComposite %3 %8 %9 +//%15 = OpConstantComposite %4 %13 %14 +//%16 = OpFunction %1 None %7 +//%31 = OpFunctionParameter %10 +//%17 = OpLabel +//%18 = OpVariable %5 Function %15 +//%19 = OpAccessChain %6 %18 %11 %12 +// OpStore %19 %8 +//%20 = OpAccessChain %6 %18 %11 %11 +// OpStore %20 %8 +//%21 = OpAccessChain %30 %18 %12 +// OpStore %21 %14 +// OpReturn +// OpFunctionEnd +// )"; +// +// SinglePassRunAndMatch(text, true); +//} + +TEST_F(ScalarReplacementTest, ElideAccessChain) { + const std::string text = R"( +; +; CHECK: [[var:%\w+]] = OpVariable +; CHECK-NOT: OpAccessChain +; CHECK: OpStore [[var]] +; +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpName %6 "elide_access_chain" +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypeStruct %2 %2 %2 %2 +%4 = OpTypePointer Function %3 +%20 = OpTypePointer Function %2 +%6 = OpTypeFunction %1 +%7 = OpConstant %2 0 +%8 = OpUndef %2 +%9 = OpConstant %2 2 +%10 = OpConstantNull %2 +%11 = OpConstantComposite %3 %7 %8 %9 %10 +%12 = OpFunction %1 None %6 +%13 = OpLabel +%14 = OpVariable %4 Function %11 +%15 = OpAccessChain %20 %14 %7 +OpStore %15 %10 +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(ScalarReplacementTest, ElideMultipleAccessChains) { + const std::string text = R"( +; +; CHECK: [[var:%\w+]] = OpVariable +; CHECK-NOT: OpInBoundsAccessChain +; CHECK OpStore [[var]] +; +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpName %6 "elide_two_access_chains" +%1 = OpTypeVoid +%2 = OpTypeFloat 32 +%3 = OpTypeStruct %2 %2 +%4 = OpTypeStruct %3 %3 +%5 = OpTypePointer Function %4 +%6 = OpTypePointer Function %2 +%7 = OpTypeFunction %1 +%8 = OpConstant %2 0.0 +%9 = OpConstant %2 1.0 +%10 = OpTypeInt 32 0 +%11 = OpConstant %10 0 +%12 = OpConstant %10 1 +%13 = OpConstantComposite %3 %9 %8 +%14 = OpConstantComposite %3 %8 %9 +%15 = OpConstantComposite %4 %13 %14 +%16 = OpFunction %1 None %7 +%17 = OpLabel +%18 = OpVariable %5 Function %15 +%19 = OpInBoundsAccessChain %6 %18 %11 %12 +OpStore %19 %8 +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(ScalarReplacementTest, ReplaceAccessChain) { + const std::string text = R"( +; +; CHECK: [[param:%\w+]] = OpFunctionParameter +; CHECK: [[var:%\w+]] = OpVariable +; CHECK: [[access:%\w+]] = OpAccessChain {{%\w+}} [[var]] [[param]] +; CHECK: OpStore [[access]] +; +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpName %7 "replace_access_chain" +%1 = OpTypeVoid +%2 = OpTypeFloat 32 +%10 = OpTypeInt 32 0 +%uint_2 = OpConstant %10 2 +%3 = OpTypeArray %2 %uint_2 +%4 = OpTypeStruct %3 %3 +%5 = OpTypePointer Function %4 +%20 = OpTypePointer Function %3 +%6 = OpTypePointer Function %2 +%7 = OpTypeFunction %1 %10 +%8 = OpConstant %2 0.0 +%9 = OpConstant %2 1.0 +%11 = OpConstant %10 0 +%12 = OpConstant %10 1 +%13 = OpConstantComposite %3 %9 %8 +%14 = OpConstantComposite %3 %8 %9 +%15 = OpConstantComposite %4 %13 %14 +%16 = OpFunction %1 None %7 +%32 = OpFunctionParameter %10 +%17 = OpLabel +%18 = OpVariable %5 Function %15 +%19 = OpAccessChain %6 %18 %11 %32 +OpStore %19 %8 +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(ScalarReplacementTest, ArrayInitialization) { + const std::string text = R"( +; +; CHECK: [[float:%\w+]] = OpTypeFloat 32 +; CHECK: [[array:%\w+]] = OpTypeArray +; CHECK: [[array_ptr:%\w+]] = OpTypePointer Function [[array]] +; CHECK: [[float_ptr:%\w+]] = OpTypePointer Function [[float]] +; CHECK: [[float0:%\w+]] = OpConstant [[float]] 0 +; CHECK: [[float1:%\w+]] = OpConstant [[float]] 1 +; CHECK: [[float2:%\w+]] = OpConstant [[float]] 2 +; CHECK-NOT: OpVariable [[array_ptr]] +; CHECK: [[var0:%\w+]] = OpVariable [[float_ptr]] Function [[float0]] +; CHECK-NEXT: [[var1:%\w+]] = OpVariable [[float_ptr]] Function [[float1]] +; CHECK-NEXT: [[var2:%\w+]] = OpVariable [[float_ptr]] Function [[float2]] +; CHECK-NOT: OpVariable [[array_ptr]] +; +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpName %func "array_init" +%void = OpTypeVoid +%uint = OpTypeInt 32 0 +%float = OpTypeFloat 32 +%uint_0 = OpConstant %uint 0 +%uint_1 = OpConstant %uint 1 +%uint_2 = OpConstant %uint 2 +%uint_3 = OpConstant %uint 3 +%float_array = OpTypeArray %float %uint_3 +%array_ptr = OpTypePointer Function %float_array +%float_ptr = OpTypePointer Function %float +%float_0 = OpConstant %float 0 +%float_1 = OpConstant %float 1 +%float_2 = OpConstant %float 2 +%const_array = OpConstantComposite %float_array %float_2 %float_1 %float_0 +%func = OpTypeFunction %void +%1 = OpFunction %void None %func +%2 = OpLabel +%3 = OpVariable %array_ptr Function %const_array +%4 = OpInBoundsAccessChain %float_ptr %3 %uint_0 +OpStore %4 %float_0 +%5 = OpInBoundsAccessChain %float_ptr %3 %uint_1 +OpStore %5 %float_0 +%6 = OpInBoundsAccessChain %float_ptr %3 %uint_2 +OpStore %6 %float_0 +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); + ; +} + +TEST_F(ScalarReplacementTest, NonUniformCompositeInitialization) { + const std::string text = R"( +; +; CHECK: [[uint:%\w+]] = OpTypeInt 32 0 +; CHECK: [[long:%\w+]] = OpTypeInt 64 1 +; CHECK: [[dvector:%\w+]] = OpTypeVector +; CHECK: [[vector:%\w+]] = OpTypeVector +; CHECK: [[array:%\w+]] = OpTypeArray +; CHECK: [[matrix:%\w+]] = OpTypeMatrix +; CHECK: [[struct1:%\w+]] = OpTypeStruct [[uint]] [[vector]] +; CHECK: [[struct2:%\w+]] = OpTypeStruct [[struct1]] [[matrix]] [[array]] [[uint]] +; CHECK: [[struct1_ptr:%\w+]] = OpTypePointer Function [[struct1]] +; CHECK: [[matrix_ptr:%\w+]] = OpTypePointer Function [[matrix]] +; CHECK: [[array_ptr:%\w+]] = OpTypePointer Function [[array]] +; CHECK: [[uint_ptr:%\w+]] = OpTypePointer Function [[uint]] +; CHECK: [[struct2_ptr:%\w+]] = OpTypePointer Function [[struct2]] +; CHECK: [[const_array:%\w+]] = OpConstantComposite [[array]] +; CHECK: [[const_matrix:%\w+]] = OpConstantNull [[matrix]] +; CHECK: [[const_struct1:%\w+]] = OpConstantComposite [[struct1]] +; CHECK: [[vector_ptr:%\w+]] = OpTypePointer Function [[vector]] +; CHECK: [[long_ptr:%\w+]] = OpTypePointer Function [[long]] +; CHECK-NOT: OpVariable [[struct2_ptr]] Function +; CHECK: OpVariable [[long_ptr]] Function +; CHECK: OpVariable [[long_ptr]] Function +; CHECK: OpVariable [[long_ptr]] Function +; CHECK: OpVariable [[vector_ptr]] Function +; CHECK: OpVariable [[uint_ptr]] Function +; CHECK: OpVariable [[uint_ptr]] Function +; CHECK-NEXT: OpVariable [[matrix_ptr]] Function [[const_matrix]] +; CHECK-NOT: OpVariable [[struct1_ptr]] Function [[const_struct1]] +; CHECK-NOT: OpVariable [[struct2_ptr]] Function +; +OpCapability Shader +OpCapability Linkage +OpCapability Int64 +OpCapability Float64 +OpMemoryModel Logical GLSL450 +OpName %func "non_uniform_composite_init" +%void = OpTypeVoid +%uint = OpTypeInt 32 0 +%int64 = OpTypeInt 64 1 +%float = OpTypeFloat 32 +%double = OpTypeFloat 64 +%double2 = OpTypeVector %double 2 +%float4 = OpTypeVector %float 4 +%int64_0 = OpConstant %int64 0 +%int64_1 = OpConstant %int64 1 +%int64_2 = OpConstant %int64 2 +%int64_3 = OpConstant %int64 3 +%int64_array3 = OpTypeArray %int64 %int64_3 +%matrix_double2 = OpTypeMatrix %double2 2 +%struct1 = OpTypeStruct %uint %float4 +%struct2 = OpTypeStruct %struct1 %matrix_double2 %int64_array3 %uint +%struct1_ptr = OpTypePointer Function %struct1 +%matrix_double2_ptr = OpTypePointer Function %matrix_double2 +%int64_array_ptr = OpTypePointer Function %int64_array3 +%uint_ptr = OpTypePointer Function %uint +%struct2_ptr = OpTypePointer Function %struct2 +%const_uint = OpConstant %uint 0 +%const_int64_array = OpConstantComposite %int64_array3 %int64_0 %int64_1 %int64_2 +%const_double2 = OpConstantNull %double2 +%const_matrix_double2 = OpConstantNull %matrix_double2 +%undef_float4 = OpUndef %float4 +%const_struct1 = OpConstantComposite %struct1 %const_uint %undef_float4 +%const_struct2 = OpConstantComposite %struct2 %const_struct1 %const_matrix_double2 %const_int64_array %const_uint +%func = OpTypeFunction %void +%1 = OpFunction %void None %func +%2 = OpLabel +%var = OpVariable %struct2_ptr Function %const_struct2 +%3 = OpAccessChain %struct1_ptr %var %int64_0 +OpStore %3 %const_struct1 +%4 = OpAccessChain %matrix_double2_ptr %var %int64_1 +OpStore %4 %const_matrix_double2 +%5 = OpAccessChain %int64_array_ptr %var %int64_2 +OpStore %5 %const_int64_array +%6 = OpAccessChain %uint_ptr %var %int64_3 +OpStore %6 %const_uint +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); + ; +} + +TEST_F(ScalarReplacementTest, ElideUncombinedAccessChains) { + const std::string text = R"( +; +; CHECK: [[uint:%\w+]] = OpTypeInt 32 0 +; CHECK: [[uint_ptr:%\w+]] = OpTypePointer Function [[uint]] +; CHECK: [[const:%\w+]] = OpConstant [[uint]] 0 +; CHECK: [[var:%\w+]] = OpVariable [[uint_ptr]] Function +; CHECK-NOT: OpAccessChain +; CHECK: OpStore [[var]] [[const]] +; +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpName %func "elide_uncombined_access_chains" +%void = OpTypeVoid +%uint = OpTypeInt 32 0 +%struct1 = OpTypeStruct %uint +%struct2 = OpTypeStruct %struct1 +%uint_ptr = OpTypePointer Function %uint +%struct1_ptr = OpTypePointer Function %struct1 +%struct2_ptr = OpTypePointer Function %struct2 +%uint_0 = OpConstant %uint 0 +%func = OpTypeFunction %void +%1 = OpFunction %void None %func +%2 = OpLabel +%var = OpVariable %struct2_ptr Function +%3 = OpAccessChain %struct1_ptr %var %uint_0 +%4 = OpAccessChain %uint_ptr %3 %uint_0 +OpStore %4 %uint_0 +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(ScalarReplacementTest, ElideSingleUncombinedAccessChains) { + const std::string text = R"( +; +; CHECK: [[uint:%\w+]] = OpTypeInt 32 0 +; CHECK: [[array:%\w+]] = OpTypeArray [[uint]] +; CHECK: [[array_ptr:%\w+]] = OpTypePointer Function [[array]] +; CHECK: [[const:%\w+]] = OpConstant [[uint]] 0 +; CHECK: [[param:%\w+]] = OpFunctionParameter [[uint]] +; CHECK: [[var:%\w+]] = OpVariable [[array_ptr]] Function +; CHECK: [[access:%\w+]] = OpAccessChain {{.*}} [[var]] [[param]] +; CHECK: OpStore [[access]] [[const]] +; +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpName %func "elide_single_uncombined_access_chains" +%void = OpTypeVoid +%uint = OpTypeInt 32 0 +%uint_1 = OpConstant %uint 1 +%array = OpTypeArray %uint %uint_1 +%struct2 = OpTypeStruct %array +%uint_ptr = OpTypePointer Function %uint +%array_ptr = OpTypePointer Function %array +%struct2_ptr = OpTypePointer Function %struct2 +%uint_0 = OpConstant %uint 0 +%func = OpTypeFunction %void %uint +%1 = OpFunction %void None %func +%param = OpFunctionParameter %uint +%2 = OpLabel +%var = OpVariable %struct2_ptr Function +%3 = OpAccessChain %array_ptr %var %uint_0 +%4 = OpAccessChain %uint_ptr %3 %param +OpStore %4 %uint_0 +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(ScalarReplacementTest, ReplaceWholeLoad) { + const std::string text = R"( +; +; CHECK: [[uint:%\w+]] = OpTypeInt 32 0 +; CHECK: [[struct1:%\w+]] = OpTypeStruct [[uint]] [[uint]] +; CHECK: [[uint_ptr:%\w+]] = OpTypePointer Function [[uint]] +; CHECK: [[const:%\w+]] = OpConstant [[uint]] 0 +; CHECK: [[var1:%\w+]] = OpVariable [[uint_ptr]] Function +; CHECK: [[var0:%\w+]] = OpVariable [[uint_ptr]] Function +; CHECK: [[l1:%\w+]] = OpLoad [[uint]] [[var1]] +; CHECK: [[l0:%\w+]] = OpLoad [[uint]] [[var0]] +; CHECK: OpCompositeConstruct [[struct1]] [[l0]] [[l1]] +; +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpName %func "replace_whole_load" +%void = OpTypeVoid +%uint = OpTypeInt 32 0 +%struct1 = OpTypeStruct %uint %uint +%uint_ptr = OpTypePointer Function %uint +%struct1_ptr = OpTypePointer Function %struct1 +%uint_0 = OpConstant %uint 0 +%func = OpTypeFunction %void +%1 = OpFunction %void None %func +%2 = OpLabel +%var = OpVariable %struct1_ptr Function +%load = OpLoad %struct1 %var +%3 = OpAccessChain %uint_ptr %var %uint_0 +OpStore %3 %uint_0 +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(ScalarReplacementTest, ReplaceWholeLoadCopyMemoryAccess) { + const std::string text = R"( +; +; CHECK: [[uint:%\w+]] = OpTypeInt 32 0 +; CHECK: [[struct1:%\w+]] = OpTypeStruct [[uint]] [[uint]] +; CHECK: [[uint_ptr:%\w+]] = OpTypePointer Function [[uint]] +; CHECK: [[const:%\w+]] = OpConstant [[uint]] 0 +; CHECK: [[var1:%\w+]] = OpVariable [[uint_ptr]] Function +; CHECK: [[var0:%\w+]] = OpVariable [[uint_ptr]] Function +; CHECK: [[l1:%\w+]] = OpLoad [[uint]] [[var1]] Nontemporal +; CHECK: [[l0:%\w+]] = OpLoad [[uint]] [[var0]] Nontemporal +; CHECK: OpCompositeConstruct [[struct1]] [[l0]] [[l1]] +; +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpName %func "replace_whole_load_copy_memory_access" +%void = OpTypeVoid +%uint = OpTypeInt 32 0 +%struct1 = OpTypeStruct %uint %uint +%uint_ptr = OpTypePointer Function %uint +%struct1_ptr = OpTypePointer Function %struct1 +%uint_0 = OpConstant %uint 0 +%func = OpTypeFunction %void +%1 = OpFunction %void None %func +%2 = OpLabel +%var = OpVariable %struct1_ptr Function +%load = OpLoad %struct1 %var Nontemporal +%3 = OpAccessChain %uint_ptr %var %uint_0 +OpStore %3 %uint_0 +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(ScalarReplacementTest, ReplaceWholeStore) { + const std::string text = R"( +; +; CHECK: [[uint:%\w+]] = OpTypeInt 32 0 +; CHECK: [[struct1:%\w+]] = OpTypeStruct [[uint]] [[uint]] +; CHECK: [[uint_ptr:%\w+]] = OpTypePointer Function [[uint]] +; CHECK: [[const:%\w+]] = OpConstant [[uint]] 0 +; CHECK: [[const_struct:%\w+]] = OpConstantComposite [[struct1]] [[const]] [[const]] +; CHECK: [[var1:%\w+]] = OpVariable [[uint_ptr]] Function +; CHECK: [[var0:%\w+]] = OpVariable [[uint_ptr]] Function +; CHECK: [[ex0:%\w+]] = OpCompositeExtract [[uint]] [[const_struct]] 0 +; CHECK: OpStore [[var0]] [[ex0]] +; CHECK: [[ex1:%\w+]] = OpCompositeExtract [[uint]] [[const_struct]] 1 +; CHECK: OpStore [[var1]] [[ex1]] +; +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpName %func "replace_whole_store" +%void = OpTypeVoid +%uint = OpTypeInt 32 0 +%struct1 = OpTypeStruct %uint %uint +%uint_ptr = OpTypePointer Function %uint +%struct1_ptr = OpTypePointer Function %struct1 +%uint_0 = OpConstant %uint 0 +%const_struct = OpConstantComposite %struct1 %uint_0 %uint_0 +%func = OpTypeFunction %void +%1 = OpFunction %void None %func +%2 = OpLabel +%var = OpVariable %struct1_ptr Function +OpStore %var %const_struct +%3 = OpAccessChain %uint_ptr %var %uint_0 +%4 = OpLoad %uint %3 +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(ScalarReplacementTest, ReplaceWholeStoreCopyMemoryAccess) { + const std::string text = R"( +; +; CHECK: [[uint:%\w+]] = OpTypeInt 32 0 +; CHECK: [[struct1:%\w+]] = OpTypeStruct [[uint]] [[uint]] +; CHECK: [[uint_ptr:%\w+]] = OpTypePointer Function [[uint]] +; CHECK: [[const:%\w+]] = OpConstant [[uint]] 0 +; CHECK: [[const_struct:%\w+]] = OpConstantComposite [[struct1]] [[const]] [[const]] +; CHECK: [[var1:%\w+]] = OpVariable [[uint_ptr]] Function +; CHECK: [[var0:%\w+]] = OpVariable [[uint_ptr]] Function +; CHECK: [[ex0:%\w+]] = OpCompositeExtract [[uint]] [[const_struct]] 0 +; CHECK: OpStore [[var0]] [[ex0]] Aligned 4 +; CHECK: [[ex1:%\w+]] = OpCompositeExtract [[uint]] [[const_struct]] 1 +; CHECK: OpStore [[var1]] [[ex1]] Aligned 4 +; +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpName %func "replace_whole_store_copy_memory_access" +%void = OpTypeVoid +%uint = OpTypeInt 32 0 +%struct1 = OpTypeStruct %uint %uint +%uint_ptr = OpTypePointer Function %uint +%struct1_ptr = OpTypePointer Function %struct1 +%uint_0 = OpConstant %uint 0 +%const_struct = OpConstantComposite %struct1 %uint_0 %uint_0 +%func = OpTypeFunction %void +%1 = OpFunction %void None %func +%2 = OpLabel +%var = OpVariable %struct1_ptr Function +OpStore %var %const_struct Aligned 4 +%3 = OpAccessChain %uint_ptr %var %uint_0 +%4 = OpLoad %uint %3 +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(ScalarReplacementTest, DontTouchVolatileLoad) { + const std::string text = R"( +; +; CHECK: [[struct:%\w+]] = OpTypeStruct +; CHECK: [[struct_ptr:%\w+]] = OpTypePointer Function [[struct]] +; CHECK: OpLabel +; CHECK-NEXT: OpVariable [[struct_ptr]] +; CHECK-NOT: OpVariable +; +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpName %func "dont_touch_volatile_load" +%void = OpTypeVoid +%uint = OpTypeInt 32 0 +%struct1 = OpTypeStruct %uint +%uint_ptr = OpTypePointer Function %uint +%struct1_ptr = OpTypePointer Function %struct1 +%uint_0 = OpConstant %uint 0 +%func = OpTypeFunction %void +%1 = OpFunction %void None %func +%2 = OpLabel +%var = OpVariable %struct1_ptr Function +%3 = OpAccessChain %uint_ptr %var %uint_0 +%4 = OpLoad %uint %3 Volatile +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(ScalarReplacementTest, DontTouchVolatileStore) { + const std::string text = R"( +; +; CHECK: [[struct:%\w+]] = OpTypeStruct +; CHECK: [[struct_ptr:%\w+]] = OpTypePointer Function [[struct]] +; CHECK: OpLabel +; CHECK-NEXT: OpVariable [[struct_ptr]] +; CHECK-NOT: OpVariable +; +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpName %func "dont_touch_volatile_store" +%void = OpTypeVoid +%uint = OpTypeInt 32 0 +%struct1 = OpTypeStruct %uint +%uint_ptr = OpTypePointer Function %uint +%struct1_ptr = OpTypePointer Function %struct1 +%uint_0 = OpConstant %uint 0 +%func = OpTypeFunction %void +%1 = OpFunction %void None %func +%2 = OpLabel +%var = OpVariable %struct1_ptr Function +%3 = OpAccessChain %uint_ptr %var %uint_0 +OpStore %3 %uint_0 Volatile +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(ScalarReplacementTest, DontTouchSpecNonFunctionVariable) { + const std::string text = R"( +; +; CHECK: [[struct:%\w+]] = OpTypeStruct +; CHECK: [[struct_ptr:%\w+]] = OpTypePointer Uniform [[struct]] +; CHECK: OpConstant +; CHECK-NEXT: OpVariable [[struct_ptr]] +; CHECK-NOT: OpVariable +; +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpName %func "dont_touch_spec_constant_access_chain" +%void = OpTypeVoid +%uint = OpTypeInt 32 0 +%struct1 = OpTypeStruct %uint +%uint_ptr = OpTypePointer Uniform %uint +%struct1_ptr = OpTypePointer Uniform %struct1 +%uint_0 = OpConstant %uint 0 +%var = OpVariable %struct1_ptr Uniform +%func = OpTypeFunction %void +%1 = OpFunction %void None %func +%2 = OpLabel +%3 = OpAccessChain %uint_ptr %var %uint_0 +OpStore %3 %uint_0 Volatile +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(ScalarReplacementTest, DontTouchSpecConstantAccessChain) { + const std::string text = R"( +; +; CHECK: [[array:%\w+]] = OpTypeArray +; CHECK: [[array_ptr:%\w+]] = OpTypePointer Function [[array]] +; CHECK: OpLabel +; CHECK-NEXT: OpVariable [[array_ptr]] +; CHECK-NOT: OpVariable +; +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpName %func "dont_touch_spec_constant_access_chain" +%void = OpTypeVoid +%uint = OpTypeInt 32 0 +%uint_1 = OpConstant %uint 1 +%array = OpTypeArray %uint %uint_1 +%uint_ptr = OpTypePointer Function %uint +%array_ptr = OpTypePointer Function %array +%uint_0 = OpConstant %uint 0 +%spec_const = OpSpecConstant %uint 0 +%func = OpTypeFunction %void +%1 = OpFunction %void None %func +%2 = OpLabel +%var = OpVariable %array_ptr Function +%3 = OpAccessChain %uint_ptr %var %spec_const +OpStore %3 %uint_0 Volatile +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(ScalarReplacementTest, NoPartialAccesses) { + const std::string text = R"( +; +; CHECK: [[uint:%\w+]] = OpTypeInt 32 0 +; CHECK: [[uint_ptr:%\w+]] = OpTypePointer Function [[uint]] +; CHECK: OpLabel +; CHECK-NEXT: OpVariable [[uint_ptr]] +; CHECK-NOT: OpVariable +; +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpName %func "no_partial_accesses" +%void = OpTypeVoid +%uint = OpTypeInt 32 0 +%struct1 = OpTypeStruct %uint +%uint_ptr = OpTypePointer Function %uint +%struct1_ptr = OpTypePointer Function %struct1 +%const = OpConstantNull %struct1 +%func = OpTypeFunction %void +%1 = OpFunction %void None %func +%2 = OpLabel +%var = OpVariable %struct1_ptr Function +OpStore %var %const +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(ScalarReplacementTest, DontTouchPtrAccessChain) { + const std::string text = R"( +; +; CHECK: [[struct:%\w+]] = OpTypeStruct +; CHECK: [[struct_ptr:%\w+]] = OpTypePointer Function [[struct]] +; CHECK: OpLabel +; CHECK-NEXT: OpVariable [[struct_ptr]] +; CHECK-NOT: OpVariable +; +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpName %func "dont_touch_ptr_access_chain" +%void = OpTypeVoid +%uint = OpTypeInt 32 0 +%struct1 = OpTypeStruct %uint +%uint_ptr = OpTypePointer Function %uint +%struct1_ptr = OpTypePointer Function %struct1 +%uint_0 = OpConstant %uint 0 +%func = OpTypeFunction %void +%1 = OpFunction %void None %func +%2 = OpLabel +%var = OpVariable %struct1_ptr Function +%3 = OpPtrAccessChain %uint_ptr %var %uint_0 %uint_0 +OpStore %3 %uint_0 +%4 = OpAccessChain %uint_ptr %var %uint_0 +OpStore %4 %uint_0 +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, false); +} + +TEST_F(ScalarReplacementTest, DontTouchInBoundsPtrAccessChain) { + const std::string text = R"( +; +; CHECK: [[struct:%\w+]] = OpTypeStruct +; CHECK: [[struct_ptr:%\w+]] = OpTypePointer Function [[struct]] +; CHECK: OpLabel +; CHECK-NEXT: OpVariable [[struct_ptr]] +; CHECK-NOT: OpVariable +; +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpName %func "dont_touch_in_bounds_ptr_access_chain" +%void = OpTypeVoid +%uint = OpTypeInt 32 0 +%struct1 = OpTypeStruct %uint +%uint_ptr = OpTypePointer Function %uint +%struct1_ptr = OpTypePointer Function %struct1 +%uint_0 = OpConstant %uint 0 +%func = OpTypeFunction %void +%1 = OpFunction %void None %func +%2 = OpLabel +%var = OpVariable %struct1_ptr Function +%3 = OpInBoundsPtrAccessChain %uint_ptr %var %uint_0 %uint_0 +OpStore %3 %uint_0 +%4 = OpInBoundsAccessChain %uint_ptr %var %uint_0 +OpStore %4 %uint_0 +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, false); +} + +TEST_F(ScalarReplacementTest, DonTouchAliasedDecoration) { + const std::string text = R"( +; +; CHECK: [[struct:%\w+]] = OpTypeStruct +; CHECK: [[struct_ptr:%\w+]] = OpTypePointer Function [[struct]] +; CHECK: OpLabel +; CHECK-NEXT: OpVariable [[struct_ptr]] +; CHECK-NOT: OpVariable +; +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpName %func "aliased" +OpDecorate %var Aliased +%void = OpTypeVoid +%uint = OpTypeInt 32 0 +%struct1 = OpTypeStruct %uint +%uint_ptr = OpTypePointer Function %uint +%struct1_ptr = OpTypePointer Function %struct1 +%uint_0 = OpConstant %uint 0 +%func = OpTypeFunction %void +%1 = OpFunction %void None %func +%2 = OpLabel +%var = OpVariable %struct1_ptr Function +%3 = OpAccessChain %uint_ptr %var %uint_0 +%4 = OpLoad %uint %3 +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(ScalarReplacementTest, CopyRestrictDecoration) { + const std::string text = R"( +; +; CHECK: OpName +; CHECK-NEXT: OpDecorate [[var0:%\w+]] Restrict +; CHECK-NEXT: OpDecorate [[var1:%\w+]] Restrict +; CHECK: [[int:%\w+]] = OpTypeInt +; CHECK: [[struct:%\w+]] = OpTypeStruct +; CHECK: [[int_ptr:%\w+]] = OpTypePointer Function [[int]] +; CHECK: [[struct_ptr:%\w+]] = OpTypePointer Function [[struct]] +; CHECK: OpLabel +; CHECK-NEXT: [[var1]] = OpVariable [[int_ptr]] +; CHECK-NEXT: [[var0]] = OpVariable [[int_ptr]] +; CHECK-NOT: OpVariable [[struct_ptr]] +; +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpName %func "restrict" +OpDecorate %var Restrict +%void = OpTypeVoid +%uint = OpTypeInt 32 0 +%struct1 = OpTypeStruct %uint %uint +%uint_ptr = OpTypePointer Function %uint +%struct1_ptr = OpTypePointer Function %struct1 +%uint_0 = OpConstant %uint 0 +%uint_1 = OpConstant %uint 1 +%func = OpTypeFunction %void +%1 = OpFunction %void None %func +%2 = OpLabel +%var = OpVariable %struct1_ptr Function +%3 = OpAccessChain %uint_ptr %var %uint_0 +%4 = OpLoad %uint %3 +%5 = OpAccessChain %uint_ptr %var %uint_1 +%6 = OpLoad %uint %5 +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(ScalarReplacementTest, DontClobberDecoratesOnSubtypes) { + const std::string text = R"( +; +; CHECK: OpDecorate [[array:%\w+]] ArrayStride 1 +; CHECK: [[uint:%\w+]] = OpTypeInt 32 0 +; CHECK: [[array]] = OpTypeArray [[uint]] +; CHECK: [[array_ptr:%\w+]] = OpTypePointer Function [[array]] +; CHECK: OpLabel +; CHECK-NEXT: OpVariable [[array_ptr]] Function +; CHECK-NOT: OpVariable +; +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpName %func "array_stride" +OpDecorate %array ArrayStride 1 +%void = OpTypeVoid +%uint = OpTypeInt 32 0 +%uint_1 = OpConstant %uint 1 +%array = OpTypeArray %uint %uint_1 +%struct1 = OpTypeStruct %array +%uint_ptr = OpTypePointer Function %uint +%struct1_ptr = OpTypePointer Function %struct1 +%uint_0 = OpConstant %uint 0 +%func = OpTypeFunction %void %uint +%1 = OpFunction %void None %func +%param = OpFunctionParameter %uint +%2 = OpLabel +%var = OpVariable %struct1_ptr Function +%3 = OpAccessChain %uint_ptr %var %uint_0 %param +%4 = OpLoad %uint %3 +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(ScalarReplacementTest, DontCopyMemberDecorate) { + const std::string text = R"( +; +; CHECK-NOT: OpDecorate +; CHECK: [[uint:%\w+]] = OpTypeInt 32 0 +; CHECK: [[struct:%\w+]] = OpTypeStruct [[uint]] +; CHECK: [[uint_ptr:%\w+]] = OpTypePointer Function [[uint]] +; CHECK: [[struct_ptr:%\w+]] = OpTypePointer Function [[struct]] +; CHECK: OpLabel +; CHECK-NEXT: OpVariable [[uint_ptr]] Function +; CHECK-NOT: OpVariable +; +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpName %func "member_decorate" +OpMemberDecorate %struct1 0 Offset 1 +%void = OpTypeVoid +%uint = OpTypeInt 32 0 +%uint_1 = OpConstant %uint 1 +%struct1 = OpTypeStruct %uint +%uint_ptr = OpTypePointer Function %uint +%struct1_ptr = OpTypePointer Function %struct1 +%uint_0 = OpConstant %uint 0 +%func = OpTypeFunction %void %uint +%1 = OpFunction %void None %func +%2 = OpLabel +%var = OpVariable %struct1_ptr Function +%3 = OpAccessChain %uint_ptr %var %uint_0 +%4 = OpLoad %uint %3 +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(ScalarReplacementTest, NoPartialAccesses2) { + const std::string text = R"( +; +; CHECK: [[float:%\w+]] = OpTypeFloat 32 +; CHECK: [[float_ptr:%\w+]] = OpTypePointer Function [[float]] +; CHECK: OpVariable [[float_ptr]] Function +; CHECK: OpVariable [[float_ptr]] Function +; CHECK: OpVariable [[float_ptr]] Function +; CHECK: OpVariable [[float_ptr]] Function +; CHECK: OpVariable [[float_ptr]] Function +; CHECK: OpVariable [[float_ptr]] Function +; CHECK: OpVariable [[float_ptr]] Function +; CHECK: OpVariable [[float_ptr]] Function +; CHECK-NOT: OpVariable +; +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %fo +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 430 +OpName %main "main" +OpName %S "S" +OpMemberName %S 0 "x" +OpMemberName %S 1 "y" +OpName %ts1 "ts1" +OpName %S_0 "S" +OpMemberName %S_0 0 "x" +OpMemberName %S_0 1 "y" +OpName %U_t "U_t" +OpMemberName %U_t 0 "g_s1" +OpMemberName %U_t 1 "g_s2" +OpMemberName %U_t 2 "g_s3" +OpName %_ "" +OpName %ts2 "ts2" +OpName %_Globals_ "_Globals_" +OpMemberName %_Globals_ 0 "g_b" +OpName %__0 "" +OpName %ts3 "ts3" +OpName %ts4 "ts4" +OpName %fo "fo" +OpMemberDecorate %S_0 0 Offset 0 +OpMemberDecorate %S_0 1 Offset 4 +OpMemberDecorate %U_t 0 Offset 0 +OpMemberDecorate %U_t 1 Offset 8 +OpMemberDecorate %U_t 2 Offset 16 +OpDecorate %U_t BufferBlock +OpDecorate %_ DescriptorSet 0 +OpMemberDecorate %_Globals_ 0 Offset 0 +OpDecorate %_Globals_ Block +OpDecorate %__0 DescriptorSet 0 +OpDecorate %__0 Binding 0 +OpDecorate %fo Location 0 +%void = OpTypeVoid +%15 = OpTypeFunction %void +%float = OpTypeFloat 32 +%S = OpTypeStruct %float %float +%_ptr_Function_S = OpTypePointer Function %S +%S_0 = OpTypeStruct %float %float +%U_t = OpTypeStruct %S_0 %S_0 %S_0 +%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t +%_ = OpVariable %_ptr_Uniform_U_t Uniform +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%_ptr_Uniform_S_0 = OpTypePointer Uniform %S_0 +%_ptr_Function_float = OpTypePointer Function %float +%int_1 = OpConstant %int 1 +%uint = OpTypeInt 32 0 +%_Globals_ = OpTypeStruct %uint +%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_ +%__0 = OpVariable %_ptr_Uniform__Globals_ Uniform +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%bool = OpTypeBool +%uint_0 = OpConstant %uint 0 +%_ptr_Output_float = OpTypePointer Output %float +%fo = OpVariable %_ptr_Output_float Output +%main = OpFunction %void None %15 +%30 = OpLabel +%ts1 = OpVariable %_ptr_Function_S Function +%ts2 = OpVariable %_ptr_Function_S Function +%ts3 = OpVariable %_ptr_Function_S Function +%ts4 = OpVariable %_ptr_Function_S Function +%31 = OpAccessChain %_ptr_Uniform_S_0 %_ %int_0 +%32 = OpLoad %S_0 %31 +%33 = OpCompositeExtract %float %32 0 +%34 = OpAccessChain %_ptr_Function_float %ts1 %int_0 +OpStore %34 %33 +%35 = OpCompositeExtract %float %32 1 +%36 = OpAccessChain %_ptr_Function_float %ts1 %int_1 +OpStore %36 %35 +%37 = OpAccessChain %_ptr_Uniform_S_0 %_ %int_1 +%38 = OpLoad %S_0 %37 +%39 = OpCompositeExtract %float %38 0 +%40 = OpAccessChain %_ptr_Function_float %ts2 %int_0 +OpStore %40 %39 +%41 = OpCompositeExtract %float %38 1 +%42 = OpAccessChain %_ptr_Function_float %ts2 %int_1 +OpStore %42 %41 +%43 = OpAccessChain %_ptr_Uniform_uint %__0 %int_0 +%44 = OpLoad %uint %43 +%45 = OpINotEqual %bool %44 %uint_0 +OpSelectionMerge %46 None +OpBranchConditional %45 %47 %48 +%47 = OpLabel +%49 = OpLoad %S %ts1 +OpStore %ts3 %49 +OpBranch %46 +%48 = OpLabel +%50 = OpLoad %S %ts2 +OpStore %ts3 %50 +OpBranch %46 +%46 = OpLabel +%51 = OpLoad %S %ts3 +OpStore %ts4 %51 +%52 = OpAccessChain %_ptr_Function_float %ts4 %int_1 +%53 = OpLoad %float %52 +OpStore %fo %53 +OpReturn +OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} +#endif // SPIRV_EFFCEE + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/set_spec_const_default_value_test.cpp b/3rdparty/spirv-tools/test/opt/set_spec_const_default_value_test.cpp new file mode 100644 index 000000000..973b5e494 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/set_spec_const_default_value_test.cpp @@ -0,0 +1,1075 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "pass_fixture.h" + +#include + +namespace { +using namespace spvtools; + +using testing::Eq; + +using SpecIdToValueStrMap = + opt::SetSpecConstantDefaultValuePass::SpecIdToValueStrMap; +using SpecIdToValueBitPatternMap = + opt::SetSpecConstantDefaultValuePass::SpecIdToValueBitPatternMap; + +struct DefaultValuesStringParsingTestCase { + const char* default_values_str; + bool expect_success; + SpecIdToValueStrMap expected_map; +}; + +using DefaultValuesStringParsingTest = + ::testing::TestWithParam; + +TEST_P(DefaultValuesStringParsingTest, TestCase) { + const auto& tc = GetParam(); + auto actual_map = + opt::SetSpecConstantDefaultValuePass::ParseDefaultValuesString( + tc.default_values_str); + if (tc.expect_success) { + EXPECT_NE(nullptr, actual_map); + if (actual_map) { + EXPECT_THAT(*actual_map, Eq(tc.expected_map)); + } + } else { + EXPECT_EQ(nullptr, actual_map); + } +} + +INSTANTIATE_TEST_CASE_P( + ValidString, DefaultValuesStringParsingTest, + ::testing::ValuesIn(std::vector{ + // 0. empty map + {"", true, SpecIdToValueStrMap{}}, + // 1. one pair + {"100:1024", true, SpecIdToValueStrMap{{100, "1024"}}}, + // 2. two pairs + {"100:1024 200:2048", true, + SpecIdToValueStrMap{{100, "1024"}, {200, "2048"}}}, + // 3. spaces between entries + {"100:1024 \n \r \t \v \f 200:2048", true, + SpecIdToValueStrMap{{100, "1024"}, {200, "2048"}}}, + // 4. \t, \n, \r and spaces before spec id + {" \n \r\t \t \v \f 100:1024", true, + SpecIdToValueStrMap{{100, "1024"}}}, + // 5. \t, \n, \r and spaces after value string + {"100:1024 \n \r\t \t \v \f ", true, + SpecIdToValueStrMap{{100, "1024"}}}, + // 6. maximum spec id + {"4294967295:0", true, SpecIdToValueStrMap{{4294967295, "0"}}}, + // 7. minimum spec id + {"0:100", true, SpecIdToValueStrMap{{0, "100"}}}, + // 8. random content without spaces are allowed + {"200:random_stuff", true, SpecIdToValueStrMap{{200, "random_stuff"}}}, + // 9. support hex format spec id (just because we use the + // ParseNumber() utility) + {"0x100:1024", true, SpecIdToValueStrMap{{256, "1024"}}}, + // 10. multiple entries + {"101:1 102:2 103:3 104:4 200:201 9999:1000 0x100:333", true, + SpecIdToValueStrMap{{101, "1"}, + {102, "2"}, + {103, "3"}, + {104, "4"}, + {200, "201"}, + {9999, "1000"}, + {256, "333"}}}, + // 11. default value in hex float format + {"100:0x0.3p10", true, SpecIdToValueStrMap{{100, "0x0.3p10"}}}, + // 12. default value in decimal float format + {"100:1.5e-13", true, SpecIdToValueStrMap{{100, "1.5e-13"}}}, + })); + +INSTANTIATE_TEST_CASE_P( + InvalidString, DefaultValuesStringParsingTest, + ::testing::ValuesIn(std::vector{ + // 0. missing default value + {"100:", false, SpecIdToValueStrMap{}}, + // 1. spec id is not an integer + {"100.0:200", false, SpecIdToValueStrMap{}}, + // 2. spec id is not a number + {"something_not_a_number:1", false, SpecIdToValueStrMap{}}, + // 3. only spec id number + {"100", false, SpecIdToValueStrMap{}}, + // 4. same spec id defined multiple times + {"100:20 100:21", false, SpecIdToValueStrMap{}}, + // 5. Multiple definition of an identical spec id in different forms + // is not allowed + {"0x100:100 256:200", false, SpecIdToValueStrMap{}}, + // 6. empty spec id + {":3", false, SpecIdToValueStrMap{}}, + // 7. only colon + {":", false, SpecIdToValueStrMap{}}, + // 8. spec id overflow + {"4294967296:200", false, SpecIdToValueStrMap{}}, + // 9. spec id less than 0 + {"-1:200", false, SpecIdToValueStrMap{}}, + // 10. nullptr + {nullptr, false, SpecIdToValueStrMap{}}, + // 11. only a number is invalid + {"1234", false, SpecIdToValueStrMap{}}, + // 12. invalid entry separator + {"12:34;23:14", false, SpecIdToValueStrMap{}}, + // 13. invalid spec id and default value separator + {"12@34", false, SpecIdToValueStrMap{}}, + // 14. spaces before colon + {"100 :1024", false, SpecIdToValueStrMap{}}, + // 15. spaces after colon + {"100: 1024", false, SpecIdToValueStrMap{}}, + // 16. spec id represented in hex float format is invalid + {"0x3p10:200", false, SpecIdToValueStrMap{}}, + })); + +struct SetSpecConstantDefaultValueInStringFormTestCase { + const char* code; + SpecIdToValueStrMap default_values; + const char* expected; +}; + +using SetSpecConstantDefaultValueInStringFormParamTest = PassTest< + ::testing::TestWithParam>; + +TEST_P(SetSpecConstantDefaultValueInStringFormParamTest, TestCase) { + const auto& tc = GetParam(); + SinglePassRunAndCheck( + tc.code, tc.expected, /* skip_nop = */ false, tc.default_values); +} + +INSTANTIATE_TEST_CASE_P( + ValidCases, SetSpecConstantDefaultValueInStringFormParamTest, + ::testing::ValuesIn(std::vector< + SetSpecConstantDefaultValueInStringFormTestCase>{ + // 0. Empty. + {"", SpecIdToValueStrMap{}, ""}, + // 1. Empty with non-empty values to set. + {"", SpecIdToValueStrMap{{1, "100"}, {2, "200"}}, ""}, + // 2. Bool type. + { + // code + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "%bool = OpTypeBool\n" + "%1 = OpSpecConstantTrue %bool\n" + "%2 = OpSpecConstantFalse %bool\n", + // default values + SpecIdToValueStrMap{{100, "false"}, {101, "true"}}, + // expected + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "%bool = OpTypeBool\n" + "%1 = OpSpecConstantFalse %bool\n" + "%2 = OpSpecConstantTrue %bool\n", + }, + // 3. 32-bit int type. + { + // code + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "OpDecorate %3 SpecId 102\n" + "%int = OpTypeInt 32 1\n" + "%1 = OpSpecConstant %int 10\n" + "%2 = OpSpecConstant %int 11\n" + "%3 = OpSpecConstant %int 11\n", + // default values + SpecIdToValueStrMap{ + {100, "2147483647"}, {101, "0xffffffff"}, {102, "-42"}}, + // expected + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "OpDecorate %3 SpecId 102\n" + "%int = OpTypeInt 32 1\n" + "%1 = OpSpecConstant %int 2147483647\n" + "%2 = OpSpecConstant %int -1\n" + "%3 = OpSpecConstant %int -42\n", + }, + // 4. 64-bit uint type. + { + // code + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "%ulong = OpTypeInt 64 0\n" + "%1 = OpSpecConstant %ulong 10\n" + "%2 = OpSpecConstant %ulong 11\n", + // default values + SpecIdToValueStrMap{{100, "18446744073709551614"}, {101, "0x100"}}, + // expected + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "%ulong = OpTypeInt 64 0\n" + "%1 = OpSpecConstant %ulong 18446744073709551614\n" + "%2 = OpSpecConstant %ulong 256\n", + }, + // 5. 32-bit float type. + { + // code + "OpDecorate %1 SpecId 101\n" + "OpDecorate %2 SpecId 102\n" + "%float = OpTypeFloat 32\n" + "%1 = OpSpecConstant %float 200\n" + "%2 = OpSpecConstant %float 201\n", + // default values + SpecIdToValueStrMap{{101, "-0x1.fffffep+128"}, {102, "2.5"}}, + // expected + "OpDecorate %1 SpecId 101\n" + "OpDecorate %2 SpecId 102\n" + "%float = OpTypeFloat 32\n" + "%1 = OpSpecConstant %float -0x1.fffffep+128\n" + "%2 = OpSpecConstant %float 2.5\n", + }, + // 6. 64-bit float type. + { + // code + "OpDecorate %1 SpecId 201\n" + "OpDecorate %2 SpecId 202\n" + "%double = OpTypeFloat 64\n" + "%1 = OpSpecConstant %double 3.14159265358979\n" + "%2 = OpSpecConstant %double 0.14285\n", + // default values + SpecIdToValueStrMap{{201, "0x1.fffffffffffffp+1024"}, + {202, "-32.5"}}, + // expected + "OpDecorate %1 SpecId 201\n" + "OpDecorate %2 SpecId 202\n" + "%double = OpTypeFloat 64\n" + "%1 = OpSpecConstant %double 0x1.fffffffffffffp+1024\n" + "%2 = OpSpecConstant %double -32.5\n", + }, + // 7. SpecId not found, expect no modification. + { + // code + "OpDecorate %1 SpecId 201\n" + "%double = OpTypeFloat 64\n" + "%1 = OpSpecConstant %double 3.14159265358979\n", + // default values + SpecIdToValueStrMap{{8888, "0.0"}}, + // expected + "OpDecorate %1 SpecId 201\n" + "%double = OpTypeFloat 64\n" + "%1 = OpSpecConstant %double 3.14159265358979\n", + }, + // 8. Multiple types of spec constants. + { + // code + "OpDecorate %1 SpecId 201\n" + "OpDecorate %2 SpecId 202\n" + "OpDecorate %3 SpecId 203\n" + "%bool = OpTypeBool\n" + "%int = OpTypeInt 32 1\n" + "%double = OpTypeFloat 64\n" + "%1 = OpSpecConstant %double 3.14159265358979\n" + "%2 = OpSpecConstant %int 1024\n" + "%3 = OpSpecConstantTrue %bool\n", + // default values + SpecIdToValueStrMap{ + {201, "0x1.fffffffffffffp+1024"}, + {202, "2048"}, + {203, "false"}, + }, + // expected + "OpDecorate %1 SpecId 201\n" + "OpDecorate %2 SpecId 202\n" + "OpDecorate %3 SpecId 203\n" + "%bool = OpTypeBool\n" + "%int = OpTypeInt 32 1\n" + "%double = OpTypeFloat 64\n" + "%1 = OpSpecConstant %double 0x1.fffffffffffffp+1024\n" + "%2 = OpSpecConstant %int 2048\n" + "%3 = OpSpecConstantFalse %bool\n", + }, + // 9. Ignore other decorations. + { + // code + "OpDecorate %1 ArrayStride 4\n" + "%int = OpTypeInt 32 1\n" + "%1 = OpSpecConstant %int 100\n", + // default values + SpecIdToValueStrMap{{4, "0x7fffffff"}}, + // expected + "OpDecorate %1 ArrayStride 4\n" + "%int = OpTypeInt 32 1\n" + "%1 = OpSpecConstant %int 100\n", + }, + // 10. Distinguish from other decorations. + { + // code + "OpDecorate %1 SpecId 100\n" + "OpDecorate %1 ArrayStride 4\n" + "%int = OpTypeInt 32 1\n" + "%1 = OpSpecConstant %int 100\n", + // default values + SpecIdToValueStrMap{{4, "0x7fffffff"}, {100, "0xffffffff"}}, + // expected + "OpDecorate %1 SpecId 100\n" + "OpDecorate %1 ArrayStride 4\n" + "%int = OpTypeInt 32 1\n" + "%1 = OpSpecConstant %int -1\n", + }, + // 11. Decorate through decoration group. + { + // code + "OpDecorate %1 SpecId 100\n" + "%1 = OpDecorationGroup\n" + "OpGroupDecorate %1 %2\n" + "%int = OpTypeInt 32 1\n" + "%2 = OpSpecConstant %int 100\n", + // default values + SpecIdToValueStrMap{{100, "0x7fffffff"}}, + // expected + "OpDecorate %1 SpecId 100\n" + "%1 = OpDecorationGroup\n" + "OpGroupDecorate %1 %2\n" + "%int = OpTypeInt 32 1\n" + "%2 = OpSpecConstant %int 2147483647\n", + }, + // 12. Ignore other decorations in decoration group. + { + // code + "OpDecorate %1 ArrayStride 4\n" + "%1 = OpDecorationGroup\n" + "OpGroupDecorate %1 %2\n" + "%int = OpTypeInt 32 1\n" + "%2 = OpSpecConstant %int 100\n", + // default values + SpecIdToValueStrMap{{4, "0x7fffffff"}}, + // expected + "OpDecorate %1 ArrayStride 4\n" + "%1 = OpDecorationGroup\n" + "OpGroupDecorate %1 %2\n" + "%int = OpTypeInt 32 1\n" + "%2 = OpSpecConstant %int 100\n", + }, + // 13. Distinguish from other decorations in decoration group. + { + // code + "OpDecorate %1 SpecId 100\n" + "OpDecorate %1 ArrayStride 4\n" + "%1 = OpDecorationGroup\n" + "OpGroupDecorate %1 %2\n" + "%int = OpTypeInt 32 1\n" + "%2 = OpSpecConstant %int 100\n", + // default values + SpecIdToValueStrMap{{100, "0x7fffffff"}, {4, "0x00000001"}}, + // expected + "OpDecorate %1 SpecId 100\n" + "OpDecorate %1 ArrayStride 4\n" + "%1 = OpDecorationGroup\n" + "OpGroupDecorate %1 %2\n" + "%int = OpTypeInt 32 1\n" + "%2 = OpSpecConstant %int 2147483647\n", + }, + // 14. Unchanged bool default value + { + // code + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "%bool = OpTypeBool\n" + "%1 = OpSpecConstantTrue %bool\n" + "%2 = OpSpecConstantFalse %bool\n", + // default values + SpecIdToValueStrMap{{100, "true"}, {101, "false"}}, + // expected + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "%bool = OpTypeBool\n" + "%1 = OpSpecConstantTrue %bool\n" + "%2 = OpSpecConstantFalse %bool\n", + }, + // 15. Unchanged int default values + { + // code + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "%int = OpTypeInt 32 1\n" + "%ulong = OpTypeInt 64 0\n" + "%1 = OpSpecConstant %int 10\n" + "%2 = OpSpecConstant %ulong 11\n", + // default values + SpecIdToValueStrMap{{100, "10"}, {101, "11"}}, + // expected + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "%int = OpTypeInt 32 1\n" + "%ulong = OpTypeInt 64 0\n" + "%1 = OpSpecConstant %int 10\n" + "%2 = OpSpecConstant %ulong 11\n", + }, + // 16. Unchanged float default values + { + // code + "OpDecorate %1 SpecId 201\n" + "OpDecorate %2 SpecId 202\n" + "%float = OpTypeFloat 32\n" + "%double = OpTypeFloat 64\n" + "%1 = OpSpecConstant %float 3.1415\n" + "%2 = OpSpecConstant %double 0.14285\n", + // default values + SpecIdToValueStrMap{{201, "3.1415"}, {202, "0.14285"}}, + // expected + "OpDecorate %1 SpecId 201\n" + "OpDecorate %2 SpecId 202\n" + "%float = OpTypeFloat 32\n" + "%double = OpTypeFloat 64\n" + "%1 = OpSpecConstant %float 3.1415\n" + "%2 = OpSpecConstant %double 0.14285\n", + }, + // 17. OpGroupDecorate may have multiple target ids defined by the same + // eligible spec constant + { + // code + "OpDecorate %1 SpecId 100\n" + "%1 = OpDecorationGroup\n" + "OpGroupDecorate %1 %2 %2 %2\n" + "%int = OpTypeInt 32 1\n" + "%2 = OpSpecConstant %int 100\n", + // default values + SpecIdToValueStrMap{{100, "0xffffffff"}}, + // expected + "OpDecorate %1 SpecId 100\n" + "%1 = OpDecorationGroup\n" + "OpGroupDecorate %1 %2 %2 %2\n" + "%int = OpTypeInt 32 1\n" + "%2 = OpSpecConstant %int -1\n", + }, + })); + +INSTANTIATE_TEST_CASE_P( + InvalidCases, SetSpecConstantDefaultValueInStringFormParamTest, + ::testing::ValuesIn(std::vector< + SetSpecConstantDefaultValueInStringFormTestCase>{ + // 0. Do not crash when decoration group is not used. + { + // code + "OpDecorate %1 SpecId 100\n" + "%1 = OpDecorationGroup\n" + "%int = OpTypeInt 32 1\n" + "%3 = OpSpecConstant %int 100\n", + // default values + SpecIdToValueStrMap{{100, "0x7fffffff"}}, + // expected + "OpDecorate %1 SpecId 100\n" + "%1 = OpDecorationGroup\n" + "%int = OpTypeInt 32 1\n" + "%3 = OpSpecConstant %int 100\n", + }, + // 1. Do not crash when target does not exist. + { + // code + "OpDecorate %1 SpecId 100\n" + "%1 = OpDecorationGroup\n" + "%int = OpTypeInt 32 1\n", + // default values + SpecIdToValueStrMap{{100, "0x7fffffff"}}, + // expected + "OpDecorate %1 SpecId 100\n" + "%1 = OpDecorationGroup\n" + "%int = OpTypeInt 32 1\n", + }, + // 2. Do nothing when SpecId decoration is not attached to a + // non-spec-constant instruction. + { + // code + "OpDecorate %1 SpecId 100\n" + "%1 = OpDecorationGroup\n" + "%int = OpTypeInt 32 1\n" + "%int_101 = OpConstant %int 101\n", + // default values + SpecIdToValueStrMap{{100, "0x7fffffff"}}, + // expected + "OpDecorate %1 SpecId 100\n" + "%1 = OpDecorationGroup\n" + "%int = OpTypeInt 32 1\n" + "%int_101 = OpConstant %int 101\n", + }, + // 3. Do nothing when SpecId decoration is not attached to a + // OpSpecConstant{|True|False} instruction. + { + // code + "OpDecorate %1 SpecId 100\n" + "%int = OpTypeInt 32 1\n" + "%3 = OpSpecConstant %int 101\n" + "%1 = OpSpecConstantOp %int IAdd %3 %3\n", + // default values + SpecIdToValueStrMap{{100, "0x7fffffff"}}, + // expected + "OpDecorate %1 SpecId 100\n" + "%int = OpTypeInt 32 1\n" + "%3 = OpSpecConstant %int 101\n" + "%1 = OpSpecConstantOp %int IAdd %3 %3\n", + }, + // 4. Do not crash and do nothing when SpecId decoration is applied to + // multiple spec constants. + { + // code + "OpDecorate %1 SpecId 100\n" + "%1 = OpDecorationGroup\n" + "OpGroupDecorate %1 %2 %3 %4\n" + "%int = OpTypeInt 32 1\n" + "%2 = OpSpecConstant %int 100\n" + "%3 = OpSpecConstant %int 200\n" + "%4 = OpSpecConstant %int 300\n", + // default values + SpecIdToValueStrMap{{100, "0xffffffff"}}, + // expected + "OpDecorate %1 SpecId 100\n" + "%1 = OpDecorationGroup\n" + "OpGroupDecorate %1 %2 %3 %4\n" + "%int = OpTypeInt 32 1\n" + "%2 = OpSpecConstant %int 100\n" + "%3 = OpSpecConstant %int 200\n" + "%4 = OpSpecConstant %int 300\n", + }, + // 5. Do not crash and do nothing when SpecId decoration is attached to + // non-spec-constants (invalid case). + { + // code + "OpDecorate %1 SpecId 100\n" + "%1 = OpDecorationGroup\n" + "%2 = OpDecorationGroup\n" + "OpGroupDecorate %1 %2\n" + "%int = OpTypeInt 32 1\n" + "%int_100 = OpConstant %int 100\n", + // default values + SpecIdToValueStrMap{{100, "0xffffffff"}}, + // expected + "OpDecorate %1 SpecId 100\n" + "%1 = OpDecorationGroup\n" + "%2 = OpDecorationGroup\n" + "OpGroupDecorate %1 %2\n" + "%int = OpTypeInt 32 1\n" + "%int_100 = OpConstant %int 100\n", + }, + // 6. Boolean type spec constant cannot be set with numeric values in + // string form. i.e. only 'true' and 'false' are acceptable for setting + // boolean type spec constants. Nothing should be done if numeric values + // in string form are provided. + { + // code + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "OpDecorate %3 SpecId 102\n" + "OpDecorate %4 SpecId 103\n" + "OpDecorate %5 SpecId 104\n" + "OpDecorate %6 SpecId 105\n" + "%bool = OpTypeBool\n" + "%1 = OpSpecConstantTrue %bool\n" + "%2 = OpSpecConstantFalse %bool\n" + "%3 = OpSpecConstantTrue %bool\n" + "%4 = OpSpecConstantTrue %bool\n" + "%5 = OpSpecConstantTrue %bool\n" + "%6 = OpSpecConstantFalse %bool\n", + // default values + SpecIdToValueStrMap{{100, "0"}, + {101, "1"}, + {102, "0x0"}, + {103, "0.0"}, + {104, "-0.0"}, + {105, "0x12345678"}}, + // expected + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "OpDecorate %3 SpecId 102\n" + "OpDecorate %4 SpecId 103\n" + "OpDecorate %5 SpecId 104\n" + "OpDecorate %6 SpecId 105\n" + "%bool = OpTypeBool\n" + "%1 = OpSpecConstantTrue %bool\n" + "%2 = OpSpecConstantFalse %bool\n" + "%3 = OpSpecConstantTrue %bool\n" + "%4 = OpSpecConstantTrue %bool\n" + "%5 = OpSpecConstantTrue %bool\n" + "%6 = OpSpecConstantFalse %bool\n", + }, + })); + +struct SetSpecConstantDefaultValueInBitPatternFormTestCase { + const char* code; + SpecIdToValueBitPatternMap default_values; + const char* expected; +}; + +using SetSpecConstantDefaultValueInBitPatternFormParamTest = + PassTest<::testing::TestWithParam< + SetSpecConstantDefaultValueInBitPatternFormTestCase>>; + +TEST_P(SetSpecConstantDefaultValueInBitPatternFormParamTest, TestCase) { + const auto& tc = GetParam(); + SinglePassRunAndCheck( + tc.code, tc.expected, /* skip_nop = */ false, tc.default_values); +} + +INSTANTIATE_TEST_CASE_P( + ValidCases, SetSpecConstantDefaultValueInBitPatternFormParamTest, + ::testing::ValuesIn(std::vector< + SetSpecConstantDefaultValueInBitPatternFormTestCase>{ + // 0. Empty. + {"", SpecIdToValueBitPatternMap{}, ""}, + // 1. Empty with non-empty values to set. + {"", SpecIdToValueBitPatternMap{{1, {100}}, {2, {200}}}, ""}, + // 2. Baisc bool type. + { + // code + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "%bool = OpTypeBool\n" + "%1 = OpSpecConstantTrue %bool\n" + "%2 = OpSpecConstantFalse %bool\n", + // default values + SpecIdToValueBitPatternMap{{100, {0x0}}, {101, {0x1}}}, + // expected + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "%bool = OpTypeBool\n" + "%1 = OpSpecConstantFalse %bool\n" + "%2 = OpSpecConstantTrue %bool\n", + }, + // 3. 32-bit int type. + { + // code + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "OpDecorate %3 SpecId 102\n" + "%int = OpTypeInt 32 1\n" + "%1 = OpSpecConstant %int 10\n" + "%2 = OpSpecConstant %int 11\n" + "%3 = OpSpecConstant %int 11\n", + // default values + SpecIdToValueBitPatternMap{ + {100, {2147483647}}, {101, {0xffffffff}}, {102, {0xffffffd6}}}, + // expected + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "OpDecorate %3 SpecId 102\n" + "%int = OpTypeInt 32 1\n" + "%1 = OpSpecConstant %int 2147483647\n" + "%2 = OpSpecConstant %int -1\n" + "%3 = OpSpecConstant %int -42\n", + }, + // 4. 64-bit uint type. + { + // code + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "%ulong = OpTypeInt 64 0\n" + "%1 = OpSpecConstant %ulong 10\n" + "%2 = OpSpecConstant %ulong 11\n", + // default values + SpecIdToValueBitPatternMap{{100, {0xFFFFFFFE, 0xFFFFFFFF}}, + {101, {0x100, 0x0}}}, + // expected + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "%ulong = OpTypeInt 64 0\n" + "%1 = OpSpecConstant %ulong 18446744073709551614\n" + "%2 = OpSpecConstant %ulong 256\n", + }, + // 5. 32-bit float type. + { + // code + "OpDecorate %1 SpecId 101\n" + "OpDecorate %2 SpecId 102\n" + "%float = OpTypeFloat 32\n" + "%1 = OpSpecConstant %float 200\n" + "%2 = OpSpecConstant %float 201\n", + // default values + SpecIdToValueBitPatternMap{{101, {0xffffffff}}, + {102, {0x40200000}}}, + // expected + "OpDecorate %1 SpecId 101\n" + "OpDecorate %2 SpecId 102\n" + "%float = OpTypeFloat 32\n" + "%1 = OpSpecConstant %float -0x1.fffffep+128\n" + "%2 = OpSpecConstant %float 2.5\n", + }, + // 6. 64-bit float type. + { + // code + "OpDecorate %1 SpecId 201\n" + "OpDecorate %2 SpecId 202\n" + "%double = OpTypeFloat 64\n" + "%1 = OpSpecConstant %double 3.14159265358979\n" + "%2 = OpSpecConstant %double 0.14285\n", + // default values + SpecIdToValueBitPatternMap{{201, {0xffffffff, 0x7fffffff}}, + {202, {0x00000000, 0xc0404000}}}, + // expected + "OpDecorate %1 SpecId 201\n" + "OpDecorate %2 SpecId 202\n" + "%double = OpTypeFloat 64\n" + "%1 = OpSpecConstant %double 0x1.fffffffffffffp+1024\n" + "%2 = OpSpecConstant %double -32.5\n", + }, + // 7. SpecId not found, expect no modification. + { + // code + "OpDecorate %1 SpecId 201\n" + "%double = OpTypeFloat 64\n" + "%1 = OpSpecConstant %double 3.14159265358979\n", + // default values + SpecIdToValueBitPatternMap{{8888, {0x0}}}, + // expected + "OpDecorate %1 SpecId 201\n" + "%double = OpTypeFloat 64\n" + "%1 = OpSpecConstant %double 3.14159265358979\n", + }, + // 8. Multiple types of spec constants. + { + // code + "OpDecorate %1 SpecId 201\n" + "OpDecorate %2 SpecId 202\n" + "OpDecorate %3 SpecId 203\n" + "%bool = OpTypeBool\n" + "%int = OpTypeInt 32 1\n" + "%double = OpTypeFloat 64\n" + "%1 = OpSpecConstant %double 3.14159265358979\n" + "%2 = OpSpecConstant %int 1024\n" + "%3 = OpSpecConstantTrue %bool\n", + // default values + SpecIdToValueBitPatternMap{ + {201, {0xffffffff, 0x7fffffff}}, + {202, {0x00000800}}, + {203, {0x0}}, + }, + // expected + "OpDecorate %1 SpecId 201\n" + "OpDecorate %2 SpecId 202\n" + "OpDecorate %3 SpecId 203\n" + "%bool = OpTypeBool\n" + "%int = OpTypeInt 32 1\n" + "%double = OpTypeFloat 64\n" + "%1 = OpSpecConstant %double 0x1.fffffffffffffp+1024\n" + "%2 = OpSpecConstant %int 2048\n" + "%3 = OpSpecConstantFalse %bool\n", + }, + // 9. Ignore other decorations. + { + // code + "OpDecorate %1 ArrayStride 4\n" + "%int = OpTypeInt 32 1\n" + "%1 = OpSpecConstant %int 100\n", + // default values + SpecIdToValueBitPatternMap{{4, {0x7fffffff}}}, + // expected + "OpDecorate %1 ArrayStride 4\n" + "%int = OpTypeInt 32 1\n" + "%1 = OpSpecConstant %int 100\n", + }, + // 10. Distinguish from other decorations. + { + // code + "OpDecorate %1 SpecId 100\n" + "OpDecorate %1 ArrayStride 4\n" + "%int = OpTypeInt 32 1\n" + "%1 = OpSpecConstant %int 100\n", + // default values + SpecIdToValueBitPatternMap{{4, {0x7fffffff}}, {100, {0xffffffff}}}, + // expected + "OpDecorate %1 SpecId 100\n" + "OpDecorate %1 ArrayStride 4\n" + "%int = OpTypeInt 32 1\n" + "%1 = OpSpecConstant %int -1\n", + }, + // 11. Decorate through decoration group. + { + // code + "OpDecorate %1 SpecId 100\n" + "%1 = OpDecorationGroup\n" + "OpGroupDecorate %1 %2\n" + "%int = OpTypeInt 32 1\n" + "%2 = OpSpecConstant %int 100\n", + // default values + SpecIdToValueBitPatternMap{{100, {0x7fffffff}}}, + // expected + "OpDecorate %1 SpecId 100\n" + "%1 = OpDecorationGroup\n" + "OpGroupDecorate %1 %2\n" + "%int = OpTypeInt 32 1\n" + "%2 = OpSpecConstant %int 2147483647\n", + }, + // 12. Ignore other decorations in decoration group. + { + // code + "OpDecorate %1 ArrayStride 4\n" + "%1 = OpDecorationGroup\n" + "OpGroupDecorate %1 %2\n" + "%int = OpTypeInt 32 1\n" + "%2 = OpSpecConstant %int 100\n", + // default values + SpecIdToValueBitPatternMap{{4, {0x7fffffff}}}, + // expected + "OpDecorate %1 ArrayStride 4\n" + "%1 = OpDecorationGroup\n" + "OpGroupDecorate %1 %2\n" + "%int = OpTypeInt 32 1\n" + "%2 = OpSpecConstant %int 100\n", + }, + // 13. Distinguish from other decorations in decoration group. + { + // code + "OpDecorate %1 SpecId 100\n" + "OpDecorate %1 ArrayStride 4\n" + "%1 = OpDecorationGroup\n" + "OpGroupDecorate %1 %2\n" + "%int = OpTypeInt 32 1\n" + "%2 = OpSpecConstant %int 100\n", + // default values + SpecIdToValueBitPatternMap{{100, {0x7fffffff}}, {4, {0x00000001}}}, + // expected + "OpDecorate %1 SpecId 100\n" + "OpDecorate %1 ArrayStride 4\n" + "%1 = OpDecorationGroup\n" + "OpGroupDecorate %1 %2\n" + "%int = OpTypeInt 32 1\n" + "%2 = OpSpecConstant %int 2147483647\n", + }, + // 14. Unchanged bool default value + { + // code + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "%bool = OpTypeBool\n" + "%1 = OpSpecConstantTrue %bool\n" + "%2 = OpSpecConstantFalse %bool\n", + // default values + SpecIdToValueBitPatternMap{{100, {0x1}}, {101, {0x0}}}, + // expected + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "%bool = OpTypeBool\n" + "%1 = OpSpecConstantTrue %bool\n" + "%2 = OpSpecConstantFalse %bool\n", + }, + // 15. Unchanged int default values + { + // code + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "%int = OpTypeInt 32 1\n" + "%ulong = OpTypeInt 64 0\n" + "%1 = OpSpecConstant %int 10\n" + "%2 = OpSpecConstant %ulong 11\n", + // default values + SpecIdToValueBitPatternMap{{100, {10}}, {101, {11, 0}}}, + // expected + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "%int = OpTypeInt 32 1\n" + "%ulong = OpTypeInt 64 0\n" + "%1 = OpSpecConstant %int 10\n" + "%2 = OpSpecConstant %ulong 11\n", + }, + // 16. Unchanged float default values + { + // code + "OpDecorate %1 SpecId 201\n" + "OpDecorate %2 SpecId 202\n" + "%float = OpTypeFloat 32\n" + "%double = OpTypeFloat 64\n" + "%1 = OpSpecConstant %float 3.25\n" + "%2 = OpSpecConstant %double 1.25\n", + // default values + SpecIdToValueBitPatternMap{{201, {0x40500000}}, + {202, {0x00000000, 0x3ff40000}}}, + // expected + "OpDecorate %1 SpecId 201\n" + "OpDecorate %2 SpecId 202\n" + "%float = OpTypeFloat 32\n" + "%double = OpTypeFloat 64\n" + "%1 = OpSpecConstant %float 3.25\n" + "%2 = OpSpecConstant %double 1.25\n", + }, + // 17. OpGroupDecorate may have multiple target ids defined by the same + // eligible spec constant + { + // code + "OpDecorate %1 SpecId 100\n" + "%1 = OpDecorationGroup\n" + "OpGroupDecorate %1 %2 %2 %2\n" + "%int = OpTypeInt 32 1\n" + "%2 = OpSpecConstant %int 100\n", + // default values + SpecIdToValueBitPatternMap{{100, {0xffffffff}}}, + // expected + "OpDecorate %1 SpecId 100\n" + "%1 = OpDecorationGroup\n" + "OpGroupDecorate %1 %2 %2 %2\n" + "%int = OpTypeInt 32 1\n" + "%2 = OpSpecConstant %int -1\n", + }, + // 18. For Boolean type spec constants,if any word in the bit pattern + // is not zero, it can be considered as a 'true', otherwise, it can be + // considered as a 'false'. + { + // code + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "OpDecorate %3 SpecId 102\n" + "%bool = OpTypeBool\n" + "%1 = OpSpecConstantTrue %bool\n" + "%2 = OpSpecConstantFalse %bool\n" + "%3 = OpSpecConstantFalse %bool\n", + // default values + SpecIdToValueBitPatternMap{ + {100, {0x0, 0x0, 0x0, 0x0}}, + {101, {0x10101010}}, + {102, {0x0, 0x0, 0x0, 0x2}}, + }, + // expected + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "OpDecorate %3 SpecId 102\n" + "%bool = OpTypeBool\n" + "%1 = OpSpecConstantFalse %bool\n" + "%2 = OpSpecConstantTrue %bool\n" + "%3 = OpSpecConstantTrue %bool\n", + }, + })); + +INSTANTIATE_TEST_CASE_P( + InvalidCases, SetSpecConstantDefaultValueInBitPatternFormParamTest, + ::testing::ValuesIn(std::vector< + SetSpecConstantDefaultValueInBitPatternFormTestCase>{ + // 0. Do not crash when decoration group is not used. + { + // code + "OpDecorate %1 SpecId 100\n" + "%1 = OpDecorationGroup\n" + "%int = OpTypeInt 32 1\n" + "%3 = OpSpecConstant %int 100\n", + // default values + SpecIdToValueBitPatternMap{{100, {0x7fffffff}}}, + // expected + "OpDecorate %1 SpecId 100\n" + "%1 = OpDecorationGroup\n" + "%int = OpTypeInt 32 1\n" + "%3 = OpSpecConstant %int 100\n", + }, + // 1. Do not crash when target does not exist. + { + // code + "OpDecorate %1 SpecId 100\n" + "%1 = OpDecorationGroup\n" + "%int = OpTypeInt 32 1\n", + // default values + SpecIdToValueBitPatternMap{{100, {0x7fffffff}}}, + // expected + "OpDecorate %1 SpecId 100\n" + "%1 = OpDecorationGroup\n" + "%int = OpTypeInt 32 1\n", + }, + // 2. Do nothing when SpecId decoration is not attached to a + // non-spec-constant instruction. + { + // code + "OpDecorate %1 SpecId 100\n" + "%1 = OpDecorationGroup\n" + "%int = OpTypeInt 32 1\n" + "%int_101 = OpConstant %int 101\n", + // default values + SpecIdToValueBitPatternMap{{100, {0x7fffffff}}}, + // expected + "OpDecorate %1 SpecId 100\n" + "%1 = OpDecorationGroup\n" + "%int = OpTypeInt 32 1\n" + "%int_101 = OpConstant %int 101\n", + }, + // 3. Do nothing when SpecId decoration is not attached to a + // OpSpecConstant{|True|False} instruction. + { + // code + "OpDecorate %1 SpecId 100\n" + "%int = OpTypeInt 32 1\n" + "%3 = OpSpecConstant %int 101\n" + "%1 = OpSpecConstantOp %int IAdd %3 %3\n", + // default values + SpecIdToValueBitPatternMap{{100, {0x7fffffff}}}, + // expected + "OpDecorate %1 SpecId 100\n" + "%int = OpTypeInt 32 1\n" + "%3 = OpSpecConstant %int 101\n" + "%1 = OpSpecConstantOp %int IAdd %3 %3\n", + }, + // 4. Do not crash and do nothing when SpecId decoration is applied to + // multiple spec constants. + { + // code + "OpDecorate %1 SpecId 100\n" + "%1 = OpDecorationGroup\n" + "OpGroupDecorate %1 %2 %3 %4\n" + "%int = OpTypeInt 32 1\n" + "%2 = OpSpecConstant %int 100\n" + "%3 = OpSpecConstant %int 200\n" + "%4 = OpSpecConstant %int 300\n", + // default values + SpecIdToValueBitPatternMap{{100, {0xffffffff}}}, + // expected + "OpDecorate %1 SpecId 100\n" + "%1 = OpDecorationGroup\n" + "OpGroupDecorate %1 %2 %3 %4\n" + "%int = OpTypeInt 32 1\n" + "%2 = OpSpecConstant %int 100\n" + "%3 = OpSpecConstant %int 200\n" + "%4 = OpSpecConstant %int 300\n", + }, + // 5. Do not crash and do nothing when SpecId decoration is attached to + // non-spec-constants (invalid case). + { + // code + "OpDecorate %1 SpecId 100\n" + "%1 = OpDecorationGroup\n" + "%2 = OpDecorationGroup\n" + "OpGroupDecorate %1 %2\n" + "%int = OpTypeInt 32 1\n" + "%int_100 = OpConstant %int 100\n", + // default values + SpecIdToValueBitPatternMap{{100, {0xffffffff}}}, + // expected + "OpDecorate %1 SpecId 100\n" + "%1 = OpDecorationGroup\n" + "%2 = OpDecorationGroup\n" + "OpGroupDecorate %1 %2\n" + "%int = OpTypeInt 32 1\n" + "%int_100 = OpConstant %int 100\n", + }, + // 6. Incompatible input bit pattern with the type. Nothing should be + // done in such a case. + { + // code + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "OpDecorate %3 SpecId 102\n" + "%int = OpTypeInt 32 1\n" + "%ulong = OpTypeInt 64 0\n" + "%double = OpTypeFloat 64\n" + "%1 = OpSpecConstant %int 100\n" + "%2 = OpSpecConstant %ulong 200\n" + "%3 = OpSpecConstant %double 3.141592653\n", + // default values + SpecIdToValueBitPatternMap{ + {100, {10, 0}}, {101, {11}}, {102, {0xffffffff}}}, + // expected + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "OpDecorate %3 SpecId 102\n" + "%int = OpTypeInt 32 1\n" + "%ulong = OpTypeInt 64 0\n" + "%double = OpTypeFloat 64\n" + "%1 = OpSpecConstant %int 100\n" + "%2 = OpSpecConstant %ulong 200\n" + "%3 = OpSpecConstant %double 3.141592653\n", + }, + })); + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/simplification_test.cpp b/3rdparty/spirv-tools/test/opt/simplification_test.cpp new file mode 100644 index 000000000..6e1ff23fd --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/simplification_test.cpp @@ -0,0 +1,205 @@ +// Copyright (c) 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "opt/simplification_pass.h" + +#include "assembly_builder.h" +#include "gmock/gmock.h" +#include "pass_fixture.h" + +namespace { + +using namespace spvtools; + +using SimplificationTest = PassTest<::testing::Test>; + +#ifdef SPIRV_EFFCEE +TEST_F(SimplificationTest, StraightLineTest) { + // Testing that folding rules are combined in simple straight line code. + const std::string text = R"(OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %i %o + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 430 + OpSourceExtension "GL_GOOGLE_cpp_style_line_directive" + OpSourceExtension "GL_GOOGLE_include_directive" + OpName %main "main" + OpName %i "i" + OpName %o "o" + OpDecorate %i Flat + OpDecorate %i Location 0 + OpDecorate %o Location 0 + %void = OpTypeVoid + %8 = OpTypeFunction %void + %int = OpTypeInt 32 1 + %v4int = OpTypeVector %int 4 + %int_0 = OpConstant %int 0 + %13 = OpConstantComposite %v4int %int_0 %int_0 %int_0 %int_0 + %int_1 = OpConstant %int 1 +%_ptr_Input_v4int = OpTypePointer Input %v4int + %i = OpVariable %_ptr_Input_v4int Input +%_ptr_Output_int = OpTypePointer Output %int + %o = OpVariable %_ptr_Output_int Output + %main = OpFunction %void None %8 + %21 = OpLabel + %31 = OpCompositeInsert %v4int %int_1 %13 0 +; CHECK: [[load:%[a-zA-Z_\d]+]] = OpLoad + %23 = OpLoad %v4int %i + %33 = OpCompositeInsert %v4int %int_0 %23 0 + %35 = OpCompositeExtract %int %31 0 +; CHECK: [[extract:%[a-zA-Z_\d]+]] = OpCompositeExtract %int [[load]] 1 + %37 = OpCompositeExtract %int %33 1 +; CHECK: [[add:%[a-zA-Z_\d]+]] = OpIAdd %int %int_1 [[extract]] + %29 = OpIAdd %int %35 %37 + OpStore %o %29 + OpReturn + OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, false); +} + +TEST_F(SimplificationTest, AcrossBasicBlocks) { + // Testing that folding rules are combined across basic blocks. + const std::string text = R"(OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %i %o + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 430 + OpSourceExtension "GL_GOOGLE_cpp_style_line_directive" + OpSourceExtension "GL_GOOGLE_include_directive" + OpName %main "main" + OpName %i "i" + OpName %o "o" + OpDecorate %i Flat + OpDecorate %i Location 0 + OpDecorate %o Location 0 + %void = OpTypeVoid + %8 = OpTypeFunction %void + %int = OpTypeInt 32 1 + %v4int = OpTypeVector %int 4 + %int_0 = OpConstant %int 0 +%_ptr_Input_v4int = OpTypePointer Input %v4int + %i = OpVariable %_ptr_Input_v4int Input + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 +%_ptr_Input_int = OpTypePointer Input %int + %int_10 = OpConstant %int 10 + %bool = OpTypeBool + %int_1 = OpConstant %int 1 +%_ptr_Output_int = OpTypePointer Output %int + %o = OpVariable %_ptr_Output_int Output + %main = OpFunction %void None %8 + %24 = OpLabel +; CHECK: [[load:%[a-zA-Z_\d]+]] = OpLoad %v4int %i + %25 = OpLoad %v4int %i + %41 = OpCompositeInsert %v4int %int_0 %25 0 + %27 = OpAccessChain %_ptr_Input_int %i %uint_0 + %28 = OpLoad %int %27 + %29 = OpSGreaterThan %bool %28 %int_10 + OpSelectionMerge %30 None + OpBranchConditional %29 %31 %32 + %31 = OpLabel + %43 = OpCopyObject %v4int %25 + OpBranch %30 + %32 = OpLabel + %45 = OpCopyObject %v4int %25 + OpBranch %30 + %30 = OpLabel + %50 = OpPhi %v4int %43 %31 %45 %32 +; CHECK: [[extract1:%[a-zA-Z_\d]+]] = OpCompositeExtract %int [[load]] 0 + %47 = OpCompositeExtract %int %50 0 +; CHECK: [[extract2:%[a-zA-Z_\d]+]] = OpCompositeExtract %int [[load]] 1 + %49 = OpCompositeExtract %int %41 1 +; CHECK: [[add:%[a-zA-Z_\d]+]] = OpIAdd %int [[extract1]] [[extract2]] + %39 = OpIAdd %int %47 %49 + OpStore %o %39 + OpReturn + OpFunctionEnd + +)"; + + SinglePassRunAndMatch(text, false); +} + +TEST_F(SimplificationTest, ThroughLoops) { + // Testing that folding rules are applied multiple times to instructions + // to be able to propagate across loop iterations. + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %o %i + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 430 + OpSourceExtension "GL_GOOGLE_cpp_style_line_directive" + OpSourceExtension "GL_GOOGLE_include_directive" + OpName %main "main" + OpName %o "o" + OpName %i "i" + OpDecorate %o Location 0 + OpDecorate %i Flat + OpDecorate %i Location 0 + %void = OpTypeVoid + %8 = OpTypeFunction %void + %int = OpTypeInt 32 1 + %v4int = OpTypeVector %int 4 + %int_0 = OpConstant %int 0 +; CHECK: [[constant:%[a-zA-Z_\d]+]] = OpConstantComposite %v4int %int_0 %int_0 %int_0 %int_0 + %13 = OpConstantComposite %v4int %int_0 %int_0 %int_0 %int_0 + %bool = OpTypeBool +%_ptr_Output_int = OpTypePointer Output %int + %o = OpVariable %_ptr_Output_int Output +%_ptr_Input_v4int = OpTypePointer Input %v4int + %i = OpVariable %_ptr_Input_v4int Input + %68 = OpUndef %v4int + %main = OpFunction %void None %8 + %23 = OpLabel +; CHECK: [[load:%[a-zA-Z_\d]+]] = OpLoad %v4int %i + %load = OpLoad %v4int %i + OpBranch %24 + %24 = OpLabel + %67 = OpPhi %v4int %load %23 %64 %26 +; CHECK: OpLoopMerge [[merge_lab:%[a-zA-Z_\d]+]] + OpLoopMerge %25 %26 None + OpBranch %27 + %27 = OpLabel + %48 = OpCompositeExtract %int %67 0 + %30 = OpIEqual %bool %48 %int_0 + OpBranchConditional %30 %31 %25 + %31 = OpLabel + %50 = OpCompositeExtract %int %67 0 + %54 = OpCompositeExtract %int %67 1 + %58 = OpCompositeExtract %int %67 2 + %62 = OpCompositeExtract %int %67 3 + %64 = OpCompositeConstruct %v4int %50 %54 %58 %62 + OpBranch %26 + %26 = OpLabel + OpBranch %24 + %25 = OpLabel +; CHECK: [[merge_lab]] = OpLabel +; CHECK: [[extract:%[a-zA-Z_\d]+]] = OpCompositeExtract %int [[load]] 0 + %66 = OpCompositeExtract %int %67 0 +; CHECK-NEXT: OpStore %o [[extract]] + OpStore %o %66 + OpReturn + OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, false); +} +#endif +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/strength_reduction_test.cpp b/3rdparty/spirv-tools/test/opt/strength_reduction_test.cpp new file mode 100644 index 000000000..3ae785a1d --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/strength_reduction_test.cpp @@ -0,0 +1,436 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "assembly_builder.h" +#include "gmock/gmock.h" +#include "pass_fixture.h" +#include "pass_utils.h" + +#include +#include +#include +#include +#include + +namespace { + +using namespace spvtools; + +using ::testing::HasSubstr; +using ::testing::MatchesRegex; + +using StrengthReductionBasicTest = PassTest<::testing::Test>; + +// Test to make sure we replace 5*8. +TEST_F(StrengthReductionBasicTest, BasicReplaceMulBy8) { + const std::vector text = { + // clang-format off + "OpCapability Shader", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Vertex %main \"main\"", + "OpName %main \"main\"", + "%void = OpTypeVoid", + "%4 = OpTypeFunction %void", + "%uint = OpTypeInt 32 0", + "%uint_5 = OpConstant %uint 5", + "%uint_8 = OpConstant %uint 8", + "%main = OpFunction %void None %4", + "%8 = OpLabel", + "%9 = OpIMul %uint %uint_5 %uint_8", + "OpReturn", + "OpFunctionEnd" + // clang-format on + }; + + auto result = SinglePassRunAndDisassemble( + JoinAllInsts(text), /* skip_nop = */ true, /* do_validation = */ false); + + EXPECT_EQ(opt::Pass::Status::SuccessWithChange, std::get<1>(result)); + const std::string& output = std::get<0>(result); + EXPECT_THAT(output, Not(HasSubstr("OpIMul"))); + EXPECT_THAT(output, HasSubstr("OpShiftLeftLogical %uint %uint_5 %uint_3")); +} + +// TODO(dneto): Add Effcee as required dependency, and make this unconditional. +#ifdef SPIRV_EFFCEE +// Test to make sure we replace 16*5 +// Also demonstrate use of Effcee matching. +TEST_F(StrengthReductionBasicTest, BasicReplaceMulBy16) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" + OpName %main "main" + %void = OpTypeVoid + %4 = OpTypeFunction %void +; We know disassembly will produce %uint here, but +; CHECK: %uint = OpTypeInt 32 0 +; CHECK-DAG: [[five:%[a-zA-Z_\d]+]] = OpConstant %uint 5 + +; We have RE2 regular expressions, so \w matches [_a-zA-Z0-9]. +; This shows the preferred pattern for matching SPIR-V identifiers. +; (We could have cheated in this case since we know the disassembler will +; generate the 'nice' name of "%uint_4". +; CHECK-DAG: [[four:%\w+]] = OpConstant %uint 4 + %uint = OpTypeInt 32 0 + %uint_5 = OpConstant %uint 5 + %uint_16 = OpConstant %uint 16 + %main = OpFunction %void None %4 +; CHECK: OpLabel + %8 = OpLabel +; CHECK-NEXT: OpShiftLeftLogical %uint [[five]] [[four]] +; The multiplication disappears. +; CHECK-NOT: OpIMul + %9 = OpIMul %uint %uint_16 %uint_5 + OpReturn +; CHECK: OpFunctionEnd + OpFunctionEnd)"; + + SinglePassRunAndMatch(text, false); +} +#endif + +// Test to make sure we replace a multiple of 32 and 4. +TEST_F(StrengthReductionBasicTest, BasicTwoPowersOf2) { + // In this case, we have two powers of 2. Need to make sure we replace only + // one of them for the bit shift. + // clang-format off + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" + OpName %main "main" + %void = OpTypeVoid + %4 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%int_32 = OpConstant %int 32 + %int_4 = OpConstant %int 4 + %main = OpFunction %void None %4 + %8 = OpLabel + %9 = OpIMul %int %int_32 %int_4 + OpReturn + OpFunctionEnd +)"; + // clang-format on + auto result = SinglePassRunAndDisassemble( + text, /* skip_nop = */ true, /* do_validation = */ false); + + EXPECT_EQ(opt::Pass::Status::SuccessWithChange, std::get<1>(result)); + const std::string& output = std::get<0>(result); + EXPECT_THAT(output, Not(HasSubstr("OpIMul"))); + EXPECT_THAT(output, HasSubstr("OpShiftLeftLogical %int %int_4 %uint_5")); +} + +// Test to make sure we don't replace 0*5. +TEST_F(StrengthReductionBasicTest, BasicDontReplace0) { + const std::vector text = { + // clang-format off + "OpCapability Shader", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Vertex %main \"main\"", + "OpName %main \"main\"", + "%void = OpTypeVoid", + "%4 = OpTypeFunction %void", + "%int = OpTypeInt 32 1", + "%int_0 = OpConstant %int 0", + "%int_5 = OpConstant %int 5", + "%main = OpFunction %void None %4", + "%8 = OpLabel", + "%9 = OpIMul %int %int_0 %int_5", + "OpReturn", + "OpFunctionEnd" + // clang-format on + }; + + auto result = SinglePassRunAndDisassemble( + JoinAllInsts(text), /* skip_nop = */ true, /* do_validation = */ false); + + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +// Test to make sure we do not replace a multiple of 5 and 7. +TEST_F(StrengthReductionBasicTest, BasicNoChange) { + const std::vector text = { + // clang-format off + "OpCapability Shader", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Vertex %2 \"main\"", + "OpName %2 \"main\"", + "%3 = OpTypeVoid", + "%4 = OpTypeFunction %3", + "%5 = OpTypeInt 32 1", + "%6 = OpTypeInt 32 0", + "%7 = OpConstant %5 5", + "%8 = OpConstant %5 7", + "%2 = OpFunction %3 None %4", + "%9 = OpLabel", + "%10 = OpIMul %5 %7 %8", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + + auto result = SinglePassRunAndDisassemble( + JoinAllInsts(text), /* skip_nop = */ true, /* do_validation = */ false); + + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +// Test to make sure constants and types are reused and not duplicated. +TEST_F(StrengthReductionBasicTest, NoDuplicateConstantsAndTypes) { + const std::vector text = { + // clang-format off + "OpCapability Shader", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Vertex %main \"main\"", + "OpName %main \"main\"", + "%void = OpTypeVoid", + "%4 = OpTypeFunction %void", + "%uint = OpTypeInt 32 0", + "%uint_8 = OpConstant %uint 8", + "%uint_3 = OpConstant %uint 3", + "%main = OpFunction %void None %4", + "%8 = OpLabel", + "%9 = OpIMul %uint %uint_8 %uint_3", + "OpReturn", + "OpFunctionEnd", + // clang-format on + }; + + auto result = SinglePassRunAndDisassemble( + JoinAllInsts(text), /* skip_nop = */ true, /* do_validation = */ false); + + EXPECT_EQ(opt::Pass::Status::SuccessWithChange, std::get<1>(result)); + const std::string& output = std::get<0>(result); + EXPECT_THAT(output, + Not(MatchesRegex(".*OpConstant %uint 3.*OpConstant %uint 3.*"))); + EXPECT_THAT(output, Not(MatchesRegex(".*OpTypeInt 32 0.*OpTypeInt 32 0.*"))); +} + +// Test to make sure we generate the constants only once +TEST_F(StrengthReductionBasicTest, BasicCreateOneConst) { + const std::vector text = { + // clang-format off + "OpCapability Shader", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Vertex %main \"main\"", + "OpName %main \"main\"", + "%void = OpTypeVoid", + "%4 = OpTypeFunction %void", + "%uint = OpTypeInt 32 0", + "%uint_5 = OpConstant %uint 5", + "%uint_9 = OpConstant %uint 9", + "%uint_128 = OpConstant %uint 128", + "%main = OpFunction %void None %4", + "%8 = OpLabel", + "%9 = OpIMul %uint %uint_5 %uint_128", + "%10 = OpIMul %uint %uint_9 %uint_128", + "OpReturn", + "OpFunctionEnd" + // clang-format on + }; + + auto result = SinglePassRunAndDisassemble( + JoinAllInsts(text), /* skip_nop = */ true, /* do_validation = */ false); + + EXPECT_EQ(opt::Pass::Status::SuccessWithChange, std::get<1>(result)); + const std::string& output = std::get<0>(result); + EXPECT_THAT(output, Not(HasSubstr("OpIMul"))); + EXPECT_THAT(output, HasSubstr("OpShiftLeftLogical %uint %uint_5 %uint_7")); + EXPECT_THAT(output, HasSubstr("OpShiftLeftLogical %uint %uint_9 %uint_7")); +} + +// Test to make sure we generate the instructions in the correct position and +// that the uses get replaced as well. Here we check that the use in the return +// is replaced, we also check that we can replace two OpIMuls when one feeds the +// other. +TEST_F(StrengthReductionBasicTest, BasicCheckPositionAndReplacement) { + // This is just the preamble to set up the test. + const std::vector common_text = { + // clang-format off + "OpCapability Shader", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Fragment %main \"main\" %gl_FragColor", + "OpExecutionMode %main OriginUpperLeft", + "OpName %main \"main\"", + "OpName %foo_i1_ \"foo(i1;\"", + "OpName %n \"n\"", + "OpName %gl_FragColor \"gl_FragColor\"", + "OpName %param \"param\"", + "OpDecorate %gl_FragColor Location 0", + "%void = OpTypeVoid", + "%3 = OpTypeFunction %void", + "%int = OpTypeInt 32 1", +"%_ptr_Function_int = OpTypePointer Function %int", + "%8 = OpTypeFunction %int %_ptr_Function_int", + "%int_256 = OpConstant %int 256", + "%int_2 = OpConstant %int 2", + "%float = OpTypeFloat 32", + "%v4float = OpTypeVector %float 4", +"%_ptr_Output_v4float = OpTypePointer Output %v4float", +"%gl_FragColor = OpVariable %_ptr_Output_v4float Output", + "%float_1 = OpConstant %float 1", + "%int_10 = OpConstant %int 10", + "%float_0_375 = OpConstant %float 0.375", + "%float_0_75 = OpConstant %float 0.75", + "%uint = OpTypeInt 32 0", + "%uint_8 = OpConstant %uint 8", + "%uint_1 = OpConstant %uint 1", + "%main = OpFunction %void None %3", + "%5 = OpLabel", + "%param = OpVariable %_ptr_Function_int Function", + "OpStore %param %int_10", + "%26 = OpFunctionCall %int %foo_i1_ %param", + "%27 = OpConvertSToF %float %26", + "%28 = OpFDiv %float %float_1 %27", + "%31 = OpCompositeConstruct %v4float %28 %float_0_375 %float_0_75 %float_1", + "OpStore %gl_FragColor %31", + "OpReturn", + "OpFunctionEnd" + // clang-format on + }; + + // This is the real test. The two OpIMul should be replaced. The expected + // output is in |foo_after|. + const std::vector foo_before = { + // clang-format off + "%foo_i1_ = OpFunction %int None %8", + "%n = OpFunctionParameter %_ptr_Function_int", + "%11 = OpLabel", + "%12 = OpLoad %int %n", + "%14 = OpIMul %int %12 %int_256", + "%16 = OpIMul %int %14 %int_2", + "OpReturnValue %16", + "OpFunctionEnd", + + // clang-format on + }; + + const std::vector foo_after = { + // clang-format off + "%foo_i1_ = OpFunction %int None %8", + "%n = OpFunctionParameter %_ptr_Function_int", + "%11 = OpLabel", + "%12 = OpLoad %int %n", + "%33 = OpShiftLeftLogical %int %12 %uint_8", + "%34 = OpShiftLeftLogical %int %33 %uint_1", + "OpReturnValue %34", + "OpFunctionEnd", + // clang-format on + }; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck( + JoinAllInsts(Concat(common_text, foo_before)), + JoinAllInsts(Concat(common_text, foo_after)), + /* skip_nop = */ true, /* do_validate = */ true); +} + +// Test that, when the result of an OpIMul instruction has more than 1 use, and +// the instruction is replaced, all of the uses of the results are replace with +// the new result. +TEST_F(StrengthReductionBasicTest, BasicTestMultipleReplacements) { + // This is just the preamble to set up the test. + const std::vector common_text = { + // clang-format off + "OpCapability Shader", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Fragment %main \"main\" %gl_FragColor", + "OpExecutionMode %main OriginUpperLeft", + "OpName %main \"main\"", + "OpName %foo_i1_ \"foo(i1;\"", + "OpName %n \"n\"", + "OpName %gl_FragColor \"gl_FragColor\"", + "OpName %param \"param\"", + "OpDecorate %gl_FragColor Location 0", + "%void = OpTypeVoid", + "%3 = OpTypeFunction %void", + "%int = OpTypeInt 32 1", +"%_ptr_Function_int = OpTypePointer Function %int", + "%8 = OpTypeFunction %int %_ptr_Function_int", + "%int_256 = OpConstant %int 256", + "%int_2 = OpConstant %int 2", + "%float = OpTypeFloat 32", + "%v4float = OpTypeVector %float 4", +"%_ptr_Output_v4float = OpTypePointer Output %v4float", +"%gl_FragColor = OpVariable %_ptr_Output_v4float Output", + "%float_1 = OpConstant %float 1", + "%int_10 = OpConstant %int 10", + "%float_0_375 = OpConstant %float 0.375", + "%float_0_75 = OpConstant %float 0.75", + "%uint = OpTypeInt 32 0", + "%uint_8 = OpConstant %uint 8", + "%uint_1 = OpConstant %uint 1", + "%main = OpFunction %void None %3", + "%5 = OpLabel", + "%param = OpVariable %_ptr_Function_int Function", + "OpStore %param %int_10", + "%26 = OpFunctionCall %int %foo_i1_ %param", + "%27 = OpConvertSToF %float %26", + "%28 = OpFDiv %float %float_1 %27", + "%31 = OpCompositeConstruct %v4float %28 %float_0_375 %float_0_75 %float_1", + "OpStore %gl_FragColor %31", + "OpReturn", + "OpFunctionEnd" + // clang-format on + }; + + // This is the real test. The two OpIMul instructions should be replaced. In + // particular, we want to be sure that both uses of %16 are changed to use the + // new result. + const std::vector foo_before = { + // clang-format off + "%foo_i1_ = OpFunction %int None %8", + "%n = OpFunctionParameter %_ptr_Function_int", + "%11 = OpLabel", + "%12 = OpLoad %int %n", + "%14 = OpIMul %int %12 %int_256", + "%16 = OpIMul %int %14 %int_2", + "%17 = OpIAdd %int %14 %16", + "OpReturnValue %17", + "OpFunctionEnd", + + // clang-format on + }; + + const std::vector foo_after = { + // clang-format off + "%foo_i1_ = OpFunction %int None %8", + "%n = OpFunctionParameter %_ptr_Function_int", + "%11 = OpLabel", + "%12 = OpLoad %int %n", + "%34 = OpShiftLeftLogical %int %12 %uint_8", + "%35 = OpShiftLeftLogical %int %34 %uint_1", + "%17 = OpIAdd %int %34 %35", + "OpReturnValue %17", + "OpFunctionEnd", + // clang-format on + }; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck( + JoinAllInsts(Concat(common_text, foo_before)), + JoinAllInsts(Concat(common_text, foo_after)), + /* skip_nop = */ true, /* do_validate = */ true); +} +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/strip_debug_info_test.cpp b/3rdparty/spirv-tools/test/opt/strip_debug_info_test.cpp new file mode 100644 index 000000000..8cae51e9b --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/strip_debug_info_test.cpp @@ -0,0 +1,103 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; + +using StripLineDebugInfoTest = PassTest<::testing::Test>; + +TEST_F(StripLineDebugInfoTest, LineNoLine) { + std::vector text = { + // clang-format off + "OpCapability Shader", + "%1 = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint Vertex %2 \"main\"", + "%3 = OpString \"minimal.vert\"", + "OpModuleProcessed \"42\"", + "OpModuleProcessed \"43\"", + "OpModuleProcessed \"44\"", + "OpNoLine", + "OpLine %3 10 10", + "%void = OpTypeVoid", + "OpLine %3 100 100", + "%5 = OpTypeFunction %void", + "%2 = OpFunction %void None %5", + "OpLine %3 1 1", + "OpNoLine", + "OpLine %3 2 2", + "OpLine %3 3 3", + "%6 = OpLabel", + "OpLine %3 4 4", + "OpNoLine", + "OpReturn", + "OpLine %3 4 4", + "OpNoLine", + "OpFunctionEnd", + // clang-format on + }; + SinglePassRunAndCheck(JoinAllInsts(text), + JoinNonDebugInsts(text), + /* skip_nop = */ false); + + // Let's add more debug instruction before the "OpString" instruction. + const std::vector more_text = { + "OpSourceContinued \"I'm a happy shader! Yay! ;)\"", + "OpSourceContinued \"wahahaha\"", + "OpSource ESSL 310", + "OpSource ESSL 310", + "OpSourceContinued \"wahahaha\"", + "OpSourceContinued \"wahahaha\"", + "OpSourceExtension \"save-the-world-extension\"", + "OpName %2 \"main\"", + }; + text.insert(text.begin() + 4, more_text.cbegin(), more_text.cend()); + SinglePassRunAndCheck(JoinAllInsts(text), + JoinNonDebugInsts(text), + /* skip_nop = */ false); +} + +using StripDebugInfoTest = PassTest<::testing::TestWithParam>; + +TEST_P(StripDebugInfoTest, Kind) { + std::vector text = { + "OpCapability Shader", + "OpMemoryModel Logical GLSL450", + GetParam(), + }; + SinglePassRunAndCheck(JoinAllInsts(text), + JoinNonDebugInsts(text), + /* skip_nop = */ false); +} + +// Test each possible non-line debug instruction. +// clang-format off +INSTANTIATE_TEST_CASE_P( + SingleKindDebugInst, StripDebugInfoTest, + ::testing::ValuesIn(std::vector({ + "OpSourceContinued \"I'm a happy shader! Yay! ;)\"", + "OpSource ESSL 310", + "OpSourceExtension \"save-the-world-extension\"", + "OpName %main \"main\"", + "OpMemberName %struct 0 \"field\"", + "%1 = OpString \"name.vert\"", + "OpModuleProcessed \"42\"", + }))); +// clang-format on + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/strip_reflect_info_test.cpp b/3rdparty/spirv-tools/test/opt/strip_reflect_info_test.cpp new file mode 100644 index 000000000..3cf2cdce7 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/strip_reflect_info_test.cpp @@ -0,0 +1,65 @@ +// Copyright (c) 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { + +using namespace spvtools; + +using StripLineReflectInfoTest = PassTest<::testing::Test>; + +TEST_F(StripLineReflectInfoTest, StripHlslSemantic) { + // This is a non-sensical example, but exercises the instructions. + std::string before = R"(OpCapability Shader +OpCapability Linkage +OpExtension "SPV_GOOGLE_decorate_string" +OpExtension "SPV_GOOGLE_hlsl_functionality1" +OpMemoryModel Logical Simple +OpDecorateStringGOOGLE %float HlslSemanticGOOGLE "foobar" +OpDecorateStringGOOGLE %void HlslSemanticGOOGLE "my goodness" +%void = OpTypeVoid +%float = OpTypeFloat 32 +)"; + std::string after = R"(OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical Simple +%void = OpTypeVoid +%float = OpTypeFloat 32 +)"; + + SinglePassRunAndCheck(before, after, false); +} + +TEST_F(StripLineReflectInfoTest, StripHlslCounterBuffer) { + std::string before = R"(OpCapability Shader +OpCapability Linkage +OpExtension "SPV_GOOGLE_hlsl_functionality1" +OpMemoryModel Logical Simple +OpDecorateId %void HlslCounterBufferGOOGLE %float +%void = OpTypeVoid +%float = OpTypeFloat 32 +)"; + std::string after = R"(OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical Simple +%void = OpTypeVoid +%float = OpTypeFloat 32 +)"; + + SinglePassRunAndCheck(before, after, false); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/type_manager_test.cpp b/3rdparty/spirv-tools/test/opt/type_manager_test.cpp new file mode 100644 index 000000000..8832b75f1 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/type_manager_test.cpp @@ -0,0 +1,883 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#ifdef SPIRV_EFFCEE +#include "effcee/effcee.h" +#endif + +#include "opt/build_module.h" +#include "opt/instruction.h" +#include "opt/type_manager.h" +#include "spirv-tools/libspirv.hpp" + +namespace { + +using namespace spvtools; +using namespace spvtools::opt::analysis; + +#ifdef SPIRV_EFFCEE + +bool Validate(const std::vector& bin) { + spv_target_env target_env = SPV_ENV_UNIVERSAL_1_2; + spv_context spvContext = spvContextCreate(target_env); + spv_diagnostic diagnostic = nullptr; + spv_const_binary_t binary = {bin.data(), bin.size()}; + spv_result_t error = spvValidate(spvContext, &binary, &diagnostic); + if (error != 0) spvDiagnosticPrint(diagnostic); + spvDiagnosticDestroy(diagnostic); + spvContextDestroy(spvContext); + return error == 0; +} + +void Match(const std::string& original, ir::IRContext* context, + bool do_validation = true) { + std::vector bin; + context->module()->ToBinary(&bin, true); + if (do_validation) { + EXPECT_TRUE(Validate(bin)); + } + std::string assembly; + SpirvTools tools(SPV_ENV_UNIVERSAL_1_2); + EXPECT_TRUE( + tools.Disassemble(bin, &assembly, SpirvTools::kDefaultDisassembleOption)) + << "Disassembling failed for shader:\n" + << assembly << std::endl; + auto match_result = effcee::Match(assembly, original); + EXPECT_EQ(effcee::Result::Status::Ok, match_result.status()) + << match_result.message() << "\nChecking result:\n" + << assembly; +} + +#endif + +std::vector> GenerateAllTypes() { + // Types in this test case are only equal to themselves, nothing else. + std::vector> types; + + // Void, Bool + types.emplace_back(new Void()); + auto* voidt = types.back().get(); + types.emplace_back(new Bool()); + auto* boolt = types.back().get(); + + // Integer + types.emplace_back(new Integer(32, true)); + auto* s32 = types.back().get(); + types.emplace_back(new Integer(32, false)); + types.emplace_back(new Integer(64, true)); + types.emplace_back(new Integer(64, false)); + auto* u64 = types.back().get(); + + // Float + types.emplace_back(new Float(32)); + auto* f32 = types.back().get(); + types.emplace_back(new Float(64)); + + // Vector + types.emplace_back(new Vector(s32, 2)); + types.emplace_back(new Vector(s32, 3)); + auto* v3s32 = types.back().get(); + types.emplace_back(new Vector(u64, 4)); + types.emplace_back(new Vector(f32, 3)); + auto* v3f32 = types.back().get(); + + // Matrix + types.emplace_back(new Matrix(v3s32, 3)); + types.emplace_back(new Matrix(v3s32, 4)); + types.emplace_back(new Matrix(v3f32, 4)); + + // Images + types.emplace_back(new Image(s32, SpvDim2D, 0, 0, 0, 0, SpvImageFormatRg8, + SpvAccessQualifierReadOnly)); + auto* image1 = types.back().get(); + types.emplace_back(new Image(s32, SpvDim2D, 0, 1, 0, 0, SpvImageFormatRg8, + SpvAccessQualifierReadOnly)); + types.emplace_back(new Image(s32, SpvDim3D, 0, 1, 0, 0, SpvImageFormatRg8, + SpvAccessQualifierReadOnly)); + types.emplace_back(new Image(voidt, SpvDim3D, 0, 1, 0, 1, SpvImageFormatRg8, + SpvAccessQualifierReadWrite)); + auto* image2 = types.back().get(); + + // Sampler + types.emplace_back(new Sampler()); + + // Sampled Image + types.emplace_back(new SampledImage(image1)); + types.emplace_back(new SampledImage(image2)); + + // Array + types.emplace_back(new Array(f32, 100)); + types.emplace_back(new Array(f32, 42)); + auto* a42f32 = types.back().get(); + types.emplace_back(new Array(u64, 24)); + + // RuntimeArray + types.emplace_back(new RuntimeArray(v3f32)); + types.emplace_back(new RuntimeArray(v3s32)); + auto* rav3s32 = types.back().get(); + + // Struct + types.emplace_back(new Struct(std::vector{s32})); + types.emplace_back(new Struct(std::vector{s32, f32})); + auto* sts32f32 = types.back().get(); + types.emplace_back(new Struct(std::vector{u64, a42f32, rav3s32})); + + // Opaque + types.emplace_back(new Opaque("")); + types.emplace_back(new Opaque("hello")); + types.emplace_back(new Opaque("world")); + + // Pointer + types.emplace_back(new Pointer(f32, SpvStorageClassInput)); + types.emplace_back(new Pointer(sts32f32, SpvStorageClassFunction)); + types.emplace_back(new Pointer(a42f32, SpvStorageClassFunction)); + + // Function + types.emplace_back(new Function(voidt, {})); + types.emplace_back(new Function(voidt, {boolt})); + types.emplace_back(new Function(voidt, {boolt, s32})); + types.emplace_back(new Function(s32, {boolt, s32})); + + // Event, Device Event, Reserve Id, Queue, + types.emplace_back(new Event()); + types.emplace_back(new DeviceEvent()); + types.emplace_back(new ReserveId()); + types.emplace_back(new Queue()); + + // Pipe, Forward Pointer, PipeStorage, NamedBarrier + types.emplace_back(new Pipe(SpvAccessQualifierReadWrite)); + types.emplace_back(new Pipe(SpvAccessQualifierReadOnly)); + types.emplace_back(new ForwardPointer(1, SpvStorageClassInput)); + types.emplace_back(new ForwardPointer(2, SpvStorageClassInput)); + types.emplace_back(new ForwardPointer(2, SpvStorageClassUniform)); + types.emplace_back(new PipeStorage()); + types.emplace_back(new NamedBarrier()); + + return types; +} + +TEST(TypeManager, TypeStrings) { + const std::string text = R"( + OpTypeForwardPointer !20 !2 ; id for %p is 20, Uniform is 2 + OpTypeForwardPointer !10000 !1 + %void = OpTypeVoid + %bool = OpTypeBool + %u32 = OpTypeInt 32 0 + %id4 = OpConstant %u32 4 + %s32 = OpTypeInt 32 1 + %f64 = OpTypeFloat 64 + %v3u32 = OpTypeVector %u32 3 + %m3x3 = OpTypeMatrix %v3u32 3 + %img1 = OpTypeImage %s32 Cube 0 1 1 0 R32f ReadWrite + %img2 = OpTypeImage %s32 Cube 0 1 1 0 R32f + %sampler = OpTypeSampler + %si1 = OpTypeSampledImage %img1 + %si2 = OpTypeSampledImage %img2 + %a5u32 = OpTypeArray %u32 %id4 + %af64 = OpTypeRuntimeArray %f64 + %st1 = OpTypeStruct %u32 + %st2 = OpTypeStruct %f64 %s32 %v3u32 + %opaque1 = OpTypeOpaque "" + %opaque2 = OpTypeOpaque "opaque" + %p = OpTypePointer Uniform %st1 + %f = OpTypeFunction %void %u32 %u32 + %event = OpTypeEvent + %de = OpTypeDeviceEvent + %ri = OpTypeReserveId + %queue = OpTypeQueue + %pipe = OpTypePipe ReadOnly + %ps = OpTypePipeStorage + %nb = OpTypeNamedBarrier + )"; + + std::vector> type_id_strs = { + {1, "void"}, + {2, "bool"}, + {3, "uint32"}, + // Id 4 is used by the constant. + {5, "sint32"}, + {6, "float64"}, + {7, ""}, + {8, "<, 3>"}, + {9, "image(sint32, 3, 0, 1, 1, 0, 3, 2)"}, + {10, "image(sint32, 3, 0, 1, 1, 0, 3, 0)"}, + {11, "sampler"}, + {12, "sampled_image(image(sint32, 3, 0, 1, 1, 0, 3, 2))"}, + {13, "sampled_image(image(sint32, 3, 0, 1, 1, 0, 3, 0))"}, + {14, "[uint32, id(4)]"}, + {15, "[float64]"}, + {16, "{uint32}"}, + {17, "{float64, sint32, }"}, + {18, "opaque('')"}, + {19, "opaque('opaque')"}, + {20, "{uint32}*"}, + {21, "(uint32, uint32) -> void"}, + {22, "event"}, + {23, "device_event"}, + {24, "reserve_id"}, + {25, "queue"}, + {26, "pipe(0)"}, + {27, "pipe_storage"}, + {28, "named_barrier"}, + }; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text); + opt::analysis::TypeManager manager(nullptr, context.get()); + + EXPECT_EQ(type_id_strs.size(), manager.NumTypes()); + EXPECT_EQ(2u, manager.NumForwardPointers()); + + for (const auto& p : type_id_strs) { + EXPECT_EQ(p.second, manager.GetType(p.first)->str()); + EXPECT_EQ(p.first, manager.GetId(manager.GetType(p.first))); + } + EXPECT_EQ("forward_pointer({uint32}*)", manager.GetForwardPointer(0)->str()); + EXPECT_EQ("forward_pointer(10000)", manager.GetForwardPointer(1)->str()); +} + +TEST(TypeManager, DecorationOnStruct) { + const std::string text = R"( + OpDecorate %struct1 Block + OpDecorate %struct2 Block + OpDecorate %struct3 Block + OpDecorate %struct4 Block + + %u32 = OpTypeInt 32 0 ; id: 5 + %f32 = OpTypeFloat 32 ; id: 6 + %struct1 = OpTypeStruct %u32 %f32 ; base + %struct2 = OpTypeStruct %f32 %u32 ; different member order + %struct3 = OpTypeStruct %f32 ; different member list + %struct4 = OpTypeStruct %u32 %f32 ; the same + %struct7 = OpTypeStruct %f32 ; no decoration + )"; + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text); + opt::analysis::TypeManager manager(nullptr, context.get()); + + ASSERT_EQ(7u, manager.NumTypes()); + ASSERT_EQ(0u, manager.NumForwardPointers()); + // Make sure we get ids correct. + ASSERT_EQ("uint32", manager.GetType(5)->str()); + ASSERT_EQ("float32", manager.GetType(6)->str()); + + // Try all combinations of pairs. Expect to be the same type only when the + // same id or (1, 4). + for (const auto id1 : {1, 2, 3, 4, 7}) { + for (const auto id2 : {1, 2, 3, 4, 7}) { + if (id1 == id2 || (id1 == 1 && id2 == 4) || (id1 == 4 && id2 == 1)) { + EXPECT_TRUE(manager.GetType(id1)->IsSame(manager.GetType(id2))) + << "%struct" << id1 << " is expected to be the same as %struct" + << id2; + } else { + EXPECT_FALSE(manager.GetType(id1)->IsSame(manager.GetType(id2))) + << "%struct" << id1 << " is expected to be different with %struct" + << id2; + } + } + } +} + +TEST(TypeManager, DecorationOnMember) { + const std::string text = R"( + OpMemberDecorate %struct1 0 Offset 0 + OpMemberDecorate %struct2 0 Offset 0 + OpMemberDecorate %struct3 0 Offset 0 + OpMemberDecorate %struct4 0 Offset 0 + OpMemberDecorate %struct5 1 Offset 0 + OpMemberDecorate %struct6 0 Offset 4 + + OpDecorate %struct7 Block + OpMemberDecorate %struct7 0 Offset 0 + + %u32 = OpTypeInt 32 0 ; id: 8 + %f32 = OpTypeFloat 32 ; id: 9 + %struct1 = OpTypeStruct %u32 %f32 ; base + %struct2 = OpTypeStruct %f32 %u32 ; different member order + %struct3 = OpTypeStruct %f32 ; different member list + %struct4 = OpTypeStruct %u32 %f32 ; the same + %struct5 = OpTypeStruct %u32 %f32 ; member decorate different field + %struct6 = OpTypeStruct %u32 %f32 ; different member decoration parameter + %struct7 = OpTypeStruct %u32 %f32 ; extra decoration on the struct + %struct10 = OpTypeStruct %u32 %f32 ; no member decoration + )"; + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text); + opt::analysis::TypeManager manager(nullptr, context.get()); + + ASSERT_EQ(10u, manager.NumTypes()); + ASSERT_EQ(0u, manager.NumForwardPointers()); + // Make sure we get ids correct. + ASSERT_EQ("uint32", manager.GetType(8)->str()); + ASSERT_EQ("float32", manager.GetType(9)->str()); + + // Try all combinations of pairs. Expect to be the same type only when the + // same id or (1, 4). + for (const auto id1 : {1, 2, 3, 4, 5, 6, 7, 10}) { + for (const auto id2 : {1, 2, 3, 4, 5, 6, 7, 10}) { + if (id1 == id2 || (id1 == 1 && id2 == 4) || (id1 == 4 && id2 == 1)) { + EXPECT_TRUE(manager.GetType(id1)->IsSame(manager.GetType(id2))) + << "%struct" << id1 << " is expected to be the same as %struct" + << id2; + } else { + EXPECT_FALSE(manager.GetType(id1)->IsSame(manager.GetType(id2))) + << "%struct" << id1 << " is expected to be different with %struct" + << id2; + } + } + } +} + +TEST(TypeManager, DecorationEmpty) { + const std::string text = R"( + OpDecorate %struct1 Block + OpMemberDecorate %struct2 0 Offset 0 + + %u32 = OpTypeInt 32 0 ; id: 3 + %f32 = OpTypeFloat 32 ; id: 4 + %struct1 = OpTypeStruct %u32 %f32 + %struct2 = OpTypeStruct %f32 %u32 + %struct5 = OpTypeStruct %f32 + )"; + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text); + opt::analysis::TypeManager manager(nullptr, context.get()); + + ASSERT_EQ(5u, manager.NumTypes()); + ASSERT_EQ(0u, manager.NumForwardPointers()); + // Make sure we get ids correct. + ASSERT_EQ("uint32", manager.GetType(3)->str()); + ASSERT_EQ("float32", manager.GetType(4)->str()); + + // %struct1 with decoration on itself + EXPECT_FALSE(manager.GetType(1)->decoration_empty()); + // %struct2 with decoration on its member + EXPECT_FALSE(manager.GetType(2)->decoration_empty()); + EXPECT_TRUE(manager.GetType(3)->decoration_empty()); + EXPECT_TRUE(manager.GetType(4)->decoration_empty()); + // %struct5 has no decorations + EXPECT_TRUE(manager.GetType(5)->decoration_empty()); +} + +TEST(TypeManager, BeginEndForEmptyModule) { + const std::string text = ""; + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text); + opt::analysis::TypeManager manager(nullptr, context.get()); + ASSERT_EQ(0u, manager.NumTypes()); + ASSERT_EQ(0u, manager.NumForwardPointers()); + + EXPECT_EQ(manager.begin(), manager.end()); +} + +TEST(TypeManager, BeginEnd) { + const std::string text = R"( + %void1 = OpTypeVoid + %void2 = OpTypeVoid + %bool = OpTypeBool + %u32 = OpTypeInt 32 0 + %f64 = OpTypeFloat 64 + )"; + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text); + opt::analysis::TypeManager manager(nullptr, context.get()); + ASSERT_EQ(5u, manager.NumTypes()); + ASSERT_EQ(0u, manager.NumForwardPointers()); + + EXPECT_NE(manager.begin(), manager.end()); + for (const auto& t : manager) { + switch (t.first) { + case 1: + case 2: + EXPECT_EQ("void", t.second->str()); + break; + case 3: + EXPECT_EQ("bool", t.second->str()); + break; + case 4: + EXPECT_EQ("uint32", t.second->str()); + break; + case 5: + EXPECT_EQ("float64", t.second->str()); + break; + default: + EXPECT_TRUE(false && "unreachable"); + break; + } + } +} + +TEST(TypeManager, LookupType) { + const std::string text = R"( +%void = OpTypeVoid +%uint = OpTypeInt 32 0 +%int = OpTypeInt 32 1 +%vec2 = OpTypeVector %int 2 +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + EXPECT_NE(context, nullptr); + TypeManager manager(nullptr, context.get()); + + Void voidTy; + EXPECT_EQ(manager.GetId(&voidTy), 1u); + + Integer uintTy(32, false); + EXPECT_EQ(manager.GetId(&uintTy), 2u); + + Integer intTy(32, true); + EXPECT_EQ(manager.GetId(&intTy), 3u); + + Integer intTy2(32, true); + Vector vecTy(&intTy2, 2u); + EXPECT_EQ(manager.GetId(&vecTy), 4u); +} + +TEST(TypeManager, RemoveId) { + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +%1 = OpTypeInt 32 0 +%2 = OpTypeInt 32 1 + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + EXPECT_NE(context, nullptr); + + context->get_type_mgr()->RemoveId(1u); + ASSERT_EQ(context->get_type_mgr()->GetType(1u), nullptr); + ASSERT_NE(context->get_type_mgr()->GetType(2u), nullptr); + + context->get_type_mgr()->RemoveId(2u); + ASSERT_EQ(context->get_type_mgr()->GetType(1u), nullptr); + ASSERT_EQ(context->get_type_mgr()->GetType(2u), nullptr); +} + +TEST(TypeManager, RemoveIdNonDuplicateAmbiguousType) { + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +%1 = OpTypeInt 32 0 +%2 = OpTypeStruct %1 + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + EXPECT_NE(context, nullptr); + + Integer u32(32, false); + Struct st({&u32}); + ASSERT_EQ(context->get_type_mgr()->GetId(&st), 2u); + context->get_type_mgr()->RemoveId(2u); + ASSERT_EQ(context->get_type_mgr()->GetType(2u), nullptr); + ASSERT_EQ(context->get_type_mgr()->GetId(&st), 0u); +} + +TEST(TypeManager, RemoveIdDuplicateAmbiguousType) { + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +%1 = OpTypeInt 32 0 +%2 = OpTypeStruct %1 +%3 = OpTypeStruct %1 + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + EXPECT_NE(context, nullptr); + + Integer u32(32, false); + Struct st({&u32}); + uint32_t id = context->get_type_mgr()->GetId(&st); + ASSERT_NE(id, 0u); + uint32_t toRemove = id == 2u ? 2u : 3u; + uint32_t toStay = id == 2u ? 3u : 2u; + context->get_type_mgr()->RemoveId(toRemove); + ASSERT_EQ(context->get_type_mgr()->GetType(toRemove), nullptr); + ASSERT_EQ(context->get_type_mgr()->GetId(&st), toStay); +} + +TEST(TypeManager, RemoveIdDoesntUnmapOtherTypes) { + const std::string text = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +%1 = OpTypeInt 32 0 +%2 = OpTypeStruct %1 +%3 = OpTypeStruct %1 + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + EXPECT_NE(context, nullptr); + + Integer u32(32, false); + Struct st({&u32}); + + EXPECT_EQ(1u, context->get_type_mgr()->GetId(&u32)); + uint32_t id = context->get_type_mgr()->GetId(&st); + ASSERT_NE(id, 0u); + uint32_t toRemove = id == 2u ? 3u : 2u; + uint32_t toStay = id == 2u ? 2u : 3u; + context->get_type_mgr()->RemoveId(toRemove); + ASSERT_EQ(context->get_type_mgr()->GetType(toRemove), nullptr); + ASSERT_EQ(context->get_type_mgr()->GetId(&st), toStay); +} + +TEST(TypeManager, GetTypeAndPointerType) { + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +%1 = OpTypeInt 32 0 +%2 = OpTypeStruct %1 + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + EXPECT_NE(context, nullptr); + + Integer u32(32, false); + Pointer u32Ptr(&u32, SpvStorageClassFunction); + Struct st({&u32}); + Pointer stPtr(&st, SpvStorageClassInput); + + auto pair = context->get_type_mgr()->GetTypeAndPointerType( + 3u, SpvStorageClassFunction); + ASSERT_EQ(nullptr, pair.first); + ASSERT_EQ(nullptr, pair.second); + + pair = context->get_type_mgr()->GetTypeAndPointerType( + 1u, SpvStorageClassFunction); + ASSERT_TRUE(pair.first->IsSame(&u32)); + ASSERT_TRUE(pair.second->IsSame(&u32Ptr)); + + pair = + context->get_type_mgr()->GetTypeAndPointerType(2u, SpvStorageClassInput); + ASSERT_TRUE(pair.first->IsSame(&st)); + ASSERT_TRUE(pair.second->IsSame(&stPtr)); +} + +TEST(TypeManager, DuplicateType) { + const std::string text = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +%1 = OpTypeInt 32 0 +%2 = OpTypeInt 32 0 + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + EXPECT_NE(context, nullptr); + + const Type* type1 = context->get_type_mgr()->GetType(1u); + const Type* type2 = context->get_type_mgr()->GetType(2u); + EXPECT_NE(type1, nullptr); + EXPECT_NE(type2, nullptr); + EXPECT_EQ(*type1, *type2); +} + +TEST(TypeManager, MultipleStructs) { + const std::string text = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpDecorate %3 Constant +%1 = OpTypeInt 32 0 +%2 = OpTypeStruct %1 +%3 = OpTypeStruct %1 + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + EXPECT_NE(context, nullptr); + + const Type* type1 = context->get_type_mgr()->GetType(2u); + const Type* type2 = context->get_type_mgr()->GetType(3u); + EXPECT_NE(type1, nullptr); + EXPECT_NE(type2, nullptr); + EXPECT_FALSE(type1->IsSame(type2)); +} + +TEST(TypeManager, RemovingIdAvoidsUseAfterFree) { + const std::string text = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +%1 = OpTypeInt 32 0 +%2 = OpTypeStruct %1 + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + EXPECT_NE(context, nullptr); + + Integer u32(32, false); + Struct st({&u32}); + const Type* type = context->get_type_mgr()->GetType(2u); + EXPECT_NE(type, nullptr); + context->get_type_mgr()->RemoveId(1u); + EXPECT_TRUE(type->IsSame(&st)); +} + +TEST(TypeManager, RegisterAndRemoveId) { + const std::string text = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +%1 = OpTypeInt 32 0 +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + EXPECT_NE(context, nullptr); + + uint32_t id = 2u; + { + // Ensure that u32 goes out of scope. + Integer u32(32, false); + Struct st({&u32}); + context->get_type_mgr()->RegisterType(id, st); + } + + context->get_type_mgr()->RemoveId(id); + EXPECT_EQ(nullptr, context->get_type_mgr()->GetType(id)); +} + +TEST(TypeManager, RegisterAndRemoveIdAllTypes) { + const std::string text = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + EXPECT_NE(context, nullptr); + + std::vector> types = GenerateAllTypes(); + uint32_t id = 1u; + for (auto& t : types) { + context->get_type_mgr()->RegisterType(id, *t); + EXPECT_EQ(*t, *context->get_type_mgr()->GetType(id)); + } + types.clear(); + + for (; id > 0; --id) { + context->get_type_mgr()->RemoveId(id); + EXPECT_EQ(nullptr, context->get_type_mgr()->GetType(id)); + } +} + +TEST(TypeManager, RegisterAndRemoveIdWithDecorations) { + const std::string text = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +%1 = OpTypeInt 32 0 +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + EXPECT_NE(context, nullptr); + + uint32_t id = 2u; + { + Integer u32(32, false); + Struct st({&u32, &u32}); + st.AddDecoration({10}); + st.AddDecoration({11}); + st.AddMemberDecoration(0, {{35, 4}}); + st.AddMemberDecoration(1, {{35, 4}}); + st.AddMemberDecoration(1, {{36, 5}}); + context->get_type_mgr()->RegisterType(id, st); + EXPECT_EQ(st, *context->get_type_mgr()->GetType(id)); + } + + context->get_type_mgr()->RemoveId(id); + EXPECT_EQ(nullptr, context->get_type_mgr()->GetType(id)); +} + +#ifdef SPIRV_EFFCEE +TEST(TypeManager, GetTypeInstructionInt) { + const std::string text = R"( +; CHECK: OpTypeInt 32 0 +; CHECK: OpTypeInt 16 1 +OpCapability Shader +OpCapability Int16 +OpCapability Linkage +OpMemoryModel Logical GLSL450 + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + EXPECT_NE(context, nullptr); + + Integer uint_32(32, false); + context->get_type_mgr()->GetTypeInstruction(&uint_32); + + Integer int_16(16, true); + context->get_type_mgr()->GetTypeInstruction(&int_16); + + Match(text, context.get()); +} + +TEST(TypeManager, GetTypeInstructionDuplicateInts) { + const std::string text = R"( +; CHECK: OpTypeInt 32 0 +; CHECK-NOT: OpType +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + EXPECT_NE(context, nullptr); + + Integer uint_32(32, false); + uint32_t id = context->get_type_mgr()->GetTypeInstruction(&uint_32); + + Integer other(32, false); + EXPECT_EQ(context->get_type_mgr()->GetTypeInstruction(&other), id); + + Match(text, context.get()); +} + +TEST(TypeManager, GetTypeInstructionAllTypes) { + const std::string text = R"( +; CHECK: [[uint:%\w+]] = OpTypeInt 32 0 +; CHECK: [[input_ptr:%\w+]] = OpTypePointer Input [[uint]] +; CHECK: [[uniform_ptr:%\w+]] = OpTypePointer Uniform [[uint]] +; CHECK: [[uint24:%\w+]] = OpConstant [[uint]] 24 +; CHECK: [[uint42:%\w+]] = OpConstant [[uint]] 42 +; CHECK: [[uint100:%\w+]] = OpConstant [[uint]] 100 +; CHECK: [[void:%\w+]] = OpTypeVoid +; CHECK: [[bool:%\w+]] = OpTypeBool +; CHECK: [[s32:%\w+]] = OpTypeInt 32 1 +; CHECK: OpTypeInt 64 1 +; CHECK: [[u64:%\w+]] = OpTypeInt 64 0 +; CHECK: [[f32:%\w+]] = OpTypeFloat 32 +; CHECK: OpTypeFloat 64 +; CHECK: OpTypeVector [[s32]] 2 +; CHECK: [[v3s32:%\w+]] = OpTypeVector [[s32]] 3 +; CHECK: OpTypeVector [[u64]] 4 +; CHECK: [[v3f32:%\w+]] = OpTypeVector [[f32]] 3 +; CHECK: OpTypeMatrix [[v3s32]] 3 +; CHECK: OpTypeMatrix [[v3s32]] 4 +; CHECK: OpTypeMatrix [[v3f32]] 4 +; CHECK: [[image1:%\w+]] = OpTypeImage [[s32]] 2D 0 0 0 0 Rg8 ReadOnly +; CHECK: OpTypeImage [[s32]] 2D 0 1 0 0 Rg8 ReadOnly +; CHECK: OpTypeImage [[s32]] 3D 0 1 0 0 Rg8 ReadOnly +; CHECK: [[image2:%\w+]] = OpTypeImage [[void]] 3D 0 1 0 1 Rg8 ReadWrite +; CHECK: OpTypeSampler +; CHECK: OpTypeSampledImage [[image1]] +; CHECK: OpTypeSampledImage [[image2]] +; CHECK: OpTypeArray [[f32]] [[uint100]] +; CHECK: [[a42f32:%\w+]] = OpTypeArray [[f32]] [[uint42]] +; CHECK: OpTypeArray [[u64]] [[uint24]] +; CHECK: OpTypeRuntimeArray [[v3f32]] +; CHECK: [[rav3s32:%\w+]] = OpTypeRuntimeArray [[v3s32]] +; CHECK: OpTypeStruct [[s32]] +; CHECK: [[sts32f32:%\w+]] = OpTypeStruct [[s32]] [[f32]] +; CHECK: OpTypeStruct [[u64]] [[a42f32]] [[rav3s32]] +; CHECK: OpTypeOpaque "" +; CHECK: OpTypeOpaque "hello" +; CHECK: OpTypeOpaque "world" +; CHECK: OpTypePointer Input [[f32]] +; CHECK: OpTypePointer Function [[sts32f32]] +; CHECK: OpTypePointer Function [[a42f32]] +; CHECK: OpTypeFunction [[void]] +; CHECK: OpTypeFunction [[void]] [[bool]] +; CHECK: OpTypeFunction [[void]] [[bool]] [[s32]] +; CHECK: OpTypeFunction [[s32]] [[bool]] [[s32]] +; CHECK: OpTypeEvent +; CHECK: OpTypeDeviceEvent +; CHECK: OpTypeReserveId +; CHECK: OpTypeQueue +; CHECK: OpTypePipe ReadWrite +; CHECK: OpTypePipe ReadOnly +; CHECK: OpTypeForwardPointer [[input_ptr]] Input +; CHECK: OpTypeForwardPointer [[uniform_ptr]] Input +; CHECK: OpTypeForwardPointer [[uniform_ptr]] Uniform +; CHECK: OpTypePipeStorage +; CHECK: OpTypeNamedBarrier +OpCapability Shader +OpCapability Int64 +OpCapability Linkage +OpMemoryModel Logical GLSL450 +%uint = OpTypeInt 32 0 +%1 = OpTypePointer Input %uint +%2 = OpTypePointer Uniform %uint +%24 = OpConstant %uint 24 +%42 = OpConstant %uint 42 +%100 = OpConstant %uint 100 + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + EXPECT_NE(context, nullptr); + + std::vector> types = GenerateAllTypes(); + for (auto& t : types) { + context->get_type_mgr()->GetTypeInstruction(t.get()); + } + + Match(text, context.get(), false); +} + +TEST(TypeManager, GetTypeInstructionWithDecorations) { + const std::string text = R"( +; CHECK: OpDecorate [[struct:%\w+]] CPacked +; CHECK: OpMemberDecorate [[struct]] 1 Offset 4 +; CHECK: [[uint:%\w+]] = OpTypeInt 32 0 +; CHECK: [[struct]] = OpTypeStruct [[uint]] [[uint]] +OpCapability Shader +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical GLSL450 +%uint = OpTypeInt 32 0 + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + EXPECT_NE(context, nullptr); + + Integer u32(32, false); + Struct st({&u32, &u32}); + st.AddDecoration({10}); + st.AddMemberDecoration(1, {{35, 4}}); + (void)context->get_def_use_mgr(); + context->get_type_mgr()->GetTypeInstruction(&st); + + Match(text, context.get()); +} +#endif // SPIRV_EFFCEE + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/types_test.cpp b/3rdparty/spirv-tools/test/opt/types_test.cpp new file mode 100644 index 000000000..1f081d437 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/types_test.cpp @@ -0,0 +1,339 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include + +#include "opt/make_unique.h" +#include "opt/types.h" + +namespace { + +using namespace spvtools::opt::analysis; +using spvtools::MakeUnique; + +// Fixture class providing some element types. +class SameTypeTest : public ::testing::Test { + protected: + virtual void SetUp() override { + void_t_.reset(new Void()); + u32_t_.reset(new Integer(32, false)); + f64_t_.reset(new Float(64)); + v3u32_t_.reset(new Vector(u32_t_.get(), 3)); + image_t_.reset(new Image(f64_t_.get(), SpvDim2D, 1, 1, 0, 0, + SpvImageFormatR16, SpvAccessQualifierReadWrite)); + } + + // Element types to be used for constructing other types for testing. + std::unique_ptr void_t_; + std::unique_ptr u32_t_; + std::unique_ptr f64_t_; + std::unique_ptr v3u32_t_; + std::unique_ptr image_t_; +}; + +#define TestMultipleInstancesOfTheSameType(ty, ...) \ + TEST_F(SameTypeTest, MultiSame##ty) { \ + std::vector> types; \ + for (int i = 0; i < 10; ++i) types.emplace_back(new ty(__VA_ARGS__)); \ + for (size_t i = 0; i < types.size(); ++i) { \ + for (size_t j = 0; j < types.size(); ++j) { \ + EXPECT_TRUE(types[i]->IsSame(types[j].get())) \ + << "expected '" << types[i]->str() << "' is the same as '" \ + << types[j]->str() << "'"; \ + EXPECT_TRUE(*types[i] == *types[j]) \ + << "expected '" << types[i]->str() << "' is the same as '" \ + << types[j]->str() << "'"; \ + } \ + } \ + } +TestMultipleInstancesOfTheSameType(Void); +TestMultipleInstancesOfTheSameType(Bool); +TestMultipleInstancesOfTheSameType(Integer, 32, true); +TestMultipleInstancesOfTheSameType(Float, 64); +TestMultipleInstancesOfTheSameType(Vector, u32_t_.get(), 3); +TestMultipleInstancesOfTheSameType(Matrix, v3u32_t_.get(), 4); +TestMultipleInstancesOfTheSameType(Image, f64_t_.get(), SpvDimCube, 0, 0, 1, 1, + SpvImageFormatRgb10A2, + SpvAccessQualifierWriteOnly); +TestMultipleInstancesOfTheSameType(Sampler); +TestMultipleInstancesOfTheSameType(SampledImage, image_t_.get()); +TestMultipleInstancesOfTheSameType(Array, u32_t_.get(), 10); +TestMultipleInstancesOfTheSameType(RuntimeArray, u32_t_.get()); +TestMultipleInstancesOfTheSameType(Struct, std::vector{u32_t_.get(), + f64_t_.get()}); +TestMultipleInstancesOfTheSameType(Opaque, "testing rocks"); +TestMultipleInstancesOfTheSameType(Pointer, u32_t_.get(), SpvStorageClassInput); +TestMultipleInstancesOfTheSameType(Function, u32_t_.get(), + {f64_t_.get(), f64_t_.get()}); +TestMultipleInstancesOfTheSameType(Event); +TestMultipleInstancesOfTheSameType(DeviceEvent); +TestMultipleInstancesOfTheSameType(ReserveId); +TestMultipleInstancesOfTheSameType(Queue); +TestMultipleInstancesOfTheSameType(Pipe, SpvAccessQualifierReadWrite); +TestMultipleInstancesOfTheSameType(ForwardPointer, 10, SpvStorageClassUniform); +TestMultipleInstancesOfTheSameType(PipeStorage); +TestMultipleInstancesOfTheSameType(NamedBarrier); +#undef TestMultipleInstanceOfTheSameType + +std::vector> GenerateAllTypes() { + // Types in this test case are only equal to themselves, nothing else. + std::vector> types; + + // Forward Pointer + types.emplace_back(new ForwardPointer(10000, SpvStorageClassInput)); + types.emplace_back(new ForwardPointer(20000, SpvStorageClassInput)); + + // Void, Bool + types.emplace_back(new Void()); + auto* voidt = types.back().get(); + types.emplace_back(new Bool()); + auto* boolt = types.back().get(); + + // Integer + types.emplace_back(new Integer(32, true)); + auto* s32 = types.back().get(); + types.emplace_back(new Integer(32, false)); + types.emplace_back(new Integer(64, true)); + types.emplace_back(new Integer(64, false)); + auto* u64 = types.back().get(); + + // Float + types.emplace_back(new Float(32)); + auto* f32 = types.back().get(); + types.emplace_back(new Float(64)); + + // Vector + types.emplace_back(new Vector(s32, 2)); + types.emplace_back(new Vector(s32, 3)); + auto* v3s32 = types.back().get(); + types.emplace_back(new Vector(u64, 4)); + types.emplace_back(new Vector(f32, 3)); + auto* v3f32 = types.back().get(); + + // Matrix + types.emplace_back(new Matrix(v3s32, 3)); + types.emplace_back(new Matrix(v3s32, 4)); + types.emplace_back(new Matrix(v3f32, 4)); + + // Images + types.emplace_back(new Image(s32, SpvDim2D, 0, 0, 0, 0, SpvImageFormatRg8, + SpvAccessQualifierReadOnly)); + auto* image1 = types.back().get(); + types.emplace_back(new Image(s32, SpvDim2D, 0, 1, 0, 0, SpvImageFormatRg8, + SpvAccessQualifierReadOnly)); + types.emplace_back(new Image(s32, SpvDim3D, 0, 1, 0, 0, SpvImageFormatRg8, + SpvAccessQualifierReadOnly)); + types.emplace_back(new Image(voidt, SpvDim3D, 0, 1, 0, 1, SpvImageFormatRg8, + SpvAccessQualifierReadWrite)); + auto* image2 = types.back().get(); + + // Sampler + types.emplace_back(new Sampler()); + + // Sampled Image + types.emplace_back(new SampledImage(image1)); + types.emplace_back(new SampledImage(image2)); + + // Array + types.emplace_back(new Array(f32, 100)); + types.emplace_back(new Array(f32, 42)); + auto* a42f32 = types.back().get(); + types.emplace_back(new Array(u64, 24)); + + // RuntimeArray + types.emplace_back(new RuntimeArray(v3f32)); + types.emplace_back(new RuntimeArray(v3s32)); + auto* rav3s32 = types.back().get(); + + // Struct + types.emplace_back(new Struct(std::vector{s32})); + types.emplace_back(new Struct(std::vector{s32, f32})); + auto* sts32f32 = types.back().get(); + types.emplace_back(new Struct(std::vector{u64, a42f32, rav3s32})); + + // Opaque + types.emplace_back(new Opaque("")); + types.emplace_back(new Opaque("hello")); + types.emplace_back(new Opaque("world")); + + // Pointer + types.emplace_back(new Pointer(f32, SpvStorageClassInput)); + types.emplace_back(new Pointer(sts32f32, SpvStorageClassFunction)); + types.emplace_back(new Pointer(a42f32, SpvStorageClassFunction)); + types.emplace_back(new Pointer(voidt, SpvStorageClassFunction)); + + // Function + types.emplace_back(new Function(voidt, {})); + types.emplace_back(new Function(voidt, {boolt})); + types.emplace_back(new Function(voidt, {boolt, s32})); + types.emplace_back(new Function(s32, {boolt, s32})); + + // Event, Device Event, Reserve Id, Queue, + types.emplace_back(new Event()); + types.emplace_back(new DeviceEvent()); + types.emplace_back(new ReserveId()); + types.emplace_back(new Queue()); + + // Pipe, Forward Pointer, PipeStorage, NamedBarrier + types.emplace_back(new Pipe(SpvAccessQualifierReadWrite)); + types.emplace_back(new Pipe(SpvAccessQualifierReadOnly)); + types.emplace_back(new ForwardPointer(1, SpvStorageClassInput)); + types.emplace_back(new ForwardPointer(2, SpvStorageClassInput)); + types.emplace_back(new ForwardPointer(2, SpvStorageClassUniform)); + types.emplace_back(new PipeStorage()); + types.emplace_back(new NamedBarrier()); + + return types; +} + +TEST(Types, AllTypes) { + // Types in this test case are only equal to themselves, nothing else. + std::vector> types = GenerateAllTypes(); + + for (size_t i = 0; i < types.size(); ++i) { + for (size_t j = 0; j < types.size(); ++j) { + if (i == j) { + EXPECT_TRUE(types[i]->IsSame(types[j].get())) + << "expected '" << types[i]->str() << "' is the same as '" + << types[j]->str() << "'"; + } else { + EXPECT_FALSE(types[i]->IsSame(types[j].get())) + << "expected '" << types[i]->str() << "' is different to '" + << types[j]->str() << "'"; + } + } + } +} + +TEST(Types, IntSignedness) { + std::vector signednesses = {true, false, false, true}; + std::vector> types; + for (bool s : signednesses) { + types.emplace_back(new Integer(32, s)); + } + for (size_t i = 0; i < signednesses.size(); i++) { + EXPECT_EQ(signednesses[i], types[i]->IsSigned()); + } +} + +TEST(Types, IntWidth) { + std::vector widths = {1, 2, 4, 8, 16, 32, 48, 64, 128}; + std::vector> types; + for (uint32_t w : widths) { + types.emplace_back(new Integer(w, true)); + } + for (size_t i = 0; i < widths.size(); i++) { + EXPECT_EQ(widths[i], types[i]->width()); + } +} + +TEST(Types, FloatWidth) { + std::vector widths = {1, 2, 4, 8, 16, 32, 48, 64, 128}; + std::vector> types; + for (uint32_t w : widths) { + types.emplace_back(new Float(w)); + } + for (size_t i = 0; i < widths.size(); i++) { + EXPECT_EQ(widths[i], types[i]->width()); + } +} + +TEST(Types, VectorElementCount) { + auto s32 = MakeUnique(32, true); + for (uint32_t c : {2, 3, 4}) { + auto s32v = MakeUnique(s32.get(), c); + EXPECT_EQ(c, s32v->element_count()); + } +} + +TEST(Types, MatrixElementCount) { + auto s32 = MakeUnique(32, true); + auto s32v4 = MakeUnique(s32.get(), 4); + for (uint32_t c : {1, 2, 3, 4, 10, 100}) { + auto s32m = MakeUnique(s32v4.get(), c); + EXPECT_EQ(c, s32m->element_count()); + } +} + +TEST(Types, IsUniqueType) { + std::vector> types = GenerateAllTypes(); + + for (auto& t : types) { + bool expectation = true; + // Disallowing variable pointers. + switch (t->kind()) { + case Type::kArray: + case Type::kRuntimeArray: + case Type::kStruct: + expectation = false; + break; + default: + break; + } + EXPECT_EQ(t->IsUniqueType(false), expectation) + << "expected '" << t->str() << "' to be a " + << (expectation ? "" : "non-") << "unique type"; + + // Allowing variables pointers. + if (t->AsPointer()) expectation = false; + EXPECT_EQ(t->IsUniqueType(true), expectation) + << "expected '" << t->str() << "' to be a " + << (expectation ? "" : "non-") << "unique type"; + } +} + +std::vector> GenerateAllTypesWithDecorations() { + std::vector> types = GenerateAllTypes(); + uint32_t elems = 1; + uint32_t decs = 1; + for (auto& t : types) { + for (uint32_t i = 0; i < (decs % 10); ++i) { + std::vector decoration; + for (uint32_t j = 0; j < (elems % 4) + 1; ++j) { + decoration.push_back(j); + } + t->AddDecoration(std::move(decoration)); + ++elems; + ++decs; + } + } + + return types; +} + +TEST(Types, Clone) { + std::vector> types = GenerateAllTypesWithDecorations(); + for (auto& t : types) { + auto clone = t->Clone(); + EXPECT_TRUE(*t == *clone); + EXPECT_TRUE(t->HasSameDecorations(clone.get())); + EXPECT_NE(clone.get(), t.get()); + } +} + +TEST(Types, RemoveDecorations) { + std::vector> types = GenerateAllTypesWithDecorations(); + for (auto& t : types) { + auto decorationless = t->RemoveDecorations(); + EXPECT_EQ(*t == *decorationless, t->decoration_empty()); + EXPECT_EQ(t->HasSameDecorations(decorationless.get()), + t->decoration_empty()); + EXPECT_NE(t.get(), decorationless.get()); + } +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/unify_const_test.cpp b/3rdparty/spirv-tools/test/opt/unify_const_test.cpp new file mode 100644 index 000000000..5c29fa79d --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/unify_const_test.cpp @@ -0,0 +1,983 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "assembly_builder.h" +#include "pass_fixture.h" +#include "pass_utils.h" + +namespace { +using namespace spvtools; + +// Returns the types defining instructions commonly used in many tests. +std::vector CommonTypes() { + return std::vector{ + // clang-format off + // scalar types + "%bool = OpTypeBool", + "%uint = OpTypeInt 32 0", + "%int = OpTypeInt 32 1", + "%uint64 = OpTypeInt 64 0", + "%int64 = OpTypeInt 64 1", + "%float = OpTypeFloat 32", + "%double = OpTypeFloat 64", + // vector types + "%v2bool = OpTypeVector %bool 2", + "%v2uint = OpTypeVector %uint 2", + "%v2int = OpTypeVector %int 2", + "%v3int = OpTypeVector %int 3", + "%v4int = OpTypeVector %int 4", + "%v2float = OpTypeVector %float 2", + "%v3float = OpTypeVector %float 3", + "%v2double = OpTypeVector %double 2", + // struct types + "%inner_struct = OpTypeStruct %bool %float", + "%outer_struct = OpTypeStruct %inner_struct %int %double", + "%flat_struct = OpTypeStruct %bool %int %float %double", + // variable pointer types + "%_pf_bool = OpTypePointer Function %bool", + "%_pf_uint = OpTypePointer Function %uint", + "%_pf_int = OpTypePointer Function %int", + "%_pf_uint64 = OpTypePointer Function %uint64", + "%_pf_int64 = OpTypePointer Function %int64", + "%_pf_float = OpTypePointer Function %float", + "%_pf_double = OpTypePointer Function %double", + "%_pf_v2int = OpTypePointer Function %v2int", + "%_pf_v3int = OpTypePointer Function %v3int", + "%_pf_v4int = OpTypePointer Function %v4int", + "%_pf_v2float = OpTypePointer Function %v2float", + "%_pf_v3float = OpTypePointer Function %v3float", + "%_pf_v2double = OpTypePointer Function %v2double", + "%_pf_inner_struct = OpTypePointer Function %inner_struct", + "%_pf_outer_struct = OpTypePointer Function %outer_struct", + "%_pf_flat_struct = OpTypePointer Function %flat_struct", + // clang-format on + }; +} + +// A helper function to strip OpName instructions from the given string of +// disassembly code and put those debug instructions to a set. Returns the +// string with all OpName instruction stripped and a set of OpName +// instructions. +std::tuple> +StripOpNameInstructionsToSet(const std::string& str) { + std::stringstream ss(str); + std::ostringstream oss; + std::string inst_str; + std::unordered_set opname_instructions; + while (std::getline(ss, inst_str, '\n')) { + if (inst_str.find("OpName %") == std::string::npos) { + oss << inst_str << '\n'; + } else { + opname_instructions.insert(inst_str); + } + } + return std::make_tuple(oss.str(), std::move(opname_instructions)); +} + +// The test fixture for all tests of UnifyConstantPass. This fixture defines +// the rule of checking: all the optimized code should be exactly the same as +// the expected code, except the OpName instructions, which can be different in +// order. +template +class UnifyConstantTest : public PassTest { + protected: + // Runs UnifyConstantPass on the code built from the given |test_builder|, + // and checks whether the optimization result matches with the code built + // from |expected_builder|. + void Check(const AssemblyBuilder& expected_builder, + const AssemblyBuilder& test_builder) { + // unoptimized code + const std::string original_before_strip = test_builder.GetCode(); + std::string original_without_opnames; + std::unordered_set original_opnames; + std::tie(original_without_opnames, original_opnames) = + StripOpNameInstructionsToSet(original_before_strip); + + // expected code + std::string expected_without_opnames; + std::unordered_set expected_opnames; + std::tie(expected_without_opnames, expected_opnames) = + StripOpNameInstructionsToSet(expected_builder.GetCode()); + + // optimized code + std::string optimized_before_strip; + auto status = opt::Pass::Status::SuccessWithoutChange; + std::tie(optimized_before_strip, status) = + this->template SinglePassRunAndDisassemble( + test_builder.GetCode(), + /* skip_nop = */ true, /* do_validation = */ false); + std::string optimized_without_opnames; + std::unordered_set optimized_opnames; + std::tie(optimized_without_opnames, optimized_opnames) = + StripOpNameInstructionsToSet(optimized_before_strip); + + // Flag "status" should be returned correctly. + EXPECT_NE(opt::Pass::Status::Failure, status); + EXPECT_EQ(expected_without_opnames == original_without_opnames, + status == opt::Pass::Status::SuccessWithoutChange); + // Code except OpName instructions should be exactly the same. + EXPECT_EQ(expected_without_opnames, optimized_without_opnames); + // OpName instructions can be in different order, but the content must be + // the same. + EXPECT_EQ(expected_opnames, optimized_opnames); + } +}; + +using UnifyFrontEndConstantSingleTest = + UnifyConstantTest>; + +TEST_F(UnifyFrontEndConstantSingleTest, Basic) { + AssemblyBuilder test_builder; + AssemblyBuilder expected_builder; + + test_builder + .AppendTypesConstantsGlobals({ + "%uint = OpTypeInt 32 0", "%_pf_uint = OpTypePointer Function %uint", + "%unsigned_1 = OpConstant %uint 1", + "%unsigned_1_duplicate = OpConstant %uint 1", // duplicated constant + }) + .AppendInMain({ + "%uint_var = OpVariable %_pf_uint Function", + "OpStore %uint_var %unsigned_1_duplicate", + }); + + expected_builder + .AppendTypesConstantsGlobals({ + "%uint = OpTypeInt 32 0", + "%_pf_uint = OpTypePointer Function %uint", + "%unsigned_1 = OpConstant %uint 1", + }) + .AppendInMain({ + "%uint_var = OpVariable %_pf_uint Function", + "OpStore %uint_var %unsigned_1", + }) + .AppendNames({ + "OpName %unsigned_1 \"unsigned_1_duplicate\"", // the OpName + // instruction of the + // removed duplicated + // constant won't be + // erased. + }); + Check(expected_builder, test_builder); +} + +TEST_F(UnifyFrontEndConstantSingleTest, SkipWhenResultIdHasDecorations) { + AssemblyBuilder test_builder; + AssemblyBuilder expected_builder; + + test_builder + .AppendAnnotations({ + // So far we don't have valid decorations for constants. This is + // preparing for the future updates of SPIR-V. + // TODO(qining): change to a valid decoration once they are available. + "OpDecorate %f_1 RelaxedPrecision", + "OpDecorate %f_2_dup RelaxedPrecision", + }) + .AppendTypesConstantsGlobals({ + // clang-format off + "%float = OpTypeFloat 32", + "%_pf_float = OpTypePointer Function %float", + "%f_1 = OpConstant %float 1", + // %f_1 has decoration, so %f_1 will not be used to replace %f_1_dup. + "%f_1_dup = OpConstant %float 1", + "%f_2 = OpConstant %float 2", + // %_2_dup has decoration, so %f_2 will not replace %f_2_dup. + "%f_2_dup = OpConstant %float 2", + // no decoration for %f_3 or %f_3_dup, %f_3_dup should be replaced. + "%f_3 = OpConstant %float 3", + "%f_3_dup = OpConstant %float 3", + // clang-format on + }) + .AppendInMain({ + // clang-format off + "%f_var = OpVariable %_pf_float Function", + "OpStore %f_var %f_1_dup", + "OpStore %f_var %f_2_dup", + "OpStore %f_var %f_3_dup", + // clang-format on + }); + + expected_builder + .AppendAnnotations({ + "OpDecorate %f_1 RelaxedPrecision", + "OpDecorate %f_2_dup RelaxedPrecision", + }) + .AppendTypesConstantsGlobals({ + // clang-format off + "%float = OpTypeFloat 32", + "%_pf_float = OpTypePointer Function %float", + "%f_1 = OpConstant %float 1", + "%f_1_dup = OpConstant %float 1", + "%f_2 = OpConstant %float 2", + "%f_2_dup = OpConstant %float 2", + "%f_3 = OpConstant %float 3", + // clang-format on + }) + .AppendInMain({ + // clang-format off + "%f_var = OpVariable %_pf_float Function", + "OpStore %f_var %f_1_dup", + "OpStore %f_var %f_2_dup", + "OpStore %f_var %f_3", + // clang-format on + }) + .AppendNames({ + "OpName %f_3 \"f_3_dup\"", + }); + + Check(expected_builder, test_builder); +} + +TEST_F(UnifyFrontEndConstantSingleTest, UnifyWithDecorationOnTypes) { + AssemblyBuilder test_builder; + AssemblyBuilder expected_builder; + + test_builder + .AppendAnnotations({ + "OpMemberDecorate %flat_d 1 RelaxedPrecision", + }) + .AppendTypesConstantsGlobals({ + // clang-format off + "%int = OpTypeInt 32 1", + "%float = OpTypeFloat 32", + "%flat = OpTypeStruct %int %float", + "%_pf_flat = OpTypePointer Function %flat", + // decorated flat struct + "%flat_d = OpTypeStruct %int %float", + "%_pf_flat_d = OpTypePointer Function %flat_d", + // perserved contants. %flat_1 and %flat_d has same members, but + // their type are different in decorations, so they should not be + // used to replace each other. + "%int_1 = OpConstant %int 1", + "%float_1 = OpConstant %float 1", + "%flat_1 = OpConstantComposite %flat %int_1 %float_1", + "%flat_d_1 = OpConstantComposite %flat_d %int_1 %float_1", + // duplicated constants. + "%flat_1_dup = OpConstantComposite %flat %int_1 %float_1", + "%flat_d_1_dup = OpConstantComposite %flat_d %int_1 %float_1", + // clang-format on + }) + .AppendInMain({ + "%flat_var = OpVariable %_pf_flat Function", + "OpStore %flat_var %flat_1_dup", + "%flat_d_var = OpVariable %_pf_flat_d Function", + "OpStore %flat_d_var %flat_d_1_dup", + }); + + expected_builder + .AppendAnnotations({ + "OpMemberDecorate %flat_d 1 RelaxedPrecision", + }) + .AppendTypesConstantsGlobals({ + // clang-format off + "%int = OpTypeInt 32 1", + "%float = OpTypeFloat 32", + "%flat = OpTypeStruct %int %float", + "%_pf_flat = OpTypePointer Function %flat", + // decorated flat struct + "%flat_d = OpTypeStruct %int %float", + "%_pf_flat_d = OpTypePointer Function %flat_d", + "%int_1 = OpConstant %int 1", + "%float_1 = OpConstant %float 1", + "%flat_1 = OpConstantComposite %flat %int_1 %float_1", + "%flat_d_1 = OpConstantComposite %flat_d %int_1 %float_1", + // clang-format on + }) + .AppendInMain({ + "%flat_var = OpVariable %_pf_flat Function", + "OpStore %flat_var %flat_1", + "%flat_d_var = OpVariable %_pf_flat_d Function", + "OpStore %flat_d_var %flat_d_1", + }) + .AppendNames({ + "OpName %flat_1 \"flat_1_dup\"", + "OpName %flat_d_1 \"flat_d_1_dup\"", + }); + + Check(expected_builder, test_builder); +} + +struct UnifyConstantTestCase { + // preserved constants. + std::vector preserved_consts; + // expected uses of the preserved constants. + std::vector use_preserved_consts; + // duplicated constants of the preserved constants. + std::vector duplicate_consts; + // uses of the duplicated constants, expected to be updated to use the + // preserved constants. + std::vector use_duplicate_consts; + // The updated OpName instructions that originally refer to duplicated + // constants. + std::vector remapped_names; +}; + +using UnifyFrontEndConstantParamTest = UnifyConstantTest< + PassTest<::testing::TestWithParam>>; + +TEST_P(UnifyFrontEndConstantParamTest, TestCase) { + auto& tc = GetParam(); + AssemblyBuilder test_builder; + AssemblyBuilder expected_builder; + test_builder.AppendTypesConstantsGlobals(CommonTypes()); + expected_builder.AppendTypesConstantsGlobals(CommonTypes()); + + test_builder.AppendTypesConstantsGlobals(tc.preserved_consts) + .AppendTypesConstantsGlobals(tc.duplicate_consts) + .AppendInMain(tc.use_duplicate_consts); + + // Duplicated constants are killed in the expected output, and the debug + // instructions attached to those duplicated instructions will be migrated to + // the corresponding preserved constants. + expected_builder.AppendTypesConstantsGlobals(tc.preserved_consts) + .AppendInMain(tc.use_preserved_consts) + .AppendNames(tc.remapped_names); + + Check(expected_builder, test_builder); +} + +INSTANTIATE_TEST_CASE_P(Case, UnifyFrontEndConstantParamTest, + ::testing::ValuesIn(std::vector({ + // clang-format off + // basic tests for scalar constants + { + // preserved constants + { + "%bool_true = OpConstantTrue %bool", + "%signed_1 = OpConstant %int 1", + "%signed_minus_1 = OpConstant %int64 -1", + "%unsigned_max = OpConstant %uint64 18446744073709551615", + "%float_1 = OpConstant %float 1", + "%double_1 = OpConstant %double 1", + }, + // use preserved constants in main + { + "%bool_var = OpVariable %_pf_bool Function", + "OpStore %bool_var %bool_true", + "%int_var = OpVariable %_pf_int Function", + "OpStore %int_var %signed_1", + "%int64_var = OpVariable %_pf_int64 Function", + "OpStore %int64_var %signed_minus_1", + "%uint64_var = OpVariable %_pf_uint64 Function", + "OpStore %uint64_var %unsigned_max", + "%float_var = OpVariable %_pf_float Function", + "OpStore %float_var %float_1", + "%double_var = OpVariable %_pf_double Function", + "OpStore %double_var %double_1", + }, + // duplicated constants + { + "%bool_true_duplicate = OpConstantTrue %bool", + "%signed_1_duplicate = OpConstant %int 1", + "%signed_minus_1_duplicate = OpConstant %int64 -1", + "%unsigned_max_duplicate = OpConstant %uint64 18446744073709551615", + "%float_1_duplicate = OpConstant %float 1", + "%double_1_duplicate = OpConstant %double 1", + }, + // use duplicated constants in main + { + "%bool_var = OpVariable %_pf_bool Function", + "OpStore %bool_var %bool_true_duplicate", + "%int_var = OpVariable %_pf_int Function", + "OpStore %int_var %signed_1_duplicate", + "%int64_var = OpVariable %_pf_int64 Function", + "OpStore %int64_var %signed_minus_1_duplicate", + "%uint64_var = OpVariable %_pf_uint64 Function", + "OpStore %uint64_var %unsigned_max_duplicate", + "%float_var = OpVariable %_pf_float Function", + "OpStore %float_var %float_1_duplicate", + "%double_var = OpVariable %_pf_double Function", + "OpStore %double_var %double_1_duplicate", + }, + // remapped names + { + "OpName %bool_true \"bool_true_duplicate\"", + "OpName %signed_1 \"signed_1_duplicate\"", + "OpName %signed_minus_1 \"signed_minus_1_duplicate\"", + "OpName %unsigned_max \"unsigned_max_duplicate\"", + "OpName %float_1 \"float_1_duplicate\"", + "OpName %double_1 \"double_1_duplicate\"", + }, + }, + // NaN in different bit patterns should not be unified, but the ones + // using same bit pattern should be unified. + { + // preserved constants + { + "%float_nan_1 = OpConstant %float 0x1.8p+128", // !2143289344, 7FC00000 + "%float_nan_2 = OpConstant %float 0x1.800002p+128",// !2143289345 7FC00001 + }, + // use preserved constants in main + { + "%float_var = OpVariable %_pf_float Function", + "OpStore %float_var %float_nan_1", + "OpStore %float_var %float_nan_2", + }, + // duplicated constants + { + "%float_nan_1_duplicate = OpConstant %float 0x1.8p+128", // !2143289344, 7FC00000 + "%float_nan_2_duplicate = OpConstant %float 0x1.800002p+128",// !2143289345, 7FC00001 + }, + // use duplicated constants in main + { + "%float_var = OpVariable %_pf_float Function", + "OpStore %float_var %float_nan_1_duplicate", + "OpStore %float_var %float_nan_2_duplicate", + }, + // remapped names + { + "OpName %float_nan_1 \"float_nan_1_duplicate\"", + "OpName %float_nan_2 \"float_nan_2_duplicate\"", + }, + }, + // null values + { + // preserved constants + { + "%bool_null = OpConstantNull %bool", + "%signed_null = OpConstantNull %int", + "%signed_64_null = OpConstantNull %int64", + "%float_null = OpConstantNull %float", + "%double_null = OpConstantNull %double", + // zero-valued constants will not be unified with the equivalent + // null constants. + "%signed_zero = OpConstant %int 0", + }, + // use preserved constants in main + { + "%bool_var = OpVariable %_pf_bool Function", + "OpStore %bool_var %bool_null", + "%int_var = OpVariable %_pf_int Function", + "OpStore %int_var %signed_null", + "%int64_var = OpVariable %_pf_int64 Function", + "OpStore %int64_var %signed_64_null", + "%float_var = OpVariable %_pf_float Function", + "OpStore %float_var %float_null", + "%double_var = OpVariable %_pf_double Function", + "OpStore %double_var %double_null", + }, + // duplicated constants + { + "%bool_null_duplicate = OpConstantNull %bool", + "%signed_null_duplicate = OpConstantNull %int", + "%signed_64_null_duplicate = OpConstantNull %int64", + "%float_null_duplicate = OpConstantNull %float", + "%double_null_duplicate = OpConstantNull %double", + }, + // use duplicated constants in main + { + "%bool_var = OpVariable %_pf_bool Function", + "OpStore %bool_var %bool_null_duplicate", + "%int_var = OpVariable %_pf_int Function", + "OpStore %int_var %signed_null_duplicate", + "%int64_var = OpVariable %_pf_int64 Function", + "OpStore %int64_var %signed_64_null_duplicate", + "%float_var = OpVariable %_pf_float Function", + "OpStore %float_var %float_null_duplicate", + "%double_var = OpVariable %_pf_double Function", + "OpStore %double_var %double_null_duplicate", + }, + // remapped names + { + "OpName %bool_null \"bool_null_duplicate\"", + "OpName %signed_null \"signed_null_duplicate\"", + "OpName %signed_64_null \"signed_64_null_duplicate\"", + "OpName %float_null \"float_null_duplicate\"", + "OpName %double_null \"double_null_duplicate\"", + }, + }, + // constant sampler + { + // preserved constants + { + "%sampler = OpTypeSampler", + "%_pf_sampler = OpTypePointer Function %sampler", + "%sampler_1 = OpConstantSampler %sampler Repeat 0 Linear", + }, + // use preserved constants in main + { + "%sampler_var = OpVariable %_pf_sampler Function", + "OpStore %sampler_var %sampler_1", + }, + // duplicated constants + { + "%sampler_1_duplicate = OpConstantSampler %sampler Repeat 0 Linear", + }, + // use duplicated constants in main + { + "%sampler_var = OpVariable %_pf_sampler Function", + "OpStore %sampler_var %sampler_1_duplicate", + }, + // remapped names + { + "OpName %sampler_1 \"sampler_1_duplicate\"", + }, + }, + // duplicate vector built from same ids. + { + // preserved constants + { + "%signed_1 = OpConstant %int 1", + "%signed_2 = OpConstant %int 2", + "%signed_3 = OpConstant %int 3", + "%signed_4 = OpConstant %int 4", + "%vec = OpConstantComposite %v4int %signed_1 %signed_2 %signed_3 %signed_4", + }, + // use preserved constants in main + { + "%vec_var = OpVariable %_pf_v4int Function", + "OpStore %vec_var %vec", + }, + // duplicated constants + { + "%vec_duplicate = OpConstantComposite %v4int %signed_1 %signed_2 %signed_3 %signed_4", + }, + // use duplicated constants in main + { + "%vec_var = OpVariable %_pf_v4int Function", + "OpStore %vec_var %vec_duplicate", + }, + // remapped names + { + "OpName %vec \"vec_duplicate\"", + } + }, + // duplicate vector built from duplicated ids. + { + // preserved constants + { + "%signed_1 = OpConstant %int 1", + "%signed_2 = OpConstant %int 2", + "%signed_3 = OpConstant %int 3", + "%signed_4 = OpConstant %int 4", + "%vec = OpConstantComposite %v4int %signed_1 %signed_2 %signed_3 %signed_4", + }, + // use preserved constants in main + { + "%vec_var = OpVariable %_pf_v4int Function", + "OpStore %vec_var %vec", + }, + // duplicated constants + { + "%signed_3_duplicate = OpConstant %int 3", + "%signed_4_duplicate = OpConstant %int 4", + "%vec_duplicate = OpConstantComposite %v4int %signed_1 %signed_2 %signed_3_duplicate %signed_4_duplicate", + }, + // use duplicated constants in main + { + "%vec_var = OpVariable %_pf_v4int Function", + "OpStore %vec_var %vec_duplicate", + }, + // remapped names + { + "OpName %signed_3 \"signed_3_duplicate\"", + "OpName %signed_4 \"signed_4_duplicate\"", + "OpName %vec \"vec_duplicate\"", + }, + }, + // flat struct + { + // preserved constants + { + "%bool_true = OpConstantTrue %bool", + "%signed_1 = OpConstant %int 1", + "%float_1 = OpConstant %float 1", + "%double_1 = OpConstant %double 1", + "%s = OpConstantComposite %flat_struct %bool_true %signed_1 %float_1 %double_1", + }, + // use preserved constants in main + { + "%s_var = OpVariable %_pf_flat_struct Function", + "OpStore %s_var %s", + }, + // duplicated constants + { + "%float_1_duplicate = OpConstant %float 1", + "%double_1_duplicate = OpConstant %double 1", + "%s_duplicate = OpConstantComposite %flat_struct %bool_true %signed_1 %float_1_duplicate %double_1_duplicate", + }, + // use duplicated constants in main + { + "%s_var = OpVariable %_pf_flat_struct Function", + "OpStore %s_var %s_duplicate", + }, + // remapped names + { + "OpName %float_1 \"float_1_duplicate\"", + "OpName %double_1 \"double_1_duplicate\"", + "OpName %s \"s_duplicate\"", + }, + }, + // nested struct + { + // preserved constants + { + "%bool_true = OpConstantTrue %bool", + "%signed_1 = OpConstant %int 1", + "%float_1 = OpConstant %float 1", + "%double_1 = OpConstant %double 1", + "%inner = OpConstantComposite %inner_struct %bool_true %float_1", + "%outer = OpConstantComposite %outer_struct %inner %signed_1 %double_1", + }, + // use preserved constants in main + { + "%outer_var = OpVariable %_pf_outer_struct Function", + "OpStore %outer_var %outer", + }, + // duplicated constants + { + "%float_1_duplicate = OpConstant %float 1", + "%double_1_duplicate = OpConstant %double 1", + "%inner_duplicate = OpConstantComposite %inner_struct %bool_true %float_1_duplicate", + "%outer_duplicate = OpConstantComposite %outer_struct %inner_duplicate %signed_1 %double_1_duplicate", + }, + // use duplicated constants in main + { + "%outer_var = OpVariable %_pf_outer_struct Function", + "OpStore %outer_var %outer_duplicate", + }, + // remapped names + { + "OpName %float_1 \"float_1_duplicate\"", + "OpName %double_1 \"double_1_duplicate\"", + "OpName %inner \"inner_duplicate\"", + "OpName %outer \"outer_duplicate\"", + }, + }, + // composite type null constants. Null constants and zero-valued + // constants should not be used to replace each other. + { + // preserved constants + { + "%bool_zero = OpConstantFalse %bool", + "%float_zero = OpConstant %float 0", + "%int_null = OpConstantNull %int", + "%double_null = OpConstantNull %double", + // inner_struct type null constant. + "%null_inner = OpConstantNull %inner_struct", + // zero-valued composite constant built from zero-valued constant + // component. inner_zero should not be replace by null_inner. + "%inner_zero = OpConstantComposite %inner_struct %bool_zero %float_zero", + // zero-valued composite contant built from zero-valued constants + // and null constants. + "%outer_zero = OpConstantComposite %outer_struct %inner_zero %int_null %double_null", + // outer_struct type null constant, it should not be replaced by + // outer_zero. + "%null_outer = OpConstantNull %outer_struct", + }, + // use preserved constants in main + { + "%inner_var = OpVariable %_pf_inner_struct Function", + "OpStore %inner_var %inner_zero", + "OpStore %inner_var %null_inner", + "%outer_var = OpVariable %_pf_outer_struct Function", + "OpStore %outer_var %outer_zero", + "OpStore %outer_var %null_outer", + }, + // duplicated constants + { + "%null_inner_dup = OpConstantNull %inner_struct", + "%null_outer_dup = OpConstantNull %outer_struct", + "%inner_zero_dup = OpConstantComposite %inner_struct %bool_zero %float_zero", + "%outer_zero_dup = OpConstantComposite %outer_struct %inner_zero_dup %int_null %double_null", + }, + // use duplicated constants in main + { + "%inner_var = OpVariable %_pf_inner_struct Function", + "OpStore %inner_var %inner_zero_dup", + "OpStore %inner_var %null_inner_dup", + "%outer_var = OpVariable %_pf_outer_struct Function", + "OpStore %outer_var %outer_zero_dup", + "OpStore %outer_var %null_outer_dup", + }, + // remapped names + { + "OpName %null_inner \"null_inner_dup\"", + "OpName %null_outer \"null_outer_dup\"", + "OpName %inner_zero \"inner_zero_dup\"", + "OpName %outer_zero \"outer_zero_dup\"", + }, + }, + // Spec Constants with SpecId decoration should be skipped. + { + // preserved constants + { + // Assembly builder will add OpDecorate SpecId instruction for the + // following spec constant instructions automatically. + "%spec_bool_1 = OpSpecConstantTrue %bool", + "%spec_bool_2 = OpSpecConstantTrue %bool", + "%spec_int_1 = OpSpecConstant %int 1", + "%spec_int_2 = OpSpecConstant %int 1", + }, + // use preserved constants in main + { + "%bool_var = OpVariable %_pf_bool Function", + "OpStore %bool_var %spec_bool_1", + "OpStore %bool_var %spec_bool_2", + "%int_var = OpVariable %_pf_int Function", + "OpStore %int_var %spec_int_1", + "OpStore %int_var %spec_int_2", + }, + // duplicated constants. No duplicated instruction to remove in this + // case. + {}, + // use duplicated constants in main. Same as the above 'use preserved + // constants in main' defined above, as no instruction should be + // removed in this case. + { + "%bool_var = OpVariable %_pf_bool Function", + "OpStore %bool_var %spec_bool_1", + "OpStore %bool_var %spec_bool_2", + "%int_var = OpVariable %_pf_int Function", + "OpStore %int_var %spec_int_1", + "OpStore %int_var %spec_int_2", + }, + // remapped names. No duplicated instruction removed, so this is + // empty. + {} + }, + // spec constant composite + { + // preserved constants + { + "%spec_bool_true = OpSpecConstantTrue %bool", + "%spec_signed_1 = OpSpecConstant %int 1", + "%float_1 = OpConstant %float 1", + "%double_1 = OpConstant %double 1", + "%spec_inner = OpSpecConstantComposite %inner_struct %spec_bool_true %float_1", + "%spec_outer = OpSpecConstantComposite %outer_struct %spec_inner %spec_signed_1 %double_1", + "%spec_vec2 = OpSpecConstantComposite %v2float %float_1 %float_1", + }, + // use preserved constants in main + { + "%outer_var = OpVariable %_pf_outer_struct Function", + "OpStore %outer_var %spec_outer", + "%v2float_var = OpVariable %_pf_v2float Function", + "OpStore %v2float_var %spec_vec2", + }, + // duplicated constants + { + "%float_1_duplicate = OpConstant %float 1", + "%double_1_duplicate = OpConstant %double 1", + "%spec_inner_duplicate = OpSpecConstantComposite %inner_struct %spec_bool_true %float_1_duplicate", + "%spec_outer_duplicate = OpSpecConstantComposite %outer_struct %spec_inner_duplicate %spec_signed_1 %double_1_duplicate", + "%spec_vec2_duplicate = OpSpecConstantComposite %v2float %float_1 %float_1_duplicate", + }, + // use duplicated constants in main + { + "%outer_var = OpVariable %_pf_outer_struct Function", + "OpStore %outer_var %spec_outer_duplicate", + "%v2float_var = OpVariable %_pf_v2float Function", + "OpStore %v2float_var %spec_vec2_duplicate", + }, + // remapped names + { + "OpName %float_1 \"float_1_duplicate\"", + "OpName %double_1 \"double_1_duplicate\"", + "OpName %spec_inner \"spec_inner_duplicate\"", + "OpName %spec_outer \"spec_outer_duplicate\"", + "OpName %spec_vec2 \"spec_vec2_duplicate\"", + }, + }, + // spec constant op with int scalar + { + // preserved constants + { + "%spec_signed_1 = OpSpecConstant %int 1", + "%spec_signed_2 = OpSpecConstant %int 2", + "%spec_signed_add = OpSpecConstantOp %int IAdd %spec_signed_1 %spec_signed_2", + }, + // use preserved constants in main + { + "%int_var = OpVariable %_pf_int Function", + "OpStore %int_var %spec_signed_add", + }, + // duplicated constants + { + "%spec_signed_add_duplicate = OpSpecConstantOp %int IAdd %spec_signed_1 %spec_signed_2", + }, + // use duplicated contants in main + { + "%int_var = OpVariable %_pf_int Function", + "OpStore %int_var %spec_signed_add_duplicate", + }, + // remapped names + { + "OpName %spec_signed_add \"spec_signed_add_duplicate\"", + }, + }, + // spec constant op composite extract + { + // preserved constants + { + "%float_1 = OpConstant %float 1", + "%spec_vec2 = OpSpecConstantComposite %v2float %float_1 %float_1", + "%spec_extract = OpSpecConstantOp %float CompositeExtract %spec_vec2 1", + }, + // use preserved constants in main + { + "%float_var = OpVariable %_pf_float Function", + "OpStore %float_var %spec_extract", + }, + // duplicated constants + { + "%spec_extract_duplicate = OpSpecConstantOp %float CompositeExtract %spec_vec2 1", + }, + // use duplicated constants in main + { + "%float_var = OpVariable %_pf_float Function", + "OpStore %float_var %spec_extract_duplicate", + }, + // remapped names + { + "OpName %spec_extract \"spec_extract_duplicate\"", + }, + }, + // spec constant op vector shuffle + { + // preserved constants + { + "%float_1 = OpConstant %float 1", + "%float_2 = OpConstant %float 2", + "%spec_vec2_1 = OpSpecConstantComposite %v2float %float_1 %float_1", + "%spec_vec2_2 = OpSpecConstantComposite %v2float %float_2 %float_2", + "%spec_vector_shuffle = OpSpecConstantOp %v2float VectorShuffle %spec_vec2_1 %spec_vec2_2 1 2", + }, + // use preserved constants in main + { + "%v2float_var = OpVariable %_pf_v2float Function", + "OpStore %v2float_var %spec_vector_shuffle", + }, + // duplicated constants + { + "%spec_vector_shuffle_duplicate = OpSpecConstantOp %v2float VectorShuffle %spec_vec2_1 %spec_vec2_2 1 2", + }, + // use duplicated constants in main + { + "%v2float_var = OpVariable %_pf_v2float Function", + "OpStore %v2float_var %spec_vector_shuffle_duplicate", + }, + // remapped names + { + "OpName %spec_vector_shuffle \"spec_vector_shuffle_duplicate\"", + }, + }, + // long dependency chain + { + // preserved constants + { + "%array_size = OpConstant %int 4", + "%type_arr_int_4 = OpTypeArray %int %array_size", + "%signed_0 = OpConstant %int 100", + "%signed_1 = OpConstant %int 1", + "%signed_2 = OpSpecConstantOp %int IAdd %signed_0 %signed_1", + "%signed_3 = OpSpecConstantOp %int ISub %signed_0 %signed_2", + "%signed_4 = OpSpecConstantOp %int IAdd %signed_0 %signed_3", + "%signed_5 = OpSpecConstantOp %int ISub %signed_0 %signed_4", + "%signed_6 = OpSpecConstantOp %int IAdd %signed_0 %signed_5", + "%signed_7 = OpSpecConstantOp %int ISub %signed_0 %signed_6", + "%signed_8 = OpSpecConstantOp %int IAdd %signed_0 %signed_7", + "%signed_9 = OpSpecConstantOp %int ISub %signed_0 %signed_8", + "%signed_10 = OpSpecConstantOp %int IAdd %signed_0 %signed_9", + "%signed_11 = OpSpecConstantOp %int ISub %signed_0 %signed_10", + "%signed_12 = OpSpecConstantOp %int IAdd %signed_0 %signed_11", + "%signed_13 = OpSpecConstantOp %int ISub %signed_0 %signed_12", + "%signed_14 = OpSpecConstantOp %int IAdd %signed_0 %signed_13", + "%signed_15 = OpSpecConstantOp %int ISub %signed_0 %signed_14", + "%signed_16 = OpSpecConstantOp %int ISub %signed_0 %signed_15", + "%signed_17 = OpSpecConstantOp %int IAdd %signed_0 %signed_16", + "%signed_18 = OpSpecConstantOp %int ISub %signed_0 %signed_17", + "%signed_19 = OpSpecConstantOp %int IAdd %signed_0 %signed_18", + "%signed_20 = OpSpecConstantOp %int ISub %signed_0 %signed_19", + "%signed_vec_a = OpSpecConstantComposite %v2int %signed_18 %signed_19", + "%signed_vec_b = OpSpecConstantOp %v2int IMul %signed_vec_a %signed_vec_a", + "%signed_21 = OpSpecConstantOp %int CompositeExtract %signed_vec_b 0", + "%signed_array = OpConstantComposite %type_arr_int_4 %signed_20 %signed_20 %signed_21 %signed_21", + "%signed_22 = OpSpecConstantOp %int CompositeExtract %signed_array 0", + }, + // use preserved constants in main + { + "%int_var = OpVariable %_pf_int Function", + "OpStore %int_var %signed_22", + }, + // duplicated constants + { + "%signed_0_dup = OpConstant %int 100", + "%signed_1_dup = OpConstant %int 1", + "%signed_2_dup = OpSpecConstantOp %int IAdd %signed_0_dup %signed_1_dup", + "%signed_3_dup = OpSpecConstantOp %int ISub %signed_0_dup %signed_2_dup", + "%signed_4_dup = OpSpecConstantOp %int IAdd %signed_0_dup %signed_3_dup", + "%signed_5_dup = OpSpecConstantOp %int ISub %signed_0_dup %signed_4_dup", + "%signed_6_dup = OpSpecConstantOp %int IAdd %signed_0_dup %signed_5_dup", + "%signed_7_dup = OpSpecConstantOp %int ISub %signed_0_dup %signed_6_dup", + "%signed_8_dup = OpSpecConstantOp %int IAdd %signed_0_dup %signed_7_dup", + "%signed_9_dup = OpSpecConstantOp %int ISub %signed_0_dup %signed_8_dup", + "%signed_10_dup = OpSpecConstantOp %int IAdd %signed_0_dup %signed_9_dup", + "%signed_11_dup = OpSpecConstantOp %int ISub %signed_0_dup %signed_10_dup", + "%signed_12_dup = OpSpecConstantOp %int IAdd %signed_0_dup %signed_11_dup", + "%signed_13_dup = OpSpecConstantOp %int ISub %signed_0_dup %signed_12_dup", + "%signed_14_dup = OpSpecConstantOp %int IAdd %signed_0_dup %signed_13_dup", + "%signed_15_dup = OpSpecConstantOp %int ISub %signed_0_dup %signed_14_dup", + "%signed_16_dup = OpSpecConstantOp %int ISub %signed_0_dup %signed_15_dup", + "%signed_17_dup = OpSpecConstantOp %int IAdd %signed_0_dup %signed_16_dup", + "%signed_18_dup = OpSpecConstantOp %int ISub %signed_0_dup %signed_17_dup", + "%signed_19_dup = OpSpecConstantOp %int IAdd %signed_0_dup %signed_18_dup", + "%signed_20_dup = OpSpecConstantOp %int ISub %signed_0_dup %signed_19_dup", + "%signed_vec_a_dup = OpSpecConstantComposite %v2int %signed_18_dup %signed_19_dup", + "%signed_vec_b_dup = OpSpecConstantOp %v2int IMul %signed_vec_a_dup %signed_vec_a_dup", + "%signed_21_dup = OpSpecConstantOp %int CompositeExtract %signed_vec_b_dup 0", + "%signed_array_dup = OpConstantComposite %type_arr_int_4 %signed_20_dup %signed_20_dup %signed_21_dup %signed_21_dup", + "%signed_22_dup = OpSpecConstantOp %int CompositeExtract %signed_array_dup 0", + }, + // use duplicated constants in main + { + "%int_var = OpVariable %_pf_int Function", + "OpStore %int_var %signed_22_dup", + }, + // remapped names + { + "OpName %signed_0 \"signed_0_dup\"", + "OpName %signed_1 \"signed_1_dup\"", + "OpName %signed_2 \"signed_2_dup\"", + "OpName %signed_3 \"signed_3_dup\"", + "OpName %signed_4 \"signed_4_dup\"", + "OpName %signed_5 \"signed_5_dup\"", + "OpName %signed_6 \"signed_6_dup\"", + "OpName %signed_7 \"signed_7_dup\"", + "OpName %signed_8 \"signed_8_dup\"", + "OpName %signed_9 \"signed_9_dup\"", + "OpName %signed_10 \"signed_10_dup\"", + "OpName %signed_11 \"signed_11_dup\"", + "OpName %signed_12 \"signed_12_dup\"", + "OpName %signed_13 \"signed_13_dup\"", + "OpName %signed_14 \"signed_14_dup\"", + "OpName %signed_15 \"signed_15_dup\"", + "OpName %signed_16 \"signed_16_dup\"", + "OpName %signed_17 \"signed_17_dup\"", + "OpName %signed_18 \"signed_18_dup\"", + "OpName %signed_19 \"signed_19_dup\"", + "OpName %signed_20 \"signed_20_dup\"", + "OpName %signed_vec_a \"signed_vec_a_dup\"", + "OpName %signed_vec_b \"signed_vec_b_dup\"", + "OpName %signed_21 \"signed_21_dup\"", + "OpName %signed_array \"signed_array_dup\"", + "OpName %signed_22 \"signed_22_dup\"", + }, + }, + // clang-format on + }))); + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/utils_test.cpp b/3rdparty/spirv-tools/test/opt/utils_test.cpp new file mode 100644 index 000000000..e20e6856f --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/utils_test.cpp @@ -0,0 +1,106 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "pass_utils.h" + +namespace { + +using namespace spvtools; + +TEST(JoinAllInsts, Cases) { + EXPECT_EQ("", JoinAllInsts({})); + EXPECT_EQ("a\n", JoinAllInsts({"a"})); + EXPECT_EQ("a\nb\n", JoinAllInsts({"a", "b"})); + EXPECT_EQ("a\nb\nc\n", JoinAllInsts({"a", "b", "c"})); + EXPECT_EQ("hello,\nworld!\n\n\n", JoinAllInsts({"hello,", "world!", "\n"})); +} + +TEST(JoinNonDebugInsts, Cases) { + EXPECT_EQ("", JoinNonDebugInsts({})); + EXPECT_EQ("a\n", JoinNonDebugInsts({"a"})); + EXPECT_EQ("", JoinNonDebugInsts({"OpName"})); + EXPECT_EQ("a\nb\n", JoinNonDebugInsts({"a", "b"})); + EXPECT_EQ("", JoinNonDebugInsts({"OpName", "%1 = OpString \"42\""})); + EXPECT_EQ("Opstring\n", JoinNonDebugInsts({"OpName", "Opstring"})); + EXPECT_EQ("the only remaining string\n", + JoinNonDebugInsts( + {"OpSourceContinued", "OpSource", "OpSourceExtension", + "lgtm OpName", "hello OpMemberName", "this is a OpString", + "lonely OpLine", "happy OpNoLine", "OpModuleProcessed", + "the only remaining string"})); +} + +namespace { +struct SubstringReplacementTestCase { + const char* orig_str; + const char* find_substr; + const char* replace_substr; + const char* expected_str; + bool replace_should_succeed; +}; +} // namespace +using FindAndReplaceTest = + ::testing::TestWithParam; + +TEST_P(FindAndReplaceTest, SubstringReplacement) { + auto process = std::string(GetParam().orig_str); + EXPECT_EQ(GetParam().replace_should_succeed, + FindAndReplace(&process, GetParam().find_substr, + GetParam().replace_substr)) + << "Original string: " << GetParam().orig_str + << " replace: " << GetParam().find_substr + << " to: " << GetParam().replace_substr + << " should returns: " << GetParam().replace_should_succeed; + EXPECT_STREQ(GetParam().expected_str, process.c_str()) + << "Original string: " << GetParam().orig_str + << " replace: " << GetParam().find_substr + << " to: " << GetParam().replace_substr + << " expected string: " << GetParam().expected_str; +} + +INSTANTIATE_TEST_CASE_P( + SubstringReplacement, FindAndReplaceTest, + ::testing::ValuesIn(std::vector({ + // orig string, find substring, replace substring, expected string, + // replacement happened + {"", "", "", "", false}, + {"", "b", "", "", false}, + {"", "", "c", "", false}, + {"", "a", "b", "", false}, + + {"a", "", "c", "a", false}, + {"a", "b", "c", "a", false}, + {"a", "b", "", "a", false}, + {"a", "a", "", "", true}, + {"a", "a", "b", "b", true}, + + {"ab", "a", "b", "bb", true}, + {"ab", "a", "", "b", true}, + {"ab", "b", "", "a", true}, + {"ab", "ab", "", "", true}, + {"ab", "ab", "cd", "cd", true}, + {"bc", "abc", "efg", "bc", false}, + + {"abc", "ab", "bc", "bcc", true}, + {"abc", "ab", "", "c", true}, + {"abc", "bc", "", "a", true}, + {"abc", "bc", "d", "ad", true}, + {"abc", "a", "123", "123bc", true}, + {"abc", "ab", "a", "ac", true}, + {"abc", "a", "aab", "aabbc", true}, + {"abc", "abcd", "efg", "abc", false}, + }))); +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/value_table_test.cpp b/3rdparty/spirv-tools/test/opt/value_table_test.cpp new file mode 100644 index 000000000..4f42c816e --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/value_table_test.cpp @@ -0,0 +1,588 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "opt/value_number_table.h" + +#include "assembly_builder.h" +#include "gmock/gmock.h" +#include "opt/build_module.h" +#include "pass_fixture.h" + +namespace { + +using namespace spvtools; + +using ::testing::HasSubstr; +using ::testing::MatchesRegex; + +using ValueTableTest = PassTest<::testing::Test>; + +TEST_F(ValueTableTest, SameInstructionSameValue) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Function %5 + %2 = OpFunction %3 None %4 + %7 = OpLabel + %8 = OpVariable %6 Function + %9 = OpLoad %5 %8 + %10 = OpFAdd %5 %9 %9 + OpReturn + OpFunctionEnd + )"; + auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + opt::ValueNumberTable vtable(context.get()); + ir::Instruction* inst = context->get_def_use_mgr()->GetDef(10); + EXPECT_EQ(vtable.GetValueNumber(inst), vtable.GetValueNumber(inst)); +} + +TEST_F(ValueTableTest, DifferentInstructionSameValue) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Function %5 + %2 = OpFunction %3 None %4 + %7 = OpLabel + %8 = OpVariable %6 Function + %9 = OpLoad %5 %8 + %10 = OpFAdd %5 %9 %9 + %11 = OpFAdd %5 %9 %9 + OpReturn + OpFunctionEnd + )"; + auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + opt::ValueNumberTable vtable(context.get()); + ir::Instruction* inst1 = context->get_def_use_mgr()->GetDef(10); + ir::Instruction* inst2 = context->get_def_use_mgr()->GetDef(11); + EXPECT_EQ(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2)); +} + +TEST_F(ValueTableTest, SameValueDifferentBlock) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Function %5 + %2 = OpFunction %3 None %4 + %7 = OpLabel + %8 = OpVariable %6 Function + %9 = OpLoad %5 %8 + %10 = OpFAdd %5 %9 %9 + OpBranch %11 + %11 = OpLabel + %12 = OpFAdd %5 %9 %9 + OpReturn + OpFunctionEnd + )"; + auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + opt::ValueNumberTable vtable(context.get()); + ir::Instruction* inst1 = context->get_def_use_mgr()->GetDef(10); + ir::Instruction* inst2 = context->get_def_use_mgr()->GetDef(12); + EXPECT_EQ(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2)); +} + +TEST_F(ValueTableTest, DifferentValue) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Function %5 + %2 = OpFunction %3 None %4 + %7 = OpLabel + %8 = OpVariable %6 Function + %9 = OpLoad %5 %8 + %10 = OpFAdd %5 %9 %9 + %11 = OpFAdd %5 %9 %10 + OpReturn + OpFunctionEnd + )"; + auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + opt::ValueNumberTable vtable(context.get()); + ir::Instruction* inst1 = context->get_def_use_mgr()->GetDef(10); + ir::Instruction* inst2 = context->get_def_use_mgr()->GetDef(11); + EXPECT_NE(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2)); +} + +TEST_F(ValueTableTest, DifferentValueDifferentBlock) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Function %5 + %2 = OpFunction %3 None %4 + %7 = OpLabel + %8 = OpVariable %6 Function + %9 = OpLoad %5 %8 + %10 = OpFAdd %5 %9 %9 + OpBranch %11 + %11 = OpLabel + %12 = OpFAdd %5 %9 %10 + OpReturn + OpFunctionEnd + )"; + auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + opt::ValueNumberTable vtable(context.get()); + ir::Instruction* inst1 = context->get_def_use_mgr()->GetDef(10); + ir::Instruction* inst2 = context->get_def_use_mgr()->GetDef(12); + EXPECT_NE(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2)); +} + +TEST_F(ValueTableTest, SameLoad) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Function %5 + %2 = OpFunction %3 None %4 + %7 = OpLabel + %8 = OpVariable %6 Function + %9 = OpLoad %5 %8 + OpReturn + OpFunctionEnd + )"; + auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + opt::ValueNumberTable vtable(context.get()); + ir::Instruction* inst = context->get_def_use_mgr()->GetDef(9); + EXPECT_EQ(vtable.GetValueNumber(inst), vtable.GetValueNumber(inst)); +} + +// Two different loads, even from the same memory, must given different value +// numbers if the memory is not read-only. +TEST_F(ValueTableTest, DifferentFunctionLoad) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Function %5 + %2 = OpFunction %3 None %4 + %7 = OpLabel + %8 = OpVariable %6 Function + %9 = OpLoad %5 %8 + %10 = OpLoad %5 %8 + OpReturn + OpFunctionEnd + )"; + auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + opt::ValueNumberTable vtable(context.get()); + ir::Instruction* inst1 = context->get_def_use_mgr()->GetDef(9); + ir::Instruction* inst2 = context->get_def_use_mgr()->GetDef(10); + EXPECT_NE(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2)); +} + +TEST_F(ValueTableTest, DifferentUniformLoad) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Uniform %5 + %8 = OpVariable %6 Uniform + %2 = OpFunction %3 None %4 + %7 = OpLabel + %9 = OpLoad %5 %8 + %10 = OpLoad %5 %8 + OpReturn + OpFunctionEnd + )"; + auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + opt::ValueNumberTable vtable(context.get()); + ir::Instruction* inst1 = context->get_def_use_mgr()->GetDef(9); + ir::Instruction* inst2 = context->get_def_use_mgr()->GetDef(10); + EXPECT_EQ(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2)); +} + +TEST_F(ValueTableTest, DifferentInputLoad) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Input %5 + %8 = OpVariable %6 Input + %2 = OpFunction %3 None %4 + %7 = OpLabel + %9 = OpLoad %5 %8 + %10 = OpLoad %5 %8 + OpReturn + OpFunctionEnd + )"; + auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + opt::ValueNumberTable vtable(context.get()); + ir::Instruction* inst1 = context->get_def_use_mgr()->GetDef(9); + ir::Instruction* inst2 = context->get_def_use_mgr()->GetDef(10); + EXPECT_EQ(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2)); +} + +TEST_F(ValueTableTest, DifferentUniformConstantLoad) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer UniformConstant %5 + %8 = OpVariable %6 UniformConstant + %2 = OpFunction %3 None %4 + %7 = OpLabel + %9 = OpLoad %5 %8 + %10 = OpLoad %5 %8 + OpReturn + OpFunctionEnd + )"; + auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + opt::ValueNumberTable vtable(context.get()); + ir::Instruction* inst1 = context->get_def_use_mgr()->GetDef(9); + ir::Instruction* inst2 = context->get_def_use_mgr()->GetDef(10); + EXPECT_EQ(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2)); +} + +TEST_F(ValueTableTest, DifferentPushConstantLoad) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer PushConstant %5 + %8 = OpVariable %6 PushConstant + %2 = OpFunction %3 None %4 + %7 = OpLabel + %9 = OpLoad %5 %8 + %10 = OpLoad %5 %8 + OpReturn + OpFunctionEnd + )"; + auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + opt::ValueNumberTable vtable(context.get()); + ir::Instruction* inst1 = context->get_def_use_mgr()->GetDef(9); + ir::Instruction* inst2 = context->get_def_use_mgr()->GetDef(10); + EXPECT_EQ(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2)); +} + +TEST_F(ValueTableTest, SameCall) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypeFunction %5 + %7 = OpTypePointer Function %5 + %8 = OpVariable %7 Private + %2 = OpFunction %3 None %4 + %9 = OpLabel + %10 = OpFunctionCall %5 %11 + OpReturn + OpFunctionEnd + %11 = OpFunction %5 None %6 + %12 = OpLabel + %13 = OpLoad %5 %8 + OpReturnValue %13 + OpFunctionEnd + )"; + auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + opt::ValueNumberTable vtable(context.get()); + ir::Instruction* inst = context->get_def_use_mgr()->GetDef(10); + EXPECT_EQ(vtable.GetValueNumber(inst), vtable.GetValueNumber(inst)); +} + +// Function calls should be given a new value number, even if they are the same. +TEST_F(ValueTableTest, DifferentCall) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypeFunction %5 + %7 = OpTypePointer Function %5 + %8 = OpVariable %7 Private + %2 = OpFunction %3 None %4 + %9 = OpLabel + %10 = OpFunctionCall %5 %11 + %12 = OpFunctionCall %5 %11 + OpReturn + OpFunctionEnd + %11 = OpFunction %5 None %6 + %13 = OpLabel + %14 = OpLoad %5 %8 + OpReturnValue %14 + OpFunctionEnd + )"; + auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + opt::ValueNumberTable vtable(context.get()); + ir::Instruction* inst1 = context->get_def_use_mgr()->GetDef(10); + ir::Instruction* inst2 = context->get_def_use_mgr()->GetDef(12); + EXPECT_NE(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2)); +} + +// It is possible to have two instruction that compute the same numerical value, +// but with different types. They should have different value numbers. +TEST_F(ValueTableTest, DifferentTypes) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeInt 32 0 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %5 + %2 = OpFunction %3 None %4 + %8 = OpLabel + %9 = OpVariable %7 Function + %10 = OpLoad %5 %9 + %11 = OpIAdd %5 %10 %10 + %12 = OpIAdd %6 %10 %10 + OpReturn + OpFunctionEnd + )"; + auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + opt::ValueNumberTable vtable(context.get()); + ir::Instruction* inst1 = context->get_def_use_mgr()->GetDef(11); + ir::Instruction* inst2 = context->get_def_use_mgr()->GetDef(12); + EXPECT_NE(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2)); +} + +TEST_F(ValueTableTest, CopyObject) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Function %5 + %2 = OpFunction %3 None %4 + %7 = OpLabel + %8 = OpVariable %6 Function + %9 = OpLoad %5 %8 + %10 = OpCopyObject %5 %9 + OpReturn + OpFunctionEnd + )"; + auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + opt::ValueNumberTable vtable(context.get()); + ir::Instruction* inst1 = context->get_def_use_mgr()->GetDef(9); + ir::Instruction* inst2 = context->get_def_use_mgr()->GetDef(10); + EXPECT_EQ(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) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Uniform %5 + %7 = OpTypeBool + %8 = OpConstantTrue %7 + %9 = OpVariable %6 Uniform + %2 = OpFunction %3 None %4 + %10 = OpLabel + OpBranchConditional %8 %11 %12 + %11 = OpLabel + %13 = OpLoad %5 %9 + OpBranch %14 + %12 = OpLabel + %15 = OpLoad %5 %9 + OpBranch %14 + %14 = OpLabel + %16 = OpPhi %5 %13 %11 %15 %12 + OpReturn + OpFunctionEnd + )"; + auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + opt::ValueNumberTable vtable(context.get()); + ir::Instruction* inst1 = context->get_def_use_mgr()->GetDef(13); + ir::Instruction* inst2 = context->get_def_use_mgr()->GetDef(15); + ir::Instruction* phi = context->get_def_use_mgr()->GetDef(16); + EXPECT_EQ(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2)); + EXPECT_EQ(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) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Uniform %5 + %7 = OpTypeBool + %8 = OpConstantTrue %7 + %9 = OpVariable %6 Uniform + %10 = OpVariable %6 Uniform + %2 = OpFunction %3 None %4 + %11 = OpLabel + OpBranchConditional %8 %12 %13 + %12 = OpLabel + %14 = OpLoad %5 %9 + OpBranch %15 + %13 = OpLabel + %16 = OpLoad %5 %10 + OpBranch %15 + %15 = OpLabel + %17 = OpPhi %14 %12 %16 %13 + OpReturn + OpFunctionEnd + )"; + auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + opt::ValueNumberTable vtable(context.get()); + ir::Instruction* inst1 = context->get_def_use_mgr()->GetDef(14); + ir::Instruction* inst2 = context->get_def_use_mgr()->GetDef(16); + ir::Instruction* phi = context->get_def_use_mgr()->GetDef(17); + EXPECT_NE(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2)); + EXPECT_NE(vtable.GetValueNumber(inst1), vtable.GetValueNumber(phi)); + EXPECT_NE(vtable.GetValueNumber(inst2), vtable.GetValueNumber(phi)); +} + +// Test that a phi node in a loop header gets a new value because one of its +// inputs comes from later in the loop. +TEST_F(ValueTableTest, PhiLoopTest) { + 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 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeFloat 32 + %6 = OpTypePointer Uniform %5 + %7 = OpTypeBool + %8 = OpConstantTrue %7 + %9 = OpVariable %6 Uniform + %10 = OpVariable %6 Uniform + %2 = OpFunction %3 None %4 + %11 = OpLabel + %12 = OpLoad %5 %9 + OpSelectionMerge %13 None + OpBranchConditional %8 %14 %13 + %14 = OpLabel + %15 = OpPhi %5 %12 %11 %16 %14 + %16 = OpLoad %5 %9 + OpLoopMerge %17 %14 None + OpBranchConditional %8 %14 %17 + %17 = OpLabel + OpBranch %13 + %13 = OpLabel + %18 = OpPhi %5 %12 %11 %16 %17 + OpReturn + OpFunctionEnd + )"; + auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + opt::ValueNumberTable vtable(context.get()); + ir::Instruction* inst1 = context->get_def_use_mgr()->GetDef(12); + ir::Instruction* inst2 = context->get_def_use_mgr()->GetDef(16); + EXPECT_EQ(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2)); + + ir::Instruction* phi1 = context->get_def_use_mgr()->GetDef(15); + EXPECT_NE(vtable.GetValueNumber(inst1), vtable.GetValueNumber(phi1)); + + ir::Instruction* phi2 = context->get_def_use_mgr()->GetDef(18); + EXPECT_EQ(vtable.GetValueNumber(inst1), vtable.GetValueNumber(phi2)); + EXPECT_NE(vtable.GetValueNumber(phi1), vtable.GetValueNumber(phi2)); +} +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/opt/workaround1209_test.cpp b/3rdparty/spirv-tools/test/opt/workaround1209_test.cpp new file mode 100644 index 000000000..5708b7e6a --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/workaround1209_test.cpp @@ -0,0 +1,421 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "assembly_builder.h" +#include "gmock/gmock.h" +#include "pass_fixture.h" +#include "pass_utils.h" + +#include +#include +#include +#include +#include + +namespace { + +using namespace spvtools; + +using Workaround1209Test = PassTest<::testing::Test>; + +#ifdef SPIRV_EFFCEE +TEST_F(Workaround1209Test, RemoveOpUnreachableInLoop) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" %texcoord %gl_VertexIndex %_ + OpSource GLSL 400 + OpSourceExtension "GL_ARB_separate_shader_objects" + OpSourceExtension "GL_ARB_shading_language_420pack" + OpName %main "main" + OpName %texcoord "texcoord" + OpName %buf "buf" + OpMemberName %buf 0 "MVP" + OpMemberName %buf 1 "position" + OpMemberName %buf 2 "attr" + OpName %ubuf "ubuf" + OpName %gl_VertexIndex "gl_VertexIndex" + OpName %gl_PerVertex "gl_PerVertex" + OpMemberName %gl_PerVertex 0 "gl_Position" + OpName %_ "" + OpDecorate %texcoord Location 0 + OpDecorate %_arr_v4float_uint_72 ArrayStride 16 + OpDecorate %_arr_v4float_uint_72_0 ArrayStride 16 + OpMemberDecorate %buf 0 ColMajor + OpMemberDecorate %buf 0 Offset 0 + OpMemberDecorate %buf 0 MatrixStride 16 + OpMemberDecorate %buf 1 Offset 64 + OpMemberDecorate %buf 2 Offset 1216 + OpDecorate %buf Block + OpDecorate %ubuf DescriptorSet 0 + OpDecorate %ubuf Binding 0 + OpDecorate %gl_VertexIndex BuiltIn VertexIndex + OpMemberDecorate %gl_PerVertex 0 BuiltIn Position + OpDecorate %gl_PerVertex Block + %void = OpTypeVoid + %12 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %texcoord = OpVariable %_ptr_Output_v4float Output +%mat4v4float = OpTypeMatrix %v4float 4 + %uint = OpTypeInt 32 0 + %uint_72 = OpConstant %uint 72 +%_arr_v4float_uint_72 = OpTypeArray %v4float %uint_72 +%_arr_v4float_uint_72_0 = OpTypeArray %v4float %uint_72 + %buf = OpTypeStruct %mat4v4float %_arr_v4float_uint_72 %_arr_v4float_uint_72_0 +%_ptr_Uniform_buf = OpTypePointer Uniform %buf + %ubuf = OpVariable %_ptr_Uniform_buf Uniform + %int = OpTypeInt 32 1 + %int_2 = OpConstant %int 2 +%_ptr_Input_int = OpTypePointer Input %int +%gl_VertexIndex = OpVariable %_ptr_Input_int Input +%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float +%gl_PerVertex = OpTypeStruct %v4float +%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex + %_ = OpVariable %_ptr_Output_gl_PerVertex Output + %int_0 = OpConstant %int 0 + %int_1 = OpConstant %int 1 + %float_1 = OpConstant %float 1 + %28 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 + %main = OpFunction %void None %12 + %29 = OpLabel + OpBranch %30 + %30 = OpLabel +; CHECK: OpLoopMerge [[merge:%[a-zA-Z_\d]+]] + OpLoopMerge %31 %32 None + OpBranch %33 + %33 = OpLabel +; CHECK: OpSelectionMerge [[sel_merge:%[a-zA-Z_\d]+]] + OpSelectionMerge %34 None + OpSwitch %int_1 %35 + %35 = OpLabel + %36 = OpLoad %int %gl_VertexIndex + %37 = OpAccessChain %_ptr_Uniform_v4float %ubuf %int_2 %36 + %38 = OpLoad %v4float %37 + OpStore %texcoord %38 + %39 = OpAccessChain %_ptr_Output_v4float %_ %int_0 + OpStore %39 %28 + OpBranch %31 +; CHECK: [[sel_merge]] = OpLabel + %34 = OpLabel +; CHECK-NEXT: OpBranch [[merge]] + OpUnreachable + %32 = OpLabel + OpBranch %30 + %31 = OpLabel + OpReturn + OpFunctionEnd)"; + + SinglePassRunAndMatch(text, false); +} + +TEST_F(Workaround1209Test, RemoveOpUnreachableInNestedLoop) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %2 "main" %3 %4 %5 + OpSource GLSL 400 + OpSourceExtension "GL_ARB_separate_shader_objects" + OpSourceExtension "GL_ARB_shading_language_420pack" + OpName %2 "main" + OpName %3 "texcoord" + OpName %6 "buf" + OpMemberName %6 0 "MVP" + OpMemberName %6 1 "position" + OpMemberName %6 2 "attr" + OpName %7 "ubuf" + OpName %4 "gl_VertexIndex" + OpName %8 "gl_PerVertex" + OpMemberName %8 0 "gl_Position" + OpName %5 "" + OpDecorate %3 Location 0 + OpDecorate %9 ArrayStride 16 + OpDecorate %10 ArrayStride 16 + OpMemberDecorate %6 0 ColMajor + OpMemberDecorate %6 0 Offset 0 + OpMemberDecorate %6 0 MatrixStride 16 + OpMemberDecorate %6 1 Offset 64 + OpMemberDecorate %6 2 Offset 1216 + OpDecorate %6 Block + OpDecorate %7 DescriptorSet 0 + OpDecorate %7 Binding 0 + OpDecorate %4 BuiltIn VertexIndex + OpMemberDecorate %8 0 BuiltIn Position + OpDecorate %8 Block + %11 = OpTypeVoid + %12 = OpTypeFunction %11 + %13 = OpTypeFloat 32 + %14 = OpTypeVector %13 4 + %15 = OpTypePointer Output %14 + %3 = OpVariable %15 Output + %16 = OpTypeMatrix %14 4 + %17 = OpTypeInt 32 0 + %18 = OpConstant %17 72 + %9 = OpTypeArray %14 %18 + %10 = OpTypeArray %14 %18 + %6 = OpTypeStruct %16 %9 %10 + %19 = OpTypePointer Uniform %6 + %7 = OpVariable %19 Uniform + %20 = OpTypeInt 32 1 + %21 = OpConstant %20 2 + %22 = OpTypePointer Input %20 + %4 = OpVariable %22 Input + %23 = OpTypePointer Uniform %14 + %8 = OpTypeStruct %14 + %24 = OpTypePointer Output %8 + %5 = OpVariable %24 Output + %25 = OpConstant %20 0 + %26 = OpConstant %20 1 + %27 = OpConstant %13 1 + %28 = OpConstantComposite %14 %27 %27 %27 %27 + %2 = OpFunction %11 None %12 + %29 = OpLabel + OpBranch %31 + %31 = OpLabel +; CHECK: OpLoopMerge + OpLoopMerge %32 %33 None + OpBranch %30 + %30 = OpLabel +; CHECK: OpLoopMerge [[merge:%[a-zA-Z_\d]+]] + OpLoopMerge %34 %35 None + OpBranch %36 + %36 = OpLabel +; CHECK: OpSelectionMerge [[sel_merge:%[a-zA-Z_\d]+]] + OpSelectionMerge %37 None + OpSwitch %26 %38 + %38 = OpLabel + %39 = OpLoad %20 %4 + %40 = OpAccessChain %23 %7 %21 %39 + %41 = OpLoad %14 %40 + OpStore %3 %41 + %42 = OpAccessChain %15 %5 %25 + OpStore %42 %28 + OpBranch %34 +; CHECK: [[sel_merge]] = OpLabel + %37 = OpLabel +; CHECK-NEXT: OpBranch [[merge]] + OpUnreachable + %35 = OpLabel + OpBranch %30 + %34 = OpLabel + OpBranch %32 + %33 = OpLabel + OpBranch %31 + %32 = OpLabel + OpReturn + OpFunctionEnd)"; + + SinglePassRunAndMatch(text, false); +} + +TEST_F(Workaround1209Test, RemoveOpUnreachableInAdjacentLoops) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %2 "main" %3 %4 %5 + OpSource GLSL 400 + OpSourceExtension "GL_ARB_separate_shader_objects" + OpSourceExtension "GL_ARB_shading_language_420pack" + OpName %2 "main" + OpName %3 "texcoord" + OpName %6 "buf" + OpMemberName %6 0 "MVP" + OpMemberName %6 1 "position" + OpMemberName %6 2 "attr" + OpName %7 "ubuf" + OpName %4 "gl_VertexIndex" + OpName %8 "gl_PerVertex" + OpMemberName %8 0 "gl_Position" + OpName %5 "" + OpDecorate %3 Location 0 + OpDecorate %9 ArrayStride 16 + OpDecorate %10 ArrayStride 16 + OpMemberDecorate %6 0 ColMajor + OpMemberDecorate %6 0 Offset 0 + OpMemberDecorate %6 0 MatrixStride 16 + OpMemberDecorate %6 1 Offset 64 + OpMemberDecorate %6 2 Offset 1216 + OpDecorate %6 Block + OpDecorate %7 DescriptorSet 0 + OpDecorate %7 Binding 0 + OpDecorate %4 BuiltIn VertexIndex + OpMemberDecorate %8 0 BuiltIn Position + OpDecorate %8 Block + %11 = OpTypeVoid + %12 = OpTypeFunction %11 + %13 = OpTypeFloat 32 + %14 = OpTypeVector %13 4 + %15 = OpTypePointer Output %14 + %3 = OpVariable %15 Output + %16 = OpTypeMatrix %14 4 + %17 = OpTypeInt 32 0 + %18 = OpConstant %17 72 + %9 = OpTypeArray %14 %18 + %10 = OpTypeArray %14 %18 + %6 = OpTypeStruct %16 %9 %10 + %19 = OpTypePointer Uniform %6 + %7 = OpVariable %19 Uniform + %20 = OpTypeInt 32 1 + %21 = OpConstant %20 2 + %22 = OpTypePointer Input %20 + %4 = OpVariable %22 Input + %23 = OpTypePointer Uniform %14 + %8 = OpTypeStruct %14 + %24 = OpTypePointer Output %8 + %5 = OpVariable %24 Output + %25 = OpConstant %20 0 + %26 = OpConstant %20 1 + %27 = OpConstant %13 1 + %28 = OpConstantComposite %14 %27 %27 %27 %27 + %2 = OpFunction %11 None %12 + %29 = OpLabel + OpBranch %30 + %30 = OpLabel +; CHECK: OpLoopMerge [[merge1:%[a-zA-Z_\d]+]] + OpLoopMerge %31 %32 None + OpBranch %33 + %33 = OpLabel +; CHECK: OpSelectionMerge [[sel_merge1:%[a-zA-Z_\d]+]] + OpSelectionMerge %34 None + OpSwitch %26 %35 + %35 = OpLabel + %36 = OpLoad %20 %4 + %37 = OpAccessChain %23 %7 %21 %36 + %38 = OpLoad %14 %37 + OpStore %3 %38 + %39 = OpAccessChain %15 %5 %25 + OpStore %39 %28 + OpBranch %31 +; CHECK: [[sel_merge1]] = OpLabel + %34 = OpLabel +; CHECK-NEXT: OpBranch [[merge1]] + OpUnreachable + %32 = OpLabel + OpBranch %30 + %31 = OpLabel +; CHECK: OpLoopMerge [[merge2:%[a-zA-Z_\d]+]] + OpLoopMerge %40 %41 None + OpBranch %42 + %42 = OpLabel +; CHECK: OpSelectionMerge [[sel_merge2:%[a-zA-Z_\d]+]] + OpSelectionMerge %43 None + OpSwitch %26 %44 + %44 = OpLabel + %45 = OpLoad %20 %4 + %46 = OpAccessChain %23 %7 %21 %45 + %47 = OpLoad %14 %46 + OpStore %3 %47 + %48 = OpAccessChain %15 %5 %25 + OpStore %48 %28 + OpBranch %40 +; CHECK: [[sel_merge2]] = OpLabel + %43 = OpLabel +; CHECK-NEXT: OpBranch [[merge2]] + OpUnreachable + %41 = OpLabel + OpBranch %31 + %40 = OpLabel + OpReturn + OpFunctionEnd)"; + + SinglePassRunAndMatch(text, false); +} + +TEST_F(Workaround1209Test, LeaveUnreachableNotInLoop) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" %texcoord %gl_VertexIndex %_ + OpSource GLSL 400 + OpSourceExtension "GL_ARB_separate_shader_objects" + OpSourceExtension "GL_ARB_shading_language_420pack" + OpName %main "main" + OpName %texcoord "texcoord" + OpName %buf "buf" + OpMemberName %buf 0 "MVP" + OpMemberName %buf 1 "position" + OpMemberName %buf 2 "attr" + OpName %ubuf "ubuf" + OpName %gl_VertexIndex "gl_VertexIndex" + OpName %gl_PerVertex "gl_PerVertex" + OpMemberName %gl_PerVertex 0 "gl_Position" + OpName %_ "" + OpDecorate %texcoord Location 0 + OpDecorate %_arr_v4float_uint_72 ArrayStride 16 + OpDecorate %_arr_v4float_uint_72_0 ArrayStride 16 + OpMemberDecorate %buf 0 ColMajor + OpMemberDecorate %buf 0 Offset 0 + OpMemberDecorate %buf 0 MatrixStride 16 + OpMemberDecorate %buf 1 Offset 64 + OpMemberDecorate %buf 2 Offset 1216 + OpDecorate %buf Block + OpDecorate %ubuf DescriptorSet 0 + OpDecorate %ubuf Binding 0 + OpDecorate %gl_VertexIndex BuiltIn VertexIndex + OpMemberDecorate %gl_PerVertex 0 BuiltIn Position + OpDecorate %gl_PerVertex Block + %void = OpTypeVoid + %12 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %texcoord = OpVariable %_ptr_Output_v4float Output +%mat4v4float = OpTypeMatrix %v4float 4 + %uint = OpTypeInt 32 0 + %uint_72 = OpConstant %uint 72 +%_arr_v4float_uint_72 = OpTypeArray %v4float %uint_72 +%_arr_v4float_uint_72_0 = OpTypeArray %v4float %uint_72 + %buf = OpTypeStruct %mat4v4float %_arr_v4float_uint_72 %_arr_v4float_uint_72_0 +%_ptr_Uniform_buf = OpTypePointer Uniform %buf + %ubuf = OpVariable %_ptr_Uniform_buf Uniform + %int = OpTypeInt 32 1 + %int_2 = OpConstant %int 2 +%_ptr_Input_int = OpTypePointer Input %int +%gl_VertexIndex = OpVariable %_ptr_Input_int Input +%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float +%gl_PerVertex = OpTypeStruct %v4float +%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex + %_ = OpVariable %_ptr_Output_gl_PerVertex Output + %int_0 = OpConstant %int 0 + %int_1 = OpConstant %int 1 + %float_1 = OpConstant %float 1 + %28 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 + %main = OpFunction %void None %12 + %29 = OpLabel + OpBranch %30 + %30 = OpLabel + OpSelectionMerge %34 None + OpSwitch %int_1 %35 + %35 = OpLabel + %36 = OpLoad %int %gl_VertexIndex + %37 = OpAccessChain %_ptr_Uniform_v4float %ubuf %int_2 %36 + %38 = OpLoad %v4float %37 + OpStore %texcoord %38 + %39 = OpAccessChain %_ptr_Output_v4float %_ %int_0 + OpStore %39 %28 + OpReturn + %34 = OpLabel +; CHECK: OpUnreachable + OpUnreachable + OpFunctionEnd)"; + + SinglePassRunAndMatch(text, false); +} +#endif +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/parse_number_test.cpp b/3rdparty/spirv-tools/test/parse_number_test.cpp new file mode 100644 index 000000000..9189fbe4e --- /dev/null +++ b/3rdparty/spirv-tools/test/parse_number_test.cpp @@ -0,0 +1,970 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "gmock/gmock.h" +#include "source/util/parse_number.h" +#include "spirv-tools/libspirv.h" + +namespace { +using spvutils::EncodeNumberStatus; +using spvutils::NumberType; +using spvutils::ParseAndEncodeFloatingPointNumber; +using spvutils::ParseAndEncodeIntegerNumber; +using spvutils::ParseAndEncodeNumber; +using spvutils::ParseNumber; +using testing::Eq; +using testing::IsNull; +using testing::NotNull; + +TEST(ParseNarrowSignedIntegers, Sample) { + int16_t i16; + + EXPECT_FALSE(ParseNumber(nullptr, &i16)); + EXPECT_FALSE(ParseNumber("", &i16)); + EXPECT_FALSE(ParseNumber("0=", &i16)); + + EXPECT_TRUE(ParseNumber("0", &i16)); + EXPECT_EQ(0, i16); + EXPECT_TRUE(ParseNumber("32767", &i16)); + EXPECT_EQ(32767, i16); + EXPECT_TRUE(ParseNumber("-32768", &i16)); + EXPECT_EQ(-32768, i16); + EXPECT_TRUE(ParseNumber("-0", &i16)); + EXPECT_EQ(0, i16); + + // These are out of range, so they should return an error. + // The error code depends on whether this is an optional value. + EXPECT_FALSE(ParseNumber("32768", &i16)); + EXPECT_FALSE(ParseNumber("65535", &i16)); + + // Check hex parsing. + EXPECT_TRUE(ParseNumber("0x7fff", &i16)); + EXPECT_EQ(32767, i16); + // This is out of range. + EXPECT_FALSE(ParseNumber("0xffff", &i16)); +} + +TEST(ParseNarrowUnsignedIntegers, Sample) { + uint16_t u16; + + EXPECT_FALSE(ParseNumber(nullptr, &u16)); + EXPECT_FALSE(ParseNumber("", &u16)); + EXPECT_FALSE(ParseNumber("0=", &u16)); + + EXPECT_TRUE(ParseNumber("0", &u16)); + EXPECT_EQ(0, u16); + EXPECT_TRUE(ParseNumber("65535", &u16)); + EXPECT_EQ(65535, u16); + EXPECT_FALSE(ParseNumber("65536", &u16)); + + // We don't care about -0 since it's rejected at a higher level. + EXPECT_FALSE(ParseNumber("-1", &u16)); + EXPECT_TRUE(ParseNumber("0xffff", &u16)); + EXPECT_EQ(0xffff, u16); + EXPECT_FALSE(ParseNumber("0x10000", &u16)); +} + +TEST(ParseSignedIntegers, Sample) { + int32_t i32; + + // Invalid parse. + EXPECT_FALSE(ParseNumber(nullptr, &i32)); + EXPECT_FALSE(ParseNumber("", &i32)); + EXPECT_FALSE(ParseNumber("0=", &i32)); + + // Decimal values. + EXPECT_TRUE(ParseNumber("0", &i32)); + EXPECT_EQ(0, i32); + EXPECT_TRUE(ParseNumber("2147483647", &i32)); + EXPECT_EQ(std::numeric_limits::max(), i32); + EXPECT_FALSE(ParseNumber("2147483648", &i32)); + EXPECT_TRUE(ParseNumber("-0", &i32)); + EXPECT_EQ(0, i32); + EXPECT_TRUE(ParseNumber("-1", &i32)); + EXPECT_EQ(-1, i32); + EXPECT_TRUE(ParseNumber("-2147483648", &i32)); + EXPECT_EQ(std::numeric_limits::min(), i32); + + // Hex values. + EXPECT_TRUE(ParseNumber("0x7fffffff", &i32)); + EXPECT_EQ(std::numeric_limits::max(), i32); + EXPECT_FALSE(ParseNumber("0x80000000", &i32)); + EXPECT_TRUE(ParseNumber("-0x000", &i32)); + EXPECT_EQ(0, i32); + EXPECT_TRUE(ParseNumber("-0x001", &i32)); + EXPECT_EQ(-1, i32); + EXPECT_TRUE(ParseNumber("-0x80000000", &i32)); + EXPECT_EQ(std::numeric_limits::min(), i32); +} + +TEST(ParseUnsignedIntegers, Sample) { + uint32_t u32; + + // Invalid parse. + EXPECT_FALSE(ParseNumber(nullptr, &u32)); + EXPECT_FALSE(ParseNumber("", &u32)); + EXPECT_FALSE(ParseNumber("0=", &u32)); + + // Valid values. + EXPECT_TRUE(ParseNumber("0", &u32)); + EXPECT_EQ(0u, u32); + EXPECT_TRUE(ParseNumber("4294967295", &u32)); + EXPECT_EQ(std::numeric_limits::max(), u32); + EXPECT_FALSE(ParseNumber("4294967296", &u32)); + + // Hex values. + EXPECT_TRUE(ParseNumber("0xffffffff", &u32)); + EXPECT_EQ(std::numeric_limits::max(), u32); + + // We don't care about -0 since it's rejected at a higher level. + EXPECT_FALSE(ParseNumber("-1", &u32)); +} + +TEST(ParseWideSignedIntegers, Sample) { + int64_t i64; + EXPECT_FALSE(ParseNumber(nullptr, &i64)); + EXPECT_FALSE(ParseNumber("", &i64)); + EXPECT_FALSE(ParseNumber("0=", &i64)); + EXPECT_TRUE(ParseNumber("0", &i64)); + EXPECT_EQ(0, i64); + EXPECT_TRUE(ParseNumber("0x7fffffffffffffff", &i64)); + EXPECT_EQ(0x7fffffffffffffff, i64); + EXPECT_TRUE(ParseNumber("-0", &i64)); + EXPECT_EQ(0, i64); + EXPECT_TRUE(ParseNumber("-1", &i64)); + EXPECT_EQ(-1, i64); +} + +TEST(ParseWideUnsignedIntegers, Sample) { + uint64_t u64; + EXPECT_FALSE(ParseNumber(nullptr, &u64)); + EXPECT_FALSE(ParseNumber("", &u64)); + EXPECT_FALSE(ParseNumber("0=", &u64)); + EXPECT_TRUE(ParseNumber("0", &u64)); + EXPECT_EQ(0u, u64); + EXPECT_TRUE(ParseNumber("0xffffffffffffffff", &u64)); + EXPECT_EQ(0xffffffffffffffffULL, u64); + + // We don't care about -0 since it's rejected at a higher level. + EXPECT_FALSE(ParseNumber("-1", &u64)); +} + +TEST(ParseFloat, Sample) { + float f; + + EXPECT_FALSE(ParseNumber(nullptr, &f)); + EXPECT_FALSE(ParseNumber("", &f)); + EXPECT_FALSE(ParseNumber("0=", &f)); + + // These values are exactly representatble. + EXPECT_TRUE(ParseNumber("0", &f)); + EXPECT_EQ(0.0f, f); + EXPECT_TRUE(ParseNumber("42", &f)); + EXPECT_EQ(42.0f, f); + EXPECT_TRUE(ParseNumber("2.5", &f)); + EXPECT_EQ(2.5f, f); + EXPECT_TRUE(ParseNumber("-32.5", &f)); + EXPECT_EQ(-32.5f, f); + EXPECT_TRUE(ParseNumber("1e38", &f)); + EXPECT_EQ(1e38f, f); + EXPECT_TRUE(ParseNumber("-1e38", &f)); + EXPECT_EQ(-1e38f, f); +} + +TEST(ParseFloat, Overflow) { + // The assembler parses using HexFloat>. Make + // sure that succeeds for in-range values, and fails for out of + // range values. When it does overflow, the value is set to the + // nearest finite value, matching C++11 behavior for operator>> + // on floating point. + spvutils::HexFloat> f(0.0f); + + EXPECT_TRUE(ParseNumber("1e38", &f)); + EXPECT_EQ(1e38f, f.value().getAsFloat()); + EXPECT_TRUE(ParseNumber("-1e38", &f)); + EXPECT_EQ(-1e38f, f.value().getAsFloat()); + EXPECT_FALSE(ParseNumber("1e40", &f)); + EXPECT_FALSE(ParseNumber("-1e40", &f)); + EXPECT_FALSE(ParseNumber("1e400", &f)); + EXPECT_FALSE(ParseNumber("-1e400", &f)); +} + +TEST(ParseDouble, Sample) { + double f; + + EXPECT_FALSE(ParseNumber(nullptr, &f)); + EXPECT_FALSE(ParseNumber("", &f)); + EXPECT_FALSE(ParseNumber("0=", &f)); + + // These values are exactly representatble. + EXPECT_TRUE(ParseNumber("0", &f)); + EXPECT_EQ(0.0, f); + EXPECT_TRUE(ParseNumber("42", &f)); + EXPECT_EQ(42.0, f); + EXPECT_TRUE(ParseNumber("2.5", &f)); + EXPECT_EQ(2.5, f); + EXPECT_TRUE(ParseNumber("-32.5", &f)); + EXPECT_EQ(-32.5, f); + EXPECT_TRUE(ParseNumber("1e38", &f)); + EXPECT_EQ(1e38, f); + EXPECT_TRUE(ParseNumber("-1e38", &f)); + EXPECT_EQ(-1e38, f); + // These are out of range for 32-bit float, but in range for 64-bit float. + EXPECT_TRUE(ParseNumber("1e40", &f)); + EXPECT_EQ(1e40, f); + EXPECT_TRUE(ParseNumber("-1e40", &f)); + EXPECT_EQ(-1e40, f); +} + +TEST(ParseDouble, Overflow) { + // The assembler parses using HexFloat>. Make + // sure that succeeds for in-range values, and fails for out of + // range values. When it does overflow, the value is set to the + // nearest finite value, matching C++11 behavior for operator>> + // on floating point. + spvutils::HexFloat> f(0.0); + + EXPECT_TRUE(ParseNumber("1e38", &f)); + EXPECT_EQ(1e38, f.value().getAsFloat()); + EXPECT_TRUE(ParseNumber("-1e38", &f)); + EXPECT_EQ(-1e38, f.value().getAsFloat()); + EXPECT_TRUE(ParseNumber("1e40", &f)); + EXPECT_EQ(1e40, f.value().getAsFloat()); + EXPECT_TRUE(ParseNumber("-1e40", &f)); + EXPECT_EQ(-1e40, f.value().getAsFloat()); + EXPECT_FALSE(ParseNumber("1e400", &f)); + EXPECT_FALSE(ParseNumber("-1e400", &f)); +} + +TEST(ParseFloat16, Overflow) { + // The assembler parses using HexFloat>. Make + // sure that succeeds for in-range values, and fails for out of + // range values. When it does overflow, the value is set to the + // nearest finite value, matching C++11 behavior for operator>> + // on floating point. + spvutils::HexFloat> f(0); + + EXPECT_FALSE(ParseNumber(nullptr, &f)); + EXPECT_TRUE(ParseNumber("-0.0", &f)); + EXPECT_EQ(uint16_t{0x8000}, f.value().getAsFloat().get_value()); + EXPECT_TRUE(ParseNumber("1.0", &f)); + EXPECT_EQ(uint16_t{0x3c00}, f.value().getAsFloat().get_value()); + + // Overflows 16-bit but not 32-bit + EXPECT_FALSE(ParseNumber("1e38", &f)); + EXPECT_FALSE(ParseNumber("-1e38", &f)); + + // Overflows 32-bit but not 64-bit + EXPECT_FALSE(ParseNumber("1e40", &f)); + EXPECT_FALSE(ParseNumber("-1e40", &f)); + + // Overflows 64-bit + EXPECT_FALSE(ParseNumber("1e400", &f)); + EXPECT_FALSE(ParseNumber("-1e400", &f)); +} + +void AssertEmitFunc(uint32_t) { + ASSERT_FALSE(true) + << "Should not call emit() function when the number can not be parsed."; + return; +} + +TEST(ParseAndEncodeNarrowSignedIntegers, Invalid) { + // The error message should be overwritten after each parsing call. + EncodeNumberStatus rc = EncodeNumberStatus::kSuccess; + std::string err_msg; + NumberType type = {16, SPV_NUMBER_SIGNED_INT}; + + rc = ParseAndEncodeIntegerNumber(nullptr, type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("The given text is a nullptr", err_msg); + rc = ParseAndEncodeIntegerNumber("", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid unsigned integer literal: ", err_msg); + rc = ParseAndEncodeIntegerNumber("=", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid unsigned integer literal: =", err_msg); + rc = ParseAndEncodeIntegerNumber("-", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid signed integer literal: -", err_msg); + rc = ParseAndEncodeIntegerNumber("0=", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid unsigned integer literal: 0=", err_msg); +} + +TEST(ParseAndEncodeNarrowSignedIntegers, Overflow) { + // The error message should be overwritten after each parsing call. + EncodeNumberStatus rc = EncodeNumberStatus::kSuccess; + std::string err_msg; + NumberType type = {16, SPV_NUMBER_SIGNED_INT}; + + rc = ParseAndEncodeIntegerNumber("32768", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Integer 32768 does not fit in a 16-bit signed integer", err_msg); + rc = ParseAndEncodeIntegerNumber("-32769", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Integer -32769 does not fit in a 16-bit signed integer", err_msg); +} + +TEST(ParseAndEncodeNarrowSignedIntegers, Success) { + // Don't care the error message in this case. + EncodeNumberStatus rc = EncodeNumberStatus::kInvalidText; + NumberType type = {16, SPV_NUMBER_SIGNED_INT}; + + // Zero, maximum, and minimum value + rc = ParseAndEncodeIntegerNumber( + "0", type, [](uint32_t word) { EXPECT_EQ(0u, word); }, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + rc = ParseAndEncodeIntegerNumber( + "-0", type, [](uint32_t word) { EXPECT_EQ(0u, word); }, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + rc = ParseAndEncodeIntegerNumber( + "32767", type, [](uint32_t word) { EXPECT_EQ(0x00007fffu, word); }, + nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + rc = ParseAndEncodeIntegerNumber( + "-32768", type, [](uint32_t word) { EXPECT_EQ(0xffff8000u, word); }, + nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + + // Hex parsing + rc = ParseAndEncodeIntegerNumber( + "0x7fff", type, [](uint32_t word) { EXPECT_EQ(0x00007fffu, word); }, + nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + rc = ParseAndEncodeIntegerNumber( + "0xffff", type, [](uint32_t word) { EXPECT_EQ(0xffffffffu, word); }, + nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); +} + +TEST(ParseAndEncodeNarrowUnsignedIntegers, Invalid) { + // The error message should be overwritten after each parsing call. + EncodeNumberStatus rc = EncodeNumberStatus::kSuccess; + std::string err_msg; + NumberType type = {16, SPV_NUMBER_UNSIGNED_INT}; + + rc = ParseAndEncodeIntegerNumber(nullptr, type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("The given text is a nullptr", err_msg); + rc = ParseAndEncodeIntegerNumber("", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid unsigned integer literal: ", err_msg); + rc = ParseAndEncodeIntegerNumber("=", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid unsigned integer literal: =", err_msg); + rc = ParseAndEncodeIntegerNumber("-", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidUsage, rc); + EXPECT_EQ("Cannot put a negative number in an unsigned literal", err_msg); + rc = ParseAndEncodeIntegerNumber("0=", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid unsigned integer literal: 0=", err_msg); + rc = ParseAndEncodeIntegerNumber("-0", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidUsage, rc); + EXPECT_EQ("Cannot put a negative number in an unsigned literal", err_msg); + rc = ParseAndEncodeIntegerNumber("-1", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidUsage, rc); + EXPECT_EQ("Cannot put a negative number in an unsigned literal", err_msg); +} + +TEST(ParseAndEncodeNarrowUnsignedIntegers, Overflow) { + // The error message should be overwritten after each parsing call. + EncodeNumberStatus rc = EncodeNumberStatus::kSuccess; + std::string err_msg("random content"); + NumberType type = {16, SPV_NUMBER_UNSIGNED_INT}; + + // Overflow + rc = ParseAndEncodeIntegerNumber("65536", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Integer 65536 does not fit in a 16-bit unsigned integer", err_msg); +} + +TEST(ParseAndEncodeNarrowUnsignedIntegers, Success) { + // Don't care the error message in this case. + EncodeNumberStatus rc = EncodeNumberStatus::kInvalidText; + NumberType type = {16, SPV_NUMBER_UNSIGNED_INT}; + + // Zero, maximum, and minimum value + rc = ParseAndEncodeIntegerNumber( + "0", type, [](uint32_t word) { EXPECT_EQ(0u, word); }, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + rc = ParseAndEncodeIntegerNumber( + "65535", type, [](uint32_t word) { EXPECT_EQ(0x0000ffffu, word); }, + nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + + // Hex parsing + rc = ParseAndEncodeIntegerNumber( + "0xffff", type, [](uint32_t word) { EXPECT_EQ(0x0000ffffu, word); }, + nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); +} + +TEST(ParseAndEncodeSignedIntegers, Invalid) { + // The error message should be overwritten after each parsing call. + EncodeNumberStatus rc = EncodeNumberStatus::kSuccess; + std::string err_msg; + NumberType type = {32, SPV_NUMBER_SIGNED_INT}; + + rc = ParseAndEncodeIntegerNumber(nullptr, type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("The given text is a nullptr", err_msg); + rc = ParseAndEncodeIntegerNumber("", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid unsigned integer literal: ", err_msg); + rc = ParseAndEncodeIntegerNumber("=", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid unsigned integer literal: =", err_msg); + rc = ParseAndEncodeIntegerNumber("-", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid signed integer literal: -", err_msg); + rc = ParseAndEncodeIntegerNumber("0=", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid unsigned integer literal: 0=", err_msg); +} + +TEST(ParseAndEncodeSignedIntegers, Overflow) { + // The error message should be overwritten after each parsing call. + EncodeNumberStatus rc = EncodeNumberStatus::kSuccess; + std::string err_msg; + NumberType type = {32, SPV_NUMBER_SIGNED_INT}; + + rc = + ParseAndEncodeIntegerNumber("2147483648", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Integer 2147483648 does not fit in a 32-bit signed integer", + err_msg); + rc = ParseAndEncodeIntegerNumber("-2147483649", type, AssertEmitFunc, + &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Integer -2147483649 does not fit in a 32-bit signed integer", + err_msg); +} + +TEST(ParseAndEncodeSignedIntegers, Success) { + // Don't care the error message in this case. + EncodeNumberStatus rc = EncodeNumberStatus::kInvalidText; + NumberType type = {32, SPV_NUMBER_SIGNED_INT}; + + // Zero, maximum, and minimum value + rc = ParseAndEncodeIntegerNumber( + "0", type, [](uint32_t word) { EXPECT_EQ(0u, word); }, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + rc = ParseAndEncodeIntegerNumber( + "-0", type, [](uint32_t word) { EXPECT_EQ(0u, word); }, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + rc = ParseAndEncodeIntegerNumber( + "2147483647", type, [](uint32_t word) { EXPECT_EQ(0x7fffffffu, word); }, + nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + rc = ParseAndEncodeIntegerNumber( + "-2147483648", type, [](uint32_t word) { EXPECT_EQ(0x80000000u, word); }, + nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + + // Hex parsing + rc = ParseAndEncodeIntegerNumber( + "0x7fffffff", type, [](uint32_t word) { EXPECT_EQ(0x7fffffffu, word); }, + nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + rc = ParseAndEncodeIntegerNumber( + "0xffffffff", type, [](uint32_t word) { EXPECT_EQ(0xffffffffu, word); }, + nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); +} + +TEST(ParseAndEncodeUnsignedIntegers, Invalid) { + // The error message should be overwritten after each parsing call. + EncodeNumberStatus rc = EncodeNumberStatus::kSuccess; + std::string err_msg; + NumberType type = {32, SPV_NUMBER_UNSIGNED_INT}; + + rc = ParseAndEncodeIntegerNumber(nullptr, type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("The given text is a nullptr", err_msg); + rc = ParseAndEncodeIntegerNumber("", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid unsigned integer literal: ", err_msg); + rc = ParseAndEncodeIntegerNumber("=", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid unsigned integer literal: =", err_msg); + rc = ParseAndEncodeIntegerNumber("-", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidUsage, rc); + EXPECT_EQ("Cannot put a negative number in an unsigned literal", err_msg); + rc = ParseAndEncodeIntegerNumber("0=", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid unsigned integer literal: 0=", err_msg); + rc = ParseAndEncodeIntegerNumber("-0", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidUsage, rc); + EXPECT_EQ("Cannot put a negative number in an unsigned literal", err_msg); + rc = ParseAndEncodeIntegerNumber("-1", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidUsage, rc); + EXPECT_EQ("Cannot put a negative number in an unsigned literal", err_msg); +} + +TEST(ParseAndEncodeUnsignedIntegers, Overflow) { + // The error message should be overwritten after each parsing call. + EncodeNumberStatus rc = EncodeNumberStatus::kSuccess; + std::string err_msg("random content"); + NumberType type = {32, SPV_NUMBER_UNSIGNED_INT}; + + // Overflow + rc = + ParseAndEncodeIntegerNumber("4294967296", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Integer 4294967296 does not fit in a 32-bit unsigned integer", + err_msg); +} + +TEST(ParseAndEncodeUnsignedIntegers, Success) { + // Don't care the error message in this case. + EncodeNumberStatus rc = EncodeNumberStatus::kInvalidText; + NumberType type = {32, SPV_NUMBER_UNSIGNED_INT}; + + // Zero, maximum, and minimum value + rc = ParseAndEncodeIntegerNumber( + "0", type, [](uint32_t word) { EXPECT_EQ(0u, word); }, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + rc = ParseAndEncodeIntegerNumber( + "4294967295", type, [](uint32_t word) { EXPECT_EQ(0xffffffffu, word); }, + nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + + // Hex parsing + rc = ParseAndEncodeIntegerNumber( + "0xffffffff", type, [](uint32_t word) { EXPECT_EQ(0xffffffffu, word); }, + nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); +} + +TEST(ParseAndEncodeWideSignedIntegers, Invalid) { + // The error message should be overwritten after each parsing call. + EncodeNumberStatus rc = EncodeNumberStatus::kSuccess; + std::string err_msg; + NumberType type = {64, SPV_NUMBER_SIGNED_INT}; + + rc = ParseAndEncodeIntegerNumber(nullptr, type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("The given text is a nullptr", err_msg); + rc = ParseAndEncodeIntegerNumber("", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid unsigned integer literal: ", err_msg); + rc = ParseAndEncodeIntegerNumber("=", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid unsigned integer literal: =", err_msg); + rc = ParseAndEncodeIntegerNumber("-", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid signed integer literal: -", err_msg); + rc = ParseAndEncodeIntegerNumber("0=", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid unsigned integer literal: 0=", err_msg); +} + +TEST(ParseAndEncodeWideSignedIntegers, Overflow) { + // The error message should be overwritten after each parsing call. + EncodeNumberStatus rc = EncodeNumberStatus::kSuccess; + std::string err_msg; + NumberType type = {64, SPV_NUMBER_SIGNED_INT}; + + rc = ParseAndEncodeIntegerNumber("9223372036854775808", type, AssertEmitFunc, + &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ( + "Integer 9223372036854775808 does not fit in a 64-bit signed integer", + err_msg); + rc = ParseAndEncodeIntegerNumber("-9223372036854775809", type, AssertEmitFunc, + &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid signed integer literal: -9223372036854775809", err_msg); +} + +TEST(ParseAndEncodeWideSignedIntegers, Success) { + // Don't care the error message in this case. + EncodeNumberStatus rc = EncodeNumberStatus::kInvalidText; + NumberType type = {64, SPV_NUMBER_SIGNED_INT}; + std::vector word_buffer; + auto emit = [&word_buffer](uint32_t word) { + if (word_buffer.size() == 2) word_buffer.clear(); + word_buffer.push_back(word); + }; + + // Zero, maximum, and minimum value + rc = ParseAndEncodeIntegerNumber("0", type, emit, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + EXPECT_THAT(word_buffer, Eq(std::vector{0u, 0u})); + rc = ParseAndEncodeIntegerNumber("-0", type, emit, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + EXPECT_THAT(word_buffer, Eq(std::vector{0u, 0u})); + rc = ParseAndEncodeIntegerNumber("9223372036854775807", type, emit, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + EXPECT_THAT(word_buffer, Eq(std::vector{0xffffffffu, 0x7fffffffu})); + rc = ParseAndEncodeIntegerNumber("-9223372036854775808", type, emit, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + EXPECT_THAT(word_buffer, Eq(std::vector{0u, 0x80000000u})); + rc = ParseAndEncodeIntegerNumber("-1", type, emit, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + EXPECT_THAT(word_buffer, Eq(std::vector{0xffffffffu, 0xffffffffu})); + + // Hex parsing + rc = ParseAndEncodeIntegerNumber("0x7fffffffffffffff", type, emit, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + EXPECT_THAT(word_buffer, Eq(std::vector{0xffffffffu, 0x7fffffffu})); + rc = ParseAndEncodeIntegerNumber("0xffffffffffffffff", type, emit, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + EXPECT_THAT(word_buffer, Eq(std::vector{0xffffffffu, 0xffffffffu})); +} + +TEST(ParseAndEncodeWideUnsignedIntegers, Invalid) { + // The error message should be overwritten after each parsing call. + EncodeNumberStatus rc = EncodeNumberStatus::kSuccess; + std::string err_msg; + NumberType type = {64, SPV_NUMBER_UNSIGNED_INT}; + + // Invalid + rc = ParseAndEncodeIntegerNumber(nullptr, type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("The given text is a nullptr", err_msg); + rc = ParseAndEncodeIntegerNumber("", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid unsigned integer literal: ", err_msg); + rc = ParseAndEncodeIntegerNumber("=", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid unsigned integer literal: =", err_msg); + rc = ParseAndEncodeIntegerNumber("-", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidUsage, rc); + EXPECT_EQ("Cannot put a negative number in an unsigned literal", err_msg); + rc = ParseAndEncodeIntegerNumber("0=", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid unsigned integer literal: 0=", err_msg); + rc = ParseAndEncodeIntegerNumber("-0", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidUsage, rc); + EXPECT_EQ("Cannot put a negative number in an unsigned literal", err_msg); + rc = ParseAndEncodeIntegerNumber("-1", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidUsage, rc); + EXPECT_EQ("Cannot put a negative number in an unsigned literal", err_msg); +} + +TEST(ParseAndEncodeWideUnsignedIntegers, Overflow) { + // The error message should be overwritten after each parsing call. + EncodeNumberStatus rc = EncodeNumberStatus::kSuccess; + std::string err_msg; + NumberType type = {64, SPV_NUMBER_UNSIGNED_INT}; + + // Overflow + rc = ParseAndEncodeIntegerNumber("18446744073709551616", type, AssertEmitFunc, + &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid unsigned integer literal: 18446744073709551616", err_msg); +} + +TEST(ParseAndEncodeWideUnsignedIntegers, Success) { + // Don't care the error message in this case. + EncodeNumberStatus rc = EncodeNumberStatus::kInvalidText; + NumberType type = {64, SPV_NUMBER_UNSIGNED_INT}; + std::vector word_buffer; + auto emit = [&word_buffer](uint32_t word) { + if (word_buffer.size() == 2) word_buffer.clear(); + word_buffer.push_back(word); + }; + + // Zero, maximum, and minimum value + rc = ParseAndEncodeIntegerNumber("0", type, emit, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + EXPECT_THAT(word_buffer, Eq(std::vector{0u, 0u})); + rc = ParseAndEncodeIntegerNumber("18446744073709551615", type, emit, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + EXPECT_THAT(word_buffer, Eq(std::vector{0xffffffffu, 0xffffffffu})); + + // Hex parsing + rc = ParseAndEncodeIntegerNumber("0xffffffffffffffff", type, emit, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + EXPECT_THAT(word_buffer, Eq(std::vector{0xffffffffu, 0xffffffffu})); +} + +TEST(ParseAndEncodeIntegerNumber, TypeNone) { + EncodeNumberStatus rc = EncodeNumberStatus::kSuccess; + std::string err_msg; + NumberType type = {32, SPV_NUMBER_NONE}; + + rc = ParseAndEncodeIntegerNumber( + "0.0", type, [](uint32_t word) { EXPECT_EQ(0x0u, word); }, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidUsage, rc); + EXPECT_EQ("The expected type is not a integer type", err_msg); +} + +TEST(ParseAndEncodeIntegerNumber, InvalidCaseWithoutErrorMessageString) { + EncodeNumberStatus rc = EncodeNumberStatus::kSuccess; + NumberType type = {32, SPV_NUMBER_SIGNED_INT}; + + rc = ParseAndEncodeIntegerNumber("invalid", type, AssertEmitFunc, nullptr); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); +} + +TEST(ParseAndEncodeIntegerNumber, DoNotTouchErrorMessageStringOnSuccess) { + EncodeNumberStatus rc = EncodeNumberStatus::kInvalidText; + std::string err_msg("random content"); + NumberType type = {32, SPV_NUMBER_SIGNED_INT}; + + rc = ParseAndEncodeIntegerNumber( + "100", type, [](uint32_t word) { EXPECT_EQ(100u, word); }, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + EXPECT_EQ("random content", err_msg); +} + +TEST(ParseAndEncodeFloat, Sample) { + EncodeNumberStatus rc = EncodeNumberStatus::kSuccess; + std::string err_msg; + NumberType type = {32, SPV_NUMBER_FLOATING}; + + // Invalid + rc = ParseAndEncodeFloatingPointNumber("", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid 32-bit float literal: ", err_msg); + rc = ParseAndEncodeFloatingPointNumber("0=", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid 32-bit float literal: 0=", err_msg); + + // Representative samples + rc = ParseAndEncodeFloatingPointNumber( + "0.0", type, [](uint32_t word) { EXPECT_EQ(0x0u, word); }, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + rc = ParseAndEncodeFloatingPointNumber( + "-0.0", type, [](uint32_t word) { EXPECT_EQ(0x80000000u, word); }, + nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + rc = ParseAndEncodeFloatingPointNumber( + "42", type, [](uint32_t word) { EXPECT_EQ(0x42280000u, word); }, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + rc = ParseAndEncodeFloatingPointNumber( + "2.5", type, [](uint32_t word) { EXPECT_EQ(0x40200000u, word); }, + nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + rc = ParseAndEncodeFloatingPointNumber( + "-32.5", type, [](uint32_t word) { EXPECT_EQ(0xc2020000u, word); }, + nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + rc = ParseAndEncodeFloatingPointNumber( + "1e38", type, [](uint32_t word) { EXPECT_EQ(0x7e967699u, word); }, + nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + rc = ParseAndEncodeFloatingPointNumber( + "-1e38", type, [](uint32_t word) { EXPECT_EQ(0xfe967699u, word); }, + nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + + // Overflow + rc = + ParseAndEncodeFloatingPointNumber("1e40", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid 32-bit float literal: 1e40", err_msg); + rc = ParseAndEncodeFloatingPointNumber("-1e40", type, AssertEmitFunc, + &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid 32-bit float literal: -1e40", err_msg); + rc = ParseAndEncodeFloatingPointNumber("1e400", type, AssertEmitFunc, + &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid 32-bit float literal: 1e400", err_msg); + rc = ParseAndEncodeFloatingPointNumber("-1e400", type, AssertEmitFunc, + &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid 32-bit float literal: -1e400", err_msg); +} + +TEST(ParseAndEncodeDouble, Sample) { + EncodeNumberStatus rc = EncodeNumberStatus::kSuccess; + std::string err_msg; + NumberType type = {64, SPV_NUMBER_FLOATING}; + std::vector word_buffer; + auto emit = [&word_buffer](uint32_t word) { + if (word_buffer.size() == 2) word_buffer.clear(); + word_buffer.push_back(word); + }; + + // Invalid + rc = ParseAndEncodeFloatingPointNumber("", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid 64-bit float literal: ", err_msg); + rc = ParseAndEncodeFloatingPointNumber("0=", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid 64-bit float literal: 0=", err_msg); + + // Representative samples + rc = ParseAndEncodeFloatingPointNumber("0.0", type, emit, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + EXPECT_THAT(word_buffer, Eq(std::vector{0u, 0u})); + rc = ParseAndEncodeFloatingPointNumber("-0.0", type, emit, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + EXPECT_THAT(word_buffer, Eq(std::vector{0u, 0x80000000u})); + rc = ParseAndEncodeFloatingPointNumber("42", type, emit, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + EXPECT_THAT(word_buffer, Eq(std::vector{0u, 0x40450000u})); + rc = ParseAndEncodeFloatingPointNumber("2.5", type, emit, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + EXPECT_THAT(word_buffer, Eq(std::vector{0u, 0x40040000u})); + rc = ParseAndEncodeFloatingPointNumber("32.5", type, emit, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + EXPECT_THAT(word_buffer, Eq(std::vector{0u, 0x40404000u})); + rc = ParseAndEncodeFloatingPointNumber("1e38", type, emit, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + EXPECT_THAT(word_buffer, Eq(std::vector{0x2a16a1b1u, 0x47d2ced3u})); + rc = ParseAndEncodeFloatingPointNumber("-1e38", type, emit, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + EXPECT_THAT(word_buffer, Eq(std::vector{0x2a16a1b1u, 0xc7d2ced3u})); + rc = ParseAndEncodeFloatingPointNumber("1e40", type, emit, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + EXPECT_THAT(word_buffer, Eq(std::vector{0xf1c35ca5u, 0x483d6329u})); + rc = ParseAndEncodeFloatingPointNumber("-1e40", type, emit, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + EXPECT_THAT(word_buffer, Eq(std::vector{0xf1c35ca5u, 0xc83d6329u})); + + // Overflow + rc = ParseAndEncodeFloatingPointNumber("1e400", type, AssertEmitFunc, + &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid 64-bit float literal: 1e400", err_msg); + rc = ParseAndEncodeFloatingPointNumber("-1e400", type, AssertEmitFunc, + &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid 64-bit float literal: -1e400", err_msg); +} + +TEST(ParseAndEncodeFloat16, Sample) { + EncodeNumberStatus rc = EncodeNumberStatus::kSuccess; + std::string err_msg; + NumberType type = {16, SPV_NUMBER_FLOATING}; + + // Invalid + rc = ParseAndEncodeFloatingPointNumber("", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid 16-bit float literal: ", err_msg); + rc = ParseAndEncodeFloatingPointNumber("0=", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid 16-bit float literal: 0=", err_msg); + + // Representative samples + rc = ParseAndEncodeFloatingPointNumber( + "0.0", type, [](uint32_t word) { EXPECT_EQ(0x0u, word); }, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + rc = ParseAndEncodeFloatingPointNumber( + "-0.0", type, [](uint32_t word) { EXPECT_EQ(0x8000u, word); }, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + rc = ParseAndEncodeFloatingPointNumber( + "1.0", type, [](uint32_t word) { EXPECT_EQ(0x3c00u, word); }, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + rc = ParseAndEncodeFloatingPointNumber( + "2.5", type, [](uint32_t word) { EXPECT_EQ(0x4100u, word); }, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + rc = ParseAndEncodeFloatingPointNumber( + "32.5", type, [](uint32_t word) { EXPECT_EQ(0x5010u, word); }, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + + // Overflow + rc = + ParseAndEncodeFloatingPointNumber("1e38", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid 16-bit float literal: 1e38", err_msg); + rc = ParseAndEncodeFloatingPointNumber("-1e38", type, AssertEmitFunc, + &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid 16-bit float literal: -1e38", err_msg); + rc = + ParseAndEncodeFloatingPointNumber("1e40", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid 16-bit float literal: 1e40", err_msg); + rc = ParseAndEncodeFloatingPointNumber("-1e40", type, AssertEmitFunc, + &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid 16-bit float literal: -1e40", err_msg); + rc = ParseAndEncodeFloatingPointNumber("1e400", type, AssertEmitFunc, + &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid 16-bit float literal: 1e400", err_msg); + rc = ParseAndEncodeFloatingPointNumber("-1e400", type, AssertEmitFunc, + &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid 16-bit float literal: -1e400", err_msg); +} + +TEST(ParseAndEncodeFloatingPointNumber, TypeNone) { + EncodeNumberStatus rc = EncodeNumberStatus::kSuccess; + std::string err_msg; + NumberType type = {32, SPV_NUMBER_NONE}; + + rc = ParseAndEncodeFloatingPointNumber( + "0.0", type, [](uint32_t word) { EXPECT_EQ(0x0u, word); }, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidUsage, rc); + EXPECT_EQ("The expected type is not a float type", err_msg); +} + +TEST(ParseAndEncodeFloatingPointNumber, InvalidCaseWithoutErrorMessageString) { + EncodeNumberStatus rc = EncodeNumberStatus::kSuccess; + NumberType type = {32, SPV_NUMBER_FLOATING}; + + rc = ParseAndEncodeFloatingPointNumber("invalid", type, AssertEmitFunc, + nullptr); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); +} + +TEST(ParseAndEncodeFloatingPointNumber, DoNotTouchErrorMessageStringOnSuccess) { + EncodeNumberStatus rc = EncodeNumberStatus::kInvalidText; + std::string err_msg("random content"); + NumberType type = {32, SPV_NUMBER_FLOATING}; + + rc = ParseAndEncodeFloatingPointNumber( + "0.0", type, [](uint32_t word) { EXPECT_EQ(0x0u, word); }, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + EXPECT_EQ("random content", err_msg); +} + +TEST(ParseAndEncodeNumber, Sample) { + EncodeNumberStatus rc = EncodeNumberStatus::kSuccess; + std::string err_msg; + NumberType type = {32, SPV_NUMBER_SIGNED_INT}; + + // Invalid with error message string + rc = ParseAndEncodeNumber("something wrong", type, AssertEmitFunc, &err_msg); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + EXPECT_EQ("Invalid unsigned integer literal: something wrong", err_msg); + + // Invalid without error message string + rc = ParseAndEncodeNumber("something wrong", type, AssertEmitFunc, nullptr); + EXPECT_EQ(EncodeNumberStatus::kInvalidText, rc); + + // Signed integer, should not touch the error message string. + err_msg = "random content"; + rc = ParseAndEncodeNumber("-1", type, + [](uint32_t word) { EXPECT_EQ(0xffffffffu, word); }, + &err_msg); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + EXPECT_EQ("random content", err_msg); + + // Unsigned integer + type = {32, SPV_NUMBER_UNSIGNED_INT}; + rc = ParseAndEncodeNumber( + "1", type, [](uint32_t word) { EXPECT_EQ(1u, word); }, nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); + + // Float + type = {32, SPV_NUMBER_FLOATING}; + rc = ParseAndEncodeNumber("-1.0", type, + [](uint32_t word) { EXPECT_EQ(0xbf800000, word); }, + nullptr); + EXPECT_EQ(EncodeNumberStatus::kSuccess, rc); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/preserve_numeric_ids_test.cpp b/3rdparty/spirv-tools/test/preserve_numeric_ids_test.cpp new file mode 100644 index 000000000..75d730d17 --- /dev/null +++ b/3rdparty/spirv-tools/test/preserve_numeric_ids_test.cpp @@ -0,0 +1,157 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Tests for unique type declaration rules validator. + +#include + +#include "source/text.h" +#include "source/text_handler.h" +#include "test_fixture.h" + +namespace { + +using spvtest::ScopedContext; + +// Converts code to binary and then back to text. +spv_result_t ToBinaryAndBack( + const std::string& before, std::string* after, + uint32_t text_to_binary_options = SPV_TEXT_TO_BINARY_OPTION_NONE, + uint32_t binary_to_text_options = SPV_BINARY_TO_TEXT_OPTION_NONE, + spv_target_env env = SPV_ENV_UNIVERSAL_1_0) { + ScopedContext ctx(env); + spv_binary binary; + spv_text text; + + spv_result_t result = + spvTextToBinaryWithOptions(ctx.context, before.c_str(), before.size(), + text_to_binary_options, &binary, nullptr); + if (result != SPV_SUCCESS) { + return result; + } + + result = spvBinaryToText(ctx.context, binary->code, binary->wordCount, + binary_to_text_options, &text, nullptr); + if (result != SPV_SUCCESS) { + return result; + } + + *after = std::string(text->str, text->length); + + spvBinaryDestroy(binary); + spvTextDestroy(text); + + return SPV_SUCCESS; +} + +TEST(ToBinaryAndBack, DontPreserveNumericIds) { + const std::string before = + R"(OpCapability Addresses +OpCapability Kernel +OpCapability GenericPointer +OpCapability Linkage +OpMemoryModel Physical32 OpenCL +%i32 = OpTypeInt 32 1 +%u32 = OpTypeInt 32 0 +%f32 = OpTypeFloat 32 +%200 = OpTypeVoid +%300 = OpTypeFunction %200 +%main = OpFunction %200 None %300 +%entry = OpLabel +%100 = OpConstant %u32 100 +%1 = OpConstant %u32 200 +%2 = OpConstant %u32 300 +OpReturn +OpFunctionEnd +)"; + + const std::string expected = + R"(OpCapability Addresses +OpCapability Kernel +OpCapability GenericPointer +OpCapability Linkage +OpMemoryModel Physical32 OpenCL +%1 = OpTypeInt 32 1 +%2 = OpTypeInt 32 0 +%3 = OpTypeFloat 32 +%4 = OpTypeVoid +%5 = OpTypeFunction %4 +%6 = OpFunction %4 None %5 +%7 = OpLabel +%8 = OpConstant %2 100 +%9 = OpConstant %2 200 +%10 = OpConstant %2 300 +OpReturn +OpFunctionEnd +)"; + + std::string after; + EXPECT_EQ(SPV_SUCCESS, + ToBinaryAndBack(before, &after, SPV_TEXT_TO_BINARY_OPTION_NONE, + SPV_BINARY_TO_TEXT_OPTION_NO_HEADER)); + + EXPECT_EQ(expected, after); +} + +TEST(TextHandler, PreserveNumericIds) { + const std::string before = + R"(OpCapability Addresses +OpCapability Kernel +OpCapability GenericPointer +OpCapability Linkage +OpMemoryModel Physical32 OpenCL +%i32 = OpTypeInt 32 1 +%u32 = OpTypeInt 32 0 +%f32 = OpTypeFloat 32 +%200 = OpTypeVoid +%300 = OpTypeFunction %200 +%main = OpFunction %200 None %300 +%entry = OpLabel +%100 = OpConstant %u32 100 +%1 = OpConstant %u32 200 +%2 = OpConstant %u32 300 +OpReturn +OpFunctionEnd +)"; + + const std::string expected = + R"(OpCapability Addresses +OpCapability Kernel +OpCapability GenericPointer +OpCapability Linkage +OpMemoryModel Physical32 OpenCL +%3 = OpTypeInt 32 1 +%4 = OpTypeInt 32 0 +%5 = OpTypeFloat 32 +%200 = OpTypeVoid +%300 = OpTypeFunction %200 +%6 = OpFunction %200 None %300 +%7 = OpLabel +%100 = OpConstant %4 100 +%1 = OpConstant %4 200 +%2 = OpConstant %4 300 +OpReturn +OpFunctionEnd +)"; + + std::string after; + EXPECT_EQ(SPV_SUCCESS, + ToBinaryAndBack(before, &after, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS, + SPV_BINARY_TO_TEXT_OPTION_NO_HEADER)); + + EXPECT_EQ(expected, after); +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/scripts/test_compact_ids.py b/3rdparty/spirv-tools/test/scripts/test_compact_ids.py new file mode 100644 index 000000000..b9b5b1bc0 --- /dev/null +++ b/3rdparty/spirv-tools/test/scripts/test_compact_ids.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python +# Copyright (c) 2017 Google Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Tests correctness of opt pass tools/opt --compact-ids.""" + +from __future__ import print_function + +import os.path +import sys +import tempfile + +def test_spirv_file(path, temp_dir): + optimized_spv_path = os.path.join(temp_dir, 'optimized.spv') + optimized_dis_path = os.path.join(temp_dir, 'optimized.dis') + converted_spv_path = os.path.join(temp_dir, 'converted.spv') + converted_dis_path = os.path.join(temp_dir, 'converted.dis') + + os.system('tools/spirv-opt ' + path + ' -o ' + optimized_spv_path + + ' --compact-ids') + os.system('tools/spirv-dis ' + optimized_spv_path + ' -o ' + + optimized_dis_path) + + os.system('tools/spirv-dis ' + path + ' -o ' + converted_dis_path) + os.system('tools/spirv-as ' + converted_dis_path + ' -o ' + + converted_spv_path) + os.system('tools/spirv-dis ' + converted_spv_path + ' -o ' + + converted_dis_path) + + with open(converted_dis_path, 'r') as f: + converted_dis = f.readlines()[3:] + + with open(optimized_dis_path, 'r') as f: + optimized_dis = f.readlines()[3:] + + return converted_dis == optimized_dis + +def print_usage(): + template= \ +"""{script} tests correctness of opt pass tools/opt --compact-ids + +USAGE: python {script} [] + +Requires tools/spirv-dis, tools/spirv-as and tools/spirv-opt to be in path +(call the script from the SPIRV-Tools build output directory). + +TIP: In order to test all .spv files under current dir use +find -name "*.spv" -print0 | xargs -0 -s 2000000 python {script} +""" + print(template.format(script=sys.argv[0])); + +def main(): + if not os.path.isfile('tools/spirv-dis'): + print('error: tools/spirv-dis not found') + print_usage() + exit(1) + + if not os.path.isfile('tools/spirv-as'): + print('error: tools/spirv-as not found') + print_usage() + exit(1) + + if not os.path.isfile('tools/spirv-opt'): + print('error: tools/spirv-opt not found') + print_usage() + exit(1) + + paths = sys.argv[1:] + if not paths: + print_usage() + + num_failed = 0 + + temp_dir = tempfile.mkdtemp() + + for path in paths: + success = test_spirv_file(path, temp_dir) + if not success: + print('Test failed for ' + path) + num_failed += 1 + + print('Tested ' + str(len(paths)) + ' files') + + if num_failed: + print(str(num_failed) + ' tests failed') + exit(1) + else: + print('All tests successful') + exit(0) + +if __name__ == '__main__': + main() diff --git a/3rdparty/spirv-tools/test/software_version_test.cpp b/3rdparty/spirv-tools/test/software_version_test.cpp new file mode 100644 index 000000000..e1b350440 --- /dev/null +++ b/3rdparty/spirv-tools/test/software_version_test.cpp @@ -0,0 +1,65 @@ +// Copyright (c) 2015-2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "unit_spirv.h" + +#include + +#include "gmock/gmock.h" + +using ::testing::AnyOf; +using ::testing::Eq; +using ::testing::Ge; +using ::testing::StartsWith; + +namespace { + +void CheckFormOfHighLevelVersion(const std::string& version) { + std::istringstream s(version); + char v = 'x'; + int year = -1; + char period = 'x'; + int index = -1; + s >> v >> year >> period >> index; + EXPECT_THAT(v, Eq('v')); + EXPECT_THAT(year, Ge(2016)); + EXPECT_THAT(period, Eq('.')); + EXPECT_THAT(index, Ge(0)); + EXPECT_TRUE(s.good() || s.eof()); + + std::string rest; + s >> rest; + EXPECT_THAT(rest, AnyOf("", "-dev")); +} + +TEST(SoftwareVersion, ShortIsCorrectForm) { + SCOPED_TRACE("short form"); + CheckFormOfHighLevelVersion(spvSoftwareVersionString()); +} + +TEST(SoftwareVersion, DetailedIsCorrectForm) { + const std::string detailed_version(spvSoftwareVersionDetailsString()); + EXPECT_THAT(detailed_version, StartsWith("SPIRV-Tools v")); + + // Parse the high level version. + const std::string from_v = + detailed_version.substr(detailed_version.find_first_of('v')); + const size_t first_space_after_v_or_npos = from_v.find_first_of(' '); + SCOPED_TRACE(detailed_version); + CheckFormOfHighLevelVersion(from_v.substr(0, first_space_after_v_or_npos)); + + // We don't actually care about what comes after the version number. +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/stats/CMakeLists.txt b/3rdparty/spirv-tools/test/stats/CMakeLists.txt new file mode 100644 index 000000000..20f05fd0f --- /dev/null +++ b/3rdparty/spirv-tools/test/stats/CMakeLists.txt @@ -0,0 +1,31 @@ +# Copyright (c) 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(VAL_TEST_COMMON_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/../test_fixture.h + ${CMAKE_CURRENT_SOURCE_DIR}/../unit_spirv.h +) + +add_spvtools_unittest(TARGET stats_aggregate + SRCS stats_aggregate_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) + +add_spvtools_unittest(TARGET stats_analyzer + SRCS stats_analyzer_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../tools/stats/stats_analyzer.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) diff --git a/3rdparty/spirv-tools/test/stats/stats_aggregate_test.cpp b/3rdparty/spirv-tools/test/stats/stats_aggregate_test.cpp new file mode 100644 index 000000000..11c71aa6c --- /dev/null +++ b/3rdparty/spirv-tools/test/stats/stats_aggregate_test.cpp @@ -0,0 +1,484 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Tests for unique type declaration rules validator. + +#include + +#include "source/spirv_stats.h" +#include "test_fixture.h" +#include "unit_spirv.h" + +namespace { + +using libspirv::SetContextMessageConsumer; +using libspirv::SpirvStats; +using spvtest::ScopedContext; + +void DiagnosticsMessageHandler(spv_message_level_t level, const char*, + const spv_position_t& position, + const char* message) { + switch (level) { + case SPV_MSG_FATAL: + case SPV_MSG_INTERNAL_ERROR: + case SPV_MSG_ERROR: + std::cerr << "error: " << position.index << ": " << message << std::endl; + break; + case SPV_MSG_WARNING: + std::cout << "warning: " << position.index << ": " << message + << std::endl; + break; + case SPV_MSG_INFO: + std::cout << "info: " << position.index << ": " << message << std::endl; + break; + default: + break; + } +} + +// Calls libspirv::AggregateStats for binary compiled from |code|. +void CompileAndAggregateStats(const std::string& code, SpirvStats* stats, + spv_target_env env = SPV_ENV_UNIVERSAL_1_1) { + ScopedContext ctx(env); + SetContextMessageConsumer(ctx.context, DiagnosticsMessageHandler); + spv_binary binary; + ASSERT_EQ(SPV_SUCCESS, spvTextToBinary(ctx.context, code.c_str(), code.size(), + &binary, nullptr)); + + ASSERT_EQ(SPV_SUCCESS, AggregateStats(*ctx.context, binary->code, + binary->wordCount, nullptr, stats)); + spvBinaryDestroy(binary); +} + +TEST(AggregateStats, CapabilityHistogram) { + const std::string code1 = R"( +OpCapability Addresses +OpCapability Kernel +OpCapability GenericPointer +OpCapability Linkage +OpMemoryModel Physical32 OpenCL +)"; + + const std::string code2 = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +)"; + + SpirvStats stats; + + CompileAndAggregateStats(code1, &stats); + EXPECT_EQ(4u, stats.capability_hist.size()); + EXPECT_EQ(0u, stats.capability_hist.count(SpvCapabilityShader)); + EXPECT_EQ(1u, stats.capability_hist.at(SpvCapabilityAddresses)); + EXPECT_EQ(1u, stats.capability_hist.at(SpvCapabilityKernel)); + EXPECT_EQ(1u, stats.capability_hist.at(SpvCapabilityGenericPointer)); + EXPECT_EQ(1u, stats.capability_hist.at(SpvCapabilityLinkage)); + + CompileAndAggregateStats(code2, &stats); + EXPECT_EQ(5u, stats.capability_hist.size()); + EXPECT_EQ(1u, stats.capability_hist.at(SpvCapabilityShader)); + EXPECT_EQ(1u, stats.capability_hist.at(SpvCapabilityAddresses)); + EXPECT_EQ(1u, stats.capability_hist.at(SpvCapabilityKernel)); + EXPECT_EQ(1u, stats.capability_hist.at(SpvCapabilityGenericPointer)); + EXPECT_EQ(2u, stats.capability_hist.at(SpvCapabilityLinkage)); + + CompileAndAggregateStats(code1, &stats); + EXPECT_EQ(5u, stats.capability_hist.size()); + EXPECT_EQ(1u, stats.capability_hist.at(SpvCapabilityShader)); + EXPECT_EQ(2u, stats.capability_hist.at(SpvCapabilityAddresses)); + EXPECT_EQ(2u, stats.capability_hist.at(SpvCapabilityKernel)); + EXPECT_EQ(2u, stats.capability_hist.at(SpvCapabilityGenericPointer)); + EXPECT_EQ(3u, stats.capability_hist.at(SpvCapabilityLinkage)); + + CompileAndAggregateStats(code2, &stats); + EXPECT_EQ(5u, stats.capability_hist.size()); + EXPECT_EQ(2u, stats.capability_hist.at(SpvCapabilityShader)); + EXPECT_EQ(2u, stats.capability_hist.at(SpvCapabilityAddresses)); + EXPECT_EQ(2u, stats.capability_hist.at(SpvCapabilityKernel)); + EXPECT_EQ(2u, stats.capability_hist.at(SpvCapabilityGenericPointer)); + EXPECT_EQ(4u, stats.capability_hist.at(SpvCapabilityLinkage)); +} + +TEST(AggregateStats, ExtensionHistogram) { + const std::string code1 = R"( +OpCapability Addresses +OpCapability Kernel +OpCapability GenericPointer +OpCapability Linkage +OpExtension "SPV_KHR_16bit_storage" +OpMemoryModel Physical32 OpenCL +)"; + + const std::string code2 = R"( +OpCapability Shader +OpCapability Linkage +OpExtension "SPV_NV_viewport_array2" +OpExtension "greatest_extension_ever" +OpMemoryModel Logical GLSL450 +)"; + + SpirvStats stats; + + CompileAndAggregateStats(code1, &stats); + EXPECT_EQ(1u, stats.extension_hist.size()); + EXPECT_EQ(0u, stats.extension_hist.count("SPV_NV_viewport_array2")); + EXPECT_EQ(1u, stats.extension_hist.at("SPV_KHR_16bit_storage")); + + CompileAndAggregateStats(code2, &stats); + EXPECT_EQ(3u, stats.extension_hist.size()); + EXPECT_EQ(1u, stats.extension_hist.at("SPV_NV_viewport_array2")); + EXPECT_EQ(1u, stats.extension_hist.at("SPV_KHR_16bit_storage")); + EXPECT_EQ(1u, stats.extension_hist.at("greatest_extension_ever")); + + CompileAndAggregateStats(code1, &stats); + EXPECT_EQ(3u, stats.extension_hist.size()); + EXPECT_EQ(1u, stats.extension_hist.at("SPV_NV_viewport_array2")); + EXPECT_EQ(2u, stats.extension_hist.at("SPV_KHR_16bit_storage")); + EXPECT_EQ(1u, stats.extension_hist.at("greatest_extension_ever")); + + CompileAndAggregateStats(code2, &stats); + EXPECT_EQ(3u, stats.extension_hist.size()); + EXPECT_EQ(2u, stats.extension_hist.at("SPV_NV_viewport_array2")); + EXPECT_EQ(2u, stats.extension_hist.at("SPV_KHR_16bit_storage")); + EXPECT_EQ(2u, stats.extension_hist.at("greatest_extension_ever")); +} + +TEST(AggregateStats, VersionHistogram) { + const std::string code1 = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +)"; + + SpirvStats stats; + + CompileAndAggregateStats(code1, &stats); + EXPECT_EQ(1u, stats.version_hist.size()); + EXPECT_EQ(1u, stats.version_hist.at(0x00010100)); + + CompileAndAggregateStats(code1, &stats, SPV_ENV_UNIVERSAL_1_0); + EXPECT_EQ(2u, stats.version_hist.size()); + EXPECT_EQ(1u, stats.version_hist.at(0x00010100)); + EXPECT_EQ(1u, stats.version_hist.at(0x00010000)); + + CompileAndAggregateStats(code1, &stats); + EXPECT_EQ(2u, stats.version_hist.size()); + EXPECT_EQ(2u, stats.version_hist.at(0x00010100)); + EXPECT_EQ(1u, stats.version_hist.at(0x00010000)); + + CompileAndAggregateStats(code1, &stats, SPV_ENV_UNIVERSAL_1_0); + EXPECT_EQ(2u, stats.version_hist.size()); + EXPECT_EQ(2u, stats.version_hist.at(0x00010100)); + EXPECT_EQ(2u, stats.version_hist.at(0x00010000)); +} + +TEST(AggregateStats, GeneratorHistogram) { + const std::string code1 = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +)"; + + const uint32_t kGeneratorKhronosAssembler = SPV_GENERATOR_KHRONOS_ASSEMBLER + << 16; + + SpirvStats stats; + + CompileAndAggregateStats(code1, &stats); + EXPECT_EQ(1u, stats.generator_hist.size()); + EXPECT_EQ(1u, stats.generator_hist.at(kGeneratorKhronosAssembler)); + + CompileAndAggregateStats(code1, &stats); + EXPECT_EQ(1u, stats.generator_hist.size()); + EXPECT_EQ(2u, stats.generator_hist.at(kGeneratorKhronosAssembler)); +} + +TEST(AggregateStats, OpcodeHistogram) { + const std::string code1 = R"( +OpCapability Addresses +OpCapability Kernel +OpCapability Int64 +OpCapability Linkage +OpMemoryModel Physical32 OpenCL +%u64 = OpTypeInt 64 0 +%u32 = OpTypeInt 32 0 +%f32 = OpTypeFloat 32 +)"; + + const std::string code2 = R"( +OpCapability Shader +OpCapability Linkage +OpExtension "SPV_NV_viewport_array2" +OpMemoryModel Logical GLSL450 +)"; + + SpirvStats stats; + + CompileAndAggregateStats(code1, &stats); + EXPECT_EQ(4u, stats.opcode_hist.size()); + EXPECT_EQ(4u, stats.opcode_hist.at(SpvOpCapability)); + EXPECT_EQ(1u, stats.opcode_hist.at(SpvOpMemoryModel)); + EXPECT_EQ(2u, stats.opcode_hist.at(SpvOpTypeInt)); + EXPECT_EQ(1u, stats.opcode_hist.at(SpvOpTypeFloat)); + + CompileAndAggregateStats(code2, &stats); + EXPECT_EQ(5u, stats.opcode_hist.size()); + EXPECT_EQ(6u, stats.opcode_hist.at(SpvOpCapability)); + EXPECT_EQ(2u, stats.opcode_hist.at(SpvOpMemoryModel)); + EXPECT_EQ(2u, stats.opcode_hist.at(SpvOpTypeInt)); + EXPECT_EQ(1u, stats.opcode_hist.at(SpvOpTypeFloat)); + EXPECT_EQ(1u, stats.opcode_hist.at(SpvOpExtension)); + + CompileAndAggregateStats(code1, &stats); + EXPECT_EQ(5u, stats.opcode_hist.size()); + EXPECT_EQ(10u, stats.opcode_hist.at(SpvOpCapability)); + EXPECT_EQ(3u, stats.opcode_hist.at(SpvOpMemoryModel)); + EXPECT_EQ(4u, stats.opcode_hist.at(SpvOpTypeInt)); + EXPECT_EQ(2u, stats.opcode_hist.at(SpvOpTypeFloat)); + EXPECT_EQ(1u, stats.opcode_hist.at(SpvOpExtension)); + + CompileAndAggregateStats(code2, &stats); + EXPECT_EQ(5u, stats.opcode_hist.size()); + EXPECT_EQ(12u, stats.opcode_hist.at(SpvOpCapability)); + EXPECT_EQ(4u, stats.opcode_hist.at(SpvOpMemoryModel)); + EXPECT_EQ(4u, stats.opcode_hist.at(SpvOpTypeInt)); + EXPECT_EQ(2u, stats.opcode_hist.at(SpvOpTypeFloat)); + EXPECT_EQ(2u, stats.opcode_hist.at(SpvOpExtension)); +} + +TEST(AggregateStats, OpcodeMarkovHistogram) { + const std::string code1 = R"( +OpCapability Shader +OpCapability Linkage +OpExtension "SPV_NV_viewport_array2" +OpMemoryModel Logical GLSL450 +)"; + + const std::string code2 = R"( +OpCapability Addresses +OpCapability Kernel +OpCapability Int64 +OpCapability Linkage +OpMemoryModel Physical32 OpenCL +%u64 = OpTypeInt 64 0 +%u32 = OpTypeInt 32 0 +%f32 = OpTypeFloat 32 +)"; + + SpirvStats stats; + stats.opcode_markov_hist.resize(2); + + CompileAndAggregateStats(code1, &stats); + ASSERT_EQ(2u, stats.opcode_markov_hist.size()); + EXPECT_EQ(2u, stats.opcode_markov_hist[0].size()); + EXPECT_EQ(2u, stats.opcode_markov_hist[0].at(SpvOpCapability).size()); + EXPECT_EQ(1u, stats.opcode_markov_hist[0].at(SpvOpExtension).size()); + EXPECT_EQ( + 1u, stats.opcode_markov_hist[0].at(SpvOpCapability).at(SpvOpCapability)); + EXPECT_EQ(1u, + stats.opcode_markov_hist[0].at(SpvOpCapability).at(SpvOpExtension)); + EXPECT_EQ( + 1u, stats.opcode_markov_hist[0].at(SpvOpExtension).at(SpvOpMemoryModel)); + + EXPECT_EQ(1u, stats.opcode_markov_hist[1].size()); + EXPECT_EQ(2u, stats.opcode_markov_hist[1].at(SpvOpCapability).size()); + EXPECT_EQ(1u, + stats.opcode_markov_hist[1].at(SpvOpCapability).at(SpvOpExtension)); + EXPECT_EQ( + 1u, stats.opcode_markov_hist[1].at(SpvOpCapability).at(SpvOpMemoryModel)); + + CompileAndAggregateStats(code2, &stats); + ASSERT_EQ(2u, stats.opcode_markov_hist.size()); + EXPECT_EQ(4u, stats.opcode_markov_hist[0].size()); + EXPECT_EQ(3u, stats.opcode_markov_hist[0].at(SpvOpCapability).size()); + EXPECT_EQ(1u, stats.opcode_markov_hist[0].at(SpvOpExtension).size()); + EXPECT_EQ(1u, stats.opcode_markov_hist[0].at(SpvOpMemoryModel).size()); + EXPECT_EQ(2u, stats.opcode_markov_hist[0].at(SpvOpTypeInt).size()); + EXPECT_EQ( + 4u, stats.opcode_markov_hist[0].at(SpvOpCapability).at(SpvOpCapability)); + EXPECT_EQ(1u, + stats.opcode_markov_hist[0].at(SpvOpCapability).at(SpvOpExtension)); + EXPECT_EQ( + 1u, stats.opcode_markov_hist[0].at(SpvOpCapability).at(SpvOpMemoryModel)); + EXPECT_EQ( + 1u, stats.opcode_markov_hist[0].at(SpvOpExtension).at(SpvOpMemoryModel)); + EXPECT_EQ(1u, + stats.opcode_markov_hist[0].at(SpvOpMemoryModel).at(SpvOpTypeInt)); + EXPECT_EQ(1u, stats.opcode_markov_hist[0].at(SpvOpTypeInt).at(SpvOpTypeInt)); + EXPECT_EQ(1u, + stats.opcode_markov_hist[0].at(SpvOpTypeInt).at(SpvOpTypeFloat)); + + EXPECT_EQ(3u, stats.opcode_markov_hist[1].size()); + EXPECT_EQ(4u, stats.opcode_markov_hist[1].at(SpvOpCapability).size()); + EXPECT_EQ(1u, stats.opcode_markov_hist[1].at(SpvOpMemoryModel).size()); + EXPECT_EQ(1u, stats.opcode_markov_hist[1].at(SpvOpTypeInt).size()); + EXPECT_EQ( + 2u, stats.opcode_markov_hist[1].at(SpvOpCapability).at(SpvOpCapability)); + EXPECT_EQ(1u, + stats.opcode_markov_hist[1].at(SpvOpCapability).at(SpvOpExtension)); + EXPECT_EQ( + 2u, stats.opcode_markov_hist[1].at(SpvOpCapability).at(SpvOpMemoryModel)); + EXPECT_EQ(1u, + stats.opcode_markov_hist[1].at(SpvOpCapability).at(SpvOpTypeInt)); + EXPECT_EQ(1u, + stats.opcode_markov_hist[1].at(SpvOpMemoryModel).at(SpvOpTypeInt)); + EXPECT_EQ(1u, + stats.opcode_markov_hist[1].at(SpvOpTypeInt).at(SpvOpTypeFloat)); +} + +TEST(AggregateStats, ConstantLiteralsHistogram) { + const std::string code1 = R"( +OpCapability Addresses +OpCapability Kernel +OpCapability GenericPointer +OpCapability Linkage +OpCapability Float64 +OpCapability Int16 +OpCapability Int64 +OpMemoryModel Physical32 OpenCL +%u16 = OpTypeInt 16 0 +%u32 = OpTypeInt 32 0 +%u64 = OpTypeInt 64 0 +%f32 = OpTypeFloat 32 +%f64 = OpTypeFloat 64 +%1 = OpConstant %f32 0.1 +%2 = OpConstant %f32 -2 +%3 = OpConstant %f64 -2 +%4 = OpConstant %u16 16 +%5 = OpConstant %u16 2 +%6 = OpConstant %u32 32 +%7 = OpConstant %u64 64 +)"; + + const std::string code2 = R"( +OpCapability Shader +OpCapability Linkage +OpCapability Int16 +OpCapability Int64 +OpMemoryModel Logical GLSL450 +%f32 = OpTypeFloat 32 +%u16 = OpTypeInt 16 0 +%s16 = OpTypeInt 16 1 +%u32 = OpTypeInt 32 0 +%s32 = OpTypeInt 32 1 +%u64 = OpTypeInt 64 0 +%s64 = OpTypeInt 64 1 +%1 = OpConstant %f32 0.1 +%2 = OpConstant %f32 -2 +%3 = OpConstant %u16 1 +%4 = OpConstant %u16 16 +%5 = OpConstant %u16 2 +%6 = OpConstant %s16 -16 +%7 = OpConstant %u32 32 +%8 = OpConstant %s32 2 +%9 = OpConstant %s32 -32 +%10 = OpConstant %u64 64 +%11 = OpConstant %s64 -64 +)"; + + SpirvStats stats; + + CompileAndAggregateStats(code1, &stats); + EXPECT_EQ(2u, stats.f32_constant_hist.size()); + EXPECT_EQ(1u, stats.f64_constant_hist.size()); + EXPECT_EQ(1u, stats.f32_constant_hist.at(0.1f)); + EXPECT_EQ(1u, stats.f32_constant_hist.at(-2.f)); + EXPECT_EQ(1u, stats.f64_constant_hist.at(-2)); + + EXPECT_EQ(2u, stats.u16_constant_hist.size()); + EXPECT_EQ(0u, stats.s16_constant_hist.size()); + EXPECT_EQ(1u, stats.u32_constant_hist.size()); + EXPECT_EQ(0u, stats.s32_constant_hist.size()); + EXPECT_EQ(1u, stats.u64_constant_hist.size()); + EXPECT_EQ(0u, stats.s64_constant_hist.size()); + EXPECT_EQ(1u, stats.u16_constant_hist.at(16)); + EXPECT_EQ(1u, stats.u16_constant_hist.at(2)); + EXPECT_EQ(1u, stats.u32_constant_hist.at(32)); + EXPECT_EQ(1u, stats.u64_constant_hist.at(64)); + + CompileAndAggregateStats(code2, &stats); + EXPECT_EQ(2u, stats.f32_constant_hist.size()); + EXPECT_EQ(1u, stats.f64_constant_hist.size()); + EXPECT_EQ(2u, stats.f32_constant_hist.at(0.1f)); + EXPECT_EQ(2u, stats.f32_constant_hist.at(-2.f)); + EXPECT_EQ(1u, stats.f64_constant_hist.at(-2)); + + EXPECT_EQ(3u, stats.u16_constant_hist.size()); + EXPECT_EQ(1u, stats.s16_constant_hist.size()); + EXPECT_EQ(1u, stats.u32_constant_hist.size()); + EXPECT_EQ(2u, stats.s32_constant_hist.size()); + EXPECT_EQ(1u, stats.u64_constant_hist.size()); + EXPECT_EQ(1u, stats.s64_constant_hist.size()); + EXPECT_EQ(2u, stats.u16_constant_hist.at(16)); + EXPECT_EQ(2u, stats.u16_constant_hist.at(2)); + EXPECT_EQ(1u, stats.u16_constant_hist.at(1)); + EXPECT_EQ(1u, stats.s16_constant_hist.at(-16)); + EXPECT_EQ(2u, stats.u32_constant_hist.at(32)); + EXPECT_EQ(1u, stats.s32_constant_hist.at(2)); + EXPECT_EQ(1u, stats.s32_constant_hist.at(-32)); + EXPECT_EQ(2u, stats.u64_constant_hist.at(64)); + EXPECT_EQ(1u, stats.s64_constant_hist.at(-64)); +} + +TEST(AggregateStats, IdDescriptor) { + const std::string code1 = R"( +OpCapability Addresses +OpCapability Kernel +OpCapability GenericPointer +OpCapability Linkage +OpMemoryModel Physical32 OpenCL +%u32 = OpTypeInt 32 0 +%f32 = OpTypeFloat 32 +%1 = OpConstant %f32 1 +%2 = OpConstant %f32 1 +%3 = OpConstant %u32 32 +)"; + + const std::string code2 = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +%f32 = OpTypeFloat 32 +%u32 = OpTypeInt 32 0 +%1 = OpConstant %f32 1 +%2 = OpConstant %f32 3 +%3 = OpConstant %u32 32 +)"; + + const uint32_t kF32 = 1951208733; + const uint32_t kU32 = 2430404313; + const uint32_t kF32_1 = 296981500; + const uint32_t kF32_3 = 1450415100; + const uint32_t kU32_32 = 827246872; + + SpirvStats stats; + + CompileAndAggregateStats(code1, &stats); + + { + const std::unordered_map expected = { + {kF32, 3}, {kU32, 2}, {kF32_1, 2}, {kU32_32, 1}}; + EXPECT_EQ(expected, stats.id_descriptor_hist); + } + + CompileAndAggregateStats(code2, &stats); + { + const std::unordered_map expected = { + {kF32, 6}, {kU32, 4}, {kF32_1, 3}, {kF32_3, 1}, {kU32_32, 2}}; + EXPECT_EQ(expected, stats.id_descriptor_hist); + } +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/stats/stats_analyzer_test.cpp b/3rdparty/spirv-tools/test/stats/stats_analyzer_test.cpp new file mode 100644 index 000000000..9608af8db --- /dev/null +++ b/3rdparty/spirv-tools/test/stats/stats_analyzer_test.cpp @@ -0,0 +1,172 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Tests for unique type declaration rules validator. + +#include +#include + +#include "latest_version_spirv_header.h" +#include "test_fixture.h" +#include "tools/stats/stats_analyzer.h" + +namespace { + +using libspirv::SpirvStats; + +// Fills |stats| with some synthetic header stats, as if aggregated from 100 +// modules (100 used for simpler percentage evaluation). +void FillDefaultStats(SpirvStats* stats) { + *stats = SpirvStats(); + stats->version_hist[0x00010000] = 40; + stats->version_hist[0x00010100] = 60; + stats->generator_hist[0x00000000] = 64; + stats->generator_hist[0x00010000] = 1; + stats->generator_hist[0x00020000] = 2; + stats->generator_hist[0x00030000] = 3; + stats->generator_hist[0x00040000] = 4; + stats->generator_hist[0x00050000] = 5; + stats->generator_hist[0x00060000] = 6; + stats->generator_hist[0x00070000] = 7; + stats->generator_hist[0x00080000] = 8; + + int num_version_entries = 0; + for (const auto& pair : stats->version_hist) { + num_version_entries += pair.second; + } + + int num_generator_entries = 0; + for (const auto& pair : stats->generator_hist) { + num_generator_entries += pair.second; + } + + EXPECT_EQ(num_version_entries, num_generator_entries); +} + +TEST(StatsAnalyzer, Version) { + SpirvStats stats; + FillDefaultStats(&stats); + + StatsAnalyzer analyzer(stats); + + std::stringstream ss; + analyzer.WriteVersion(ss); + const std::string output = ss.str(); + const std::string expected_output = "Version 1.1 60%\nVersion 1.0 40%\n"; + + EXPECT_EQ(expected_output, output); +} + +TEST(StatsAnalyzer, Generator) { + SpirvStats stats; + FillDefaultStats(&stats); + + StatsAnalyzer analyzer(stats); + + std::stringstream ss; + analyzer.WriteGenerator(ss); + const std::string output = ss.str(); + const std::string expected_output = + "Khronos 64%\nKhronos Glslang Reference Front End 8%\n" + "Khronos SPIR-V Tools Assembler 7%\nKhronos LLVM/SPIR-V Translator 6%" + "\nARM 5%\nNVIDIA 4%\nCodeplay 3%\nValve 2%\nLunarG 1%\n"; + + EXPECT_EQ(expected_output, output); +} + +TEST(StatsAnalyzer, Capability) { + SpirvStats stats; + FillDefaultStats(&stats); + + stats.capability_hist[SpvCapabilityShader] = 25; + stats.capability_hist[SpvCapabilityKernel] = 75; + + StatsAnalyzer analyzer(stats); + + std::stringstream ss; + analyzer.WriteCapability(ss); + const std::string output = ss.str(); + const std::string expected_output = "Kernel 75%\nShader 25%\n"; + + EXPECT_EQ(expected_output, output); +} + +TEST(StatsAnalyzer, Extension) { + SpirvStats stats; + FillDefaultStats(&stats); + + stats.extension_hist["greatest_extension_ever"] = 1; + stats.extension_hist["worst_extension_ever"] = 10; + + StatsAnalyzer analyzer(stats); + + std::stringstream ss; + analyzer.WriteExtension(ss); + const std::string output = ss.str(); + const std::string expected_output = + "worst_extension_ever 10%\ngreatest_extension_ever 1%\n"; + + EXPECT_EQ(expected_output, output); +} + +TEST(StatsAnalyzer, Opcode) { + SpirvStats stats; + FillDefaultStats(&stats); + + stats.opcode_hist[SpvOpCapability] = 20; + stats.opcode_hist[SpvOpConstant] = 80; + stats.opcode_hist[SpvOpDecorate] = 100; + + StatsAnalyzer analyzer(stats); + + std::stringstream ss; + analyzer.WriteOpcode(ss); + const std::string output = ss.str(); + const std::string expected_output = + "Total unique opcodes used: 3\nDecorate 50%\n" + "Constant 40%\nCapability 10%\n"; + + EXPECT_EQ(expected_output, output); +} + +TEST(StatsAnalyzer, OpcodeMarkov) { + SpirvStats stats; + FillDefaultStats(&stats); + + stats.opcode_hist[SpvOpFMul] = 400; + stats.opcode_hist[SpvOpFAdd] = 200; + stats.opcode_hist[SpvOpFSub] = 400; + + stats.opcode_markov_hist.resize(1); + auto& hist = stats.opcode_markov_hist[0]; + hist[SpvOpFMul][SpvOpFAdd] = 100; + hist[SpvOpFMul][SpvOpFSub] = 300; + hist[SpvOpFAdd][SpvOpFMul] = 100; + hist[SpvOpFAdd][SpvOpFAdd] = 100; + + StatsAnalyzer analyzer(stats); + + std::stringstream ss; + analyzer.WriteOpcodeMarkov(ss); + const std::string output = ss.str(); + const std::string expected_output = + "FMul -> FSub 75% (base rate 40%, pair occurrences 300)\n" + "FMul -> FAdd 25% (base rate 20%, pair occurrences 100)\n" + "FAdd -> FAdd 50% (base rate 20%, pair occurrences 100)\n" + "FAdd -> FMul 50% (base rate 40%, pair occurrences 100)\n"; + + EXPECT_EQ(expected_output, output); +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/string_utils_test.cpp b/3rdparty/spirv-tools/test/string_utils_test.cpp new file mode 100644 index 000000000..ea1bfba62 --- /dev/null +++ b/3rdparty/spirv-tools/test/string_utils_test.cpp @@ -0,0 +1,190 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "gtest/gtest.h" +#include "source/util/string_utils.h" +#include "spirv-tools/libspirv.h" + +namespace { + +using ::spvutils::CardinalToOrdinal; +using ::spvutils::ToString; + +TEST(ToString, Int) { + EXPECT_EQ("0", ToString(0)); + EXPECT_EQ("1000", ToString(1000)); + EXPECT_EQ("-1", ToString(-1)); + EXPECT_EQ("0", ToString(0LL)); + EXPECT_EQ("1000", ToString(1000LL)); + EXPECT_EQ("-1", ToString(-1LL)); +} + +TEST(ToString, Uint) { + EXPECT_EQ("0", ToString(0U)); + EXPECT_EQ("1000", ToString(1000U)); + EXPECT_EQ("0", ToString(0ULL)); + EXPECT_EQ("1000", ToString(1000ULL)); +} + +TEST(ToString, Float) { + EXPECT_EQ("0", ToString(0.f)); + EXPECT_EQ("1000", ToString(1000.f)); + EXPECT_EQ("-1.5", ToString(-1.5f)); +} + +TEST(ToString, Double) { + EXPECT_EQ("0", ToString(0.)); + EXPECT_EQ("1000", ToString(1000.)); + EXPECT_EQ("-1.5", ToString(-1.5)); +} + +TEST(CardinalToOrdinal, Test) { + EXPECT_EQ("1st", CardinalToOrdinal(1)); + EXPECT_EQ("2nd", CardinalToOrdinal(2)); + EXPECT_EQ("3rd", CardinalToOrdinal(3)); + EXPECT_EQ("4th", CardinalToOrdinal(4)); + EXPECT_EQ("5th", CardinalToOrdinal(5)); + EXPECT_EQ("6th", CardinalToOrdinal(6)); + EXPECT_EQ("7th", CardinalToOrdinal(7)); + EXPECT_EQ("8th", CardinalToOrdinal(8)); + EXPECT_EQ("9th", CardinalToOrdinal(9)); + EXPECT_EQ("10th", CardinalToOrdinal(10)); + EXPECT_EQ("11th", CardinalToOrdinal(11)); + EXPECT_EQ("12th", CardinalToOrdinal(12)); + EXPECT_EQ("13th", CardinalToOrdinal(13)); + EXPECT_EQ("14th", CardinalToOrdinal(14)); + EXPECT_EQ("15th", CardinalToOrdinal(15)); + EXPECT_EQ("16th", CardinalToOrdinal(16)); + EXPECT_EQ("17th", CardinalToOrdinal(17)); + EXPECT_EQ("18th", CardinalToOrdinal(18)); + EXPECT_EQ("19th", CardinalToOrdinal(19)); + EXPECT_EQ("20th", CardinalToOrdinal(20)); + EXPECT_EQ("21st", CardinalToOrdinal(21)); + EXPECT_EQ("22nd", CardinalToOrdinal(22)); + EXPECT_EQ("23rd", CardinalToOrdinal(23)); + EXPECT_EQ("24th", CardinalToOrdinal(24)); + EXPECT_EQ("25th", CardinalToOrdinal(25)); + EXPECT_EQ("26th", CardinalToOrdinal(26)); + EXPECT_EQ("27th", CardinalToOrdinal(27)); + EXPECT_EQ("28th", CardinalToOrdinal(28)); + EXPECT_EQ("29th", CardinalToOrdinal(29)); + EXPECT_EQ("30th", CardinalToOrdinal(30)); + EXPECT_EQ("31st", CardinalToOrdinal(31)); + EXPECT_EQ("32nd", CardinalToOrdinal(32)); + EXPECT_EQ("33rd", CardinalToOrdinal(33)); + EXPECT_EQ("34th", CardinalToOrdinal(34)); + EXPECT_EQ("35th", CardinalToOrdinal(35)); + EXPECT_EQ("100th", CardinalToOrdinal(100)); + EXPECT_EQ("101st", CardinalToOrdinal(101)); + EXPECT_EQ("102nd", CardinalToOrdinal(102)); + EXPECT_EQ("103rd", CardinalToOrdinal(103)); + EXPECT_EQ("104th", CardinalToOrdinal(104)); + EXPECT_EQ("105th", CardinalToOrdinal(105)); + EXPECT_EQ("106th", CardinalToOrdinal(106)); + EXPECT_EQ("107th", CardinalToOrdinal(107)); + EXPECT_EQ("108th", CardinalToOrdinal(108)); + EXPECT_EQ("109th", CardinalToOrdinal(109)); + EXPECT_EQ("110th", CardinalToOrdinal(110)); + EXPECT_EQ("111th", CardinalToOrdinal(111)); + EXPECT_EQ("112th", CardinalToOrdinal(112)); + EXPECT_EQ("113th", CardinalToOrdinal(113)); + EXPECT_EQ("114th", CardinalToOrdinal(114)); + EXPECT_EQ("115th", CardinalToOrdinal(115)); + EXPECT_EQ("116th", CardinalToOrdinal(116)); + EXPECT_EQ("117th", CardinalToOrdinal(117)); + EXPECT_EQ("118th", CardinalToOrdinal(118)); + EXPECT_EQ("119th", CardinalToOrdinal(119)); + EXPECT_EQ("120th", CardinalToOrdinal(120)); + EXPECT_EQ("121st", CardinalToOrdinal(121)); + EXPECT_EQ("122nd", CardinalToOrdinal(122)); + EXPECT_EQ("123rd", CardinalToOrdinal(123)); + EXPECT_EQ("124th", CardinalToOrdinal(124)); + EXPECT_EQ("125th", CardinalToOrdinal(125)); + EXPECT_EQ("126th", CardinalToOrdinal(126)); + EXPECT_EQ("127th", CardinalToOrdinal(127)); + EXPECT_EQ("128th", CardinalToOrdinal(128)); + EXPECT_EQ("129th", CardinalToOrdinal(129)); + EXPECT_EQ("130th", CardinalToOrdinal(130)); + EXPECT_EQ("131st", CardinalToOrdinal(131)); + EXPECT_EQ("132nd", CardinalToOrdinal(132)); + EXPECT_EQ("133rd", CardinalToOrdinal(133)); + EXPECT_EQ("134th", CardinalToOrdinal(134)); + EXPECT_EQ("135th", CardinalToOrdinal(135)); + EXPECT_EQ("1000th", CardinalToOrdinal(1000)); + EXPECT_EQ("1001st", CardinalToOrdinal(1001)); + EXPECT_EQ("1002nd", CardinalToOrdinal(1002)); + EXPECT_EQ("1003rd", CardinalToOrdinal(1003)); + EXPECT_EQ("1004th", CardinalToOrdinal(1004)); + EXPECT_EQ("1005th", CardinalToOrdinal(1005)); + EXPECT_EQ("1006th", CardinalToOrdinal(1006)); + EXPECT_EQ("1007th", CardinalToOrdinal(1007)); + EXPECT_EQ("1008th", CardinalToOrdinal(1008)); + EXPECT_EQ("1009th", CardinalToOrdinal(1009)); + EXPECT_EQ("1010th", CardinalToOrdinal(1010)); + EXPECT_EQ("1011th", CardinalToOrdinal(1011)); + EXPECT_EQ("1012th", CardinalToOrdinal(1012)); + EXPECT_EQ("1013th", CardinalToOrdinal(1013)); + EXPECT_EQ("1014th", CardinalToOrdinal(1014)); + EXPECT_EQ("1015th", CardinalToOrdinal(1015)); + EXPECT_EQ("1016th", CardinalToOrdinal(1016)); + EXPECT_EQ("1017th", CardinalToOrdinal(1017)); + EXPECT_EQ("1018th", CardinalToOrdinal(1018)); + EXPECT_EQ("1019th", CardinalToOrdinal(1019)); + EXPECT_EQ("1020th", CardinalToOrdinal(1020)); + EXPECT_EQ("1021st", CardinalToOrdinal(1021)); + EXPECT_EQ("1022nd", CardinalToOrdinal(1022)); + EXPECT_EQ("1023rd", CardinalToOrdinal(1023)); + EXPECT_EQ("1024th", CardinalToOrdinal(1024)); + EXPECT_EQ("1025th", CardinalToOrdinal(1025)); + EXPECT_EQ("1026th", CardinalToOrdinal(1026)); + EXPECT_EQ("1027th", CardinalToOrdinal(1027)); + EXPECT_EQ("1028th", CardinalToOrdinal(1028)); + EXPECT_EQ("1029th", CardinalToOrdinal(1029)); + EXPECT_EQ("1030th", CardinalToOrdinal(1030)); + EXPECT_EQ("1031st", CardinalToOrdinal(1031)); + EXPECT_EQ("1032nd", CardinalToOrdinal(1032)); + EXPECT_EQ("1033rd", CardinalToOrdinal(1033)); + EXPECT_EQ("1034th", CardinalToOrdinal(1034)); + EXPECT_EQ("1035th", CardinalToOrdinal(1035)); + EXPECT_EQ("1200th", CardinalToOrdinal(1200)); + EXPECT_EQ("1201st", CardinalToOrdinal(1201)); + EXPECT_EQ("1202nd", CardinalToOrdinal(1202)); + EXPECT_EQ("1203rd", CardinalToOrdinal(1203)); + EXPECT_EQ("1204th", CardinalToOrdinal(1204)); + EXPECT_EQ("1205th", CardinalToOrdinal(1205)); + EXPECT_EQ("1206th", CardinalToOrdinal(1206)); + EXPECT_EQ("1207th", CardinalToOrdinal(1207)); + EXPECT_EQ("1208th", CardinalToOrdinal(1208)); + EXPECT_EQ("1209th", CardinalToOrdinal(1209)); + EXPECT_EQ("1210th", CardinalToOrdinal(1210)); + EXPECT_EQ("1211th", CardinalToOrdinal(1211)); + EXPECT_EQ("1212th", CardinalToOrdinal(1212)); + EXPECT_EQ("1213th", CardinalToOrdinal(1213)); + EXPECT_EQ("1214th", CardinalToOrdinal(1214)); + EXPECT_EQ("1215th", CardinalToOrdinal(1215)); + EXPECT_EQ("1216th", CardinalToOrdinal(1216)); + EXPECT_EQ("1217th", CardinalToOrdinal(1217)); + EXPECT_EQ("1218th", CardinalToOrdinal(1218)); + EXPECT_EQ("1219th", CardinalToOrdinal(1219)); + EXPECT_EQ("1220th", CardinalToOrdinal(1220)); + EXPECT_EQ("1221st", CardinalToOrdinal(1221)); + EXPECT_EQ("1222nd", CardinalToOrdinal(1222)); + EXPECT_EQ("1223rd", CardinalToOrdinal(1223)); + EXPECT_EQ("1224th", CardinalToOrdinal(1224)); + EXPECT_EQ("1225th", CardinalToOrdinal(1225)); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/target_env_test.cpp b/3rdparty/spirv-tools/test/target_env_test.cpp new file mode 100644 index 000000000..0c7062158 --- /dev/null +++ b/3rdparty/spirv-tools/test/target_env_test.cpp @@ -0,0 +1,103 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "unit_spirv.h" + +#include "source/spirv_target_env.h" + +namespace { + +using ::testing::AnyOf; +using ::testing::Eq; +using ::testing::StartsWith; +using ::testing::ValuesIn; + +using TargetEnvTest = ::testing::TestWithParam; +TEST_P(TargetEnvTest, CreateContext) { + spv_target_env env = GetParam(); + spv_context context = spvContextCreate(env); + ASSERT_NE(nullptr, context); + spvContextDestroy(context); // Avoid leaking +} + +TEST_P(TargetEnvTest, ValidDescription) { + const char* description = spvTargetEnvDescription(GetParam()); + ASSERT_NE(nullptr, description); + ASSERT_THAT(description, StartsWith("SPIR-V ")); +} + +TEST_P(TargetEnvTest, ValidSpirvVersion) { + auto spirv_version = spvVersionForTargetEnv(GetParam()); + ASSERT_THAT(spirv_version, AnyOf(0x10000, 0x10100, 0x10200, 0x10300)); +} + +INSTANTIATE_TEST_CASE_P(AllTargetEnvs, TargetEnvTest, + ValuesIn(spvtest::AllTargetEnvironments())); + +TEST(GetContextTest, InvalidTargetEnvProducesNull) { + // Use a value beyond the last valid enum value. + spv_context context = spvContextCreate(static_cast(30)); + EXPECT_EQ(context, nullptr); +} + +// A test case for parsing an environment string. +struct ParseCase { + const char* input; + bool success; // Expect to successfully parse? + spv_target_env env; // The parsed environment, if successful. +}; + +using TargetParseTest = ::testing::TestWithParam; + +TEST_P(TargetParseTest, InvalidTargetEnvProducesNull) { + spv_target_env env; + bool parsed = spvParseTargetEnv(GetParam().input, &env); + EXPECT_THAT(parsed, Eq(GetParam().success)); + EXPECT_THAT(env, Eq(GetParam().env)); +} + +INSTANTIATE_TEST_CASE_P( + TargetParsing, TargetParseTest, + ValuesIn(std::vector{ + {"spv1.0", true, SPV_ENV_UNIVERSAL_1_0}, + {"spv1.1", true, SPV_ENV_UNIVERSAL_1_1}, + {"spv1.2", true, SPV_ENV_UNIVERSAL_1_2}, + {"spv1.3", true, SPV_ENV_UNIVERSAL_1_3}, + {"vulkan1.0", true, SPV_ENV_VULKAN_1_0}, + {"vulkan1.1", true, SPV_ENV_VULKAN_1_1}, + {"opencl2.1", true, SPV_ENV_OPENCL_2_1}, + {"opencl2.2", true, SPV_ENV_OPENCL_2_2}, + {"opengl4.0", true, SPV_ENV_OPENGL_4_0}, + {"opengl4.1", true, SPV_ENV_OPENGL_4_1}, + {"opengl4.2", true, SPV_ENV_OPENGL_4_2}, + {"opengl4.3", true, SPV_ENV_OPENGL_4_3}, + {"opengl4.5", true, SPV_ENV_OPENGL_4_5}, + {"opencl1.2", true, SPV_ENV_OPENCL_1_2}, + {"opencl1.2embedded", true, SPV_ENV_OPENCL_EMBEDDED_1_2}, + {"opencl2.0", true, SPV_ENV_OPENCL_2_0}, + {"opencl2.0embedded", true, SPV_ENV_OPENCL_EMBEDDED_2_0}, + {"opencl2.1embedded", true, SPV_ENV_OPENCL_EMBEDDED_2_1}, + {"opencl2.2embedded", true, SPV_ENV_OPENCL_EMBEDDED_2_2}, + {"opencl2.3", false, SPV_ENV_UNIVERSAL_1_0}, + {"opencl3.0", false, SPV_ENV_UNIVERSAL_1_0}, + {"vulkan1.2", false, SPV_ENV_UNIVERSAL_1_0}, + {"vulkan2.0", false, SPV_ENV_UNIVERSAL_1_0}, + {nullptr, false, SPV_ENV_UNIVERSAL_1_0}, + {"", false, SPV_ENV_UNIVERSAL_1_0}, + {"abc", false, SPV_ENV_UNIVERSAL_1_0}, + })); + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/test_fixture.h b/3rdparty/spirv-tools/test/test_fixture.h new file mode 100644 index 000000000..e2948a899 --- /dev/null +++ b/3rdparty/spirv-tools/test/test_fixture.h @@ -0,0 +1,182 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_TEST_TEST_FIXTURE_H_ +#define LIBSPIRV_TEST_TEST_FIXTURE_H_ + +#include "unit_spirv.h" + +namespace spvtest { + +// RAII for spv_context. +struct ScopedContext { + ScopedContext(spv_target_env env = SPV_ENV_UNIVERSAL_1_0) + : context(spvContextCreate(env)) {} + ~ScopedContext() { spvContextDestroy(context); } + spv_context context; +}; + +// Common setup for TextToBinary tests. SetText() should be called to populate +// the actual test text. +template +class TextToBinaryTestBase : public T { + public: + // Shorthand for SPIR-V compilation result. + using SpirvVector = std::vector; + + // Offset into a SpirvVector at which the first instruction starts. + static const SpirvVector::size_type kFirstInstruction = 5; + + TextToBinaryTestBase() : diagnostic(nullptr), text(), binary(nullptr) { + char textStr[] = "substitute the text member variable with your test"; + text = {textStr, strlen(textStr)}; + } + + virtual ~TextToBinaryTestBase() { + DestroyBinary(); + if (diagnostic) spvDiagnosticDestroy(diagnostic); + } + + // Returns subvector v[from:end). + SpirvVector Subvector(const SpirvVector& v, SpirvVector::size_type from) { + assert(from <= v.size()); + return SpirvVector(v.begin() + from, v.end()); + } + + // Compiles SPIR-V text in the given assembly syntax format, asserting + // compilation success. Returns the compiled code. + SpirvVector CompileSuccessfully(const std::string& txt, + spv_target_env env = SPV_ENV_UNIVERSAL_1_0) { + spv_result_t status = + spvTextToBinary(ScopedContext(env).context, txt.c_str(), txt.size(), + &binary, &diagnostic); + EXPECT_EQ(SPV_SUCCESS, status) << txt; + SpirvVector code_copy; + if (status == SPV_SUCCESS) { + code_copy = SpirvVector(binary->code, binary->code + binary->wordCount); + DestroyBinary(); + } else { + spvDiagnosticPrint(diagnostic); + } + return code_copy; + } + + // Compiles SPIR-V text with the given format, asserting compilation failure. + // Returns the error message(s). + std::string CompileFailure(const std::string& txt, + spv_target_env env = SPV_ENV_UNIVERSAL_1_0) { + EXPECT_NE(SPV_SUCCESS, + spvTextToBinary(ScopedContext(env).context, txt.c_str(), + txt.size(), &binary, &diagnostic)) + << txt; + DestroyBinary(); + return diagnostic->error; + } + + // Encodes SPIR-V text into binary and then decodes the binary using + // given options. Returns the decoded text. + std::string EncodeAndDecodeSuccessfully( + const std::string& txt, + uint32_t disassemble_options = SPV_BINARY_TO_TEXT_OPTION_NONE, + spv_target_env env = SPV_ENV_UNIVERSAL_1_0) { + DestroyBinary(); + ScopedContext context(env); + disassemble_options |= SPV_BINARY_TO_TEXT_OPTION_NO_HEADER; + spv_result_t error = spvTextToBinary(context.context, txt.c_str(), + txt.size(), &binary, &diagnostic); + if (error) { + spvDiagnosticPrint(diagnostic); + spvDiagnosticDestroy(diagnostic); + } + EXPECT_EQ(SPV_SUCCESS, error); + if (!binary) return ""; + + spv_text decoded_text; + error = spvBinaryToText(context.context, binary->code, binary->wordCount, + disassemble_options, &decoded_text, &diagnostic); + if (error) { + spvDiagnosticPrint(diagnostic); + spvDiagnosticDestroy(diagnostic); + } + EXPECT_EQ(SPV_SUCCESS, error) << txt; + + const std::string decoded_string = decoded_text->str; + spvTextDestroy(decoded_text); + + return decoded_string; + } + + // Encodes SPIR-V text into binary. This is expected to succeed. + // The given words are then appended to the binary, and the result + // is then decoded. This is expected to fail. + // Returns the error message. + std::string EncodeSuccessfullyDecodeFailed( + const std::string& txt, const SpirvVector& words_to_append) { + SpirvVector code = + spvtest::Concatenate({CompileSuccessfully(txt), words_to_append}); + + spv_text decoded_text; + EXPECT_NE(SPV_SUCCESS, + spvBinaryToText(ScopedContext().context, code.data(), code.size(), + SPV_BINARY_TO_TEXT_OPTION_NONE, &decoded_text, + &diagnostic)); + if (diagnostic) { + std::string error_message = diagnostic->error; + spvDiagnosticDestroy(diagnostic); + diagnostic = nullptr; + return error_message; + } + return ""; + } + + // Compiles SPIR-V text, asserts success, and returns the words representing + // the instructions. In particular, skip the words in the SPIR-V header. + SpirvVector CompiledInstructions(const std::string& txt, + spv_target_env env = SPV_ENV_UNIVERSAL_1_0) { + const SpirvVector code = CompileSuccessfully(txt, env); + SpirvVector result; + // Extract just the instructions. + // If the code fails to compile, then return the empty vector. + // In any case, don't crash or invoke undefined behaviour. + if (code.size() >= kFirstInstruction) + result = Subvector(code, kFirstInstruction); + return result; + } + + void SetText(const std::string& code) { + textString = code; + text.str = textString.c_str(); + text.length = textString.size(); + } + + // Destroys the binary, if it exists. + void DestroyBinary() { + spvBinaryDestroy(binary); + binary = nullptr; + } + + spv_diagnostic diagnostic; + + std::string textString; + spv_text_t text; + spv_binary binary; +}; + +using TextToBinaryTest = TextToBinaryTestBase<::testing::Test>; +} // namespace spvtest + +using RoundTripTest = + spvtest::TextToBinaryTestBase<::testing::TestWithParam>; + +#endif // LIBSPIRV_TEST_TEST_FIXTURE_H_ diff --git a/3rdparty/spirv-tools/test/text_advance_test.cpp b/3rdparty/spirv-tools/test/text_advance_test.cpp new file mode 100644 index 000000000..ffc8dbb21 --- /dev/null +++ b/3rdparty/spirv-tools/test/text_advance_test.cpp @@ -0,0 +1,131 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "unit_spirv.h" + +namespace { + +using libspirv::AssemblyContext; +using spvtest::AutoText; + +TEST(TextAdvance, LeadingNewLines) { + AutoText input("\n\nWord"); + AssemblyContext data(input, nullptr); + ASSERT_EQ(SPV_SUCCESS, data.advance()); + ASSERT_EQ(0u, data.position().column); + ASSERT_EQ(2u, data.position().line); + ASSERT_EQ(2u, data.position().index); +} + +TEST(TextAdvance, LeadingSpaces) { + AutoText input(" Word"); + AssemblyContext data(input, nullptr); + ASSERT_EQ(SPV_SUCCESS, data.advance()); + ASSERT_EQ(4u, data.position().column); + ASSERT_EQ(0u, data.position().line); + ASSERT_EQ(4u, data.position().index); +} + +TEST(TextAdvance, LeadingTabs) { + AutoText input("\t\t\tWord"); + AssemblyContext data(input, nullptr); + ASSERT_EQ(SPV_SUCCESS, data.advance()); + ASSERT_EQ(3u, data.position().column); + ASSERT_EQ(0u, data.position().line); + ASSERT_EQ(3u, data.position().index); +} + +TEST(TextAdvance, LeadingNewLinesSpacesAndTabs) { + AutoText input("\n\n\t Word"); + AssemblyContext data(input, nullptr); + ASSERT_EQ(SPV_SUCCESS, data.advance()); + ASSERT_EQ(3u, data.position().column); + ASSERT_EQ(2u, data.position().line); + ASSERT_EQ(5u, data.position().index); +} + +TEST(TextAdvance, LeadingWhitespaceAfterCommentLine) { + AutoText input("; comment\n \t \tWord"); + AssemblyContext data(input, nullptr); + ASSERT_EQ(SPV_SUCCESS, data.advance()); + ASSERT_EQ(4u, data.position().column); + ASSERT_EQ(1u, data.position().line); + ASSERT_EQ(14u, data.position().index); +} + +TEST(TextAdvance, EOFAfterCommentLine) { + AutoText input("; comment"); + AssemblyContext data(input, nullptr); + ASSERT_EQ(SPV_END_OF_STREAM, data.advance()); +} + +TEST(TextAdvance, NullTerminator) { + AutoText input(""); + AssemblyContext data(input, nullptr); + ASSERT_EQ(SPV_END_OF_STREAM, data.advance()); +} + +TEST(TextAdvance, NoNullTerminatorAfterCommentLine) { + std::string input = "; comment|padding beyond the end"; + spv_text_t text = {input.data(), 9}; + AssemblyContext data(&text, nullptr); + ASSERT_EQ(SPV_END_OF_STREAM, data.advance()); + EXPECT_EQ(9u, data.position().index); +} + +TEST(TextAdvance, NoNullTerminator) { + spv_text_t text = {"OpNop\nSomething else in memory", 6}; + AssemblyContext data(&text, nullptr); + const spv_position_t line_break = {1u, 5u, 5u}; + data.setPosition(line_break); + ASSERT_EQ(SPV_END_OF_STREAM, data.advance()); +} + +// Invokes AssemblyContext::advance() on text, asserts success, and returns +// AssemblyContext::position(). +spv_position_t PositionAfterAdvance(const char* text) { + AutoText input(text); + AssemblyContext data(input, nullptr); + EXPECT_EQ(SPV_SUCCESS, data.advance()); + return data.position(); +} + +TEST(TextAdvance, SkipOverCR) { + const auto pos = PositionAfterAdvance("\rWord"); + EXPECT_EQ(1u, pos.column); + EXPECT_EQ(0u, pos.line); + EXPECT_EQ(1u, pos.index); +} + +TEST(TextAdvance, SkipOverCRs) { + const auto pos = PositionAfterAdvance("\r\r\rWord"); + EXPECT_EQ(3u, pos.column); + EXPECT_EQ(0u, pos.line); + EXPECT_EQ(3u, pos.index); +} + +TEST(TextAdvance, SkipOverCRLF) { + const auto pos = PositionAfterAdvance("\r\nWord"); + EXPECT_EQ(0u, pos.column); + EXPECT_EQ(1u, pos.line); + EXPECT_EQ(2u, pos.index); +} + +TEST(TextAdvance, SkipOverCRLFs) { + const auto pos = PositionAfterAdvance("\r\n\r\nWord"); + EXPECT_EQ(0u, pos.column); + EXPECT_EQ(2u, pos.line); + EXPECT_EQ(4u, pos.index); +} +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/text_destroy_test.cpp b/3rdparty/spirv-tools/test/text_destroy_test.cpp new file mode 100644 index 000000000..c956d72c6 --- /dev/null +++ b/3rdparty/spirv-tools/test/text_destroy_test.cpp @@ -0,0 +1,73 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "unit_spirv.h" + +namespace { + +TEST(TextDestroy, DestroyNull) { spvBinaryDestroy(nullptr); } + +TEST(TextDestroy, Default) { + spv_context context = spvContextCreate(SPV_ENV_UNIVERSAL_1_0); + char textStr[] = R"( + OpSource OpenCL_C 12 + OpMemoryModel Physical64 OpenCL + OpSourceExtension "PlaceholderExtensionName" + OpEntryPoint Kernel %0 "" + OpExecutionMode %0 LocalSizeHint 1 1 1 + %1 = OpTypeVoid + %2 = OpTypeBool + %3 = OpTypeInt 8 0 + %4 = OpTypeInt 8 1 + %5 = OpTypeInt 16 0 + %6 = OpTypeInt 16 1 + %7 = OpTypeInt 32 0 + %8 = OpTypeInt 32 1 + %9 = OpTypeInt 64 0 + %10 = OpTypeInt 64 1 + %11 = OpTypeFloat 16 + %12 = OpTypeFloat 32 + %13 = OpTypeFloat 64 + %14 = OpTypeVector %3 2 + )"; + + spv_binary binary = nullptr; + spv_diagnostic diagnostic = nullptr; + EXPECT_EQ(SPV_SUCCESS, spvTextToBinary(context, textStr, strlen(textStr), + &binary, &diagnostic)); + EXPECT_NE(nullptr, binary); + EXPECT_NE(nullptr, binary->code); + EXPECT_NE(0u, binary->wordCount); + if (diagnostic) { + spvDiagnosticPrint(diagnostic); + ASSERT_TRUE(false); + } + + spv_text resultText = nullptr; + EXPECT_EQ(SPV_SUCCESS, + spvBinaryToText(context, binary->code, binary->wordCount, 0, + &resultText, &diagnostic)); + spvBinaryDestroy(binary); + if (diagnostic) { + spvDiagnosticPrint(diagnostic); + spvDiagnosticDestroy(diagnostic); + ASSERT_TRUE(false); + } + EXPECT_NE(nullptr, resultText->str); + EXPECT_NE(0u, resultText->length); + spvTextDestroy(resultText); + spvContextDestroy(context); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/text_literal_test.cpp b/3rdparty/spirv-tools/test/text_literal_test.cpp new file mode 100644 index 000000000..dc75a8e11 --- /dev/null +++ b/3rdparty/spirv-tools/test/text_literal_test.cpp @@ -0,0 +1,413 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "unit_spirv.h" + +#include "gmock/gmock.h" +#include "message.h" +#include "test_fixture.h" + +#include + +using ::testing::Eq; +namespace { + +TEST(TextLiteral, GoodI32) { + spv_literal_t l; + + ASSERT_EQ(SPV_SUCCESS, spvTextToLiteral("-0", &l)); + EXPECT_EQ(SPV_LITERAL_TYPE_INT_32, l.type); + EXPECT_EQ(0, l.value.i32); + + ASSERT_EQ(SPV_SUCCESS, spvTextToLiteral("-2147483648", &l)); + EXPECT_EQ(SPV_LITERAL_TYPE_INT_32, l.type); + EXPECT_EQ((-2147483647L - 1), l.value.i32); +} + +TEST(TextLiteral, GoodU32) { + spv_literal_t l; + + ASSERT_EQ(SPV_SUCCESS, spvTextToLiteral("0", &l)); + EXPECT_EQ(SPV_LITERAL_TYPE_UINT_32, l.type); + EXPECT_EQ(0, l.value.i32); + + ASSERT_EQ(SPV_SUCCESS, spvTextToLiteral("4294967295", &l)); + EXPECT_EQ(SPV_LITERAL_TYPE_UINT_32, l.type); + EXPECT_EQ(4294967295, l.value.u32); +} + +TEST(TextLiteral, GoodI64) { + spv_literal_t l; + + ASSERT_EQ(SPV_SUCCESS, spvTextToLiteral("-2147483649", &l)); + EXPECT_EQ(SPV_LITERAL_TYPE_INT_64, l.type); + EXPECT_EQ(-2147483649LL, l.value.i64); +} + +TEST(TextLiteral, GoodU64) { + spv_literal_t l; + + ASSERT_EQ(SPV_SUCCESS, spvTextToLiteral("4294967296", &l)); + EXPECT_EQ(SPV_LITERAL_TYPE_UINT_64, l.type); + EXPECT_EQ(4294967296u, l.value.u64); +} + +TEST(TextLiteral, GoodFloat) { + spv_literal_t l; + + ASSERT_EQ(SPV_SUCCESS, spvTextToLiteral("1.0", &l)); + EXPECT_EQ(SPV_LITERAL_TYPE_FLOAT_32, l.type); + EXPECT_EQ(1.0, l.value.f); + + ASSERT_EQ(SPV_SUCCESS, spvTextToLiteral("1.5", &l)); + EXPECT_EQ(SPV_LITERAL_TYPE_FLOAT_32, l.type); + EXPECT_EQ(1.5, l.value.f); + + ASSERT_EQ(SPV_SUCCESS, spvTextToLiteral("-.25", &l)); + EXPECT_EQ(SPV_LITERAL_TYPE_FLOAT_32, l.type); + EXPECT_EQ(-.25, l.value.f); +} + +TEST(TextLiteral, BadString) { + spv_literal_t l; + + EXPECT_EQ(SPV_FAILED_MATCH, spvTextToLiteral("", &l)); + EXPECT_EQ(SPV_FAILED_MATCH, spvTextToLiteral("-", &l)); + EXPECT_EQ(SPV_FAILED_MATCH, spvTextToLiteral("--", &l)); + EXPECT_EQ(SPV_FAILED_MATCH, spvTextToLiteral("1-2", &l)); + EXPECT_EQ(SPV_FAILED_MATCH, spvTextToLiteral("123a", &l)); + EXPECT_EQ(SPV_FAILED_MATCH, spvTextToLiteral("12.2.3", &l)); + EXPECT_EQ(SPV_FAILED_MATCH, spvTextToLiteral("\"", &l)); + EXPECT_EQ(SPV_FAILED_MATCH, spvTextToLiteral("\"z", &l)); + EXPECT_EQ(SPV_FAILED_MATCH, spvTextToLiteral("a\"", &l)); +} + +class GoodStringTest + : public ::testing::TestWithParam> {}; + +TEST_P(GoodStringTest, GoodStrings) { + spv_literal_t l; + + ASSERT_EQ(SPV_SUCCESS, spvTextToLiteral(std::get<0>(GetParam()), &l)); + EXPECT_EQ(SPV_LITERAL_TYPE_STRING, l.type); + EXPECT_EQ(std::get<1>(GetParam()), l.str); +} + +INSTANTIATE_TEST_CASE_P( + TextLiteral, GoodStringTest, + ::testing::ValuesIn(std::vector>{ + {R"("-")", "-"}, + {R"("--")", "--"}, + {R"("1-2")", "1-2"}, + {R"("123a")", "123a"}, + {R"("12.2.3")", "12.2.3"}, + {R"("\"")", "\""}, + {R"("\\")", "\\"}, + {"\"\\foo\nbar\"", "foo\nbar"}, + {"\"\\foo\\\nbar\"", "foo\nbar"}, + {"\"\xE4\xBA\xB2\"", "\xE4\xBA\xB2"}, + {"\"\\\xE4\xBA\xB2\"", "\xE4\xBA\xB2"}, + {"\"this \\\" and this \\\\ and \\\xE4\xBA\xB2\"", + "this \" and this \\ and \xE4\xBA\xB2"}}), ); + +TEST(TextLiteral, StringTooLong) { + spv_literal_t l; + std::string too_long = + std::string("\"") + + std::string(SPV_LIMIT_LITERAL_STRING_BYTES_MAX + 1, 'a') + "\""; + EXPECT_EQ(SPV_ERROR_OUT_OF_MEMORY, spvTextToLiteral(too_long.data(), &l)); +} + +TEST(TextLiteral, GoodLongString) { + spv_literal_t l; + // The universal limit of 65535 Unicode characters might make this + // fail validation, since SPV_LIMIT_LITERAL_STRING_BYTES_MAX is 4*65535. + // However, as an implementation detail, we'll allow the assembler + // to parse it. Otherwise we'd have to scan the string for valid UTF-8 + // characters. + std::string unquoted(SPV_LIMIT_LITERAL_STRING_BYTES_MAX, 'a'); + std::string good_long = std::string("\"") + unquoted + "\""; + EXPECT_EQ(SPV_SUCCESS, spvTextToLiteral(good_long.data(), &l)); + EXPECT_EQ(SPV_LITERAL_TYPE_STRING, l.type); + EXPECT_EQ(unquoted.data(), l.str); +} + +TEST(TextLiteral, GoodUTF8String) { + const std::string unquoted = + spvtest::MakeLongUTF8String(SPV_LIMIT_LITERAL_STRING_UTF8_CHARS_MAX); + const std::string good_long = std::string("\"") + unquoted + "\""; + spv_literal_t l; + EXPECT_EQ(SPV_SUCCESS, spvTextToLiteral(good_long.data(), &l)); + EXPECT_EQ(SPV_LITERAL_TYPE_STRING, l.type); + EXPECT_EQ(unquoted.data(), l.str); +} + +// A test case for parsing literal numbers. +struct TextLiteralCase { + uint32_t bitwidth; + const char* text; + bool is_signed; + bool success; + std::vector expected_values; +}; + +using IntegerTest = + spvtest::TextToBinaryTestBase<::testing::TestWithParam>; + +std::vector successfulEncode(const TextLiteralCase& test, + libspirv::IdTypeClass type) { + spv_instruction_t inst; + std::string message; + auto capture_message = [&message](spv_message_level_t, const char*, + const spv_position_t&, + const char* m) { message = m; }; + libspirv::IdType expected_type{test.bitwidth, test.is_signed, type}; + EXPECT_EQ(SPV_SUCCESS, + libspirv::AssemblyContext(nullptr, capture_message) + .binaryEncodeNumericLiteral(test.text, SPV_ERROR_INVALID_TEXT, + expected_type, &inst)) + << message; + return inst.words; +} + +std::string failedEncode(const TextLiteralCase& test, + libspirv::IdTypeClass type) { + spv_instruction_t inst; + std::string message; + auto capture_message = [&message](spv_message_level_t, const char*, + const spv_position_t&, + const char* m) { message = m; }; + libspirv::IdType expected_type{test.bitwidth, test.is_signed, type}; + EXPECT_EQ(SPV_ERROR_INVALID_TEXT, + libspirv::AssemblyContext(nullptr, capture_message) + .binaryEncodeNumericLiteral(test.text, SPV_ERROR_INVALID_TEXT, + expected_type, &inst)); + return message; +} + +TEST_P(IntegerTest, IntegerBounds) { + if (GetParam().success) { + EXPECT_THAT( + successfulEncode(GetParam(), libspirv::IdTypeClass::kScalarIntegerType), + Eq(GetParam().expected_values)); + } else { + std::stringstream ss; + ss << "Integer " << GetParam().text << " does not fit in a " + << GetParam().bitwidth << "-bit " + << (GetParam().is_signed ? "signed" : "unsigned") << " integer"; + EXPECT_THAT( + failedEncode(GetParam(), libspirv::IdTypeClass::kScalarIntegerType), + Eq(ss.str())); + } +} + +// Four nicely named methods for making TextLiteralCase values. +// Their names have underscores in some places to make it easier +// to read the table that follows. +TextLiteralCase Make_Ok__Signed(uint32_t bitwidth, const char* text, + std::vector encoding) { + return TextLiteralCase{bitwidth, text, true, true, encoding}; +} +TextLiteralCase Make_Ok__Unsigned(uint32_t bitwidth, const char* text, + std::vector encoding) { + return TextLiteralCase{bitwidth, text, false, true, encoding}; +} +TextLiteralCase Make_Bad_Signed(uint32_t bitwidth, const char* text) { + return TextLiteralCase{bitwidth, text, true, false, {}}; +} +TextLiteralCase Make_Bad_Unsigned(uint32_t bitwidth, const char* text) { + return TextLiteralCase{bitwidth, text, false, false, {}}; +} + +// clang-format off +INSTANTIATE_TEST_CASE_P( + DecimalIntegers, IntegerTest, + ::testing::ValuesIn(std::vector{ + // Check max value and overflow value for 1-bit numbers. + Make_Ok__Signed(1, "0", {0}), + Make_Ok__Unsigned(1, "1", {1}), + Make_Bad_Signed(1, "1"), + Make_Bad_Unsigned(1, "2"), + + // Check max value and overflow value for 2-bit numbers. + Make_Ok__Signed(2, "1", {1}), + Make_Ok__Unsigned(2, "3", {3}), + Make_Bad_Signed(2, "2"), + Make_Bad_Unsigned(2, "4"), + + // Check max negative value and overflow value for signed + // 1- and 2-bit numbers. Signed negative numbers are sign-extended. + Make_Ok__Signed(1, "-0", {uint32_t(0)}), + Make_Ok__Signed(1, "-1", {uint32_t(-1)}), + Make_Ok__Signed(2, "-0", {0}), + Make_Ok__Signed(2, "-1", {uint32_t(-1)}), + Make_Ok__Signed(2, "-2", {uint32_t(-2)}), + Make_Bad_Signed(2, "-3"), + + Make_Bad_Unsigned(2, "2224323424242424"), + Make_Ok__Unsigned(16, "65535", {0xFFFF}), + Make_Bad_Unsigned(16, "65536"), + Make_Bad_Signed(16, "65535"), + Make_Ok__Signed(16, "32767", {0x7FFF}), + Make_Ok__Signed(16, "-32768", {0xFFFF8000}), + + // Check values around 32-bits in magnitude. + Make_Ok__Unsigned(33, "4294967296", {0, 1}), + Make_Ok__Unsigned(33, "4294967297", {1, 1}), + Make_Bad_Unsigned(33, "8589934592"), + Make_Bad_Signed(33, "4294967296"), + Make_Ok__Signed(33, "-4294967296", {0x0, 0xFFFFFFFF}), + Make_Ok__Unsigned(64, "4294967296", {0, 1}), + Make_Ok__Unsigned(64, "4294967297", {1, 1}), + + // Check max value and overflow value for 64-bit numbers. + Make_Ok__Signed(64, "9223372036854775807", {0xffffffff, 0x7fffffff}), + Make_Bad_Signed(64, "9223372036854775808"), + Make_Ok__Unsigned(64, "9223372036854775808", {0x00000000, 0x80000000}), + Make_Ok__Unsigned(64, "18446744073709551615", {0xffffffff, 0xffffffff}), + Make_Ok__Signed(64, "-9223372036854775808", {0x00000000, 0x80000000}), + + }),); +// clang-format on + +using IntegerLeadingMinusTest = + spvtest::TextToBinaryTestBase<::testing::TestWithParam>; + +TEST_P(IntegerLeadingMinusTest, CantHaveLeadingMinusOnUnsigned) { + EXPECT_FALSE(GetParam().success); + EXPECT_THAT( + failedEncode(GetParam(), libspirv::IdTypeClass::kScalarIntegerType), + Eq("Cannot put a negative number in an unsigned literal")); +} + +// clang-format off +INSTANTIATE_TEST_CASE_P( + DecimalAndHexIntegers, IntegerLeadingMinusTest, + ::testing::ValuesIn(std::vector{ + // Unsigned numbers never allow a leading minus sign. + Make_Bad_Unsigned(16, "-0"), + Make_Bad_Unsigned(16, "-0x0"), + Make_Bad_Unsigned(16, "-0x1"), + Make_Bad_Unsigned(32, "-0"), + Make_Bad_Unsigned(32, "-0x0"), + Make_Bad_Unsigned(32, "-0x1"), + Make_Bad_Unsigned(64, "-0"), + Make_Bad_Unsigned(64, "-0x0"), + Make_Bad_Unsigned(64, "-0x1"), + }),); + +// clang-format off +INSTANTIATE_TEST_CASE_P( + HexIntegers, IntegerTest, + ::testing::ValuesIn(std::vector{ + // Check 0x and 0X prefices. + Make_Ok__Signed(16, "0x1234", {0x1234}), + Make_Ok__Signed(16, "0X1234", {0x1234}), + + // Check 1-bit numbers + Make_Ok__Signed(1, "0x0", {0}), + Make_Ok__Signed(1, "0x1", {uint32_t(-1)}), + Make_Ok__Unsigned(1, "0x0", {0}), + Make_Ok__Unsigned(1, "0x1", {1}), + Make_Bad_Signed(1, "0x2"), + Make_Bad_Unsigned(1, "0x2"), + + // Check 2-bit numbers + Make_Ok__Signed(2, "0x0", {0}), + Make_Ok__Signed(2, "0x1", {1}), + Make_Ok__Signed(2, "0x2", {uint32_t(-2)}), + Make_Ok__Signed(2, "0x3", {uint32_t(-1)}), + Make_Ok__Unsigned(2, "0x0", {0}), + Make_Ok__Unsigned(2, "0x1", {1}), + Make_Ok__Unsigned(2, "0x2", {2}), + Make_Ok__Unsigned(2, "0x3", {3}), + Make_Bad_Signed(2, "0x4"), + Make_Bad_Unsigned(2, "0x4"), + + // Check 8-bit numbers + Make_Ok__Signed(8, "0x7f", {0x7f}), + Make_Ok__Signed(8, "0x80", {0xffffff80}), + Make_Ok__Unsigned(8, "0x80", {0x80}), + Make_Ok__Unsigned(8, "0xff", {0xff}), + Make_Bad_Signed(8, "0x100"), + Make_Bad_Unsigned(8, "0x100"), + + // Check 16-bit numbers + Make_Ok__Signed(16, "0x7fff", {0x7fff}), + Make_Ok__Signed(16, "0x8000", {0xffff8000}), + Make_Ok__Unsigned(16, "0x8000", {0x8000}), + Make_Ok__Unsigned(16, "0xffff", {0xffff}), + Make_Bad_Signed(16, "0x10000"), + Make_Bad_Unsigned(16, "0x10000"), + + // Check 32-bit numbers + Make_Ok__Signed(32, "0x7fffffff", {0x7fffffff}), + Make_Ok__Signed(32, "0x80000000", {0x80000000}), + Make_Ok__Unsigned(32, "0x80000000", {0x80000000}), + Make_Ok__Unsigned(32, "0xffffffff", {0xffffffff}), + Make_Bad_Signed(32, "0x100000000"), + Make_Bad_Unsigned(32, "0x100000000"), + + // Check 48-bit numbers + Make_Ok__Unsigned(48, "0x7ffffffff", {0xffffffff, 7}), + Make_Ok__Unsigned(48, "0x800000000", {0, 8}), + Make_Ok__Signed(48, "0x7fffffffffff", {0xffffffff, 0x7fff}), + Make_Ok__Signed(48, "0x800000000000", {0, 0xffff8000}), + Make_Bad_Signed(48, "0x1000000000000"), + Make_Bad_Unsigned(48, "0x1000000000000"), + + // Check 64-bit numbers + Make_Ok__Signed(64, "0x7fffffffffffffff", {0xffffffff, 0x7fffffff}), + Make_Ok__Signed(64, "0x8000000000000000", {0x00000000, 0x80000000}), + Make_Ok__Unsigned(64, "0x7fffffffffffffff", {0xffffffff, 0x7fffffff}), + Make_Ok__Unsigned(64, "0x8000000000000000", {0x00000000, 0x80000000}), + }),); +// clang-format on + +TEST(OverflowIntegerParse, Decimal) { + std::string signed_input = "-18446744073709551616"; + std::string expected_message0 = + "Invalid signed integer literal: " + signed_input; + EXPECT_THAT(failedEncode(Make_Bad_Signed(64, signed_input.c_str()), + libspirv::IdTypeClass::kScalarIntegerType), + Eq(expected_message0)); + + std::string unsigned_input = "18446744073709551616"; + std::string expected_message1 = + "Invalid unsigned integer literal: " + unsigned_input; + EXPECT_THAT(failedEncode(Make_Bad_Unsigned(64, unsigned_input.c_str()), + libspirv::IdTypeClass::kScalarIntegerType), + Eq(expected_message1)); + + // TODO(dneto): When the given number doesn't have a leading sign, + // we say we're trying to parse an unsigned number, even when the caller + // asked for a signed number. This is kind of weird, but it's an + // artefact of how we do the parsing. + EXPECT_THAT(failedEncode(Make_Bad_Signed(64, unsigned_input.c_str()), + libspirv::IdTypeClass::kScalarIntegerType), + Eq(expected_message1)); +} + +TEST(OverflowIntegerParse, Hex) { + std::string input = "0x10000000000000000"; + std::string expected_message = "Invalid unsigned integer literal: " + input; + EXPECT_THAT(failedEncode(Make_Bad_Signed(64, input.c_str()), + libspirv::IdTypeClass::kScalarIntegerType), + Eq(expected_message)); + EXPECT_THAT(failedEncode(Make_Bad_Unsigned(64, input.c_str()), + libspirv::IdTypeClass::kScalarIntegerType), + Eq(expected_message)); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/text_start_new_inst_test.cpp b/3rdparty/spirv-tools/test/text_start_new_inst_test.cpp new file mode 100644 index 000000000..6c3e55418 --- /dev/null +++ b/3rdparty/spirv-tools/test/text_start_new_inst_test.cpp @@ -0,0 +1,74 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "unit_spirv.h" + +#include + +namespace { + +using libspirv::AssemblyContext; +using spvtest::AutoText; + +TEST(TextStartsWithOp, YesAtStart) { + EXPECT_TRUE(AssemblyContext(AutoText("OpFoo"), nullptr).isStartOfNewInst()); + EXPECT_TRUE(AssemblyContext(AutoText("OpFoo"), nullptr).isStartOfNewInst()); + EXPECT_TRUE(AssemblyContext(AutoText("OpEnCL"), nullptr).isStartOfNewInst()); +} + +TEST(TextStartsWithOp, YesAtMiddle) { + { + AutoText text(" OpFoo"); + AssemblyContext dat(text, nullptr); + dat.seekForward(2); + EXPECT_TRUE(dat.isStartOfNewInst()); + } + { + AutoText text("xx OpFoo"); + AssemblyContext dat(text, nullptr); + dat.seekForward(2); + EXPECT_TRUE(dat.isStartOfNewInst()); + } +} + +TEST(TextStartsWithOp, NoIfTooFar) { + AutoText text(" OpFoo"); + AssemblyContext dat(text, nullptr); + dat.seekForward(3); + EXPECT_FALSE(dat.isStartOfNewInst()); +} + +TEST(TextStartsWithOp, NoRegular) { + EXPECT_FALSE( + AssemblyContext(AutoText("Fee Fi Fo Fum"), nullptr).isStartOfNewInst()); + EXPECT_FALSE(AssemblyContext(AutoText("123456"), nullptr).isStartOfNewInst()); + EXPECT_FALSE(AssemblyContext(AutoText("123456"), nullptr).isStartOfNewInst()); + EXPECT_FALSE(AssemblyContext(AutoText("OpenCL"), nullptr).isStartOfNewInst()); +} + +TEST(TextStartsWithOp, YesForValueGenerationForm) { + EXPECT_TRUE( + AssemblyContext(AutoText("%foo = OpAdd"), nullptr).isStartOfNewInst()); + EXPECT_TRUE( + AssemblyContext(AutoText("%foo = OpAdd"), nullptr).isStartOfNewInst()); +} + +TEST(TextStartsWithOp, NoForNearlyValueGeneration) { + EXPECT_FALSE( + AssemblyContext(AutoText("%foo = "), nullptr).isStartOfNewInst()); + EXPECT_FALSE(AssemblyContext(AutoText("%foo "), nullptr).isStartOfNewInst()); + EXPECT_FALSE(AssemblyContext(AutoText("%foo"), nullptr).isStartOfNewInst()); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/text_to_binary.annotation_test.cpp b/3rdparty/spirv-tools/test/text_to_binary.annotation_test.cpp new file mode 100644 index 000000000..94cc8f07b --- /dev/null +++ b/3rdparty/spirv-tools/test/text_to_binary.annotation_test.cpp @@ -0,0 +1,504 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Assembler tests for instructions in the "Annotation" section of the +// SPIR-V spec. + +#include "unit_spirv.h" + +#include +#include + +#include "gmock/gmock.h" +#include "test_fixture.h" + +namespace { + +using spvtest::EnumCase; +using spvtest::MakeInstruction; +using spvtest::MakeVector; +using spvtest::TextToBinaryTest; +using std::get; +using std::tuple; +using ::testing::Combine; +using ::testing::Eq; +using ::testing::Values; +using ::testing::ValuesIn; + +// Test OpDecorate + +using OpDecorateSimpleTest = spvtest::TextToBinaryTestBase< + ::testing::TestWithParam>>>; + +TEST_P(OpDecorateSimpleTest, AnySimpleDecoration) { + // This string should assemble, but should not validate. + std::stringstream input; + input << "OpDecorate %1 " << get<1>(GetParam()).name(); + for (auto operand : get<1>(GetParam()).operands()) input << " " << operand; + input << std::endl; + EXPECT_THAT(CompiledInstructions(input.str(), get<0>(GetParam())), + Eq(MakeInstruction(SpvOpDecorate, + {1, uint32_t(get<1>(GetParam()).value())}, + get<1>(GetParam()).operands()))); + // Also check disassembly. + EXPECT_THAT( + EncodeAndDecodeSuccessfully(input.str(), SPV_BINARY_TO_TEXT_OPTION_NONE, + get<0>(GetParam())), + Eq(input.str())); +} + +#define CASE(NAME) SpvDecoration##NAME, #NAME +INSTANTIATE_TEST_CASE_P( + TextToBinaryDecorateSimple, OpDecorateSimpleTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector>{ + // The operand literal values are arbitrarily chosen, + // but there are the right number of them. + {CASE(RelaxedPrecision), {}}, + {CASE(SpecId), {100}}, + {CASE(Block), {}}, + {CASE(BufferBlock), {}}, + {CASE(RowMajor), {}}, + {CASE(ColMajor), {}}, + {CASE(ArrayStride), {4}}, + {CASE(MatrixStride), {16}}, + {CASE(GLSLShared), {}}, + {CASE(GLSLPacked), {}}, + {CASE(CPacked), {}}, + // Placeholder line for enum value 12 + {CASE(NoPerspective), {}}, + {CASE(Flat), {}}, + {CASE(Patch), {}}, + {CASE(Centroid), {}}, + {CASE(Sample), {}}, + {CASE(Invariant), {}}, + {CASE(Restrict), {}}, + {CASE(Aliased), {}}, + {CASE(Volatile), {}}, + {CASE(Constant), {}}, + {CASE(Coherent), {}}, + {CASE(NonWritable), {}}, + {CASE(NonReadable), {}}, + {CASE(Uniform), {}}, + {CASE(SaturatedConversion), {}}, + {CASE(Stream), {2}}, + {CASE(Location), {6}}, + {CASE(Component), {3}}, + {CASE(Index), {14}}, + {CASE(Binding), {19}}, + {CASE(DescriptorSet), {7}}, + {CASE(Offset), {12}}, + {CASE(XfbBuffer), {1}}, + {CASE(XfbStride), {8}}, + {CASE(NoContraction), {}}, + {CASE(InputAttachmentIndex), {102}}, + {CASE(Alignment), {16}}, + })), ); + +INSTANTIATE_TEST_CASE_P(TextToBinaryDecorateSimpleV11, OpDecorateSimpleTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_1), + Values(EnumCase{ + CASE(MaxByteOffset), {128}})), ); +#undef CASE + +TEST_F(OpDecorateSimpleTest, WrongDecoration) { + EXPECT_THAT(CompileFailure("OpDecorate %1 xxyyzz"), + Eq("Invalid decoration 'xxyyzz'.")); +} + +TEST_F(OpDecorateSimpleTest, ExtraOperandsOnDecorationExpectingNone) { + EXPECT_THAT(CompileFailure("OpDecorate %1 RelaxedPrecision 99"), + Eq("Expected or at the beginning of an " + "instruction, found '99'.")); +} + +TEST_F(OpDecorateSimpleTest, ExtraOperandsOnDecorationExpectingOne) { + EXPECT_THAT(CompileFailure("OpDecorate %1 SpecId 99 100"), + Eq("Expected or at the beginning of an " + "instruction, found '100'.")); +} + +TEST_F(OpDecorateSimpleTest, ExtraOperandsOnDecorationExpectingTwo) { + EXPECT_THAT( + CompileFailure("OpDecorate %1 LinkageAttributes \"abc\" Import 42"), + Eq("Expected or at the beginning of an " + "instruction, found '42'.")); +} + +// A single test case for an enum decoration. +struct DecorateEnumCase { + // Place the enum value first, so it's easier to read the binary dumps when + // the test fails. + uint32_t value; // The value within the enum, e.g. Position + std::string name; + uint32_t enum_value; // Which enum, e.g. BuiltIn + std::string enum_name; +}; + +using OpDecorateEnumTest = + spvtest::TextToBinaryTestBase<::testing::TestWithParam>; + +TEST_P(OpDecorateEnumTest, AnyEnumDecoration) { + // This string should assemble, but should not validate. + const std::string input = + "OpDecorate %1 " + GetParam().enum_name + " " + GetParam().name; + EXPECT_THAT(CompiledInstructions(input), + Eq(MakeInstruction(SpvOpDecorate, {1, GetParam().enum_value, + GetParam().value}))); +} + +// Test OpDecorate BuiltIn. +// clang-format off +#define CASE(NAME) \ + { SpvBuiltIn##NAME, #NAME, SpvDecorationBuiltIn, "BuiltIn" } +INSTANTIATE_TEST_CASE_P(TextToBinaryDecorateBuiltIn, OpDecorateEnumTest, + ::testing::ValuesIn(std::vector{ + CASE(Position), + CASE(PointSize), + CASE(ClipDistance), + CASE(CullDistance), + CASE(VertexId), + CASE(InstanceId), + CASE(PrimitiveId), + CASE(InvocationId), + CASE(Layer), + CASE(ViewportIndex), + CASE(TessLevelOuter), + CASE(TessLevelInner), + CASE(TessCoord), + CASE(PatchVertices), + CASE(FragCoord), + CASE(PointCoord), + CASE(FrontFacing), + CASE(SampleId), + CASE(SamplePosition), + CASE(SampleMask), + // Value 21 intentionally missing. + CASE(FragDepth), + CASE(HelperInvocation), + CASE(NumWorkgroups), + CASE(WorkgroupSize), + CASE(WorkgroupId), + CASE(LocalInvocationId), + CASE(GlobalInvocationId), + CASE(LocalInvocationIndex), + CASE(WorkDim), + CASE(GlobalSize), + CASE(EnqueuedWorkgroupSize), + CASE(GlobalOffset), + CASE(GlobalLinearId), + // Value 35 intentionally missing. + CASE(SubgroupSize), + CASE(SubgroupMaxSize), + CASE(NumSubgroups), + CASE(NumEnqueuedSubgroups), + CASE(SubgroupId), + CASE(SubgroupLocalInvocationId), + CASE(VertexIndex), + CASE(InstanceIndex), + }),); +#undef CASE +// clang-format on + +TEST_F(OpDecorateEnumTest, WrongBuiltIn) { + EXPECT_THAT(CompileFailure("OpDecorate %1 BuiltIn xxyyzz"), + Eq("Invalid built-in 'xxyyzz'.")); +} + +// Test OpDecorate FuncParamAttr +// clang-format off +#define CASE(NAME) \ + { SpvFunctionParameterAttribute##NAME, #NAME, SpvDecorationFuncParamAttr, "FuncParamAttr" } +INSTANTIATE_TEST_CASE_P(TextToBinaryDecorateFuncParamAttr, OpDecorateEnumTest, + ::testing::ValuesIn(std::vector{ + CASE(Zext), + CASE(Sext), + CASE(ByVal), + CASE(Sret), + CASE(NoAlias), + CASE(NoCapture), + CASE(NoWrite), + CASE(NoReadWrite), + }),); +#undef CASE +// clang-format on + +TEST_F(OpDecorateEnumTest, WrongFuncParamAttr) { + EXPECT_THAT(CompileFailure("OpDecorate %1 FuncParamAttr xxyyzz"), + Eq("Invalid function parameter attribute 'xxyyzz'.")); +} + +// Test OpDecorate FPRoundingMode +// clang-format off +#define CASE(NAME) \ + { SpvFPRoundingMode##NAME, #NAME, SpvDecorationFPRoundingMode, "FPRoundingMode" } +INSTANTIATE_TEST_CASE_P(TextToBinaryDecorateFPRoundingMode, OpDecorateEnumTest, + ::testing::ValuesIn(std::vector{ + CASE(RTE), + CASE(RTZ), + CASE(RTP), + CASE(RTN), + }),); +#undef CASE +// clang-format on + +TEST_F(OpDecorateEnumTest, WrongFPRoundingMode) { + EXPECT_THAT(CompileFailure("OpDecorate %1 FPRoundingMode xxyyzz"), + Eq("Invalid floating-point rounding mode 'xxyyzz'.")); +} + +// Test OpDecorate FPFastMathMode. +// These can by named enums for the single-bit masks. However, we don't support +// symbolic combinations of the masks. Rather, they can use ! +// syntax, e.g. !0x3 + +// clang-format off +#define CASE(ENUM,NAME) \ + { SpvFPFastMathMode##ENUM, #NAME, SpvDecorationFPFastMathMode, "FPFastMathMode" } +INSTANTIATE_TEST_CASE_P(TextToBinaryDecorateFPFastMathMode, OpDecorateEnumTest, + ::testing::ValuesIn(std::vector{ + CASE(MaskNone, None), + CASE(NotNaNMask, NotNaN), + CASE(NotInfMask, NotInf), + CASE(NSZMask, NSZ), + CASE(AllowRecipMask, AllowRecip), + CASE(FastMask, Fast), + }),); +#undef CASE +// clang-format on + +TEST_F(OpDecorateEnumTest, CombinedFPFastMathMask) { + // Sample a single combination. This ensures we've integrated + // the instruction parsing logic with spvTextParseMask. + const std::string input = "OpDecorate %1 FPFastMathMode NotNaN|NotInf|NSZ"; + const uint32_t expected_enum = SpvDecorationFPFastMathMode; + const uint32_t expected_mask = SpvFPFastMathModeNotNaNMask | + SpvFPFastMathModeNotInfMask | + SpvFPFastMathModeNSZMask; + EXPECT_THAT( + CompiledInstructions(input), + Eq(MakeInstruction(SpvOpDecorate, {1, expected_enum, expected_mask}))); +} + +TEST_F(OpDecorateEnumTest, WrongFPFastMathMode) { + EXPECT_THAT( + CompileFailure("OpDecorate %1 FPFastMathMode NotNaN|xxyyzz"), + Eq("Invalid floating-point fast math mode operand 'NotNaN|xxyyzz'.")); +} + +// Test OpDecorate Linkage + +// A single test case for a linkage +struct DecorateLinkageCase { + uint32_t linkage_type_value; + std::string linkage_type_name; + std::string external_name; +}; + +using OpDecorateLinkageTest = spvtest::TextToBinaryTestBase< + ::testing::TestWithParam>; + +TEST_P(OpDecorateLinkageTest, AnyLinkageDecoration) { + // This string should assemble, but should not validate. + const std::string input = "OpDecorate %1 LinkageAttributes \"" + + GetParam().external_name + "\" " + + GetParam().linkage_type_name; + std::vector expected_operands{1, SpvDecorationLinkageAttributes}; + std::vector encoded_external_name = + MakeVector(GetParam().external_name); + expected_operands.insert(expected_operands.end(), + encoded_external_name.begin(), + encoded_external_name.end()); + expected_operands.push_back(GetParam().linkage_type_value); + EXPECT_THAT(CompiledInstructions(input), + Eq(MakeInstruction(SpvOpDecorate, expected_operands))); +} + +// clang-format off +#define CASE(ENUM) SpvLinkageType##ENUM, #ENUM +INSTANTIATE_TEST_CASE_P(TextToBinaryDecorateLinkage, OpDecorateLinkageTest, + ::testing::ValuesIn(std::vector{ + { CASE(Import), "a" }, + { CASE(Export), "foo" }, + { CASE(Import), "some kind of long name with spaces etc." }, + // TODO(dneto): utf-8, escaping, quoting cases. + }),); +#undef CASE +// clang-format on + +TEST_F(OpDecorateLinkageTest, WrongType) { + EXPECT_THAT(CompileFailure("OpDecorate %1 LinkageAttributes \"foo\" xxyyzz"), + Eq("Invalid linkage type 'xxyyzz'.")); +} + +// Test OpGroupMemberDecorate + +TEST_F(TextToBinaryTest, GroupMemberDecorateGoodOneTarget) { + EXPECT_THAT(CompiledInstructions("OpGroupMemberDecorate %group %id0 42"), + Eq(MakeInstruction(SpvOpGroupMemberDecorate, {1, 2, 42}))); +} + +TEST_F(TextToBinaryTest, GroupMemberDecorateGoodTwoTargets) { + EXPECT_THAT( + CompiledInstructions("OpGroupMemberDecorate %group %id0 96 %id1 42"), + Eq(MakeInstruction(SpvOpGroupMemberDecorate, {1, 2, 96, 3, 42}))); +} + +TEST_F(TextToBinaryTest, GroupMemberDecorateMissingGroupId) { + EXPECT_THAT(CompileFailure("OpGroupMemberDecorate"), + Eq("Expected operand, found end of stream.")); +} + +TEST_F(TextToBinaryTest, GroupMemberDecorateInvalidGroupId) { + EXPECT_THAT(CompileFailure("OpGroupMemberDecorate 16"), + Eq("Expected id to start with %.")); +} + +TEST_F(TextToBinaryTest, GroupMemberDecorateInvalidTargetId) { + EXPECT_THAT(CompileFailure("OpGroupMemberDecorate %group 12"), + Eq("Expected id to start with %.")); +} + +TEST_F(TextToBinaryTest, GroupMemberDecorateMissingTargetMemberNumber) { + EXPECT_THAT(CompileFailure("OpGroupMemberDecorate %group %id0"), + Eq("Expected operand, found end of stream.")); +} + +TEST_F(TextToBinaryTest, GroupMemberDecorateInvalidTargetMemberNumber) { + EXPECT_THAT(CompileFailure("OpGroupMemberDecorate %group %id0 %id1"), + Eq("Invalid unsigned integer literal: %id1")); +} + +TEST_F(TextToBinaryTest, GroupMemberDecorateInvalidSecondTargetId) { + EXPECT_THAT(CompileFailure("OpGroupMemberDecorate %group %id1 42 12"), + Eq("Expected id to start with %.")); +} + +TEST_F(TextToBinaryTest, GroupMemberDecorateMissingSecondTargetMemberNumber) { + EXPECT_THAT(CompileFailure("OpGroupMemberDecorate %group %id0 42 %id1"), + Eq("Expected operand, found end of stream.")); +} + +TEST_F(TextToBinaryTest, GroupMemberDecorateInvalidSecondTargetMemberNumber) { + EXPECT_THAT(CompileFailure("OpGroupMemberDecorate %group %id0 42 %id1 %id2"), + Eq("Invalid unsigned integer literal: %id2")); +} + +// Test OpMemberDecorate + +using OpMemberDecorateSimpleTest = spvtest::TextToBinaryTestBase< + ::testing::TestWithParam>>>; + +TEST_P(OpMemberDecorateSimpleTest, AnySimpleDecoration) { + // This string should assemble, but should not validate. + std::stringstream input; + input << "OpMemberDecorate %1 42 " << get<1>(GetParam()).name(); + for (auto operand : get<1>(GetParam()).operands()) input << " " << operand; + input << std::endl; + EXPECT_THAT(CompiledInstructions(input.str(), get<0>(GetParam())), + Eq(MakeInstruction(SpvOpMemberDecorate, + {1, 42, uint32_t(get<1>(GetParam()).value())}, + get<1>(GetParam()).operands()))); + // Also check disassembly. + EXPECT_THAT( + EncodeAndDecodeSuccessfully(input.str(), SPV_BINARY_TO_TEXT_OPTION_NONE, + get<0>(GetParam())), + Eq(input.str())); +} + +#define CASE(NAME) SpvDecoration##NAME, #NAME +INSTANTIATE_TEST_CASE_P( + TextToBinaryDecorateSimple, OpMemberDecorateSimpleTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector>{ + // The operand literal values are arbitrarily chosen, + // but there are the right number of them. + {CASE(RelaxedPrecision), {}}, + {CASE(SpecId), {100}}, + {CASE(Block), {}}, + {CASE(BufferBlock), {}}, + {CASE(RowMajor), {}}, + {CASE(ColMajor), {}}, + {CASE(ArrayStride), {4}}, + {CASE(MatrixStride), {16}}, + {CASE(GLSLShared), {}}, + {CASE(GLSLPacked), {}}, + {CASE(CPacked), {}}, + // Placeholder line for enum value 12 + {CASE(NoPerspective), {}}, + {CASE(Flat), {}}, + {CASE(Patch), {}}, + {CASE(Centroid), {}}, + {CASE(Sample), {}}, + {CASE(Invariant), {}}, + {CASE(Restrict), {}}, + {CASE(Aliased), {}}, + {CASE(Volatile), {}}, + {CASE(Constant), {}}, + {CASE(Coherent), {}}, + {CASE(NonWritable), {}}, + {CASE(NonReadable), {}}, + {CASE(Uniform), {}}, + {CASE(SaturatedConversion), {}}, + {CASE(Stream), {2}}, + {CASE(Location), {6}}, + {CASE(Component), {3}}, + {CASE(Index), {14}}, + {CASE(Binding), {19}}, + {CASE(DescriptorSet), {7}}, + {CASE(Offset), {12}}, + {CASE(XfbBuffer), {1}}, + {CASE(XfbStride), {8}}, + {CASE(NoContraction), {}}, + {CASE(InputAttachmentIndex), {102}}, + {CASE(Alignment), {16}}, + })), ); + +INSTANTIATE_TEST_CASE_P( + TextToBinaryDecorateSimpleV11, OpMemberDecorateSimpleTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_1), + Values(EnumCase{CASE(MaxByteOffset), {128}})), ); +#undef CASE + +TEST_F(OpMemberDecorateSimpleTest, WrongDecoration) { + EXPECT_THAT(CompileFailure("OpMemberDecorate %1 9 xxyyzz"), + Eq("Invalid decoration 'xxyyzz'.")); +} + +TEST_F(OpMemberDecorateSimpleTest, ExtraOperandsOnDecorationExpectingNone) { + EXPECT_THAT(CompileFailure("OpMemberDecorate %1 12 RelaxedPrecision 99"), + Eq("Expected or at the beginning of an " + "instruction, found '99'.")); +} + +TEST_F(OpMemberDecorateSimpleTest, ExtraOperandsOnDecorationExpectingOne) { + EXPECT_THAT(CompileFailure("OpMemberDecorate %1 0 SpecId 99 100"), + Eq("Expected or at the beginning of an " + "instruction, found '100'.")); +} + +TEST_F(OpMemberDecorateSimpleTest, ExtraOperandsOnDecorationExpectingTwo) { + EXPECT_THAT(CompileFailure( + "OpMemberDecorate %1 1 LinkageAttributes \"abc\" Import 42"), + Eq("Expected or at the beginning of an " + "instruction, found '42'.")); +} + +// TODO(dneto): OpMemberDecorate cases for decorations with parameters which +// are: not just lists of literal numbers. + +// TODO(dneto): OpDecorationGroup +// TODO(dneto): OpGroupDecorate + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/text_to_binary.barrier_test.cpp b/3rdparty/spirv-tools/test/text_to_binary.barrier_test.cpp new file mode 100644 index 000000000..743104298 --- /dev/null +++ b/3rdparty/spirv-tools/test/text_to_binary.barrier_test.cpp @@ -0,0 +1,161 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Assembler tests for instructions in the "Barrier Instructions" section +// of the SPIR-V spec. + +#include "unit_spirv.h" + +#include "gmock/gmock.h" +#include "test_fixture.h" + +namespace { + +using spvtest::MakeInstruction; +using spvtest::TextToBinaryTest; +using ::testing::_; +using ::testing::ElementsAre; +using ::testing::Eq; + +// Test OpMemoryBarrier + +using OpMemoryBarrier = spvtest::TextToBinaryTest; + +TEST_F(OpMemoryBarrier, Good) { + const std::string input = "OpMemoryBarrier %1 %2\n"; + EXPECT_THAT(CompiledInstructions(input), + Eq(MakeInstruction(SpvOpMemoryBarrier, {1, 2}))); + EXPECT_THAT(EncodeAndDecodeSuccessfully(input), Eq(input)); +} + +TEST_F(OpMemoryBarrier, BadMissingScopeId) { + const std::string input = "OpMemoryBarrier\n"; + EXPECT_THAT(CompileFailure(input), + Eq("Expected operand, found end of stream.")); +} + +TEST_F(OpMemoryBarrier, BadInvalidScopeId) { + const std::string input = "OpMemoryBarrier 99\n"; + EXPECT_THAT(CompileFailure(input), Eq("Expected id to start with %.")); +} + +TEST_F(OpMemoryBarrier, BadMissingMemorySemanticsId) { + const std::string input = "OpMemoryBarrier %scope\n"; + EXPECT_THAT(CompileFailure(input), + Eq("Expected operand, found end of stream.")); +} + +TEST_F(OpMemoryBarrier, BadInvalidMemorySemanticsId) { + const std::string input = "OpMemoryBarrier %scope 14\n"; + EXPECT_THAT(CompileFailure(input), Eq("Expected id to start with %.")); +} + +// TODO(dneto): OpControlBarrier +// TODO(dneto): OpGroupAsyncCopy +// TODO(dneto): OpGroupWaitEvents +// TODO(dneto): OpGroupAll +// TODO(dneto): OpGroupAny +// TODO(dneto): OpGroupBroadcast +// TODO(dneto): OpGroupIAdd +// TODO(dneto): OpGroupFAdd +// TODO(dneto): OpGroupFMin +// TODO(dneto): OpGroupUMin +// TODO(dneto): OpGroupSMin +// TODO(dneto): OpGroupFMax +// TODO(dneto): OpGroupUMax +// TODO(dneto): OpGroupSMax + +using NamedMemoryBarrierTest = spvtest::TextToBinaryTest; + +TEST_F(NamedMemoryBarrierTest, OpcodeUnrecognizedInV10) { + EXPECT_THAT(CompileFailure("OpMemoryNamedBarrier %bar %scope %semantics", + SPV_ENV_UNIVERSAL_1_0), + Eq("Invalid Opcode name 'OpMemoryNamedBarrier'")); +} + +TEST_F(NamedMemoryBarrierTest, ArgumentCount) { + EXPECT_THAT(CompileFailure("OpMemoryNamedBarrier", SPV_ENV_UNIVERSAL_1_1), + Eq("Expected operand, found end of stream.")); + EXPECT_THAT( + CompileFailure("OpMemoryNamedBarrier %bar", SPV_ENV_UNIVERSAL_1_1), + Eq("Expected operand, found end of stream.")); + EXPECT_THAT( + CompileFailure("OpMemoryNamedBarrier %bar %scope", SPV_ENV_UNIVERSAL_1_1), + Eq("Expected operand, found end of stream.")); + EXPECT_THAT( + CompiledInstructions("OpMemoryNamedBarrier %bar %scope %semantics", + SPV_ENV_UNIVERSAL_1_1), + ElementsAre(spvOpcodeMake(4, SpvOpMemoryNamedBarrier), _, _, _)); + EXPECT_THAT( + CompileFailure("OpMemoryNamedBarrier %bar %scope %semantics %extra", + SPV_ENV_UNIVERSAL_1_1), + Eq("Expected '=', found end of stream.")); +} + +TEST_F(NamedMemoryBarrierTest, ArgumentTypes) { + EXPECT_THAT(CompileFailure("OpMemoryNamedBarrier 123 %scope %semantics", + SPV_ENV_UNIVERSAL_1_1), + Eq("Expected id to start with %.")); + EXPECT_THAT(CompileFailure("OpMemoryNamedBarrier %bar %scope \"semantics\"", + SPV_ENV_UNIVERSAL_1_1), + Eq("Expected id to start with %.")); +} + +using TypeNamedBarrierTest = spvtest::TextToBinaryTest; + +TEST_F(TypeNamedBarrierTest, OpcodeUnrecognizedInV10) { + EXPECT_THAT(CompileFailure("%t = OpTypeNamedBarrier", SPV_ENV_UNIVERSAL_1_0), + Eq("Invalid Opcode name 'OpTypeNamedBarrier'")); +} + +TEST_F(TypeNamedBarrierTest, ArgumentCount) { + EXPECT_THAT(CompileFailure("OpTypeNamedBarrier", SPV_ENV_UNIVERSAL_1_1), + Eq("Expected at the beginning of an instruction, " + "found 'OpTypeNamedBarrier'.")); + EXPECT_THAT( + CompiledInstructions("%t = OpTypeNamedBarrier", SPV_ENV_UNIVERSAL_1_1), + ElementsAre(spvOpcodeMake(2, SpvOpTypeNamedBarrier), _)); + EXPECT_THAT( + CompileFailure("%t = OpTypeNamedBarrier 1 2 3", SPV_ENV_UNIVERSAL_1_1), + Eq("Expected or at the beginning of an instruction, " + "found '1'.")); +} + +using NamedBarrierInitializeTest = spvtest::TextToBinaryTest; + +TEST_F(NamedBarrierInitializeTest, OpcodeUnrecognizedInV10) { + EXPECT_THAT(CompileFailure("%bar = OpNamedBarrierInitialize %type %count", + SPV_ENV_UNIVERSAL_1_0), + Eq("Invalid Opcode name 'OpNamedBarrierInitialize'")); +} + +TEST_F(NamedBarrierInitializeTest, ArgumentCount) { + EXPECT_THAT( + CompileFailure("%bar = OpNamedBarrierInitialize", SPV_ENV_UNIVERSAL_1_1), + Eq("Expected operand, found end of stream.")); + EXPECT_THAT(CompileFailure("%bar = OpNamedBarrierInitialize %ype", + SPV_ENV_UNIVERSAL_1_1), + Eq("Expected operand, found end of stream.")); + EXPECT_THAT( + CompiledInstructions("%bar = OpNamedBarrierInitialize %type %count", + SPV_ENV_UNIVERSAL_1_1), + ElementsAre(spvOpcodeMake(4, SpvOpNamedBarrierInitialize), _, _, _)); + EXPECT_THAT( + CompileFailure("%bar = OpNamedBarrierInitialize %type %count \"extra\"", + SPV_ENV_UNIVERSAL_1_1), + Eq("Expected or at the beginning of an instruction, " + "found '\"extra\"'.")); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/text_to_binary.constant_test.cpp b/3rdparty/spirv-tools/test/text_to_binary.constant_test.cpp new file mode 100644 index 000000000..9aa3977bc --- /dev/null +++ b/3rdparty/spirv-tools/test/text_to_binary.constant_test.cpp @@ -0,0 +1,827 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Assembler tests for instructions in the "Group Instrucions" section of the +// SPIR-V spec. + +#include "unit_spirv.h" + +#include +#include + +#include "gmock/gmock.h" +#include "test_fixture.h" + +namespace { + +using spvtest::Concatenate; +using spvtest::EnumCase; +using spvtest::MakeInstruction; +using ::testing::Eq; + +// Test Sampler Addressing Mode enum values + +using SamplerAddressingModeTest = spvtest::TextToBinaryTestBase< + ::testing::TestWithParam>>; + +TEST_P(SamplerAddressingModeTest, AnySamplerAddressingMode) { + const std::string input = + "%result = OpConstantSampler %type " + GetParam().name() + " 0 Nearest"; + EXPECT_THAT(CompiledInstructions(input), + Eq(MakeInstruction(SpvOpConstantSampler, + {1, 2, GetParam().value(), 0, 0}))); +} + +// clang-format off +#define CASE(NAME) { SpvSamplerAddressingMode##NAME, #NAME } +INSTANTIATE_TEST_CASE_P( + TextToBinarySamplerAddressingMode, SamplerAddressingModeTest, + ::testing::ValuesIn(std::vector>{ + CASE(None), + CASE(ClampToEdge), + CASE(Clamp), + CASE(Repeat), + CASE(RepeatMirrored), + }),); +#undef CASE +// clang-format on + +TEST_F(SamplerAddressingModeTest, WrongMode) { + EXPECT_THAT(CompileFailure("%r = OpConstantSampler %t xxyyzz 0 Nearest"), + Eq("Invalid sampler addressing mode 'xxyyzz'.")); +} + +// Test Sampler Filter Mode enum values + +using SamplerFilterModeTest = spvtest::TextToBinaryTestBase< + ::testing::TestWithParam>>; + +TEST_P(SamplerFilterModeTest, AnySamplerFilterMode) { + const std::string input = + "%result = OpConstantSampler %type Clamp 0 " + GetParam().name(); + EXPECT_THAT(CompiledInstructions(input), + Eq(MakeInstruction(SpvOpConstantSampler, + {1, 2, 2, 0, GetParam().value()}))); +} + +// clang-format off +#define CASE(NAME) { SpvSamplerFilterMode##NAME, #NAME} +INSTANTIATE_TEST_CASE_P( + TextToBinarySamplerFilterMode, SamplerFilterModeTest, + ::testing::ValuesIn(std::vector>{ + CASE(Nearest), + CASE(Linear), + }),); +#undef CASE +// clang-format on + +TEST_F(SamplerFilterModeTest, WrongMode) { + EXPECT_THAT(CompileFailure("%r = OpConstantSampler %t Clamp 0 xxyyzz"), + Eq("Invalid sampler filter mode 'xxyyzz'.")); +} + +struct ConstantTestCase { + std::string constant_type; + std::string constant_value; + std::vector expected_instructions; +}; + +using OpConstantValidTest = + spvtest::TextToBinaryTestBase<::testing::TestWithParam>; + +TEST_P(OpConstantValidTest, ValidTypes) { + const std::string input = "%1 = " + GetParam().constant_type + + "\n" + "%2 = OpConstant %1 " + + GetParam().constant_value + "\n"; + std::vector instructions; + EXPECT_THAT(CompiledInstructions(input), Eq(GetParam().expected_instructions)) + << " type: " << GetParam().constant_type + << " literal: " << GetParam().constant_value; +} + +// clang-format off +INSTANTIATE_TEST_CASE_P( + TextToBinaryOpConstantValid, OpConstantValidTest, + ::testing::ValuesIn(std::vector{ + // Check 16 bits + {"OpTypeInt 16 0", "0x1234", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 16, 0}), + MakeInstruction(SpvOpConstant, {1, 2, 0x1234})})}, + {"OpTypeInt 16 0", "0x8000", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 16, 0}), + MakeInstruction(SpvOpConstant, {1, 2, 0x8000})})}, + {"OpTypeInt 16 0", "0", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 16, 0}), + MakeInstruction(SpvOpConstant, {1, 2, 0})})}, + {"OpTypeInt 16 0", "65535", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 16, 0}), + MakeInstruction(SpvOpConstant, {1, 2, 65535})})}, + {"OpTypeInt 16 0", "0xffff", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 16, 0}), + MakeInstruction(SpvOpConstant, {1, 2, 65535})})}, + {"OpTypeInt 16 1", "0x8000", // Test sign extension. + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 16, 1}), + MakeInstruction(SpvOpConstant, {1, 2, 0xffff8000})})}, + {"OpTypeInt 16 1", "-32", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 16, 1}), + MakeInstruction(SpvOpConstant, {1, 2, uint32_t(-32)})})}, + {"OpTypeInt 16 1", "0", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 16, 1}), + MakeInstruction(SpvOpConstant, {1, 2, 0})})}, + {"OpTypeInt 16 1", "-0", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 16, 1}), + MakeInstruction(SpvOpConstant, {1, 2, 0})})}, + {"OpTypeInt 16 1", "-0x0", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 16, 1}), + MakeInstruction(SpvOpConstant, {1, 2, 0})})}, + {"OpTypeInt 16 1", "-32768", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 16, 1}), + MakeInstruction(SpvOpConstant, {1, 2, uint32_t(-32768)})})}, + // Check 32 bits + {"OpTypeInt 32 0", "42", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 32, 0}), + MakeInstruction(SpvOpConstant, {1, 2, 42})})}, + {"OpTypeInt 32 1", "-32", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 32, 1}), + MakeInstruction(SpvOpConstant, {1, 2, uint32_t(-32)})})}, + {"OpTypeInt 32 1", "0", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 32, 1}), + MakeInstruction(SpvOpConstant, {1, 2, 0})})}, + {"OpTypeInt 32 1", "-0", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 32, 1}), + MakeInstruction(SpvOpConstant, {1, 2, 0})})}, + {"OpTypeInt 32 1", "-0x0", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 32, 1}), + MakeInstruction(SpvOpConstant, {1, 2, 0})})}, + {"OpTypeInt 32 1", "-0x001", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 32, 1}), + MakeInstruction(SpvOpConstant, {1, 2, uint32_t(-1)})})}, + {"OpTypeInt 32 1", "2147483647", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 32, 1}), + MakeInstruction(SpvOpConstant, {1, 2, 0x7fffffffu})})}, + {"OpTypeInt 32 1", "-2147483648", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 32, 1}), + MakeInstruction(SpvOpConstant, {1, 2, 0x80000000u})})}, + {"OpTypeFloat 32", "1.0", + Concatenate({MakeInstruction(SpvOpTypeFloat, {1, 32}), + MakeInstruction(SpvOpConstant, {1, 2, 0x3f800000})})}, + {"OpTypeFloat 32", "10.0", + Concatenate({MakeInstruction(SpvOpTypeFloat, {1, 32}), + MakeInstruction(SpvOpConstant, {1, 2, 0x41200000})})}, + {"OpTypeFloat 32", "-0x1p+128", // -infinity + Concatenate({MakeInstruction(SpvOpTypeFloat, {1, 32}), + MakeInstruction(SpvOpConstant, {1, 2, 0xFF800000})})}, + {"OpTypeFloat 32", "0x1p+128", // +infinity + Concatenate({MakeInstruction(SpvOpTypeFloat, {1, 32}), + MakeInstruction(SpvOpConstant, {1, 2, 0x7F800000})})}, + {"OpTypeFloat 32", "-0x1.8p+128", // A -NaN + Concatenate({MakeInstruction(SpvOpTypeFloat, {1, 32}), + MakeInstruction(SpvOpConstant, {1, 2, 0xFFC00000})})}, + {"OpTypeFloat 32", "-0x1.0002p+128", // A +NaN + Concatenate({MakeInstruction(SpvOpTypeFloat, {1, 32}), + MakeInstruction(SpvOpConstant, {1, 2, 0xFF800100})})}, + // Check 48 bits + {"OpTypeInt 48 0", "0x1234", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 48, 0}), + MakeInstruction(SpvOpConstant, {1, 2, 0x1234, 0})})}, + {"OpTypeInt 48 0", "0x800000000001", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 48, 0}), + MakeInstruction(SpvOpConstant, {1, 2, 1, 0x00008000})})}, + {"OpTypeInt 48 1", "0x800000000000", // Test sign extension. + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 48, 1}), + MakeInstruction(SpvOpConstant, {1, 2, 0, 0xffff8000})})}, + {"OpTypeInt 48 1", "-32", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 48, 1}), + MakeInstruction(SpvOpConstant, {1, 2, uint32_t(-32), uint32_t(-1)})})}, + // Check 64 bits + {"OpTypeInt 64 0", "0x1234", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 64, 0}), + MakeInstruction(SpvOpConstant, {1, 2, 0x1234, 0})})}, + {"OpTypeInt 64 0", "18446744073709551615", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 64, 0}), + MakeInstruction(SpvOpConstant, {1, 2, 0xffffffffu, 0xffffffffu})})}, + {"OpTypeInt 64 0", "0xffffffffffffffff", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 64, 0}), + MakeInstruction(SpvOpConstant, {1, 2, 0xffffffffu, 0xffffffffu})})}, + {"OpTypeInt 64 1", "0x1234", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 64, 1}), + MakeInstruction(SpvOpConstant, {1, 2, 0x1234, 0})})}, + {"OpTypeInt 64 1", "-42", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 64, 1}), + MakeInstruction(SpvOpConstant, {1, 2, uint32_t(-42), uint32_t(-1)})})}, + {"OpTypeInt 64 1", "-0x01", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 64, 1}), + MakeInstruction(SpvOpConstant, {1, 2, 0xffffffffu, 0xffffffffu})})}, + {"OpTypeInt 64 1", "9223372036854775807", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 64, 1}), + MakeInstruction(SpvOpConstant, {1, 2, 0xffffffffu, 0x7fffffffu})})}, + {"OpTypeInt 64 1", "0x7fffffff", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 64, 1}), + MakeInstruction(SpvOpConstant, {1, 2, 0x7fffffffu, 0})})}, + }),); +// clang-format on + +// A test case for checking OpConstant with invalid literals with a leading +// minus. +struct InvalidLeadingMinusCase { + std::string type; + std::string literal; +}; + +using OpConstantInvalidLeadingMinusTest = spvtest::TextToBinaryTestBase< + ::testing::TestWithParam>; + +TEST_P(OpConstantInvalidLeadingMinusTest, InvalidCase) { + const std::string input = "%1 = " + GetParam().type + + "\n" + "%2 = OpConstant %1 " + + GetParam().literal; + EXPECT_THAT(CompileFailure(input), + Eq("Cannot put a negative number in an unsigned literal")); +} + +// clang-format off +INSTANTIATE_TEST_CASE_P( + TextToBinaryOpConstantInvalidLeadingMinus, OpConstantInvalidLeadingMinusTest, + ::testing::ValuesIn(std::vector{ + {"OpTypeInt 16 0", "-0"}, + {"OpTypeInt 16 0", "-0x0"}, + {"OpTypeInt 16 0", "-1"}, + {"OpTypeInt 32 0", "-0"}, + {"OpTypeInt 32 0", "-0x0"}, + {"OpTypeInt 32 0", "-1"}, + {"OpTypeInt 64 0", "-0"}, + {"OpTypeInt 64 0", "-0x0"}, + {"OpTypeInt 64 0", "-1"}, + }),); +// clang-format on + +// A test case for invalid floating point literals. +struct InvalidFloatConstantCase { + uint32_t width; + std::string literal; +}; + +using OpConstantInvalidFloatConstant = spvtest::TextToBinaryTestBase< + ::testing::TestWithParam>; + +TEST_P(OpConstantInvalidFloatConstant, Samples) { + // Check both kinds of instructions that take literal floats. + for (const auto& instruction : {"OpConstant", "OpSpecConstant"}) { + std::stringstream input; + input << "%1 = OpTypeFloat " << GetParam().width << "\n" + << "%2 = " << instruction << " %1 " << GetParam().literal; + std::stringstream expected_error; + expected_error << "Invalid " << GetParam().width + << "-bit float literal: " << GetParam().literal; + EXPECT_THAT(CompileFailure(input.str()), Eq(expected_error.str())); + } +} + +// clang-format off +INSTANTIATE_TEST_CASE_P( + TextToBinaryInvalidFloatConstant, OpConstantInvalidFloatConstant, + ::testing::ValuesIn(std::vector{ + {16, "abc"}, + {16, "--1"}, + {16, "-+1"}, + {16, "+-1"}, + {16, "++1"}, + {16, "1e30"}, // Overflow is an error for 16-bit floats. + {16, "-1e30"}, + {16, "1e40"}, + {16, "-1e40"}, + {16, "1e400"}, + {16, "-1e400"}, + {32, "abc"}, + {32, "--1"}, + {32, "-+1"}, + {32, "+-1"}, + {32, "++1"}, + {32, "1e40"}, // Overflow is an error for 32-bit floats. + {32, "-1e40"}, + {32, "1e400"}, + {32, "-1e400"}, + {64, "abc"}, + {64, "--1"}, + {64, "-+1"}, + {64, "+-1"}, + {64, "++1"}, + {32, "1e400"}, // Overflow is an error for 64-bit floats. + {32, "-1e400"}, + }),); +// clang-format on + +using OpConstantInvalidTypeTest = + spvtest::TextToBinaryTestBase<::testing::TestWithParam>; + +TEST_P(OpConstantInvalidTypeTest, InvalidTypes) { + const std::string input = "%1 = " + GetParam() + + "\n" + "%2 = OpConstant %1 0\n"; + EXPECT_THAT( + CompileFailure(input), + Eq("Type for Constant must be a scalar floating point or integer type")); +} + +// clang-format off +INSTANTIATE_TEST_CASE_P( + TextToBinaryOpConstantInvalidValidType, OpConstantInvalidTypeTest, + ::testing::ValuesIn(std::vector{ + {"OpTypeVoid", + "OpTypeBool", + "OpTypeVector %a 32", + "OpTypeMatrix %a 32", + "OpTypeImage %a 1D 0 0 0 0 Unknown", + "OpTypeSampler", + "OpTypeSampledImage %a", + "OpTypeArray %a %b", + "OpTypeRuntimeArray %a", + "OpTypeStruct %a", + "OpTypeOpaque \"Foo\"", + "OpTypePointer UniformConstant %a", + "OpTypeFunction %a %b", + "OpTypeEvent", + "OpTypeDeviceEvent", + "OpTypeReserveId", + "OpTypeQueue", + "OpTypePipe ReadOnly", + "OpTypeForwardPointer %a UniformConstant", + // At least one thing that isn't a type at all + "OpNot %a %b" + }, + }),); +// clang-format on + +using OpSpecConstantValidTest = + spvtest::TextToBinaryTestBase<::testing::TestWithParam>; + +TEST_P(OpSpecConstantValidTest, ValidTypes) { + const std::string input = "%1 = " + GetParam().constant_type + + "\n" + "%2 = OpSpecConstant %1 " + + GetParam().constant_value + "\n"; + std::vector instructions; + EXPECT_THAT(CompiledInstructions(input), + Eq(GetParam().expected_instructions)); +} + +// clang-format off +INSTANTIATE_TEST_CASE_P( + TextToBinaryOpSpecConstantValid, OpSpecConstantValidTest, + ::testing::ValuesIn(std::vector{ + // Check 16 bits + {"OpTypeInt 16 0", "0x1234", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 16, 0}), + MakeInstruction(SpvOpSpecConstant, {1, 2, 0x1234})})}, + {"OpTypeInt 16 0", "0x8000", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 16, 0}), + MakeInstruction(SpvOpSpecConstant, {1, 2, 0x8000})})}, + {"OpTypeInt 16 1", "0x8000", // Test sign extension. + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 16, 1}), + MakeInstruction(SpvOpSpecConstant, {1, 2, 0xffff8000})})}, + {"OpTypeInt 16 1", "-32", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 16, 1}), + MakeInstruction(SpvOpSpecConstant, {1, 2, uint32_t(-32)})})}, + // Check 32 bits + {"OpTypeInt 32 0", "42", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 32, 0}), + MakeInstruction(SpvOpSpecConstant, {1, 2, 42})})}, + {"OpTypeInt 32 1", "-32", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 32, 1}), + MakeInstruction(SpvOpSpecConstant, {1, 2, uint32_t(-32)})})}, + {"OpTypeFloat 32", "1.0", + Concatenate({MakeInstruction(SpvOpTypeFloat, {1, 32}), + MakeInstruction(SpvOpSpecConstant, {1, 2, 0x3f800000})})}, + {"OpTypeFloat 32", "10.0", + Concatenate({MakeInstruction(SpvOpTypeFloat, {1, 32}), + MakeInstruction(SpvOpSpecConstant, {1, 2, 0x41200000})})}, + // Check 48 bits + {"OpTypeInt 48 0", "0x1234", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 48, 0}), + MakeInstruction(SpvOpSpecConstant, {1, 2, 0x1234, 0})})}, + {"OpTypeInt 48 0", "0x800000000001", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 48, 0}), + MakeInstruction(SpvOpSpecConstant, {1, 2, 1, 0x00008000})})}, + {"OpTypeInt 48 1", "0x800000000000", // Test sign extension. + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 48, 1}), + MakeInstruction(SpvOpSpecConstant, {1, 2, 0, 0xffff8000})})}, + {"OpTypeInt 48 1", "-32", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 48, 1}), + MakeInstruction(SpvOpSpecConstant, {1, 2, uint32_t(-32), uint32_t(-1)})})}, + // Check 64 bits + {"OpTypeInt 64 0", "0x1234", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 64, 0}), + MakeInstruction(SpvOpSpecConstant, {1, 2, 0x1234, 0})})}, + {"OpTypeInt 64 1", "0x1234", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 64, 1}), + MakeInstruction(SpvOpSpecConstant, {1, 2, 0x1234, 0})})}, + {"OpTypeInt 64 1", "-42", + Concatenate({MakeInstruction(SpvOpTypeInt, {1, 64, 1}), + MakeInstruction(SpvOpSpecConstant, {1, 2, uint32_t(-42), uint32_t(-1)})})}, + }),); +// clang-format on + +using OpSpecConstantInvalidTypeTest = + spvtest::TextToBinaryTestBase<::testing::TestWithParam>; + +TEST_P(OpSpecConstantInvalidTypeTest, InvalidTypes) { + const std::string input = "%1 = " + GetParam() + + "\n" + "%2 = OpSpecConstant %1 0\n"; + EXPECT_THAT(CompileFailure(input), + Eq("Type for SpecConstant must be a scalar floating point or " + "integer type")); +} + +// clang-format off +INSTANTIATE_TEST_CASE_P( + TextToBinaryOpSpecConstantInvalidValidType, OpSpecConstantInvalidTypeTest, + ::testing::ValuesIn(std::vector{ + {"OpTypeVoid", + "OpTypeBool", + "OpTypeVector %a 32", + "OpTypeMatrix %a 32", + "OpTypeImage %a 1D 0 0 0 0 Unknown", + "OpTypeSampler", + "OpTypeSampledImage %a", + "OpTypeArray %a %b", + "OpTypeRuntimeArray %a", + "OpTypeStruct %a", + "OpTypeOpaque \"Foo\"", + "OpTypePointer UniformConstant %a", + "OpTypeFunction %a %b", + "OpTypeEvent", + "OpTypeDeviceEvent", + "OpTypeReserveId", + "OpTypeQueue", + "OpTypePipe ReadOnly", + "OpTypeForwardPointer %a UniformConstant", + // At least one thing that isn't a type at all + "OpNot %a %b" + }, + }),); +// clang-format on + +const int64_t kMaxUnsigned48Bit = (int64_t(1) << 48) - 1; +const int64_t kMaxSigned48Bit = (int64_t(1) << 47) - 1; +const int64_t kMinSigned48Bit = -kMaxSigned48Bit - 1; + +INSTANTIATE_TEST_CASE_P( + OpConstantRoundTrip, RoundTripTest, + ::testing::ValuesIn(std::vector{ + // 16 bit + "%1 = OpTypeInt 16 0\n%2 = OpConstant %1 0\n", + "%1 = OpTypeInt 16 0\n%2 = OpConstant %1 65535\n", + "%1 = OpTypeInt 16 1\n%2 = OpConstant %1 -32768\n", + "%1 = OpTypeInt 16 1\n%2 = OpConstant %1 32767\n", + "%1 = OpTypeInt 32 0\n%2 = OpConstant %1 0\n", + // 32 bit + std::string("%1 = OpTypeInt 32 0\n%2 = OpConstant %1 0\n"), + std::string("%1 = OpTypeInt 32 0\n%2 = OpConstant %1 ") + + std::to_string(std::numeric_limits::max()) + "\n", + std::string("%1 = OpTypeInt 32 1\n%2 = OpConstant %1 ") + + std::to_string(std::numeric_limits::max()) + "\n", + std::string("%1 = OpTypeInt 32 1\n%2 = OpConstant %1 ") + + std::to_string(std::numeric_limits::min()) + "\n", + // 48 bit + std::string("%1 = OpTypeInt 48 0\n%2 = OpConstant %1 0\n"), + std::string("%1 = OpTypeInt 48 0\n%2 = OpConstant %1 ") + + std::to_string(kMaxUnsigned48Bit) + "\n", + std::string("%1 = OpTypeInt 48 1\n%2 = OpConstant %1 ") + + std::to_string(kMaxSigned48Bit) + "\n", + std::string("%1 = OpTypeInt 48 1\n%2 = OpConstant %1 ") + + std::to_string(kMinSigned48Bit) + "\n", + // 64 bit + std::string("%1 = OpTypeInt 64 0\n%2 = OpConstant %1 0\n"), + std::string("%1 = OpTypeInt 64 0\n%2 = OpConstant %1 ") + + std::to_string(std::numeric_limits::max()) + "\n", + std::string("%1 = OpTypeInt 64 1\n%2 = OpConstant %1 ") + + std::to_string(std::numeric_limits::max()) + "\n", + std::string("%1 = OpTypeInt 64 1\n%2 = OpConstant %1 ") + + std::to_string(std::numeric_limits::min()) + "\n", + // 32-bit float + "%1 = OpTypeFloat 32\n%2 = OpConstant %1 0\n", + "%1 = OpTypeFloat 32\n%2 = OpConstant %1 13.5\n", + "%1 = OpTypeFloat 32\n%2 = OpConstant %1 -12.5\n", + // 64-bit float + "%1 = OpTypeFloat 64\n%2 = OpConstant %1 0\n", + "%1 = OpTypeFloat 64\n%2 = OpConstant %1 1.79767e+308\n", + "%1 = OpTypeFloat 64\n%2 = OpConstant %1 -1.79767e+308\n", + }), ); + +INSTANTIATE_TEST_CASE_P( + OpConstantHalfRoundTrip, RoundTripTest, + ::testing::ValuesIn(std::vector{ + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x0p+0\n", + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x0p+0\n", + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1p+0\n", + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1.1p+0\n", + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1.01p-1\n", + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1.8p+1\n", + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1.ffcp+1\n", + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1p+0\n", + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1.1p+0\n", + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1.01p-1\n", + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1.8p+1\n", + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1.ffcp+1\n", + + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1p-16\n", // some denorms + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1p-24\n", + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1p-24\n", + + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1p+16\n", // +inf + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1p+16\n", // -inf + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1.01p+16\n", // -inf + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1.01p+16\n", // nan + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1.11p+16\n", // nan + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1.ffp+16\n", // nan + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1.ffcp+16\n", // nan + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1.004p+16\n", // nan + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1.01p+16\n", // -nan + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1.11p+16\n", // -nan + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1.ffp+16\n", // -nan + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1.ffcp+16\n", // -nan + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1.004p+16\n", // -nan + }), ); + +// clang-format off +// (Clang-format really wants to break up these strings across lines. +INSTANTIATE_TEST_CASE_P( + OpConstantRoundTripNonFinite, RoundTripTest, + ::testing::ValuesIn(std::vector{ + "%1 = OpTypeFloat 32\n%2 = OpConstant %1 -0x1p+128\n", // -inf + "%1 = OpTypeFloat 32\n%2 = OpConstant %1 0x1p+128\n", // inf + "%1 = OpTypeFloat 32\n%2 = OpConstant %1 -0x1.8p+128\n", // -nan + "%1 = OpTypeFloat 32\n%2 = OpConstant %1 -0x1.0002p+128\n", // -nan + "%1 = OpTypeFloat 32\n%2 = OpConstant %1 -0x1.0018p+128\n", // -nan + "%1 = OpTypeFloat 32\n%2 = OpConstant %1 -0x1.01ep+128\n", // -nan + "%1 = OpTypeFloat 32\n%2 = OpConstant %1 -0x1.fffffep+128\n", // -nan + "%1 = OpTypeFloat 32\n%2 = OpConstant %1 0x1.8p+128\n", // +nan + "%1 = OpTypeFloat 32\n%2 = OpConstant %1 0x1.0002p+128\n", // +nan + "%1 = OpTypeFloat 32\n%2 = OpConstant %1 0x1.0018p+128\n", // +nan + "%1 = OpTypeFloat 32\n%2 = OpConstant %1 0x1.01ep+128\n", // +nan + "%1 = OpTypeFloat 32\n%2 = OpConstant %1 0x1.fffffep+128\n", // +nan + "%1 = OpTypeFloat 64\n%2 = OpConstant %1 -0x1p+1024\n", //-inf + "%1 = OpTypeFloat 64\n%2 = OpConstant %1 0x1p+1024\n", //+inf + "%1 = OpTypeFloat 64\n%2 = OpConstant %1 -0x1.8p+1024\n", // -nan + "%1 = OpTypeFloat 64\n%2 = OpConstant %1 -0x1.0fp+1024\n", // -nan + "%1 = OpTypeFloat 64\n%2 = OpConstant %1 -0x1.0000000000001p+1024\n", // -nan + "%1 = OpTypeFloat 64\n%2 = OpConstant %1 -0x1.00003p+1024\n", // -nan + "%1 = OpTypeFloat 64\n%2 = OpConstant %1 -0x1.fffffffffffffp+1024\n", // -nan + "%1 = OpTypeFloat 64\n%2 = OpConstant %1 0x1.8p+1024\n", // +nan + "%1 = OpTypeFloat 64\n%2 = OpConstant %1 0x1.0fp+1024\n", // +nan + "%1 = OpTypeFloat 64\n%2 = OpConstant %1 0x1.0000000000001p+1024\n", // -nan + "%1 = OpTypeFloat 64\n%2 = OpConstant %1 0x1.00003p+1024\n", // -nan + "%1 = OpTypeFloat 64\n%2 = OpConstant %1 0x1.fffffffffffffp+1024\n", // -nan + }),); +// clang-format on + +INSTANTIATE_TEST_CASE_P( + OpSpecConstantRoundTrip, RoundTripTest, + ::testing::ValuesIn(std::vector{ + // 16 bit + "%1 = OpTypeInt 16 0\n%2 = OpSpecConstant %1 0\n", + "%1 = OpTypeInt 16 0\n%2 = OpSpecConstant %1 65535\n", + "%1 = OpTypeInt 16 1\n%2 = OpSpecConstant %1 -32768\n", + "%1 = OpTypeInt 16 1\n%2 = OpSpecConstant %1 32767\n", + "%1 = OpTypeInt 32 0\n%2 = OpSpecConstant %1 0\n", + // 32 bit + std::string("%1 = OpTypeInt 32 0\n%2 = OpSpecConstant %1 0\n"), + std::string("%1 = OpTypeInt 32 0\n%2 = OpSpecConstant %1 ") + + std::to_string(std::numeric_limits::max()) + "\n", + std::string("%1 = OpTypeInt 32 1\n%2 = OpSpecConstant %1 ") + + std::to_string(std::numeric_limits::max()) + "\n", + std::string("%1 = OpTypeInt 32 1\n%2 = OpSpecConstant %1 ") + + std::to_string(std::numeric_limits::min()) + "\n", + // 48 bit + std::string("%1 = OpTypeInt 48 0\n%2 = OpSpecConstant %1 0\n"), + std::string("%1 = OpTypeInt 48 0\n%2 = OpSpecConstant %1 ") + + std::to_string(kMaxUnsigned48Bit) + "\n", + std::string("%1 = OpTypeInt 48 1\n%2 = OpSpecConstant %1 ") + + std::to_string(kMaxSigned48Bit) + "\n", + std::string("%1 = OpTypeInt 48 1\n%2 = OpSpecConstant %1 ") + + std::to_string(kMinSigned48Bit) + "\n", + // 64 bit + std::string("%1 = OpTypeInt 64 0\n%2 = OpSpecConstant %1 0\n"), + std::string("%1 = OpTypeInt 64 0\n%2 = OpSpecConstant %1 ") + + std::to_string(std::numeric_limits::max()) + "\n", + std::string("%1 = OpTypeInt 64 1\n%2 = OpSpecConstant %1 ") + + std::to_string(std::numeric_limits::max()) + "\n", + std::string("%1 = OpTypeInt 64 1\n%2 = OpSpecConstant %1 ") + + std::to_string(std::numeric_limits::min()) + "\n", + // 32-bit float + "%1 = OpTypeFloat 32\n%2 = OpSpecConstant %1 0\n", + "%1 = OpTypeFloat 32\n%2 = OpSpecConstant %1 13.5\n", + "%1 = OpTypeFloat 32\n%2 = OpSpecConstant %1 -12.5\n", + // 64-bit float + "%1 = OpTypeFloat 64\n%2 = OpSpecConstant %1 0\n", + "%1 = OpTypeFloat 64\n%2 = OpSpecConstant %1 1.79767e+308\n", + "%1 = OpTypeFloat 64\n%2 = OpSpecConstant %1 -1.79767e+308\n", + }), ); + +// Test OpSpecConstantOp + +using OpSpecConstantOpTestWithIds = + spvtest::TextToBinaryTestBase<::testing::TestWithParam>>; + +// The operands to the OpSpecConstantOp opcode are all Ids. +TEST_P(OpSpecConstantOpTestWithIds, Assembly) { + std::stringstream input; + input << "%2 = OpSpecConstantOp %1 " << GetParam().name(); + for (auto id : GetParam().operands()) input << " %" << id; + input << "\n"; + + EXPECT_THAT(CompiledInstructions(input.str()), + Eq(MakeInstruction(SpvOpSpecConstantOp, + {1, 2, uint32_t(GetParam().value())}, + GetParam().operands()))); + + // Check the disassembler as well. + EXPECT_THAT(EncodeAndDecodeSuccessfully(input.str()), input.str()); +} + +// clang-format off +#define CASE1(NAME) { SpvOp##NAME, #NAME, {3} } +#define CASE2(NAME) { SpvOp##NAME, #NAME, {3, 4} } +#define CASE3(NAME) { SpvOp##NAME, #NAME, {3, 4, 5} } +#define CASE4(NAME) { SpvOp##NAME, #NAME, {3, 4, 5, 6} } +#define CASE5(NAME) { SpvOp##NAME, #NAME, {3, 4, 5, 6, 7} } +#define CASE6(NAME) { SpvOp##NAME, #NAME, {3, 4, 5, 6, 7, 8} } +INSTANTIATE_TEST_CASE_P( + TextToBinaryOpSpecConstantOp, OpSpecConstantOpTestWithIds, + ::testing::ValuesIn(std::vector>{ + // Conversion + CASE1(SConvert), + CASE1(FConvert), + CASE1(ConvertFToS), + CASE1(ConvertSToF), + CASE1(ConvertFToU), + CASE1(ConvertUToF), + CASE1(UConvert), + CASE1(ConvertPtrToU), + CASE1(ConvertUToPtr), + CASE1(GenericCastToPtr), + CASE1(PtrCastToGeneric), + CASE1(Bitcast), + CASE1(QuantizeToF16), + // Arithmetic + CASE1(SNegate), + CASE1(Not), + CASE2(IAdd), + CASE2(ISub), + CASE2(IMul), + CASE2(UDiv), + CASE2(SDiv), + CASE2(UMod), + CASE2(SRem), + CASE2(SMod), + CASE2(ShiftRightLogical), + CASE2(ShiftRightArithmetic), + CASE2(ShiftLeftLogical), + CASE2(BitwiseOr), + CASE2(BitwiseAnd), + CASE2(BitwiseXor), + CASE1(FNegate), + CASE2(FAdd), + CASE2(FSub), + CASE2(FMul), + CASE2(FDiv), + CASE2(FRem), + CASE2(FMod), + // Composite operations use literal numbers. So they're in another test. + // Logical + CASE2(LogicalOr), + CASE2(LogicalAnd), + CASE1(LogicalNot), + CASE2(LogicalEqual), + CASE2(LogicalNotEqual), + CASE3(Select), + // Comparison + CASE2(IEqual), + CASE2(INotEqual), // Allowed in 1.0 Rev 7 + CASE2(ULessThan), + CASE2(SLessThan), + CASE2(UGreaterThan), + CASE2(SGreaterThan), + CASE2(ULessThanEqual), + CASE2(SLessThanEqual), + CASE2(UGreaterThanEqual), + CASE2(SGreaterThanEqual), + // Memory + // For AccessChain, there is a base Id, then a sequence of index Ids. + // Having no index Ids is a corner case. + CASE1(AccessChain), + CASE2(AccessChain), + CASE6(AccessChain), + CASE1(InBoundsAccessChain), + CASE2(InBoundsAccessChain), + CASE6(InBoundsAccessChain), + // PtrAccessChain also has an element Id. + CASE2(PtrAccessChain), + CASE3(PtrAccessChain), + CASE6(PtrAccessChain), + CASE2(InBoundsPtrAccessChain), + CASE3(InBoundsPtrAccessChain), + CASE6(InBoundsPtrAccessChain), + }),); +#undef CASE1 +#undef CASE2 +#undef CASE3 +#undef CASE4 +#undef CASE5 +#undef CASE6 +// clang-format on + +using OpSpecConstantOpTestWithTwoIdsThenLiteralNumbers = + spvtest::TextToBinaryTestBase<::testing::TestWithParam>>; + +// The operands to the OpSpecConstantOp opcode are two Ids followed by a +// sequence of literal numbers. +TEST_P(OpSpecConstantOpTestWithTwoIdsThenLiteralNumbers, Assembly) { + std::stringstream input; + input << "%2 = OpSpecConstantOp %1 " << GetParam().name() << " %3 %4"; + for (auto number : GetParam().operands()) input << " " << number; + input << "\n"; + + EXPECT_THAT(CompiledInstructions(input.str()), + Eq(MakeInstruction(SpvOpSpecConstantOp, + {1, 2, uint32_t(GetParam().value()), 3, 4}, + GetParam().operands()))); + + // Check the disassembler as well. + EXPECT_THAT(EncodeAndDecodeSuccessfully(input.str()), input.str()); +} + +#define CASE(NAME) SpvOp##NAME, #NAME +INSTANTIATE_TEST_CASE_P( + TextToBinaryOpSpecConstantOp, + OpSpecConstantOpTestWithTwoIdsThenLiteralNumbers, + ::testing::ValuesIn(std::vector>{ + // For VectorShuffle, there are two vector operands, and at least + // two selector Ids. OpenCL can have up to 16-element vectors. + {CASE(VectorShuffle), {0, 0}}, + {CASE(VectorShuffle), {4, 3, 2, 1}}, + {CASE(VectorShuffle), {0, 2, 4, 6, 1, 3, 5, 7}}, + {CASE(VectorShuffle), + {15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}}, + // For CompositeInsert, there is an object to insert, the target + // composite, and then literal indices. + {CASE(CompositeInsert), {0}}, + {CASE(CompositeInsert), {4, 3, 99, 1}}, + }), ); + +using OpSpecConstantOpTestWithOneIdThenLiteralNumbers = + spvtest::TextToBinaryTestBase<::testing::TestWithParam>>; + +// The operands to the OpSpecConstantOp opcode are one Id followed by a +// sequence of literal numbers. +TEST_P(OpSpecConstantOpTestWithOneIdThenLiteralNumbers, Assembly) { + std::stringstream input; + input << "%2 = OpSpecConstantOp %1 " << GetParam().name() << " %3"; + for (auto number : GetParam().operands()) input << " " << number; + input << "\n"; + + EXPECT_THAT(CompiledInstructions(input.str()), + Eq(MakeInstruction(SpvOpSpecConstantOp, + {1, 2, uint32_t(GetParam().value()), 3}, + GetParam().operands()))); + + // Check the disassembler as well. + EXPECT_THAT(EncodeAndDecodeSuccessfully(input.str()), input.str()); +} + +#define CASE(NAME) SpvOp##NAME, #NAME +INSTANTIATE_TEST_CASE_P( + TextToBinaryOpSpecConstantOp, + OpSpecConstantOpTestWithOneIdThenLiteralNumbers, + ::testing::ValuesIn(std::vector>{ + // For CompositeExtract, the universal limit permits up to 255 literal + // indices. Let's only test a few. + {CASE(CompositeExtract), {0}}, + {CASE(CompositeExtract), {0, 99, 42, 16, 17, 12, 19}}, + }), ); + +// TODO(dneto): OpConstantTrue +// TODO(dneto): OpConstantFalse +// TODO(dneto): OpConstantComposite +// TODO(dneto): OpConstantSampler: other variations Param is 0 or 1 +// TODO(dneto): OpConstantNull +// TODO(dneto): OpSpecConstantTrue +// TODO(dneto): OpSpecConstantFalse +// TODO(dneto): OpSpecConstantComposite +// TODO(dneto): Negative tests for OpSpecConstantOp + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/text_to_binary.control_flow_test.cpp b/3rdparty/spirv-tools/test/text_to_binary.control_flow_test.cpp new file mode 100644 index 000000000..56bb408cf --- /dev/null +++ b/3rdparty/spirv-tools/test/text_to_binary.control_flow_test.cpp @@ -0,0 +1,395 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Assembler tests for instructions in the "Control Flow" section of the +// SPIR-V spec. + +#include +#include +#include + +#include "unit_spirv.h" + +#include "gmock/gmock.h" +#include "test_fixture.h" + +namespace { + +using spvtest::Concatenate; +using spvtest::EnumCase; +using spvtest::MakeInstruction; +using spvtest::TextToBinaryTest; +using std::get; +using std::ostringstream; +using std::tuple; +using ::testing::Combine; +using ::testing::Eq; +using ::testing::TestWithParam; +using ::testing::Values; +using ::testing::ValuesIn; + +// Test OpSelectionMerge + +using OpSelectionMergeTest = spvtest::TextToBinaryTestBase< + TestWithParam>>; + +TEST_P(OpSelectionMergeTest, AnySingleSelectionControlMask) { + const std::string input = "OpSelectionMerge %1 " + GetParam().name(); + EXPECT_THAT( + CompiledInstructions(input), + Eq(MakeInstruction(SpvOpSelectionMerge, {1, GetParam().value()}))); +} + +// clang-format off +#define CASE(VALUE,NAME) { SpvSelectionControl##VALUE, NAME} +INSTANTIATE_TEST_CASE_P(TextToBinarySelectionMerge, OpSelectionMergeTest, + ValuesIn(std::vector>{ + CASE(MaskNone, "None"), + CASE(FlattenMask, "Flatten"), + CASE(DontFlattenMask, "DontFlatten"), + }),); +#undef CASE +// clang-format on + +TEST_F(OpSelectionMergeTest, CombinedSelectionControlMask) { + const std::string input = "OpSelectionMerge %1 Flatten|DontFlatten"; + const uint32_t expected_mask = + SpvSelectionControlFlattenMask | SpvSelectionControlDontFlattenMask; + EXPECT_THAT(CompiledInstructions(input), + Eq(MakeInstruction(SpvOpSelectionMerge, {1, expected_mask}))); +} + +TEST_F(OpSelectionMergeTest, WrongSelectionControl) { + // Case sensitive: "flatten" != "Flatten" and thus wrong. + EXPECT_THAT(CompileFailure("OpSelectionMerge %1 flatten|DontFlatten"), + Eq("Invalid selection control operand 'flatten|DontFlatten'.")); +} + +// Test OpLoopMerge + +using OpLoopMergeTest = spvtest::TextToBinaryTestBase< + TestWithParam>>>; + +TEST_P(OpLoopMergeTest, AnySingleLoopControlMask) { + const auto ctrl = get<1>(GetParam()); + ostringstream input; + input << "OpLoopMerge %merge %continue " << ctrl.name(); + for (auto num : ctrl.operands()) input << " " << num; + EXPECT_THAT(CompiledInstructions(input.str(), get<0>(GetParam())), + Eq(MakeInstruction(SpvOpLoopMerge, {1, 2, ctrl.value()}, + ctrl.operands()))); +} + +#define CASE(VALUE, NAME) \ + { SpvLoopControl##VALUE, NAME } +#define CASE1(VALUE, NAME, PARM) \ + { \ + SpvLoopControl##VALUE, NAME, { PARM } \ + } +INSTANTIATE_TEST_CASE_P( + TextToBinaryLoopMerge, OpLoopMergeTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector>{ + // clang-format off + CASE(MaskNone, "None"), + CASE(UnrollMask, "Unroll"), + CASE(DontUnrollMask, "DontUnroll"), + // clang-format on + })), ); + +INSTANTIATE_TEST_CASE_P( + TextToBinaryLoopMergeV11, OpLoopMergeTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector>{ + // clang-format off + CASE(DependencyInfiniteMask, "DependencyInfinite"), + CASE1(DependencyLengthMask, "DependencyLength", 234), + {SpvLoopControlUnrollMask|SpvLoopControlDependencyLengthMask, + "DependencyLength|Unroll", {33}}, + // clang-format on + })), ); +#undef CASE +#undef CASE1 + +TEST_F(OpLoopMergeTest, CombinedLoopControlMask) { + const std::string input = "OpLoopMerge %merge %continue Unroll|DontUnroll"; + const uint32_t expected_mask = + SpvLoopControlUnrollMask | SpvLoopControlDontUnrollMask; + EXPECT_THAT(CompiledInstructions(input), + Eq(MakeInstruction(SpvOpLoopMerge, {1, 2, expected_mask}))); +} + +TEST_F(OpLoopMergeTest, WrongLoopControl) { + EXPECT_THAT(CompileFailure("OpLoopMerge %m %c none"), + Eq("Invalid loop control operand 'none'.")); +} + +// Test OpSwitch + +TEST_F(TextToBinaryTest, SwitchGoodZeroTargets) { + EXPECT_THAT(CompiledInstructions("OpSwitch %selector %default"), + Eq(MakeInstruction(SpvOpSwitch, {1, 2}))); +} + +TEST_F(TextToBinaryTest, SwitchGoodOneTarget) { + EXPECT_THAT(CompiledInstructions("%1 = OpTypeInt 32 0\n" + "%2 = OpConstant %1 52\n" + "OpSwitch %2 %default 12 %target0"), + Eq(Concatenate({MakeInstruction(SpvOpTypeInt, {1, 32, 0}), + MakeInstruction(SpvOpConstant, {1, 2, 52}), + MakeInstruction(SpvOpSwitch, {2, 3, 12, 4})}))); +} + +TEST_F(TextToBinaryTest, SwitchGoodTwoTargets) { + EXPECT_THAT( + CompiledInstructions("%1 = OpTypeInt 32 0\n" + "%2 = OpConstant %1 52\n" + "OpSwitch %2 %default 12 %target0 42 %target1"), + Eq(Concatenate({ + MakeInstruction(SpvOpTypeInt, {1, 32, 0}), + MakeInstruction(SpvOpConstant, {1, 2, 52}), + MakeInstruction(SpvOpSwitch, {2, 3, 12, 4, 42, 5}), + }))); +} + +TEST_F(TextToBinaryTest, SwitchBadMissingSelector) { + EXPECT_THAT(CompileFailure("OpSwitch"), + Eq("Expected operand, found end of stream.")); +} + +TEST_F(TextToBinaryTest, SwitchBadInvalidSelector) { + EXPECT_THAT(CompileFailure("OpSwitch 12"), + Eq("Expected id to start with %.")); +} + +TEST_F(TextToBinaryTest, SwitchBadMissingDefault) { + EXPECT_THAT(CompileFailure("OpSwitch %selector"), + Eq("Expected operand, found end of stream.")); +} + +TEST_F(TextToBinaryTest, SwitchBadInvalidDefault) { + EXPECT_THAT(CompileFailure("OpSwitch %selector 12"), + Eq("Expected id to start with %.")); +} + +TEST_F(TextToBinaryTest, SwitchBadInvalidLiteral) { + // The assembler recognizes "OpSwitch %selector %default" as a complete + // instruction. Then it tries to parse "%abc" as the start of a new + // instruction, but can't since it hits the end of stream. + const auto input = R"(%i32 = OpTypeInt 32 0 + %selector = OpConstant %i32 42 + OpSwitch %selector %default %abc)"; + EXPECT_THAT(CompileFailure(input), Eq("Expected '=', found end of stream.")); +} + +TEST_F(TextToBinaryTest, SwitchBadMissingTarget) { + EXPECT_THAT(CompileFailure("%1 = OpTypeInt 32 0\n" + "%2 = OpConstant %1 52\n" + "OpSwitch %2 %default 12"), + Eq("Expected operand, found end of stream.")); +} + +// A test case for an OpSwitch. +// It is also parameterized to test encodings OpConstant +// integer literals. This can capture both single and multi-word +// integer literal tests. +struct SwitchTestCase { + std::string constant_type_args; + std::string constant_value_arg; + std::string case_value_arg; + std::vector expected_instructions; +}; + +using OpSwitchValidTest = + spvtest::TextToBinaryTestBase>; + +// Tests the encoding of OpConstant literal values, and also +// the literal integer cases in an OpSwitch. This can +// test both single and multi-word integer literal encodings. +TEST_P(OpSwitchValidTest, ValidTypes) { + const std::string input = "%1 = OpTypeInt " + GetParam().constant_type_args + + "\n" + "%2 = OpConstant %1 " + + GetParam().constant_value_arg + + "\n" + "OpSwitch %2 %default " + + GetParam().case_value_arg + " %4\n"; + std::vector instructions; + EXPECT_THAT(CompiledInstructions(input), + Eq(GetParam().expected_instructions)); +} + +// Constructs a SwitchTestCase from the given integer_width, signedness, +// constant value string, and expected encoded constant. +SwitchTestCase MakeSwitchTestCase(uint32_t integer_width, + uint32_t integer_signedness, + std::string constant_str, + std::vector encoded_constant, + std::string case_value_str, + std::vector encoded_case_value) { + std::stringstream ss; + ss << integer_width << " " << integer_signedness; + return SwitchTestCase{ + ss.str(), + constant_str, + case_value_str, + {Concatenate( + {MakeInstruction(SpvOpTypeInt, + {1, integer_width, integer_signedness}), + MakeInstruction(SpvOpConstant, + Concatenate({{1, 2}, encoded_constant})), + MakeInstruction(SpvOpSwitch, + Concatenate({{2, 3}, encoded_case_value, {4}}))})}}; +} + +INSTANTIATE_TEST_CASE_P( + TextToBinaryOpSwitchValid1Word, OpSwitchValidTest, + ValuesIn(std::vector({ + MakeSwitchTestCase(32, 0, "42", {42}, "100", {100}), + MakeSwitchTestCase(32, 1, "-1", {0xffffffff}, "100", {100}), + // SPIR-V 1.0 Rev 1 clarified that for an integer narrower than 32-bits, + // its bits will appear in the lower order bits of the 32-bit word, and + // a signed integer is sign-extended. + MakeSwitchTestCase(7, 0, "127", {127}, "100", {100}), + MakeSwitchTestCase(14, 0, "99", {99}, "100", {100}), + MakeSwitchTestCase(16, 0, "65535", {65535}, "100", {100}), + MakeSwitchTestCase(16, 1, "101", {101}, "100", {100}), + // Demonstrate sign extension + MakeSwitchTestCase(16, 1, "-2", {0xfffffffe}, "100", {100}), + // Hex cases + MakeSwitchTestCase(16, 1, "0x7ffe", {0x7ffe}, "0x1234", {0x1234}), + MakeSwitchTestCase(16, 1, "0x8000", {0xffff8000}, "0x8100", + {0xffff8100}), + MakeSwitchTestCase(16, 0, "0x8000", {0x00008000}, "0x8100", {0x8100}), + })), ); + +// NB: The words LOW ORDER bits show up first. +INSTANTIATE_TEST_CASE_P( + TextToBinaryOpSwitchValid2Words, OpSwitchValidTest, + ValuesIn(std::vector({ + MakeSwitchTestCase(33, 0, "101", {101, 0}, "500", {500, 0}), + MakeSwitchTestCase(48, 1, "-1", {0xffffffff, 0xffffffff}, "900", + {900, 0}), + MakeSwitchTestCase(64, 1, "-2", {0xfffffffe, 0xffffffff}, "-5", + {0xfffffffb, uint32_t(-1)}), + // Hex cases + MakeSwitchTestCase(48, 1, "0x7fffffffffff", {0xffffffff, 0x00007fff}, + "100", {100, 0}), + MakeSwitchTestCase(48, 1, "0x800000000000", {0x00000000, 0xffff8000}, + "0x800000000000", {0x00000000, 0xffff8000}), + MakeSwitchTestCase(48, 0, "0x800000000000", {0x00000000, 0x00008000}, + "0x800000000000", {0x00000000, 0x00008000}), + MakeSwitchTestCase(63, 0, "0x500000000", {0, 5}, "12", {12, 0}), + MakeSwitchTestCase(64, 0, "0x600000000", {0, 6}, "12", {12, 0}), + MakeSwitchTestCase(64, 1, "0x700000123", {0x123, 7}, "12", {12, 0}), + })), ); + +INSTANTIATE_TEST_CASE_P( + OpSwitchRoundTripUnsignedIntegers, RoundTripTest, + ValuesIn(std::vector({ + // Unsigned 16-bit. + "%1 = OpTypeInt 16 0\n%2 = OpConstant %1 65535\nOpSwitch %2 %3\n", + // Unsigned 32-bit, three non-default cases. + "%1 = OpTypeInt 32 0\n%2 = OpConstant %1 123456\n" + "OpSwitch %2 %3 100 %4 102 %5 1000000 %6\n", + // Unsigned 48-bit, three non-default cases. + "%1 = OpTypeInt 48 0\n%2 = OpConstant %1 5000000000\n" + "OpSwitch %2 %3 100 %4 102 %5 6000000000 %6\n", + // Unsigned 64-bit, three non-default cases. + "%1 = OpTypeInt 64 0\n%2 = OpConstant %1 9223372036854775807\n" + "OpSwitch %2 %3 100 %4 102 %5 9000000000000000000 %6\n", + })), ); + +INSTANTIATE_TEST_CASE_P( + OpSwitchRoundTripSignedIntegers, RoundTripTest, + ValuesIn(std::vector{ + // Signed 16-bit, with two non-default cases + "%1 = OpTypeInt 16 1\n%2 = OpConstant %1 32767\n" + "OpSwitch %2 %3 99 %4 -102 %5\n", + "%1 = OpTypeInt 16 1\n%2 = OpConstant %1 -32768\n" + "OpSwitch %2 %3 99 %4 -102 %5\n", + // Signed 32-bit, two non-default cases. + "%1 = OpTypeInt 32 1\n%2 = OpConstant %1 -123456\n" + "OpSwitch %2 %3 100 %4 -123456 %5\n", + "%1 = OpTypeInt 32 1\n%2 = OpConstant %1 123456\n" + "OpSwitch %2 %3 100 %4 123456 %5\n", + // Signed 48-bit, three non-default cases. + "%1 = OpTypeInt 48 1\n%2 = OpConstant %1 5000000000\n" + "OpSwitch %2 %3 100 %4 -7000000000 %5 6000000000 %6\n", + "%1 = OpTypeInt 48 1\n%2 = OpConstant %1 -5000000000\n" + "OpSwitch %2 %3 100 %4 -7000000000 %5 6000000000 %6\n", + // Signed 64-bit, three non-default cases. + "%1 = OpTypeInt 64 1\n%2 = OpConstant %1 9223372036854775807\n" + "OpSwitch %2 %3 100 %4 7000000000 %5 -1000000000000000000 %6\n", + "%1 = OpTypeInt 64 1\n%2 = OpConstant %1 -9223372036854775808\n" + "OpSwitch %2 %3 100 %4 7000000000 %5 -1000000000000000000 %6\n", + }), ); + +using OpSwitchInvalidTypeTestCase = + spvtest::TextToBinaryTestBase>; + +TEST_P(OpSwitchInvalidTypeTestCase, InvalidTypes) { + const std::string input = + "%1 = " + GetParam() + + "\n" + "%3 = OpCopyObject %1 %2\n" // We only care the type of the expression + "%4 = OpSwitch %3 %default 32 %c\n"; + EXPECT_THAT(CompileFailure(input), + Eq("The selector operand for OpSwitch must be the result of an " + "instruction that generates an integer scalar")); +} + +// clang-format off +INSTANTIATE_TEST_CASE_P( + TextToBinaryOpSwitchInvalidTests, OpSwitchInvalidTypeTestCase, + ValuesIn(std::vector{ + {"OpTypeVoid", + "OpTypeBool", + "OpTypeFloat 32", + "OpTypeVector %a 32", + "OpTypeMatrix %a 32", + "OpTypeImage %a 1D 0 0 0 0 Unknown", + "OpTypeSampler", + "OpTypeSampledImage %a", + "OpTypeArray %a %b", + "OpTypeRuntimeArray %a", + "OpTypeStruct %a", + "OpTypeOpaque \"Foo\"", + "OpTypePointer UniformConstant %a", + "OpTypeFunction %a %b", + "OpTypeEvent", + "OpTypeDeviceEvent", + "OpTypeReserveId", + "OpTypeQueue", + "OpTypePipe ReadOnly", + "OpTypeForwardPointer %a UniformConstant", + // At least one thing that isn't a type at all + "OpNot %a %b" + }, + }),); +// clang-format on + +// TODO(dneto): OpPhi +// TODO(dneto): OpLoopMerge +// TODO(dneto): OpLabel +// TODO(dneto): OpBranch +// TODO(dneto): OpSwitch +// TODO(dneto): OpKill +// TODO(dneto): OpReturn +// TODO(dneto): OpReturnValue +// TODO(dneto): OpUnreachable +// TODO(dneto): OpLifetimeStart +// TODO(dneto): OpLifetimeStop + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/text_to_binary.debug_test.cpp b/3rdparty/spirv-tools/test/text_to_binary.debug_test.cpp new file mode 100644 index 000000000..93b71eeea --- /dev/null +++ b/3rdparty/spirv-tools/test/text_to_binary.debug_test.cpp @@ -0,0 +1,211 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Assembler tests for instructions in the "Debug" section of the +// SPIR-V spec. + +#include "unit_spirv.h" + +#include + +#include "gmock/gmock.h" +#include "test_fixture.h" + +namespace { + +using spvtest::MakeInstruction; +using spvtest::MakeVector; +using spvtest::TextToBinaryTest; +using ::testing::Eq; + +// Test OpSource + +// A single test case for OpSource +struct LanguageCase { + uint32_t get_language_value() const { + return static_cast(language_value); + } + const char* language_name; + SpvSourceLanguage language_value; + uint32_t version; +}; + +// clang-format off +// The list of OpSource cases to use. +const LanguageCase kLanguageCases[] = { +#define CASE(NAME, VERSION) \ + { #NAME, SpvSourceLanguage##NAME, VERSION } + CASE(Unknown, 0), + CASE(Unknown, 999), + CASE(ESSL, 310), + CASE(GLSL, 450), + CASE(OpenCL_C, 120), + CASE(OpenCL_C, 200), + CASE(OpenCL_C, 210), + CASE(OpenCL_CPP, 210), + CASE(HLSL, 5), + CASE(HLSL, 6), +#undef CASE +}; +// clang-format on + +using OpSourceTest = + spvtest::TextToBinaryTestBase<::testing::TestWithParam>; + +TEST_P(OpSourceTest, AnyLanguage) { + const std::string input = std::string("OpSource ") + + GetParam().language_name + " " + + std::to_string(GetParam().version); + EXPECT_THAT(CompiledInstructions(input), + Eq(MakeInstruction(SpvOpSource, {GetParam().get_language_value(), + GetParam().version}))); +} + +INSTANTIATE_TEST_CASE_P(TextToBinaryTestDebug, OpSourceTest, + ::testing::ValuesIn(kLanguageCases), ); + +TEST_F(OpSourceTest, WrongLanguage) { + EXPECT_THAT(CompileFailure("OpSource xxyyzz 12345"), + Eq("Invalid source language 'xxyyzz'.")); +} + +TEST_F(TextToBinaryTest, OpSourceAcceptsOptionalFileId) { + // In the grammar, the file id is an OperandOptionalId. + const std::string input = "OpSource GLSL 450 %file_id"; + EXPECT_THAT( + CompiledInstructions(input), + Eq(MakeInstruction(SpvOpSource, {SpvSourceLanguageGLSL, 450, 1}))); +} + +TEST_F(TextToBinaryTest, OpSourceAcceptsOptionalSourceText) { + std::string fake_source = "To be or not to be"; + const std::string input = + "OpSource GLSL 450 %file_id \"" + fake_source + "\""; + EXPECT_THAT(CompiledInstructions(input), + Eq(MakeInstruction(SpvOpSource, {SpvSourceLanguageGLSL, 450, 1}, + MakeVector(fake_source)))); +} + +// Test OpSourceContinued + +using OpSourceContinuedTest = + spvtest::TextToBinaryTestBase<::testing::TestWithParam>; + +TEST_P(OpSourceContinuedTest, AnyExtension) { + // TODO(dneto): utf-8, quoting, escaping + const std::string input = + std::string("OpSourceContinued \"") + GetParam() + "\""; + EXPECT_THAT( + CompiledInstructions(input), + Eq(MakeInstruction(SpvOpSourceContinued, MakeVector(GetParam())))); +} + +// TODO(dneto): utf-8, quoting, escaping +INSTANTIATE_TEST_CASE_P(TextToBinaryTestDebug, OpSourceContinuedTest, + ::testing::ValuesIn(std::vector{ + "", "foo bar this and that"}), ); + +// Test OpSourceExtension + +using OpSourceExtensionTest = + spvtest::TextToBinaryTestBase<::testing::TestWithParam>; + +TEST_P(OpSourceExtensionTest, AnyExtension) { + // TODO(dneto): utf-8, quoting, escaping + const std::string input = + std::string("OpSourceExtension \"") + GetParam() + "\""; + EXPECT_THAT( + CompiledInstructions(input), + Eq(MakeInstruction(SpvOpSourceExtension, MakeVector(GetParam())))); +} + +// TODO(dneto): utf-8, quoting, escaping +INSTANTIATE_TEST_CASE_P(TextToBinaryTestDebug, OpSourceExtensionTest, + ::testing::ValuesIn(std::vector{ + "", "foo bar this and that"}), ); + +TEST_F(TextToBinaryTest, OpLine) { + EXPECT_THAT(CompiledInstructions("OpLine %srcfile 42 99"), + Eq(MakeInstruction(SpvOpLine, {1, 42, 99}))); +} + +TEST_F(TextToBinaryTest, OpNoLine) { + EXPECT_THAT(CompiledInstructions("OpNoLine"), + Eq(MakeInstruction(SpvOpNoLine, {}))); +} + +using OpStringTest = + spvtest::TextToBinaryTestBase<::testing::TestWithParam>; + +TEST_P(OpStringTest, AnyString) { + // TODO(dneto): utf-8, quoting, escaping + const std::string input = + std::string("%result = OpString \"") + GetParam() + "\""; + EXPECT_THAT(CompiledInstructions(input), + Eq(MakeInstruction(SpvOpString, {1}, MakeVector(GetParam())))); +} + +// TODO(dneto): utf-8, quoting, escaping +INSTANTIATE_TEST_CASE_P(TextToBinaryTestDebug, OpStringTest, + ::testing::ValuesIn(std::vector{ + "", "foo bar this and that"}), ); + +using OpNameTest = + spvtest::TextToBinaryTestBase<::testing::TestWithParam>; + +TEST_P(OpNameTest, AnyString) { + const std::string input = + std::string("OpName %target \"") + GetParam() + "\""; + EXPECT_THAT(CompiledInstructions(input), + Eq(MakeInstruction(SpvOpName, {1}, MakeVector(GetParam())))); +} + +// UTF-8, quoting, escaping, etc. are covered in the StringLiterals tests in +// BinaryToText.Literal.cpp. +INSTANTIATE_TEST_CASE_P(TextToBinaryTestDebug, OpNameTest, + ::testing::Values("", "foo bar this and that"), ); + +using OpMemberNameTest = + spvtest::TextToBinaryTestBase<::testing::TestWithParam>; + +TEST_P(OpMemberNameTest, AnyString) { + // TODO(dneto): utf-8, quoting, escaping + const std::string input = + std::string("OpMemberName %type 42 \"") + GetParam() + "\""; + EXPECT_THAT( + CompiledInstructions(input), + Eq(MakeInstruction(SpvOpMemberName, {1, 42}, MakeVector(GetParam())))); +} + +// TODO(dneto): utf-8, quoting, escaping +INSTANTIATE_TEST_CASE_P(TextToBinaryTestDebug, OpMemberNameTest, + ::testing::ValuesIn(std::vector{ + "", "foo bar this and that"}), ); + +// TODO(dneto): Parse failures? + +using OpModuleProcessedTest = + spvtest::TextToBinaryTestBase<::testing::TestWithParam>; + +TEST_P(OpModuleProcessedTest, AnyString) { + const std::string input = + std::string("OpModuleProcessed \"") + GetParam() + "\""; + EXPECT_THAT( + CompiledInstructions(input, SPV_ENV_UNIVERSAL_1_1), + Eq(MakeInstruction(SpvOpModuleProcessed, MakeVector(GetParam())))); +} + +INSTANTIATE_TEST_CASE_P(TextToBinaryTestDebug, OpModuleProcessedTest, + ::testing::Values("", "foo bar this and that"), ); +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/text_to_binary.device_side_enqueue_test.cpp b/3rdparty/spirv-tools/test/text_to_binary.device_side_enqueue_test.cpp new file mode 100644 index 000000000..782961194 --- /dev/null +++ b/3rdparty/spirv-tools/test/text_to_binary.device_side_enqueue_test.cpp @@ -0,0 +1,107 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Assembler tests for instructions in the "Device-Side Enqueue Instructions" +// section of the SPIR-V spec. + +#include "unit_spirv.h" + +#include "gmock/gmock.h" +#include "test_fixture.h" + +namespace { +using spvtest::MakeInstruction; +using ::testing::Eq; + +// Test OpEnqueueKernel + +struct KernelEnqueueCase { + std::string local_size_source; + std::vector local_size_operands; +}; + +using OpEnqueueKernelGood = + spvtest::TextToBinaryTestBase<::testing::TestWithParam>; + +TEST_P(OpEnqueueKernelGood, Sample) { + const std::string input = + "%result = OpEnqueueKernel %type %queue %flags %NDRange %num_events" + " %wait_events %ret_event %invoke %param %param_size %param_align " + + GetParam().local_size_source; + EXPECT_THAT(CompiledInstructions(input), + Eq(MakeInstruction(SpvOpEnqueueKernel, + {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, + GetParam().local_size_operands))); +} + +INSTANTIATE_TEST_CASE_P( + TextToBinaryTest, OpEnqueueKernelGood, + ::testing::ValuesIn(std::vector{ + // Provide IDs for pointer-to-local arguments for the + // invoked function. + // Test up to 10 such arguments. + // I (dneto) can't find a limit on the number of kernel + // arguments in OpenCL C 2.0 Rev 29, e.g. in section 6.9 + // Restrictions. + {"", {}}, + {"%l0", {13}}, + {"%l0 %l1", {13, 14}}, + {"%l0 %l1 %l2", {13, 14, 15}}, + {"%l0 %l1 %l2 %l3", {13, 14, 15, 16}}, + {"%l0 %l1 %l2 %l3 %l4", {13, 14, 15, 16, 17}}, + {"%l0 %l1 %l2 %l3 %l4 %l5", {13, 14, 15, 16, 17, 18}}, + {"%l0 %l1 %l2 %l3 %l4 %l5 %l6", {13, 14, 15, 16, 17, 18, 19}}, + {"%l0 %l1 %l2 %l3 %l4 %l5 %l6 %l7", {13, 14, 15, 16, 17, 18, 19, 20}}, + {"%l0 %l1 %l2 %l3 %l4 %l5 %l6 %l7 %l8", + {13, 14, 15, 16, 17, 18, 19, 20, 21}}, + {"%l0 %l1 %l2 %l3 %l4 %l5 %l6 %l7 %l8 %l9", + {13, 14, 15, 16, 17, 18, 19, 20, 21, 22}}, + }), ); + +// Test some bad parses of OpEnqueueKernel. For other cases, we're relying +// on the uniformity of the parsing algorithm. The following two tests, ensure +// that every required ID operand is specified, and is actually an ID operand. +using OpKernelEnqueueBad = spvtest::TextToBinaryTest; + +TEST_F(OpKernelEnqueueBad, MissingLastOperand) { + EXPECT_THAT( + CompileFailure( + "%result = OpEnqueueKernel %type %queue %flags %NDRange %num_events" + " %wait_events %ret_event %invoke %param %param_size"), + Eq("Expected operand, found end of stream.")); +} + +TEST_F(OpKernelEnqueueBad, InvalidLastOperand) { + EXPECT_THAT( + CompileFailure( + "%result = OpEnqueueKernel %type %queue %flags %NDRange %num_events" + " %wait_events %ret_event %invoke %param %param_size 42"), + Eq("Expected id to start with %.")); +} + +// TODO(dneto): OpEnqueueMarker +// TODO(dneto): OpGetKernelNDRangeSubGroupCount +// TODO(dneto): OpGetKernelNDRangeMaxSubGroupSize +// TODO(dneto): OpGetKernelWorkGroupSize +// TODO(dneto): OpGetKernelPreferredWorkGroupSizeMultiple +// TODO(dneto): OpRetainEvent +// TODO(dneto): OpReleaseEvent +// TODO(dneto): OpCreateUserEvent +// TODO(dneto): OpSetUserEventStatus +// TODO(dneto): OpCaptureEventProfilingInfo +// TODO(dneto): OpGetDefaultQueue +// TODO(dneto): OpBuildNDRange +// TODO(dneto): OpBuildNDRange + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/text_to_binary.extension_test.cpp b/3rdparty/spirv-tools/test/text_to_binary.extension_test.cpp new file mode 100644 index 000000000..690075f2b --- /dev/null +++ b/3rdparty/spirv-tools/test/text_to_binary.extension_test.cpp @@ -0,0 +1,660 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Assembler tests for instructions in the "Extension Instruction" section +// of the SPIR-V spec. + +#include "unit_spirv.h" + +#include "gmock/gmock.h" +#include "latest_version_glsl_std_450_header.h" +#include "latest_version_opencl_std_header.h" +#include "test_fixture.h" + +namespace { + +using spvtest::Concatenate; +using spvtest::MakeInstruction; +using spvtest::MakeVector; +using spvtest::TextToBinaryTest; +using ::testing::Combine; +using ::testing::Eq; +using ::testing::Values; +using ::testing::ValuesIn; + +// Returns a generator of common Vulkan environment values to be tested. +std::vector CommonVulkanEnvs() { + return {SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, SPV_ENV_UNIVERSAL_1_2, + SPV_ENV_UNIVERSAL_1_3, SPV_ENV_VULKAN_1_0, SPV_ENV_VULKAN_1_1}; +} + +TEST_F(TextToBinaryTest, InvalidExtInstImportName) { + EXPECT_THAT(CompileFailure("%1 = OpExtInstImport \"Haskell.std\""), + Eq("Invalid extended instruction import 'Haskell.std'")); +} + +TEST_F(TextToBinaryTest, InvalidImportId) { + EXPECT_THAT(CompileFailure("%1 = OpTypeVoid\n" + "%2 = OpExtInst %1 %1"), + Eq("Invalid extended instruction import Id 2")); +} + +TEST_F(TextToBinaryTest, InvalidImportInstruction) { + const std::string input = R"(%1 = OpTypeVoid + %2 = OpExtInstImport "OpenCL.std" + %3 = OpExtInst %1 %2 not_in_the_opencl)"; + EXPECT_THAT(CompileFailure(input), + Eq("Invalid extended instruction name 'not_in_the_opencl'.")); +} + +TEST_F(TextToBinaryTest, MultiImport) { + const std::string input = R"(%2 = OpExtInstImport "OpenCL.std" + %2 = OpExtInstImport "OpenCL.std")"; + EXPECT_THAT(CompileFailure(input), + Eq("Import Id is being defined a second time")); +} + +TEST_F(TextToBinaryTest, TooManyArguments) { + const std::string input = R"(%opencl = OpExtInstImport "OpenCL.std" + %2 = OpExtInst %float %opencl cos %x %oops")"; + EXPECT_THAT(CompileFailure(input), Eq("Expected '=', found end of stream.")); +} + +TEST_F(TextToBinaryTest, ExtInstFromTwoDifferentImports) { + const std::string input = R"(%1 = OpExtInstImport "OpenCL.std" +%2 = OpExtInstImport "GLSL.std.450" +%4 = OpExtInst %3 %1 native_sqrt %5 +%7 = OpExtInst %6 %2 MatrixInverse %8 +)"; + + // Make sure it assembles correctly. + EXPECT_THAT( + CompiledInstructions(input), + Eq(Concatenate({ + MakeInstruction(SpvOpExtInstImport, {1}, MakeVector("OpenCL.std")), + MakeInstruction(SpvOpExtInstImport, {2}, MakeVector("GLSL.std.450")), + MakeInstruction( + SpvOpExtInst, + {3, 4, 1, uint32_t(OpenCLLIB::Entrypoints::Native_sqrt), 5}), + MakeInstruction(SpvOpExtInst, + {6, 7, 2, uint32_t(GLSLstd450MatrixInverse), 8}), + }))); + + // Make sure it disassembles correctly. + EXPECT_THAT(EncodeAndDecodeSuccessfully(input), Eq(input)); +} + +// A test case for assembling into words in an instruction. +struct AssemblyCase { + std::string input; + std::vector expected; +}; + +using ExtensionAssemblyTest = spvtest::TextToBinaryTestBase< + ::testing::TestWithParam>>; + +TEST_P(ExtensionAssemblyTest, Samples) { + const spv_target_env& env = std::get<0>(GetParam()); + const AssemblyCase& ac = std::get<1>(GetParam()); + + // Check that it assembles correctly. + EXPECT_THAT(CompiledInstructions(ac.input, env), Eq(ac.expected)); +} + +using ExtensionRoundTripTest = spvtest::TextToBinaryTestBase< + ::testing::TestWithParam>>; + +TEST_P(ExtensionRoundTripTest, Samples) { + const spv_target_env& env = std::get<0>(GetParam()); + const AssemblyCase& ac = std::get<1>(GetParam()); + + // Check that it assembles correctly. + EXPECT_THAT(CompiledInstructions(ac.input, env), Eq(ac.expected)); + + // Check round trip through the disassembler. + EXPECT_THAT(EncodeAndDecodeSuccessfully(ac.input, + SPV_BINARY_TO_TEXT_OPTION_NONE, env), + Eq(ac.input)) + << "target env: " << spvTargetEnvDescription(env) << "\n"; +} + +// SPV_KHR_shader_ballot + +INSTANTIATE_TEST_CASE_P( + SPV_KHR_shader_ballot, ExtensionRoundTripTest, + // We'll get coverage over operand tables by trying the universal + // environments, and at least one specific environment. + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, + SPV_ENV_VULKAN_1_0), + ValuesIn(std::vector{ + {"OpCapability SubgroupBallotKHR\n", + MakeInstruction(SpvOpCapability, + {SpvCapabilitySubgroupBallotKHR})}, + {"%2 = OpSubgroupBallotKHR %1 %3\n", + MakeInstruction(SpvOpSubgroupBallotKHR, {1, 2, 3})}, + {"%2 = OpSubgroupFirstInvocationKHR %1 %3\n", + MakeInstruction(SpvOpSubgroupFirstInvocationKHR, {1, 2, 3})}, + {"OpDecorate %1 BuiltIn SubgroupEqMaskKHR\n", + MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn, + SpvBuiltInSubgroupEqMaskKHR})}, + {"OpDecorate %1 BuiltIn SubgroupGeMaskKHR\n", + MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn, + SpvBuiltInSubgroupGeMaskKHR})}, + {"OpDecorate %1 BuiltIn SubgroupGtMaskKHR\n", + MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn, + SpvBuiltInSubgroupGtMaskKHR})}, + {"OpDecorate %1 BuiltIn SubgroupLeMaskKHR\n", + MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn, + SpvBuiltInSubgroupLeMaskKHR})}, + {"OpDecorate %1 BuiltIn SubgroupLtMaskKHR\n", + MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn, + SpvBuiltInSubgroupLtMaskKHR})}, + })), ); + +INSTANTIATE_TEST_CASE_P( + SPV_KHR_shader_ballot_vulkan_1_1, ExtensionRoundTripTest, + // In SPIR-V 1.3 and Vulkan 1.1 we can drop the KHR suffix on the + // builtin enums. + Combine(Values(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_VULKAN_1_1), + ValuesIn(std::vector{ + {"OpCapability SubgroupBallotKHR\n", + MakeInstruction(SpvOpCapability, + {SpvCapabilitySubgroupBallotKHR})}, + {"%2 = OpSubgroupBallotKHR %1 %3\n", + MakeInstruction(SpvOpSubgroupBallotKHR, {1, 2, 3})}, + {"%2 = OpSubgroupFirstInvocationKHR %1 %3\n", + MakeInstruction(SpvOpSubgroupFirstInvocationKHR, {1, 2, 3})}, + {"OpDecorate %1 BuiltIn SubgroupEqMask\n", + MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn, + SpvBuiltInSubgroupEqMask})}, + {"OpDecorate %1 BuiltIn SubgroupGeMask\n", + MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn, + SpvBuiltInSubgroupGeMask})}, + {"OpDecorate %1 BuiltIn SubgroupGtMask\n", + MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn, + SpvBuiltInSubgroupGtMask})}, + {"OpDecorate %1 BuiltIn SubgroupLeMask\n", + MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn, + SpvBuiltInSubgroupLeMask})}, + {"OpDecorate %1 BuiltIn SubgroupLtMask\n", + MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn, + SpvBuiltInSubgroupLtMask})}, + })), ); + +// The old builtin names (with KHR suffix) still work in the assmebler, and +// map to the enums without the KHR. +INSTANTIATE_TEST_CASE_P( + SPV_KHR_shader_ballot_vulkan_1_1_alias_check, ExtensionAssemblyTest, + // In SPIR-V 1.3 and Vulkan 1.1 we can drop the KHR suffix on the + // builtin enums. + Combine(Values(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_VULKAN_1_1), + ValuesIn(std::vector{ + {"OpDecorate %1 BuiltIn SubgroupEqMaskKHR\n", + MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn, + SpvBuiltInSubgroupEqMask})}, + {"OpDecorate %1 BuiltIn SubgroupGeMaskKHR\n", + MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn, + SpvBuiltInSubgroupGeMask})}, + {"OpDecorate %1 BuiltIn SubgroupGtMaskKHR\n", + MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn, + SpvBuiltInSubgroupGtMask})}, + {"OpDecorate %1 BuiltIn SubgroupLeMaskKHR\n", + MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn, + SpvBuiltInSubgroupLeMask})}, + {"OpDecorate %1 BuiltIn SubgroupLtMaskKHR\n", + MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn, + SpvBuiltInSubgroupLtMask})}, + })), ); + +// SPV_KHR_shader_draw_parameters + +INSTANTIATE_TEST_CASE_P( + SPV_KHR_shader_draw_parameters, ExtensionRoundTripTest, + // We'll get coverage over operand tables by trying the universal + // environments, and at least one specific environment. + Combine( + ValuesIn(CommonVulkanEnvs()), + ValuesIn(std::vector{ + {"OpCapability DrawParameters\n", + MakeInstruction(SpvOpCapability, {SpvCapabilityDrawParameters})}, + {"OpDecorate %1 BuiltIn BaseVertex\n", + MakeInstruction(SpvOpDecorate, + {1, SpvDecorationBuiltIn, SpvBuiltInBaseVertex})}, + {"OpDecorate %1 BuiltIn BaseInstance\n", + MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn, + SpvBuiltInBaseInstance})}, + {"OpDecorate %1 BuiltIn DrawIndex\n", + MakeInstruction(SpvOpDecorate, + {1, SpvDecorationBuiltIn, SpvBuiltInDrawIndex})}, + })), ); + +// SPV_KHR_subgroup_vote + +INSTANTIATE_TEST_CASE_P( + SPV_KHR_subgroup_vote, ExtensionRoundTripTest, + // We'll get coverage over operand tables by trying the universal + // environments, and at least one specific environment. + Combine(ValuesIn(CommonVulkanEnvs()), + ValuesIn(std::vector{ + {"OpCapability SubgroupVoteKHR\n", + MakeInstruction(SpvOpCapability, + {SpvCapabilitySubgroupVoteKHR})}, + {"%2 = OpSubgroupAnyKHR %1 %3\n", + MakeInstruction(SpvOpSubgroupAnyKHR, {1, 2, 3})}, + {"%2 = OpSubgroupAllKHR %1 %3\n", + MakeInstruction(SpvOpSubgroupAllKHR, {1, 2, 3})}, + {"%2 = OpSubgroupAllEqualKHR %1 %3\n", + MakeInstruction(SpvOpSubgroupAllEqualKHR, {1, 2, 3})}, + })), ); + +// SPV_KHR_16bit_storage + +INSTANTIATE_TEST_CASE_P( + SPV_KHR_16bit_storage, ExtensionRoundTripTest, + // We'll get coverage over operand tables by trying the universal + // environments, and at least one specific environment. + Combine(ValuesIn(CommonVulkanEnvs()), + ValuesIn(std::vector{ + {"OpCapability StorageBuffer16BitAccess\n", + MakeInstruction(SpvOpCapability, + {SpvCapabilityStorageUniformBufferBlock16})}, + {"OpCapability StorageBuffer16BitAccess\n", + MakeInstruction(SpvOpCapability, + {SpvCapabilityStorageBuffer16BitAccess})}, + {"OpCapability StorageUniform16\n", + MakeInstruction( + SpvOpCapability, + {SpvCapabilityUniformAndStorageBuffer16BitAccess})}, + {"OpCapability StorageUniform16\n", + MakeInstruction(SpvOpCapability, + {SpvCapabilityStorageUniform16})}, + {"OpCapability StoragePushConstant16\n", + MakeInstruction(SpvOpCapability, + {SpvCapabilityStoragePushConstant16})}, + {"OpCapability StorageInputOutput16\n", + MakeInstruction(SpvOpCapability, + {SpvCapabilityStorageInputOutput16})}, + })), ); + +INSTANTIATE_TEST_CASE_P( + SPV_KHR_16bit_storage_alias_check, ExtensionAssemblyTest, + Combine(ValuesIn(CommonVulkanEnvs()), + ValuesIn(std::vector{ + // The old name maps to the new enum. + {"OpCapability StorageUniformBufferBlock16\n", + MakeInstruction(SpvOpCapability, + {SpvCapabilityStorageBuffer16BitAccess})}, + // The new name maps to the old enum. + {"OpCapability UniformAndStorageBuffer16BitAccess\n", + MakeInstruction(SpvOpCapability, + {SpvCapabilityStorageUniform16})}, + })), ); + +// SPV_KHR_device_group + +INSTANTIATE_TEST_CASE_P( + SPV_KHR_device_group, ExtensionRoundTripTest, + // We'll get coverage over operand tables by trying the universal + // environments, and at least one specific environment. + Combine(ValuesIn(CommonVulkanEnvs()), + ValuesIn(std::vector{ + {"OpCapability DeviceGroup\n", + MakeInstruction(SpvOpCapability, {SpvCapabilityDeviceGroup})}, + {"OpDecorate %1 BuiltIn DeviceIndex\n", + MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn, + SpvBuiltInDeviceIndex})}, + })), ); + +// SPV_KHR_multiview + +INSTANTIATE_TEST_CASE_P( + SPV_KHR_multiview, ExtensionRoundTripTest, + // We'll get coverage over operand tables by trying the universal + // environments, and at least one specific environment. + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, + SPV_ENV_VULKAN_1_0), + ValuesIn(std::vector{ + {"OpCapability MultiView\n", + MakeInstruction(SpvOpCapability, {SpvCapabilityMultiView})}, + {"OpDecorate %1 BuiltIn ViewIndex\n", + MakeInstruction(SpvOpDecorate, {1, SpvDecorationBuiltIn, + SpvBuiltInViewIndex})}, + })), ); + +// SPV_AMD_shader_explicit_vertex_parameter + +#define PREAMBLE \ + "%1 = OpExtInstImport \"SPV_AMD_shader_explicit_vertex_parameter\"\n" +INSTANTIATE_TEST_CASE_P( + SPV_AMD_shader_explicit_vertex_parameter, ExtensionRoundTripTest, + // We'll get coverage over operand tables by trying the universal + // environments, and at least one specific environment. + Combine( + Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, + SPV_ENV_VULKAN_1_0), + ValuesIn(std::vector{ + {PREAMBLE "%3 = OpExtInst %2 %1 InterpolateAtVertexAMD %4 %5\n", + Concatenate( + {MakeInstruction( + SpvOpExtInstImport, {1}, + MakeVector("SPV_AMD_shader_explicit_vertex_parameter")), + MakeInstruction(SpvOpExtInst, {2, 3, 1, 1, 4, 5})})}, + })), ); +#undef PREAMBLE + +// SPV_AMD_shader_trinary_minmax + +#define PREAMBLE "%1 = OpExtInstImport \"SPV_AMD_shader_trinary_minmax\"\n" +INSTANTIATE_TEST_CASE_P( + SPV_AMD_shader_trinary_minmax, ExtensionRoundTripTest, + // We'll get coverage over operand tables by trying the universal + // environments, and at least one specific environment. + Combine( + Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, + SPV_ENV_VULKAN_1_0), + ValuesIn(std::vector{ + {PREAMBLE "%3 = OpExtInst %2 %1 FMin3AMD %4 %5 %6\n", + Concatenate( + {MakeInstruction(SpvOpExtInstImport, {1}, + MakeVector("SPV_AMD_shader_trinary_minmax")), + MakeInstruction(SpvOpExtInst, {2, 3, 1, 1, 4, 5, 6})})}, + {PREAMBLE "%3 = OpExtInst %2 %1 UMin3AMD %4 %5 %6\n", + Concatenate( + {MakeInstruction(SpvOpExtInstImport, {1}, + MakeVector("SPV_AMD_shader_trinary_minmax")), + MakeInstruction(SpvOpExtInst, {2, 3, 1, 2, 4, 5, 6})})}, + {PREAMBLE "%3 = OpExtInst %2 %1 SMin3AMD %4 %5 %6\n", + Concatenate( + {MakeInstruction(SpvOpExtInstImport, {1}, + MakeVector("SPV_AMD_shader_trinary_minmax")), + MakeInstruction(SpvOpExtInst, {2, 3, 1, 3, 4, 5, 6})})}, + {PREAMBLE "%3 = OpExtInst %2 %1 FMax3AMD %4 %5 %6\n", + Concatenate( + {MakeInstruction(SpvOpExtInstImport, {1}, + MakeVector("SPV_AMD_shader_trinary_minmax")), + MakeInstruction(SpvOpExtInst, {2, 3, 1, 4, 4, 5, 6})})}, + {PREAMBLE "%3 = OpExtInst %2 %1 UMax3AMD %4 %5 %6\n", + Concatenate( + {MakeInstruction(SpvOpExtInstImport, {1}, + MakeVector("SPV_AMD_shader_trinary_minmax")), + MakeInstruction(SpvOpExtInst, {2, 3, 1, 5, 4, 5, 6})})}, + {PREAMBLE "%3 = OpExtInst %2 %1 SMax3AMD %4 %5 %6\n", + Concatenate( + {MakeInstruction(SpvOpExtInstImport, {1}, + MakeVector("SPV_AMD_shader_trinary_minmax")), + MakeInstruction(SpvOpExtInst, {2, 3, 1, 6, 4, 5, 6})})}, + {PREAMBLE "%3 = OpExtInst %2 %1 FMid3AMD %4 %5 %6\n", + Concatenate( + {MakeInstruction(SpvOpExtInstImport, {1}, + MakeVector("SPV_AMD_shader_trinary_minmax")), + MakeInstruction(SpvOpExtInst, {2, 3, 1, 7, 4, 5, 6})})}, + {PREAMBLE "%3 = OpExtInst %2 %1 UMid3AMD %4 %5 %6\n", + Concatenate( + {MakeInstruction(SpvOpExtInstImport, {1}, + MakeVector("SPV_AMD_shader_trinary_minmax")), + MakeInstruction(SpvOpExtInst, {2, 3, 1, 8, 4, 5, 6})})}, + {PREAMBLE "%3 = OpExtInst %2 %1 SMid3AMD %4 %5 %6\n", + Concatenate( + {MakeInstruction(SpvOpExtInstImport, {1}, + MakeVector("SPV_AMD_shader_trinary_minmax")), + MakeInstruction(SpvOpExtInst, {2, 3, 1, 9, 4, 5, 6})})}, + })), ); +#undef PREAMBLE + +// SPV_AMD_gcn_shader + +#define PREAMBLE "%1 = OpExtInstImport \"SPV_AMD_gcn_shader\"\n" +INSTANTIATE_TEST_CASE_P( + SPV_AMD_gcn_shader, ExtensionRoundTripTest, + // We'll get coverage over operand tables by trying the universal + // environments, and at least one specific environment. + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, + SPV_ENV_VULKAN_1_0), + ValuesIn(std::vector{ + {PREAMBLE "%3 = OpExtInst %2 %1 CubeFaceIndexAMD %4\n", + Concatenate({MakeInstruction(SpvOpExtInstImport, {1}, + MakeVector("SPV_AMD_gcn_shader")), + MakeInstruction(SpvOpExtInst, {2, 3, 1, 1, 4})})}, + {PREAMBLE "%3 = OpExtInst %2 %1 CubeFaceCoordAMD %4\n", + Concatenate({MakeInstruction(SpvOpExtInstImport, {1}, + MakeVector("SPV_AMD_gcn_shader")), + MakeInstruction(SpvOpExtInst, {2, 3, 1, 2, 4})})}, + {PREAMBLE "%3 = OpExtInst %2 %1 TimeAMD\n", + Concatenate({MakeInstruction(SpvOpExtInstImport, {1}, + MakeVector("SPV_AMD_gcn_shader")), + MakeInstruction(SpvOpExtInst, {2, 3, 1, 3})})}, + })), ); +#undef PREAMBLE + +// SPV_AMD_shader_ballot + +#define PREAMBLE "%1 = OpExtInstImport \"SPV_AMD_shader_ballot\"\n" +INSTANTIATE_TEST_CASE_P( + SPV_AMD_shader_ballot, ExtensionRoundTripTest, + // We'll get coverage over operand tables by trying the universal + // environments, and at least one specific environment. + Combine( + Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, + SPV_ENV_VULKAN_1_0), + ValuesIn(std::vector{ + {PREAMBLE "%3 = OpExtInst %2 %1 SwizzleInvocationsAMD %4 %5\n", + Concatenate({MakeInstruction(SpvOpExtInstImport, {1}, + MakeVector("SPV_AMD_shader_ballot")), + MakeInstruction(SpvOpExtInst, {2, 3, 1, 1, 4, 5})})}, + {PREAMBLE + "%3 = OpExtInst %2 %1 SwizzleInvocationsMaskedAMD %4 %5\n", + Concatenate({MakeInstruction(SpvOpExtInstImport, {1}, + MakeVector("SPV_AMD_shader_ballot")), + MakeInstruction(SpvOpExtInst, {2, 3, 1, 2, 4, 5})})}, + {PREAMBLE "%3 = OpExtInst %2 %1 WriteInvocationAMD %4 %5 %6\n", + Concatenate({MakeInstruction(SpvOpExtInstImport, {1}, + MakeVector("SPV_AMD_shader_ballot")), + MakeInstruction(SpvOpExtInst, + {2, 3, 1, 3, 4, 5, 6})})}, + {PREAMBLE "%3 = OpExtInst %2 %1 MbcntAMD %4\n", + Concatenate({MakeInstruction(SpvOpExtInstImport, {1}, + MakeVector("SPV_AMD_shader_ballot")), + MakeInstruction(SpvOpExtInst, {2, 3, 1, 4, 4})})}, + })), ); +#undef PREAMBLE + +// SPV_KHR_variable_pointers + +INSTANTIATE_TEST_CASE_P( + SPV_KHR_variable_pointers, ExtensionRoundTripTest, + // We'll get coverage over operand tables by trying the universal + // environments, and at least one specific environment. + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, + SPV_ENV_VULKAN_1_0), + ValuesIn(std::vector{ + {"OpCapability VariablePointers\n", + MakeInstruction(SpvOpCapability, + {SpvCapabilityVariablePointers})}, + {"OpCapability VariablePointersStorageBuffer\n", + MakeInstruction(SpvOpCapability, + {SpvCapabilityVariablePointersStorageBuffer})}, + })), ); + +// SPV_GOOGLE_decorate_string + +INSTANTIATE_TEST_CASE_P( + SPV_GOOGLE_decorate_string, ExtensionRoundTripTest, + Combine( + // We'll get coverage over operand tables by trying the universal + // environments, and at least one specific environment. + Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, + SPV_ENV_UNIVERSAL_1_2, SPV_ENV_VULKAN_1_0), + ValuesIn(std::vector{ + {"OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE \"ABC\"\n", + MakeInstruction(SpvOpDecorateStringGOOGLE, + {1, SpvDecorationHlslSemanticGOOGLE}, + MakeVector("ABC"))}, + {"OpMemberDecorateStringGOOGLE %1 3 HlslSemanticGOOGLE \"DEF\"\n", + MakeInstruction(SpvOpMemberDecorateStringGOOGLE, + {1, 3, SpvDecorationHlslSemanticGOOGLE}, + MakeVector("DEF"))}, + })), ); + +// SPV_GOOGLE_hlsl_functionality1 + +INSTANTIATE_TEST_CASE_P( + SPV_GOOGLE_hlsl_functionality1, ExtensionRoundTripTest, + Combine( + // We'll get coverage over operand tables by trying the universal + // environments, and at least one specific environment. + Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, + SPV_ENV_UNIVERSAL_1_2, SPV_ENV_VULKAN_1_0), + // HlslSemanticGOOGLE is tested in SPV_GOOGLE_decorate_string, since + // they are coupled together. + ValuesIn(std::vector{ + {"OpDecorateId %1 HlslCounterBufferGOOGLE %2\n", + MakeInstruction(SpvOpDecorateId, + {1, SpvDecorationHlslCounterBufferGOOGLE, 2})}, + })), ); + +// SPV_NV_shader_subgroup_partitioned + +INSTANTIATE_TEST_CASE_P( + SPV_NV_shader_subgroup_partitioned, ExtensionRoundTripTest, + Combine( + Values(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_VULKAN_1_1), + ValuesIn(std::vector{ + {"OpExtension \"SPV_NV_shader_subgroup_partitioned\"\n", + MakeInstruction(SpvOpExtension, + MakeVector("SPV_NV_shader_subgroup_partitioned"))}, + {"OpCapability GroupNonUniformPartitionedNV\n", + MakeInstruction(SpvOpCapability, + {SpvCapabilityGroupNonUniformPartitionedNV})}, + // Check the new capability's token number + {"OpCapability GroupNonUniformPartitionedNV\n", + MakeInstruction(SpvOpCapability, {5297})}, + {"%2 = OpGroupNonUniformPartitionNV %1 %3\n", + MakeInstruction(SpvOpGroupNonUniformPartitionNV, {1, 2, 3})}, + // Check the new instruction's token number + {"%2 = OpGroupNonUniformPartitionNV %1 %3\n", + MakeInstruction(static_cast(5296), {1, 2, 3})}, + // Check the new group operations + {"%2 = OpGroupIAdd %1 %3 PartitionedReduceNV %4\n", + MakeInstruction(SpvOpGroupIAdd, + {1, 2, 3, SpvGroupOperationPartitionedReduceNV, + 4})}, + {"%2 = OpGroupIAdd %1 %3 PartitionedReduceNV %4\n", + MakeInstruction(SpvOpGroupIAdd, {1, 2, 3, 6, 4})}, + {"%2 = OpGroupIAdd %1 %3 PartitionedInclusiveScanNV %4\n", + MakeInstruction(SpvOpGroupIAdd, + {1, 2, 3, + SpvGroupOperationPartitionedInclusiveScanNV, 4})}, + {"%2 = OpGroupIAdd %1 %3 PartitionedInclusiveScanNV %4\n", + MakeInstruction(SpvOpGroupIAdd, {1, 2, 3, 7, 4})}, + {"%2 = OpGroupIAdd %1 %3 PartitionedExclusiveScanNV %4\n", + MakeInstruction(SpvOpGroupIAdd, + {1, 2, 3, + SpvGroupOperationPartitionedExclusiveScanNV, 4})}, + {"%2 = OpGroupIAdd %1 %3 PartitionedExclusiveScanNV %4\n", + MakeInstruction(SpvOpGroupIAdd, {1, 2, 3, 8, 4})}, + })), ); + +// SPV_EXT_descriptor_indexing + +INSTANTIATE_TEST_CASE_P( + SPV_EXT_descriptor_indexing, ExtensionRoundTripTest, + Combine( + Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, + SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_3, SPV_ENV_VULKAN_1_0, + SPV_ENV_VULKAN_1_1), + ValuesIn(std::vector{ + {"OpExtension \"SPV_EXT_descriptor_indexing\"\n", + MakeInstruction(SpvOpExtension, + MakeVector("SPV_EXT_descriptor_indexing"))}, + // Check capabilities, by name + {"OpCapability ShaderNonUniformEXT\n", + MakeInstruction(SpvOpCapability, + {SpvCapabilityShaderNonUniformEXT})}, + {"OpCapability RuntimeDescriptorArrayEXT\n", + MakeInstruction(SpvOpCapability, + {SpvCapabilityRuntimeDescriptorArrayEXT})}, + {"OpCapability InputAttachmentArrayDynamicIndexingEXT\n", + MakeInstruction( + SpvOpCapability, + {SpvCapabilityInputAttachmentArrayDynamicIndexingEXT})}, + {"OpCapability UniformTexelBufferArrayDynamicIndexingEXT\n", + MakeInstruction( + SpvOpCapability, + {SpvCapabilityUniformTexelBufferArrayDynamicIndexingEXT})}, + {"OpCapability StorageTexelBufferArrayDynamicIndexingEXT\n", + MakeInstruction( + SpvOpCapability, + {SpvCapabilityStorageTexelBufferArrayDynamicIndexingEXT})}, + {"OpCapability UniformBufferArrayNonUniformIndexingEXT\n", + MakeInstruction( + SpvOpCapability, + {SpvCapabilityUniformBufferArrayNonUniformIndexingEXT})}, + {"OpCapability SampledImageArrayNonUniformIndexingEXT\n", + MakeInstruction( + SpvOpCapability, + {SpvCapabilitySampledImageArrayNonUniformIndexingEXT})}, + {"OpCapability StorageBufferArrayNonUniformIndexingEXT\n", + MakeInstruction( + SpvOpCapability, + {SpvCapabilityStorageBufferArrayNonUniformIndexingEXT})}, + {"OpCapability StorageImageArrayNonUniformIndexingEXT\n", + MakeInstruction( + SpvOpCapability, + {SpvCapabilityStorageImageArrayNonUniformIndexingEXT})}, + {"OpCapability InputAttachmentArrayNonUniformIndexingEXT\n", + MakeInstruction( + SpvOpCapability, + {SpvCapabilityInputAttachmentArrayNonUniformIndexingEXT})}, + {"OpCapability UniformTexelBufferArrayNonUniformIndexingEXT\n", + MakeInstruction( + SpvOpCapability, + {SpvCapabilityUniformTexelBufferArrayNonUniformIndexingEXT})}, + {"OpCapability StorageTexelBufferArrayNonUniformIndexingEXT\n", + MakeInstruction( + SpvOpCapability, + {SpvCapabilityStorageTexelBufferArrayNonUniformIndexingEXT})}, + // Check capabilities, by number + {"OpCapability ShaderNonUniformEXT\n", + MakeInstruction(SpvOpCapability, {5301})}, + {"OpCapability RuntimeDescriptorArrayEXT\n", + MakeInstruction(SpvOpCapability, {5302})}, + {"OpCapability InputAttachmentArrayDynamicIndexingEXT\n", + MakeInstruction(SpvOpCapability, {5303})}, + {"OpCapability UniformTexelBufferArrayDynamicIndexingEXT\n", + MakeInstruction(SpvOpCapability, {5304})}, + {"OpCapability StorageTexelBufferArrayDynamicIndexingEXT\n", + MakeInstruction(SpvOpCapability, {5305})}, + {"OpCapability UniformBufferArrayNonUniformIndexingEXT\n", + MakeInstruction(SpvOpCapability, {5306})}, + {"OpCapability SampledImageArrayNonUniformIndexingEXT\n", + MakeInstruction(SpvOpCapability, {5307})}, + {"OpCapability StorageBufferArrayNonUniformIndexingEXT\n", + MakeInstruction(SpvOpCapability, {5308})}, + {"OpCapability StorageImageArrayNonUniformIndexingEXT\n", + MakeInstruction(SpvOpCapability, {5309})}, + {"OpCapability InputAttachmentArrayNonUniformIndexingEXT\n", + MakeInstruction(SpvOpCapability, {5310})}, + {"OpCapability UniformTexelBufferArrayNonUniformIndexingEXT\n", + MakeInstruction(SpvOpCapability, {5311})}, + {"OpCapability StorageTexelBufferArrayNonUniformIndexingEXT\n", + MakeInstruction(SpvOpCapability, {5312})}, + + // Check the decoration token + {"OpDecorate %1 NonUniformEXT\n", + MakeInstruction(SpvOpDecorate, {1, SpvDecorationNonUniformEXT})}, + {"OpDecorate %1 NonUniformEXT\n", + MakeInstruction(SpvOpDecorate, {1, 5300})}, + })), ); + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/text_to_binary.function_test.cpp b/3rdparty/spirv-tools/test/text_to_binary.function_test.cpp new file mode 100644 index 000000000..5f1b72a04 --- /dev/null +++ b/3rdparty/spirv-tools/test/text_to_binary.function_test.cpp @@ -0,0 +1,77 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Assembler tests for instructions in the "Function" section of the +// SPIR-V spec. + +#include "unit_spirv.h" + +#include "gmock/gmock.h" +#include "test_fixture.h" + +namespace { + +using spvtest::EnumCase; +using spvtest::MakeInstruction; +using spvtest::TextToBinaryTest; +using ::testing::Eq; + +// Test OpFunction + +using OpFunctionControlTest = spvtest::TextToBinaryTestBase< + ::testing::TestWithParam>>; + +TEST_P(OpFunctionControlTest, AnySingleFunctionControlMask) { + const std::string input = "%result_id = OpFunction %result_type " + + GetParam().name() + " %function_type "; + EXPECT_THAT( + CompiledInstructions(input), + Eq(MakeInstruction(SpvOpFunction, {1, 2, GetParam().value(), 3}))); +} + +// clang-format off +#define CASE(VALUE,NAME) { SpvFunctionControl##VALUE, NAME } +INSTANTIATE_TEST_CASE_P(TextToBinaryFunctionTest, OpFunctionControlTest, + ::testing::ValuesIn(std::vector>{ + CASE(MaskNone, "None"), + CASE(InlineMask, "Inline"), + CASE(DontInlineMask, "DontInline"), + CASE(PureMask, "Pure"), + CASE(ConstMask, "Const"), + }),); +#undef CASE +// clang-format on + +TEST_F(OpFunctionControlTest, CombinedFunctionControlMask) { + // Sample a single combination. This ensures we've integrated + // the instruction parsing logic with spvTextParseMask. + const std::string input = + "%result_id = OpFunction %result_type Inline|Pure|Const %function_type"; + const uint32_t expected_mask = SpvFunctionControlInlineMask | + SpvFunctionControlPureMask | + SpvFunctionControlConstMask; + EXPECT_THAT(CompiledInstructions(input), + Eq(MakeInstruction(SpvOpFunction, {1, 2, expected_mask, 3}))); +} + +TEST_F(OpFunctionControlTest, WrongFunctionControl) { + EXPECT_THAT(CompileFailure("%r = OpFunction %t Inline|Unroll %ft"), + Eq("Invalid function control operand 'Inline|Unroll'.")); +} + +// TODO(dneto): OpFunctionParameter +// TODO(dneto): OpFunctionEnd +// TODO(dneto): OpFunctionCall + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/text_to_binary.group_test.cpp b/3rdparty/spirv-tools/test/text_to_binary.group_test.cpp new file mode 100644 index 000000000..6fb570669 --- /dev/null +++ b/3rdparty/spirv-tools/test/text_to_binary.group_test.cpp @@ -0,0 +1,72 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Assembler tests for instructions in the "Group Instrucions" section of the +// SPIR-V spec. + +#include "unit_spirv.h" + +#include "gmock/gmock.h" +#include "test_fixture.h" + +namespace { + +using spvtest::EnumCase; +using spvtest::MakeInstruction; +using ::testing::Eq; + +// Test GroupOperation enum + +using GroupOperationTest = spvtest::TextToBinaryTestBase< + ::testing::TestWithParam>>; + +TEST_P(GroupOperationTest, AnyGroupOperation) { + const std::string input = + "%result = OpGroupIAdd %type %scope " + GetParam().name() + " %x"; + EXPECT_THAT( + CompiledInstructions(input), + Eq(MakeInstruction(SpvOpGroupIAdd, {1, 2, 3, GetParam().value(), 4}))); +} + +// clang-format off +#define CASE(NAME) { SpvGroupOperation##NAME, #NAME} +INSTANTIATE_TEST_CASE_P(TextToBinaryGroupOperation, GroupOperationTest, + ::testing::ValuesIn(std::vector>{ + CASE(Reduce), + CASE(InclusiveScan), + CASE(ExclusiveScan), + }),); +#undef CASE +// clang-format on + +TEST_F(GroupOperationTest, WrongGroupOperation) { + EXPECT_THAT(CompileFailure("%r = OpGroupUMin %t %e xxyyzz %x"), + Eq("Invalid group operation 'xxyyzz'.")); +} + +// TODO(dneto): OpGroupAsyncCopy +// TODO(dneto): OpGroupWaitEvents +// TODO(dneto): OpGroupAll +// TODO(dneto): OpGroupAny +// TODO(dneto): OpGroupBroadcast +// TODO(dneto): OpGroupIAdd +// TODO(dneto): OpGroupFAdd +// TODO(dneto): OpGroupFMin +// TODO(dneto): OpGroupUMin +// TODO(dneto): OpGroupSMin +// TODO(dneto): OpGroupFMax +// TODO(dneto): OpGroupUMax +// TODO(dneto): OpGroupSMax + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/text_to_binary.image_test.cpp b/3rdparty/spirv-tools/test/text_to_binary.image_test.cpp new file mode 100644 index 000000000..65f0af379 --- /dev/null +++ b/3rdparty/spirv-tools/test/text_to_binary.image_test.cpp @@ -0,0 +1,272 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Assembler tests for instructions in the "Image Instructions" section of +// the SPIR-V spec. + +#include "unit_spirv.h" + +#include "gmock/gmock.h" +#include "test_fixture.h" + +namespace { + +using spvtest::MakeInstruction; +using spvtest::TextToBinaryTest; +using ::testing::Eq; + +// An example case for a mask value with operands. +struct ImageOperandsCase { + std::string image_operands; + // The expected mask, followed by its operands. + std::vector expected_mask_and_operands; +}; + +// Test all kinds of image operands. + +using ImageOperandsTest = + spvtest::TextToBinaryTestBase<::testing::TestWithParam>; + +TEST_P(ImageOperandsTest, Sample) { + const std::string input = + "%2 = OpImageFetch %1 %3 %4" + GetParam().image_operands + "\n"; + EXPECT_THAT(CompiledInstructions(input), + Eq(MakeInstruction(SpvOpImageFetch, {1, 2, 3, 4}, + GetParam().expected_mask_and_operands))); +} + +#define MASK(NAME) SpvImageOperands##NAME##Mask +INSTANTIATE_TEST_CASE_P( + TextToBinaryImageOperandsAny, ImageOperandsTest, + ::testing::ValuesIn(std::vector{ + // TODO(dneto): Rev32 adds many more values, and rearranges their + // values. + // Image operands are optional. + {"", {}}, + // Test each kind, alone. + {" Bias %5", {MASK(Bias), 5}}, + {" Lod %5", {MASK(Lod), 5}}, + {" Grad %5 %6", {MASK(Grad), 5, 6}}, + {" ConstOffset %5", {MASK(ConstOffset), 5}}, + {" Offset %5", {MASK(Offset), 5}}, + {" ConstOffsets %5", {MASK(ConstOffsets), 5}}, + {" Sample %5", {MASK(Sample), 5}}, + {" MinLod %5", {MASK(MinLod), 5}}, + }), ); +#undef MASK +#define MASK(NAME) static_cast(SpvImageOperands##NAME##Mask) +INSTANTIATE_TEST_CASE_P( + TextToBinaryImageOperandsCombination, ImageOperandsTest, + ::testing::ValuesIn(std::vector{ + // TODO(dneto): Rev32 adds many more values, and rearranges their + // values. + // Test adjacent pairs, so we can easily debug the values when it fails. + {" Bias|Lod %5 %6", {MASK(Bias) | MASK(Lod), 5, 6}}, + {" Lod|Grad %5 %6 %7", {MASK(Lod) | MASK(Grad), 5, 6, 7}}, + {" Grad|ConstOffset %5 %6 %7", + {MASK(Grad) | MASK(ConstOffset), 5, 6, 7}}, + {" ConstOffset|Offset %5 %6", {MASK(ConstOffset) | MASK(Offset), 5, 6}}, + {" Offset|ConstOffsets %5 %6", + {MASK(Offset) | MASK(ConstOffsets), 5, 6}}, + {" ConstOffsets|Sample %5 %6", + {MASK(ConstOffsets) | MASK(Sample), 5, 6}}, + // Test all masks together. + {" Bias|Lod|Grad|ConstOffset|Offset|ConstOffsets|Sample" + " %5 %6 %7 %8 %9 %10 %11 %12", + {MASK(Bias) | MASK(Lod) | MASK(Grad) | MASK(ConstOffset) | + MASK(Offset) | MASK(ConstOffsets) | MASK(Sample), + 5, 6, 7, 8, 9, 10, 11, 12}}, + // The same, but with mask value names reversed. + {" Sample|ConstOffsets|Offset|ConstOffset|Grad|Lod|Bias" + " %5 %6 %7 %8 %9 %10 %11 %12", + {MASK(Bias) | MASK(Lod) | MASK(Grad) | MASK(ConstOffset) | + MASK(Offset) | MASK(ConstOffsets) | MASK(Sample), + 5, 6, 7, 8, 9, 10, 11, 12}}}), ); +#undef MASK + +TEST_F(ImageOperandsTest, WrongOperand) { + EXPECT_THAT(CompileFailure("%r = OpImageFetch %t %i %c xxyyzz"), + Eq("Invalid image operand 'xxyyzz'.")); +} + +// Test OpImage + +using OpImageTest = TextToBinaryTest; + +TEST_F(OpImageTest, Valid) { + const std::string input = "%2 = OpImage %1 %3\n"; + EXPECT_THAT(CompiledInstructions(input), + Eq(MakeInstruction(SpvOpImage, {1, 2, 3}))); + + // Test the disassembler. + EXPECT_THAT(EncodeAndDecodeSuccessfully(input), input); +} + +TEST_F(OpImageTest, InvalidTypeOperand) { + EXPECT_THAT(CompileFailure("%2 = OpImage 42"), + Eq("Expected id to start with %.")); +} + +TEST_F(OpImageTest, MissingSampledImageOperand) { + EXPECT_THAT(CompileFailure("%2 = OpImage %1"), + Eq("Expected operand, found end of stream.")); +} + +TEST_F(OpImageTest, InvalidSampledImageOperand) { + EXPECT_THAT(CompileFailure("%2 = OpImage %1 1000"), + Eq("Expected id to start with %.")); +} + +TEST_F(OpImageTest, TooManyOperands) { + // We should improve this message, to say what instruction we're trying to + // parse. + EXPECT_THAT(CompileFailure("%2 = OpImage %1 %3 %4"), // an Id + Eq("Expected '=', found end of stream.")); + + EXPECT_THAT(CompileFailure("%2 = OpImage %1 %3 99"), // a number + Eq("Expected or at the beginning of an " + "instruction, found '99'.")); + EXPECT_THAT(CompileFailure("%2 = OpImage %1 %3 \"abc\""), // a string + Eq("Expected or at the beginning of an " + "instruction, found '\"abc\"'.")); +} + +// Test OpImageSparseRead + +using OpImageSparseReadTest = TextToBinaryTest; + +TEST_F(OpImageSparseReadTest, OnlyRequiredOperands) { + const std::string input = "%2 = OpImageSparseRead %1 %3 %4\n"; + EXPECT_THAT(CompiledInstructions(input), + Eq(MakeInstruction(SpvOpImageSparseRead, {1, 2, 3, 4}))); + // Test the disassembler. + EXPECT_THAT(EncodeAndDecodeSuccessfully(input), input); +} + +// Test all kinds of image operands on OpImageSparseRead + +using ImageSparseReadImageOperandsTest = + spvtest::TextToBinaryTestBase<::testing::TestWithParam>; + +TEST_P(ImageSparseReadImageOperandsTest, Sample) { + const std::string input = + "%2 = OpImageSparseRead %1 %3 %4" + GetParam().image_operands + "\n"; + EXPECT_THAT(CompiledInstructions(input), + Eq(MakeInstruction(SpvOpImageSparseRead, {1, 2, 3, 4}, + GetParam().expected_mask_and_operands))); + // Test the disassembler. + EXPECT_THAT(EncodeAndDecodeSuccessfully(input), input); +} + +#define MASK(NAME) SpvImageOperands##NAME##Mask +INSTANTIATE_TEST_CASE_P(ImageSparseReadImageOperandsAny, + ImageSparseReadImageOperandsTest, + ::testing::ValuesIn(std::vector{ + // Image operands are optional. + {"", {}}, + // Test each kind, alone. + {" Bias %5", {MASK(Bias), 5}}, + {" Lod %5", {MASK(Lod), 5}}, + {" Grad %5 %6", {MASK(Grad), 5, 6}}, + {" ConstOffset %5", {MASK(ConstOffset), 5}}, + {" Offset %5", {MASK(Offset), 5}}, + {" ConstOffsets %5", {MASK(ConstOffsets), 5}}, + {" Sample %5", {MASK(Sample), 5}}, + {" MinLod %5", {MASK(MinLod), 5}}, + }), ); +#undef MASK +#define MASK(NAME) static_cast(SpvImageOperands##NAME##Mask) +INSTANTIATE_TEST_CASE_P( + ImageSparseReadImageOperandsCombination, ImageSparseReadImageOperandsTest, + ::testing::ValuesIn(std::vector{ + // values. + // Test adjacent pairs, so we can easily debug the values when it fails. + {" Bias|Lod %5 %6", {MASK(Bias) | MASK(Lod), 5, 6}}, + {" Lod|Grad %5 %6 %7", {MASK(Lod) | MASK(Grad), 5, 6, 7}}, + {" Grad|ConstOffset %5 %6 %7", + {MASK(Grad) | MASK(ConstOffset), 5, 6, 7}}, + {" ConstOffset|Offset %5 %6", {MASK(ConstOffset) | MASK(Offset), 5, 6}}, + {" Offset|ConstOffsets %5 %6", + {MASK(Offset) | MASK(ConstOffsets), 5, 6}}, + {" ConstOffsets|Sample %5 %6", + {MASK(ConstOffsets) | MASK(Sample), 5, 6}}, + // Test all masks together. + {" Bias|Lod|Grad|ConstOffset|Offset|ConstOffsets|Sample" + " %5 %6 %7 %8 %9 %10 %11 %12", + {MASK(Bias) | MASK(Lod) | MASK(Grad) | MASK(ConstOffset) | + MASK(Offset) | MASK(ConstOffsets) | MASK(Sample), + 5, 6, 7, 8, 9, 10, 11, 12}}, + // Don't try the masks reversed, since this is a round trip test, + // and the disassembler will sort them. + }), ); +#undef MASK + +TEST_F(OpImageSparseReadTest, InvalidTypeOperand) { + EXPECT_THAT(CompileFailure("%2 = OpImageSparseRead 42"), + Eq("Expected id to start with %.")); +} + +TEST_F(OpImageSparseReadTest, MissingImageOperand) { + EXPECT_THAT(CompileFailure("%2 = OpImageSparseRead %1"), + Eq("Expected operand, found end of stream.")); +} + +TEST_F(OpImageSparseReadTest, InvalidImageOperand) { + EXPECT_THAT(CompileFailure("%2 = OpImageSparseRead %1 1000"), + Eq("Expected id to start with %.")); +} + +TEST_F(OpImageSparseReadTest, MissingCoordinateOperand) { + EXPECT_THAT(CompileFailure("%2 = OpImageSparseRead %1 %2"), + Eq("Expected operand, found end of stream.")); +} + +TEST_F(OpImageSparseReadTest, InvalidCoordinateOperand) { + EXPECT_THAT(CompileFailure("%2 = OpImageSparseRead %1 %2 1000"), + Eq("Expected id to start with %.")); +} + +// TODO(dneto): OpSampledImage +// TODO(dneto): OpImageSampleImplicitLod +// TODO(dneto): OpImageSampleExplicitLod +// TODO(dneto): OpImageSampleDrefImplicitLod +// TODO(dneto): OpImageSampleDrefExplicitLod +// TODO(dneto): OpImageSampleProjImplicitLod +// TODO(dneto): OpImageSampleProjExplicitLod +// TODO(dneto): OpImageSampleProjDrefImplicitLod +// TODO(dneto): OpImageSampleProjDrefExplicitLod +// TODO(dneto): OpImageGather +// TODO(dneto): OpImageDrefGather +// TODO(dneto): OpImageRead +// TODO(dneto): OpImageWrite +// TODO(dneto): OpImageQueryFormat +// TODO(dneto): OpImageQueryOrder +// TODO(dneto): OpImageQuerySizeLod +// TODO(dneto): OpImageQuerySize +// TODO(dneto): OpImageQueryLod +// TODO(dneto): OpImageQueryLevels +// TODO(dneto): OpImageQuerySamples +// TODO(dneto): OpImageSparseSampleImplicitLod +// TODO(dneto): OpImageSparseSampleExplicitLod +// TODO(dneto): OpImageSparseSampleDrefImplicitLod +// TODO(dneto): OpImageSparseSampleDrefExplicitLod +// TODO(dneto): OpImageSparseSampleProjImplicitLod +// TODO(dneto): OpImageSparseSampleProjExplicitLod +// TODO(dneto): OpImageSparseSampleProjDrefImplicitLod +// TODO(dneto): OpImageSparseSampleProjDrefExplicitLod +// TODO(dneto): OpImageSparseFetch +// TODO(dneto): OpImageSparseDrefGather +// TODO(dneto): OpImageSparseTexelsResident + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/text_to_binary.literal_test.cpp b/3rdparty/spirv-tools/test/text_to_binary.literal_test.cpp new file mode 100644 index 000000000..463558188 --- /dev/null +++ b/3rdparty/spirv-tools/test/text_to_binary.literal_test.cpp @@ -0,0 +1,121 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Assembler tests for literal numbers and literal strings. + +#include "test_fixture.h" + +namespace { + +using spvtest::TextToBinaryTest; + +TEST_F(TextToBinaryTest, LiteralStringInPlaceOfLiteralNumber) { + EXPECT_EQ( + R"(Invalid unsigned integer literal: "I shouldn't be a string")", + CompileFailure(R"(OpSource GLSL "I shouldn't be a string")")); +} + +TEST_F(TextToBinaryTest, GarbageInPlaceOfLiteralString) { + EXPECT_EQ("Invalid literal string 'nice-source-code'.", + CompileFailure("OpSourceExtension nice-source-code")); +} + +TEST_F(TextToBinaryTest, LiteralNumberInPlaceOfLiteralString) { + EXPECT_EQ("Expected literal string, found literal number '1000'.", + CompileFailure("OpSourceExtension 1000")); +} + +TEST_F(TextToBinaryTest, LiteralFloatInPlaceOfLiteralInteger) { + EXPECT_EQ("Invalid unsigned integer literal: 10.5", + CompileFailure("OpSource GLSL 10.5")); + + EXPECT_EQ("Invalid unsigned integer literal: 0.2", + CompileFailure(R"(OpMemberName %type 0.2 "member0.2")")); + + EXPECT_EQ("Invalid unsigned integer literal: 32.42", + CompileFailure("%int = OpTypeInt 32.42 0")); + + EXPECT_EQ("Invalid unsigned integer literal: 4.5", + CompileFailure("%mat = OpTypeMatrix %vec 4.5")); + + EXPECT_EQ("Invalid unsigned integer literal: 1.5", + CompileFailure("OpExecutionMode %main LocalSize 1.5 1.6 1.7")); + + EXPECT_EQ("Invalid unsigned integer literal: 0.123", + CompileFailure("%i32 = OpTypeInt 32 1\n" + "%c = OpConstant %i32 0.123")); +} + +TEST_F(TextToBinaryTest, LiteralInt64) { + const std::string code = + "%1 = OpTypeInt 64 0\n%2 = OpConstant %1 123456789021\n"; + EXPECT_EQ(code, EncodeAndDecodeSuccessfully(code)); +} + +TEST_F(TextToBinaryTest, LiteralDouble) { + const std::string code = + "%1 = OpTypeFloat 64\n%2 = OpSpecConstant %1 3.14159265358979\n"; + EXPECT_EQ(code, EncodeAndDecodeSuccessfully(code)); +} + +TEST_F(TextToBinaryTest, LiteralStringASCIILong) { + // SPIR-V allows strings up to 65535 characters. + // Test the simple case of UTF-8 code points corresponding + // to ASCII characters. + EXPECT_EQ(65535, SPV_LIMIT_LITERAL_STRING_UTF8_CHARS_MAX); + const std::string code = + "OpSourceExtension \"" + + std::string(SPV_LIMIT_LITERAL_STRING_UTF8_CHARS_MAX, 'o') + "\"\n"; + EXPECT_EQ(code, EncodeAndDecodeSuccessfully(code)); +} + +TEST_F(TextToBinaryTest, LiteralStringUTF8LongEncodings) { + // SPIR-V allows strings up to 65535 characters. + // Test the case of many Unicode characters, each of which has + // a 4-byte UTF-8 encoding. + + // An instruction is at most 65535 words long. The first one + // contains the wordcount and opcode. So the worst case number of + // 4-byte UTF-8 characters is 65533, since we also need to + // store a terminating null character. + + // This string fits exactly into 65534 words. + const std::string good_string = + spvtest::MakeLongUTF8String(65533) + // The following single character has a 3 byte encoding, + // which fits snugly against the terminating null. + + "\xe8\x80\x80"; + + // These strings will overflow any instruction with 0 or 1 other + // arguments, respectively. + const std::string bad_0_arg_string = spvtest::MakeLongUTF8String(65534); + const std::string bad_1_arg_string = spvtest::MakeLongUTF8String(65533); + + const std::string good_code = "OpSourceExtension \"" + good_string + "\"\n"; + EXPECT_EQ(good_code, EncodeAndDecodeSuccessfully(good_code)); + + // Prove that it works on more than one instruction. + const std::string good_code_2 = "OpSourceContinued \"" + good_string + "\"\n"; + EXPECT_EQ(good_code, EncodeAndDecodeSuccessfully(good_code)); + + // Failure cases. + EXPECT_EQ("Instruction too long: more than 65535 words.", + CompileFailure("OpSourceExtension \"" + bad_0_arg_string + "\"\n")); + EXPECT_EQ("Instruction too long: more than 65535 words.", + CompileFailure("OpSourceContinued \"" + bad_0_arg_string + "\"\n")); + EXPECT_EQ("Instruction too long: more than 65535 words.", + CompileFailure("OpName %target \"" + bad_1_arg_string + "\"\n")); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/text_to_binary.memory_test.cpp b/3rdparty/spirv-tools/test/text_to_binary.memory_test.cpp new file mode 100644 index 000000000..2267b0626 --- /dev/null +++ b/3rdparty/spirv-tools/test/text_to_binary.memory_test.cpp @@ -0,0 +1,108 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Assembler tests for instructions in the "Memory Instructions" section of +// the SPIR-V spec. + +#include "unit_spirv.h" + +#include + +#include "gmock/gmock.h" +#include "test_fixture.h" + +namespace { + +using spvtest::EnumCase; +using spvtest::MakeInstruction; +using spvtest::TextToBinaryTest; +using ::testing::Eq; + +// Test assembly of Memory Access masks + +using MemoryAccessTest = spvtest::TextToBinaryTestBase< + ::testing::TestWithParam>>; + +TEST_P(MemoryAccessTest, AnySingleMemoryAccessMask) { + std::stringstream input; + input << "OpStore %ptr %value " << GetParam().name(); + for (auto operand : GetParam().operands()) input << " " << operand; + EXPECT_THAT(CompiledInstructions(input.str()), + Eq(MakeInstruction(SpvOpStore, {1, 2, GetParam().value()}, + GetParam().operands()))); +} + +INSTANTIATE_TEST_CASE_P( + TextToBinaryMemoryAccessTest, MemoryAccessTest, + ::testing::ValuesIn(std::vector>{ + {SpvMemoryAccessMaskNone, "None", {}}, + {SpvMemoryAccessVolatileMask, "Volatile", {}}, + {SpvMemoryAccessAlignedMask, "Aligned", {16}}, + {SpvMemoryAccessNontemporalMask, "Nontemporal", {}}, + }), ); + +TEST_F(TextToBinaryTest, CombinedMemoryAccessMask) { + const std::string input = "OpStore %ptr %value Volatile|Aligned 16"; + const uint32_t expected_mask = + SpvMemoryAccessVolatileMask | SpvMemoryAccessAlignedMask; + EXPECT_THAT(expected_mask, Eq(3u)); + EXPECT_THAT(CompiledInstructions(input), + Eq(MakeInstruction(SpvOpStore, {1, 2, expected_mask, 16}))); +} + +// Test Storage Class enum values + +using StorageClassTest = spvtest::TextToBinaryTestBase< + ::testing::TestWithParam>>; + +TEST_P(StorageClassTest, AnyStorageClass) { + const std::string input = "%1 = OpVariable %2 " + GetParam().name(); + EXPECT_THAT(CompiledInstructions(input), + Eq(MakeInstruction(SpvOpVariable, {1, 2, GetParam().value()}))); +} + +// clang-format off +#define CASE(NAME) { SpvStorageClass##NAME, #NAME, {} } +INSTANTIATE_TEST_CASE_P( + TextToBinaryStorageClassTest, StorageClassTest, + ::testing::ValuesIn(std::vector>{ + CASE(UniformConstant), + CASE(Input), + CASE(Uniform), + CASE(Output), + CASE(Workgroup), + CASE(CrossWorkgroup), + CASE(Private), + CASE(Function), + CASE(Generic), + CASE(PushConstant), + CASE(AtomicCounter), + CASE(Image), + }),); +#undef CASE +// clang-format on + +// TODO(dneto): OpVariable with initializers +// TODO(dneto): OpImageTexelPointer +// TODO(dneto): OpLoad +// TODO(dneto): OpStore +// TODO(dneto): OpCopyMemory +// TODO(dneto): OpCopyMemorySized +// TODO(dneto): OpAccessChain +// TODO(dneto): OpInBoundsAccessChain +// TODO(dneto): OpPtrAccessChain +// TODO(dneto): OpArrayLength +// TODO(dneto): OpGenercPtrMemSemantics + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/text_to_binary.misc_test.cpp b/3rdparty/spirv-tools/test/text_to_binary.misc_test.cpp new file mode 100644 index 000000000..3d21b4320 --- /dev/null +++ b/3rdparty/spirv-tools/test/text_to_binary.misc_test.cpp @@ -0,0 +1,56 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Assembler tests for instructions in the "Miscellaneous" section of the +// SPIR-V spec. + +#include "unit_spirv.h" + +#include "gmock/gmock.h" +#include "test_fixture.h" + +namespace { + +using SpirvVector = spvtest::TextToBinaryTest::SpirvVector; +using spvtest::MakeInstruction; +using ::testing::Eq; +using TextToBinaryMisc = spvtest::TextToBinaryTest; + +TEST_F(TextToBinaryMisc, OpNop) { + EXPECT_THAT(CompiledInstructions("OpNop"), Eq(MakeInstruction(SpvOpNop, {}))); +} + +TEST_F(TextToBinaryMisc, OpUndef) { + const SpirvVector code = CompiledInstructions(R"(%f32 = OpTypeFloat 32 + %u = OpUndef %f32)"); + const uint32_t typeID = 1; + EXPECT_THAT(code[1], Eq(typeID)); + EXPECT_THAT(Subvector(code, 3), Eq(MakeInstruction(SpvOpUndef, {typeID, 2}))); +} + +TEST_F(TextToBinaryMisc, OpWrong) { + EXPECT_THAT(CompileFailure(" OpWrong %1 %2"), + Eq("Invalid Opcode name 'OpWrong'")); +} + +TEST_F(TextToBinaryMisc, OpWrongAfterRight) { + const auto assembly = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpXYZ +)"; + EXPECT_THAT(CompileFailure(assembly), Eq("Invalid Opcode name 'OpXYZ'")); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/text_to_binary.mode_setting_test.cpp b/3rdparty/spirv-tools/test/text_to_binary.mode_setting_test.cpp new file mode 100644 index 000000000..972a2f361 --- /dev/null +++ b/3rdparty/spirv-tools/test/text_to_binary.mode_setting_test.cpp @@ -0,0 +1,298 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Assembler tests for instructions in the "Mode-Setting" section of the +// SPIR-V spec. + +#include "unit_spirv.h" + +#include "gmock/gmock.h" +#include "test_fixture.h" + +namespace { + +using spvtest::EnumCase; +using spvtest::MakeInstruction; +using spvtest::MakeVector; +using std::get; +using std::tuple; +using ::testing::Combine; +using ::testing::Eq; +using ::testing::TestWithParam; +using ::testing::Values; +using ::testing::ValuesIn; + +// Test OpMemoryModel + +// An example case for OpMemoryModel +struct MemoryModelCase { + uint32_t get_addressing_value() const { + return static_cast(addressing_value); + } + uint32_t get_memory_value() const { + return static_cast(memory_value); + } + SpvAddressingModel addressing_value; + std::string addressing_name; + SpvMemoryModel memory_value; + std::string memory_name; +}; + +using OpMemoryModelTest = + spvtest::TextToBinaryTestBase>; + +TEST_P(OpMemoryModelTest, AnyMemoryModelCase) { + const std::string input = "OpMemoryModel " + GetParam().addressing_name + + " " + GetParam().memory_name; + EXPECT_THAT( + CompiledInstructions(input), + Eq(MakeInstruction(SpvOpMemoryModel, {GetParam().get_addressing_value(), + GetParam().get_memory_value()}))); +} + +#define CASE(ADDRESSING, MEMORY) \ + { \ + SpvAddressingModel##ADDRESSING, #ADDRESSING, SpvMemoryModel##MEMORY, \ + #MEMORY \ + } +// clang-format off +INSTANTIATE_TEST_CASE_P(TextToBinaryMemoryModel, OpMemoryModelTest, + ValuesIn(std::vector{ + // These cases exercise each addressing model, and + // each memory model, but not necessarily in + // combination. + CASE(Logical,Simple), + CASE(Logical,GLSL450), + CASE(Physical32,OpenCL), + CASE(Physical64,OpenCL), + }),); +#undef CASE +// clang-format on + +TEST_F(OpMemoryModelTest, WrongModel) { + EXPECT_THAT(CompileFailure("OpMemoryModel xxyyzz Simple"), + Eq("Invalid addressing model 'xxyyzz'.")); + EXPECT_THAT(CompileFailure("OpMemoryModel Logical xxyyzz"), + Eq("Invalid memory model 'xxyyzz'.")); +} + +// Test OpEntryPoint + +// An example case for OpEntryPoint +struct EntryPointCase { + uint32_t get_execution_value() const { + return static_cast(execution_value); + } + SpvExecutionModel execution_value; + std::string execution_name; + std::string entry_point_name; +}; + +using OpEntryPointTest = + spvtest::TextToBinaryTestBase>; + +TEST_P(OpEntryPointTest, AnyEntryPointCase) { + // TODO(dneto): utf-8, escaping, quoting cases for entry point name. + const std::string input = "OpEntryPoint " + GetParam().execution_name + + " %1 \"" + GetParam().entry_point_name + "\""; + EXPECT_THAT( + CompiledInstructions(input), + Eq(MakeInstruction(SpvOpEntryPoint, {GetParam().get_execution_value(), 1}, + MakeVector(GetParam().entry_point_name)))); +} + +// clang-format off +#define CASE(NAME) SpvExecutionModel##NAME, #NAME +INSTANTIATE_TEST_CASE_P(TextToBinaryEntryPoint, OpEntryPointTest, + ValuesIn(std::vector{ + { CASE(Vertex), "" }, + { CASE(TessellationControl), "my tess" }, + { CASE(TessellationEvaluation), "really fancy" }, + { CASE(Geometry), "Euclid" }, + { CASE(Fragment), "FAT32" }, + { CASE(GLCompute), "cubic" }, + { CASE(Kernel), "Sanders" }, + }),); +#undef CASE +// clang-format on + +TEST_F(OpEntryPointTest, WrongModel) { + EXPECT_THAT(CompileFailure("OpEntryPoint xxyyzz %1 \"fun\""), + Eq("Invalid execution model 'xxyyzz'.")); +} + +// Test OpExecutionMode +using OpExecutionModeTest = spvtest::TextToBinaryTestBase< + TestWithParam>>>; + +TEST_P(OpExecutionModeTest, AnyExecutionMode) { + // This string should assemble, but should not validate. + std::stringstream input; + input << "OpExecutionMode %1 " << get<1>(GetParam()).name(); + for (auto operand : get<1>(GetParam()).operands()) input << " " << operand; + EXPECT_THAT( + CompiledInstructions(input.str(), get<0>(GetParam())), + Eq(MakeInstruction(SpvOpExecutionMode, {1, get<1>(GetParam()).value()}, + get<1>(GetParam()).operands()))); +} + +#define CASE(NAME) SpvExecutionMode##NAME, #NAME +INSTANTIATE_TEST_CASE_P( + TextToBinaryExecutionMode, OpExecutionModeTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector>{ + // The operand literal values are arbitrarily chosen, + // but there are the right number of them. + {CASE(Invocations), {101}}, + {CASE(SpacingEqual), {}}, + {CASE(SpacingFractionalEven), {}}, + {CASE(SpacingFractionalOdd), {}}, + {CASE(VertexOrderCw), {}}, + {CASE(VertexOrderCcw), {}}, + {CASE(PixelCenterInteger), {}}, + {CASE(OriginUpperLeft), {}}, + {CASE(OriginLowerLeft), {}}, + {CASE(EarlyFragmentTests), {}}, + {CASE(PointMode), {}}, + {CASE(Xfb), {}}, + {CASE(DepthReplacing), {}}, + {CASE(DepthGreater), {}}, + {CASE(DepthLess), {}}, + {CASE(DepthUnchanged), {}}, + {CASE(LocalSize), {64, 1, 2}}, + {CASE(LocalSizeHint), {8, 2, 4}}, + {CASE(InputPoints), {}}, + {CASE(InputLines), {}}, + {CASE(InputLinesAdjacency), {}}, + {CASE(Triangles), {}}, + {CASE(InputTrianglesAdjacency), {}}, + {CASE(Quads), {}}, + {CASE(Isolines), {}}, + {CASE(OutputVertices), {21}}, + {CASE(OutputPoints), {}}, + {CASE(OutputLineStrip), {}}, + {CASE(OutputTriangleStrip), {}}, + {CASE(VecTypeHint), {96}}, + {CASE(ContractionOff), {}}, + })), ); + +INSTANTIATE_TEST_CASE_P( + TextToBinaryExecutionModeV11, OpExecutionModeTest, + Combine(Values(SPV_ENV_UNIVERSAL_1_1), + ValuesIn(std::vector>{ + {CASE(Initializer)}, + {CASE(Finalizer)}, + {CASE(SubgroupSize), {12}}, + {CASE(SubgroupsPerWorkgroup), {64}}})), ); +#undef CASE + +TEST_F(OpExecutionModeTest, WrongMode) { + EXPECT_THAT(CompileFailure("OpExecutionMode %1 xxyyzz"), + Eq("Invalid execution mode 'xxyyzz'.")); +} + +TEST_F(OpExecutionModeTest, TooManyModes) { + EXPECT_THAT(CompileFailure("OpExecutionMode %1 Xfb PointMode"), + Eq("Expected or at the beginning of an " + "instruction, found 'PointMode'.")); +} + +// Test OpCapability + +using OpCapabilityTest = + spvtest::TextToBinaryTestBase>>; + +TEST_P(OpCapabilityTest, AnyCapability) { + const std::string input = "OpCapability " + GetParam().name(); + EXPECT_THAT(CompiledInstructions(input), + Eq(MakeInstruction(SpvOpCapability, {GetParam().value()}))); +} + +// clang-format off +#define CASE(NAME) { SpvCapability##NAME, #NAME } +INSTANTIATE_TEST_CASE_P(TextToBinaryCapability, OpCapabilityTest, + ValuesIn(std::vector>{ + CASE(Matrix), + CASE(Shader), + CASE(Geometry), + CASE(Tessellation), + CASE(Addresses), + CASE(Linkage), + CASE(Kernel), + CASE(Vector16), + CASE(Float16Buffer), + CASE(Float16), + CASE(Float64), + CASE(Int64), + CASE(Int64Atomics), + CASE(ImageBasic), + CASE(ImageReadWrite), + CASE(ImageMipmap), + // Value 16 intentionally missing + CASE(Pipes), + CASE(Groups), + CASE(DeviceEnqueue), + CASE(LiteralSampler), + CASE(AtomicStorage), + CASE(Int16), + CASE(TessellationPointSize), + CASE(GeometryPointSize), + CASE(ImageGatherExtended), + // Value 26 intentionally missing + CASE(StorageImageMultisample), + CASE(UniformBufferArrayDynamicIndexing), + CASE(SampledImageArrayDynamicIndexing), + CASE(StorageBufferArrayDynamicIndexing), + CASE(StorageImageArrayDynamicIndexing), + CASE(ClipDistance), + CASE(CullDistance), + CASE(ImageCubeArray), + CASE(SampleRateShading), + CASE(ImageRect), + CASE(SampledRect), + CASE(GenericPointer), + CASE(Int8), + CASE(InputAttachment), + CASE(SparseResidency), + CASE(MinLod), + CASE(Sampled1D), + CASE(Image1D), + CASE(SampledCubeArray), + CASE(SampledBuffer), + CASE(ImageBuffer), + CASE(ImageMSArray), + CASE(StorageImageExtendedFormats), + CASE(ImageQuery), + CASE(DerivativeControl), + CASE(InterpolationFunction), + CASE(TransformFeedback), + }),); +#undef CASE +// clang-format on + +using TextToBinaryCapability = spvtest::TextToBinaryTest; + +TEST_F(TextToBinaryCapability, BadMissingCapability) { + EXPECT_THAT(CompileFailure("OpCapability"), + Eq("Expected operand, found end of stream.")); +} + +TEST_F(TextToBinaryCapability, BadInvalidCapability) { + EXPECT_THAT(CompileFailure("OpCapability 123"), + Eq("Invalid capability '123'.")); +} + +// TODO(dneto): OpExecutionMode + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/text_to_binary.pipe_storage_test.cpp b/3rdparty/spirv-tools/test/text_to_binary.pipe_storage_test.cpp new file mode 100644 index 000000000..97dc286f5 --- /dev/null +++ b/3rdparty/spirv-tools/test/text_to_binary.pipe_storage_test.cpp @@ -0,0 +1,121 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "gmock/gmock.h" +#include "test_fixture.h" + +namespace { + +using ::spvtest::MakeInstruction; +using ::testing::Eq; + +using OpTypePipeStorageTest = spvtest::TextToBinaryTest; + +TEST_F(OpTypePipeStorageTest, OpcodeUnrecognizedInV10) { + EXPECT_THAT(CompileFailure("%res = OpTypePipeStorage", SPV_ENV_UNIVERSAL_1_0), + Eq("Invalid Opcode name 'OpTypePipeStorage'")); +} + +TEST_F(OpTypePipeStorageTest, ArgumentCount) { + EXPECT_THAT( + CompileFailure("OpTypePipeStorage", SPV_ENV_UNIVERSAL_1_1), + Eq("Expected at the beginning of an instruction, found " + "'OpTypePipeStorage'.")); + EXPECT_THAT( + CompiledInstructions("%res = OpTypePipeStorage", SPV_ENV_UNIVERSAL_1_1), + Eq(MakeInstruction(SpvOpTypePipeStorage, {1}))); + EXPECT_THAT(CompileFailure("%res = OpTypePipeStorage %1 %2 %3 %4 %5", + SPV_ENV_UNIVERSAL_1_1), + Eq("'=' expected after result id.")); +} + +using OpConstantPipeStorageTest = spvtest::TextToBinaryTest; + +TEST_F(OpConstantPipeStorageTest, OpcodeUnrecognizedInV10) { + EXPECT_THAT(CompileFailure("%1 = OpConstantPipeStorage %2 3 4 5", + SPV_ENV_UNIVERSAL_1_0), + Eq("Invalid Opcode name 'OpConstantPipeStorage'")); +} + +TEST_F(OpConstantPipeStorageTest, ArgumentCount) { + EXPECT_THAT( + CompileFailure("OpConstantPipeStorage", SPV_ENV_UNIVERSAL_1_1), + Eq("Expected at the beginning of an instruction, found " + "'OpConstantPipeStorage'.")); + EXPECT_THAT( + CompileFailure("%1 = OpConstantPipeStorage", SPV_ENV_UNIVERSAL_1_1), + Eq("Expected operand, found end of stream.")); + EXPECT_THAT(CompileFailure("%1 = OpConstantPipeStorage %2 3 4", + SPV_ENV_UNIVERSAL_1_1), + Eq("Expected operand, found end of stream.")); + EXPECT_THAT(CompiledInstructions("%1 = OpConstantPipeStorage %2 3 4 5", + SPV_ENV_UNIVERSAL_1_1), + Eq(MakeInstruction(SpvOpConstantPipeStorage, {1, 2, 3, 4, 5}))); + EXPECT_THAT(CompileFailure("%1 = OpConstantPipeStorage %2 3 4 5 %6 %7", + SPV_ENV_UNIVERSAL_1_1), + Eq("'=' expected after result id.")); +} + +TEST_F(OpConstantPipeStorageTest, ArgumentTypes) { + EXPECT_THAT(CompileFailure("%1 = OpConstantPipeStorage %2 %3 4 5", + SPV_ENV_UNIVERSAL_1_1), + Eq("Invalid unsigned integer literal: %3")); + EXPECT_THAT(CompileFailure("%1 = OpConstantPipeStorage %2 3 %4 5", + SPV_ENV_UNIVERSAL_1_1), + Eq("Invalid unsigned integer literal: %4")); + EXPECT_THAT(CompileFailure("%1 = OpConstantPipeStorage 2 3 4 5", + SPV_ENV_UNIVERSAL_1_1), + Eq("Expected id to start with %.")); + EXPECT_THAT(CompileFailure("%1 = OpConstantPipeStorage %2 3 4 \"ab\"", + SPV_ENV_UNIVERSAL_1_1), + Eq("Invalid unsigned integer literal: \"ab\"")); +} + +using OpCreatePipeFromPipeStorageTest = spvtest::TextToBinaryTest; + +TEST_F(OpCreatePipeFromPipeStorageTest, OpcodeUnrecognizedInV10) { + EXPECT_THAT(CompileFailure("%1 = OpCreatePipeFromPipeStorage %2 %3", + SPV_ENV_UNIVERSAL_1_0), + Eq("Invalid Opcode name 'OpCreatePipeFromPipeStorage'")); +} + +TEST_F(OpCreatePipeFromPipeStorageTest, ArgumentCount) { + EXPECT_THAT( + CompileFailure("OpCreatePipeFromPipeStorage", SPV_ENV_UNIVERSAL_1_1), + Eq("Expected at the beginning of an instruction, found " + "'OpCreatePipeFromPipeStorage'.")); + EXPECT_THAT( + CompileFailure("%1 = OpCreatePipeFromPipeStorage", SPV_ENV_UNIVERSAL_1_1), + Eq("Expected operand, found end of stream.")); + EXPECT_THAT(CompileFailure("%1 = OpCreatePipeFromPipeStorage %2 OpNop", + SPV_ENV_UNIVERSAL_1_1), + Eq("Expected operand, found next instruction instead.")); + EXPECT_THAT(CompiledInstructions("%1 = OpCreatePipeFromPipeStorage %2 %3", + SPV_ENV_UNIVERSAL_1_1), + Eq(MakeInstruction(SpvOpCreatePipeFromPipeStorage, {1, 2, 3}))); + EXPECT_THAT(CompileFailure("%1 = OpCreatePipeFromPipeStorage %2 %3 %4 %5", + SPV_ENV_UNIVERSAL_1_1), + Eq("'=' expected after result id.")); +} + +TEST_F(OpCreatePipeFromPipeStorageTest, ArgumentTypes) { + EXPECT_THAT(CompileFailure("%1 = OpCreatePipeFromPipeStorage \"\" %3", + SPV_ENV_UNIVERSAL_1_1), + Eq("Expected id to start with %.")); + EXPECT_THAT(CompileFailure("%1 = OpCreatePipeFromPipeStorage %2 3", + SPV_ENV_UNIVERSAL_1_1), + Eq("Expected id to start with %.")); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/text_to_binary.reserved_sampling_test.cpp b/3rdparty/spirv-tools/test/text_to_binary.reserved_sampling_test.cpp new file mode 100644 index 000000000..db9a53dc6 --- /dev/null +++ b/3rdparty/spirv-tools/test/text_to_binary.reserved_sampling_test.cpp @@ -0,0 +1,57 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Validation tests for illegal instructions + +#include "unit_spirv.h" + +#include "gmock/gmock.h" +#include "test_fixture.h" + +namespace { + +using ::testing::Eq; + +using ReservedSamplingInstTest = spvtest::TextToBinaryTest; + +TEST_F(ReservedSamplingInstTest, OpImageSparseSampleProjImplicitLod) { + const std::string input = "OpImageSparseSampleProjImplicitLod %1 %2 %3\n"; + EXPECT_THAT(CompileFailure(input), + Eq("Invalid Opcode name 'OpImageSparseSampleProjImplicitLod'")); +} + +TEST_F(ReservedSamplingInstTest, OpImageSparseSampleProjExplicitLod) { + const std::string input = + "OpImageSparseSampleProjExplicitLod %1 %2 %3 Lod %4\n"; + EXPECT_THAT(CompileFailure(input), + Eq("Invalid Opcode name 'OpImageSparseSampleProjExplicitLod'")); +} + +TEST_F(ReservedSamplingInstTest, OpImageSparseSampleProjDrefImplicitLod) { + const std::string input = + "OpImageSparseSampleProjDrefImplicitLod %1 %2 %3 %4\n"; + EXPECT_THAT( + CompileFailure(input), + Eq("Invalid Opcode name 'OpImageSparseSampleProjDrefImplicitLod'")); +} + +TEST_F(ReservedSamplingInstTest, OpImageSparseSampleProjDrefExplicitLod) { + const std::string input = + "OpImageSparseSampleProjDrefExplicitLod %1 %2 %3 %4 Lod %5\n"; + EXPECT_THAT( + CompileFailure(input), + Eq("Invalid Opcode name 'OpImageSparseSampleProjDrefExplicitLod'")); +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/text_to_binary.subgroup_dispatch_test.cpp b/3rdparty/spirv-tools/test/text_to_binary.subgroup_dispatch_test.cpp new file mode 100644 index 000000000..8cc8896ff --- /dev/null +++ b/3rdparty/spirv-tools/test/text_to_binary.subgroup_dispatch_test.cpp @@ -0,0 +1,117 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Assembler tests for instructions in the "Barrier Instructions" section +// of the SPIR-V spec. + +#include "unit_spirv.h" + +#include "gmock/gmock.h" +#include "test_fixture.h" + +namespace { + +using ::spvtest::MakeInstruction; +using std::vector; +using ::testing::Eq; + +using OpGetKernelLocalSizeForSubgroupCountTest = spvtest::TextToBinaryTest; + +TEST_F(OpGetKernelLocalSizeForSubgroupCountTest, OpcodeUnrecognizedInV10) { + EXPECT_THAT( + CompileFailure("%res = OpGetKernelLocalSizeForSubgroupCount %type " + "%sgcount %invoke %param %param_size %param_align", + SPV_ENV_UNIVERSAL_1_0), + Eq("Invalid Opcode name 'OpGetKernelLocalSizeForSubgroupCount'")); +} + +TEST_F(OpGetKernelLocalSizeForSubgroupCountTest, ArgumentCount) { + EXPECT_THAT(CompileFailure("OpGetKernelLocalSizeForSubgroupCount", + SPV_ENV_UNIVERSAL_1_1), + Eq("Expected at the beginning of an instruction, " + "found 'OpGetKernelLocalSizeForSubgroupCount'.")); + EXPECT_THAT(CompileFailure("%res = OpGetKernelLocalSizeForSubgroupCount", + SPV_ENV_UNIVERSAL_1_1), + Eq("Expected operand, found end of stream.")); + EXPECT_THAT( + CompileFailure("%1 = OpGetKernelLocalSizeForSubgroupCount %2 %3 %4 %5 %6", + SPV_ENV_UNIVERSAL_1_1), + Eq("Expected operand, found end of stream.")); + EXPECT_THAT( + CompiledInstructions("%res = OpGetKernelLocalSizeForSubgroupCount %type " + "%sgcount %invoke %param %param_size %param_align", + SPV_ENV_UNIVERSAL_1_1), + Eq(MakeInstruction(SpvOpGetKernelLocalSizeForSubgroupCount, + {1, 2, 3, 4, 5, 6, 7}))); + EXPECT_THAT( + CompileFailure("%res = OpGetKernelLocalSizeForSubgroupCount %type " + "%sgcount %invoke %param %param_size %param_align %extra", + SPV_ENV_UNIVERSAL_1_1), + Eq("Expected '=', found end of stream.")); +} + +TEST_F(OpGetKernelLocalSizeForSubgroupCountTest, ArgumentTypes) { + EXPECT_THAT(CompileFailure( + "%1 = OpGetKernelLocalSizeForSubgroupCount 2 %3 %4 %5 %6 %7", + SPV_ENV_UNIVERSAL_1_1), + Eq("Expected id to start with %.")); + EXPECT_THAT( + CompileFailure( + "%1 = OpGetKernelLocalSizeForSubgroupCount %2 %3 %4 %5 %6 \"abc\"", + SPV_ENV_UNIVERSAL_1_1), + Eq("Expected id to start with %.")); +} + +using OpGetKernelMaxNumSubgroupsTest = spvtest::TextToBinaryTest; + +TEST_F(OpGetKernelMaxNumSubgroupsTest, OpcodeUnrecognizedInV10) { + EXPECT_THAT(CompileFailure("%res = OpGetKernelLocalSizeForSubgroupCount " + "%type %invoke %param %param_size %param_align", + SPV_ENV_UNIVERSAL_1_0), + Eq("Invalid Opcode name 'OpGetKernelLocalSizeForSubgroupCount'")); +} + +TEST_F(OpGetKernelMaxNumSubgroupsTest, ArgumentCount) { + EXPECT_THAT( + CompileFailure("OpGetKernelMaxNumSubgroups", SPV_ENV_UNIVERSAL_1_1), + Eq("Expected at the beginning of an instruction, found " + "'OpGetKernelMaxNumSubgroups'.")); + EXPECT_THAT(CompileFailure("%res = OpGetKernelMaxNumSubgroups", + SPV_ENV_UNIVERSAL_1_1), + Eq("Expected operand, found end of stream.")); + EXPECT_THAT(CompileFailure("%1 = OpGetKernelMaxNumSubgroups %2 %3 %4 %5", + SPV_ENV_UNIVERSAL_1_1), + Eq("Expected operand, found end of stream.")); + EXPECT_THAT( + CompiledInstructions("%res = OpGetKernelMaxNumSubgroups %type " + "%invoke %param %param_size %param_align", + SPV_ENV_UNIVERSAL_1_1), + Eq(MakeInstruction(SpvOpGetKernelMaxNumSubgroups, {1, 2, 3, 4, 5, 6}))); + EXPECT_THAT(CompileFailure("%res = OpGetKernelMaxNumSubgroups %type %invoke " + "%param %param_size %param_align %extra", + SPV_ENV_UNIVERSAL_1_1), + Eq("Expected '=', found end of stream.")); +} + +TEST_F(OpGetKernelMaxNumSubgroupsTest, ArgumentTypes) { + EXPECT_THAT(CompileFailure("%1 = OpGetKernelMaxNumSubgroups 2 %3 %4 %5 %6", + SPV_ENV_UNIVERSAL_1_1), + Eq("Expected id to start with %.")); + EXPECT_THAT( + CompileFailure("%1 = OpGetKernelMaxNumSubgroups %2 %3 %4 %5 \"abc\"", + SPV_ENV_UNIVERSAL_1_1), + Eq("Expected id to start with %.")); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/text_to_binary.type_declaration_test.cpp b/3rdparty/spirv-tools/test/text_to_binary.type_declaration_test.cpp new file mode 100644 index 000000000..be8d6916e --- /dev/null +++ b/3rdparty/spirv-tools/test/text_to_binary.type_declaration_test.cpp @@ -0,0 +1,286 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Assembler tests for instructions in the "Type-Declaration" section of the +// SPIR-V spec. + +#include "unit_spirv.h" + +#include "gmock/gmock.h" +#include "test_fixture.h" + +namespace { + +using spvtest::EnumCase; +using spvtest::MakeInstruction; +using ::testing::Eq; + +// Test Dim enums via OpTypeImage + +using DimTest = + spvtest::TextToBinaryTestBase<::testing::TestWithParam>>; + +TEST_P(DimTest, AnyDim) { + const std::string input = + "%1 = OpTypeImage %2 " + GetParam().name() + " 2 3 0 4 Rgba8\n"; + EXPECT_THAT( + CompiledInstructions(input), + Eq(MakeInstruction(SpvOpTypeImage, {1, 2, GetParam().value(), 2, 3, 0, 4, + SpvImageFormatRgba8}))); + + // Check the disassembler as well. + EXPECT_THAT(EncodeAndDecodeSuccessfully(input), Eq(input)); +} + +// clang-format off +#define CASE(NAME) {SpvDim##NAME, #NAME} +INSTANTIATE_TEST_CASE_P( + TextToBinaryDim, DimTest, + ::testing::ValuesIn(std::vector>{ + CASE(1D), + CASE(2D), + CASE(3D), + CASE(Cube), + CASE(Rect), + CASE(Buffer), + CASE(SubpassData), + }),); +#undef CASE +// clang-format on + +TEST_F(DimTest, WrongDim) { + EXPECT_THAT(CompileFailure("%i = OpTypeImage %t xxyyzz 1 2 3 4 R8"), + Eq("Invalid dimensionality 'xxyyzz'.")); +} + +// Test ImageFormat enums via OpTypeImage + +using ImageFormatTest = spvtest::TextToBinaryTestBase< + ::testing::TestWithParam>>; + +TEST_P(ImageFormatTest, AnyImageFormatAndNoAccessQualifier) { + const std::string input = + "%1 = OpTypeImage %2 1D 2 3 0 4 " + GetParam().name() + "\n"; + EXPECT_THAT(CompiledInstructions(input), + Eq(MakeInstruction(SpvOpTypeImage, {1, 2, SpvDim1D, 2, 3, 0, 4, + GetParam().value()}))); + // Check the disassembler as well. + EXPECT_THAT(EncodeAndDecodeSuccessfully(input), Eq(input)); +} + +// clang-format off +#define CASE(NAME) {SpvImageFormat##NAME, #NAME} +INSTANTIATE_TEST_CASE_P( + TextToBinaryImageFormat, ImageFormatTest, + ::testing::ValuesIn(std::vector>{ + CASE(Unknown), + CASE(Rgba32f), + CASE(Rgba16f), + CASE(R32f), + CASE(Rgba8), + CASE(Rgba8Snorm), + CASE(Rg32f), + CASE(Rg16f), + CASE(R11fG11fB10f), + CASE(R16f), + CASE(Rgba16), + CASE(Rgb10A2), + CASE(Rg16), + CASE(Rg8), + CASE(R16), + CASE(R8), + CASE(Rgba16Snorm), + CASE(Rg16Snorm), + CASE(Rg8Snorm), + CASE(R16Snorm), + CASE(R8Snorm), + CASE(Rgba32i), + CASE(Rgba16i), + CASE(Rgba8i), + CASE(R32i), + CASE(Rg32i), + CASE(Rg16i), + CASE(Rg8i), + CASE(R16i), + CASE(R8i), + CASE(Rgba32ui), + CASE(Rgba16ui), + CASE(Rgba8ui), + CASE(R32ui), + CASE(Rgb10a2ui), + CASE(Rg32ui), + CASE(Rg16ui), + CASE(Rg8ui), + CASE(R16ui), + CASE(R8ui), + }),); +#undef CASE +// clang-format on + +TEST_F(ImageFormatTest, WrongFormat) { + EXPECT_THAT(CompileFailure("%r = OpTypeImage %t 1D 2 3 0 4 xxyyzz"), + Eq("Invalid image format 'xxyyzz'.")); +} + +// Test AccessQualifier enums via OpTypeImage. +using ImageAccessQualifierTest = spvtest::TextToBinaryTestBase< + ::testing::TestWithParam>>; + +TEST_P(ImageAccessQualifierTest, AnyAccessQualifier) { + const std::string input = + "%1 = OpTypeImage %2 1D 2 3 0 4 Rgba8 " + GetParam().name() + "\n"; + EXPECT_THAT(CompiledInstructions(input), + Eq(MakeInstruction(SpvOpTypeImage, + {1, 2, SpvDim1D, 2, 3, 0, 4, + SpvImageFormatRgba8, GetParam().value()}))); + // Check the disassembler as well. + EXPECT_THAT(EncodeAndDecodeSuccessfully(input), Eq(input)); +} + +// clang-format off +#define CASE(NAME) {SpvAccessQualifier##NAME, #NAME} +INSTANTIATE_TEST_CASE_P( + AccessQualifier, ImageAccessQualifierTest, + ::testing::ValuesIn(std::vector>{ + CASE(ReadOnly), + CASE(WriteOnly), + CASE(ReadWrite), + }),); +// clang-format on +#undef CASE + +// Test AccessQualifier enums via OpTypePipe. + +using OpTypePipeTest = spvtest::TextToBinaryTestBase< + ::testing::TestWithParam>>; + +TEST_P(OpTypePipeTest, AnyAccessQualifier) { + const std::string input = "%1 = OpTypePipe " + GetParam().name() + "\n"; + EXPECT_THAT(CompiledInstructions(input), + Eq(MakeInstruction(SpvOpTypePipe, {1, GetParam().value()}))); + // Check the disassembler as well. + EXPECT_THAT(EncodeAndDecodeSuccessfully(input), Eq(input)); +} + +// clang-format off +#define CASE(NAME) {SpvAccessQualifier##NAME, #NAME} +INSTANTIATE_TEST_CASE_P( + TextToBinaryTypePipe, OpTypePipeTest, + ::testing::ValuesIn(std::vector>{ + CASE(ReadOnly), + CASE(WriteOnly), + CASE(ReadWrite), + }),); +#undef CASE +// clang-format on + +TEST_F(OpTypePipeTest, WrongAccessQualifier) { + EXPECT_THAT(CompileFailure("%1 = OpTypePipe xxyyzz"), + Eq("Invalid access qualifier 'xxyyzz'.")); +} + +using OpTypeForwardPointerTest = spvtest::TextToBinaryTest; + +#define CASE(storage_class) \ + do { \ + EXPECT_THAT( \ + CompiledInstructions("OpTypeForwardPointer %pt " #storage_class), \ + Eq(MakeInstruction(SpvOpTypeForwardPointer, \ + {1, SpvStorageClass##storage_class}))); \ + } while (0) + +TEST_F(OpTypeForwardPointerTest, ValidStorageClass) { + CASE(UniformConstant); + CASE(Input); + CASE(Uniform); + CASE(Output); + CASE(Workgroup); + CASE(CrossWorkgroup); + CASE(Private); + CASE(Function); + CASE(Generic); + CASE(PushConstant); + CASE(AtomicCounter); + CASE(Image); + CASE(StorageBuffer); +} + +#undef CASE + +TEST_F(OpTypeForwardPointerTest, MissingType) { + EXPECT_THAT(CompileFailure("OpTypeForwardPointer"), + Eq("Expected operand, found end of stream.")); +} + +TEST_F(OpTypeForwardPointerTest, MissingClass) { + EXPECT_THAT(CompileFailure("OpTypeForwardPointer %pt"), + Eq("Expected operand, found end of stream.")); +} + +TEST_F(OpTypeForwardPointerTest, WrongClass) { + EXPECT_THAT(CompileFailure("OpTypeForwardPointer %pt xxyyzz"), + Eq("Invalid storage class 'xxyyzz'.")); +} + +using OpSizeOfTest = spvtest::TextToBinaryTest; + +TEST_F(OpSizeOfTest, OpcodeUnrecognizedInV10) { + EXPECT_THAT(CompileFailure("%1 = OpSizeOf %2 %3", SPV_ENV_UNIVERSAL_1_0), + Eq("Invalid Opcode name 'OpSizeOf'")); +} + +TEST_F(OpSizeOfTest, ArgumentCount) { + EXPECT_THAT( + CompileFailure("OpSizeOf", SPV_ENV_UNIVERSAL_1_1), + Eq("Expected at the beginning of an instruction, found " + "'OpSizeOf'.")); + EXPECT_THAT(CompileFailure("%res = OpSizeOf OpNop", SPV_ENV_UNIVERSAL_1_1), + Eq("Expected operand, found next instruction instead.")); + EXPECT_THAT( + CompiledInstructions("%1 = OpSizeOf %2 %3", SPV_ENV_UNIVERSAL_1_1), + Eq(MakeInstruction(SpvOpSizeOf, {1, 2, 3}))); + EXPECT_THAT( + CompileFailure("%1 = OpSizeOf %2 %3 44 55 ", SPV_ENV_UNIVERSAL_1_1), + Eq("Expected or at the beginning of an instruction, " + "found '44'.")); +} + +TEST_F(OpSizeOfTest, ArgumentTypes) { + EXPECT_THAT(CompileFailure("%1 = OpSizeOf 2 %3", SPV_ENV_UNIVERSAL_1_1), + Eq("Expected id to start with %.")); + EXPECT_THAT(CompileFailure("%1 = OpSizeOf %2 \"abc\"", SPV_ENV_UNIVERSAL_1_1), + Eq("Expected id to start with %.")); +} + +// TODO(dneto): OpTypeVoid +// TODO(dneto): OpTypeBool +// TODO(dneto): OpTypeInt +// TODO(dneto): OpTypeFloat +// TODO(dneto): OpTypeVector +// TODO(dneto): OpTypeMatrix +// TODO(dneto): OpTypeImage +// TODO(dneto): OpTypeSampler +// TODO(dneto): OpTypeSampledImage +// TODO(dneto): OpTypeArray +// TODO(dneto): OpTypeRuntimeArray +// TODO(dneto): OpTypeStruct +// TODO(dneto): OpTypeOpaque +// TODO(dneto): OpTypePointer +// TODO(dneto): OpTypeFunction +// TODO(dneto): OpTypeEvent +// TODO(dneto): OpTypeDeviceEvent +// TODO(dneto): OpTypeReserveId +// TODO(dneto): OpTypeQueue + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/text_to_binary_test.cpp b/3rdparty/spirv-tools/test/text_to_binary_test.cpp new file mode 100644 index 000000000..e9a709b6f --- /dev/null +++ b/3rdparty/spirv-tools/test/text_to_binary_test.cpp @@ -0,0 +1,269 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include "gmock/gmock.h" + +#include "source/spirv_constant.h" +#include "source/util/bitutils.h" +#include "source/util/hex_float.h" +#include "test_fixture.h" +#include "unit_spirv.h" + +namespace { + +using libspirv::AssemblyContext; +using libspirv::AssemblyGrammar; +using spvtest::AutoText; +using spvtest::Concatenate; +using spvtest::MakeInstruction; +using spvtest::ScopedContext; +using spvtest::TextToBinaryTest; +using testing::Eq; +using testing::IsNull; +using testing::NotNull; + +// An mask parsing test case. +struct MaskCase { + spv_operand_type_t which_enum; + uint32_t expected_value; + const char* expression; +}; + +using GoodMaskParseTest = ::testing::TestWithParam; + +TEST_P(GoodMaskParseTest, GoodMaskExpressions) { + spv_context context = spvContextCreate(SPV_ENV_UNIVERSAL_1_0); + + uint32_t value; + EXPECT_EQ(SPV_SUCCESS, + AssemblyGrammar(context).parseMaskOperand( + GetParam().which_enum, GetParam().expression, &value)); + EXPECT_EQ(GetParam().expected_value, value); + + spvContextDestroy(context); +} + +INSTANTIATE_TEST_CASE_P( + ParseMask, GoodMaskParseTest, + ::testing::ValuesIn(std::vector{ + {SPV_OPERAND_TYPE_FP_FAST_MATH_MODE, 0, "None"}, + {SPV_OPERAND_TYPE_FP_FAST_MATH_MODE, 1, "NotNaN"}, + {SPV_OPERAND_TYPE_FP_FAST_MATH_MODE, 2, "NotInf"}, + {SPV_OPERAND_TYPE_FP_FAST_MATH_MODE, 3, "NotNaN|NotInf"}, + // Mask experssions are symmetric. + {SPV_OPERAND_TYPE_FP_FAST_MATH_MODE, 3, "NotInf|NotNaN"}, + // Repeating a value has no effect. + {SPV_OPERAND_TYPE_FP_FAST_MATH_MODE, 3, "NotInf|NotNaN|NotInf"}, + // Using 3 operands still works. + {SPV_OPERAND_TYPE_FP_FAST_MATH_MODE, 0x13, "NotInf|NotNaN|Fast"}, + {SPV_OPERAND_TYPE_SELECTION_CONTROL, 0, "None"}, + {SPV_OPERAND_TYPE_SELECTION_CONTROL, 1, "Flatten"}, + {SPV_OPERAND_TYPE_SELECTION_CONTROL, 2, "DontFlatten"}, + // Weirdly, you can specify to flatten and don't flatten a selection. + {SPV_OPERAND_TYPE_SELECTION_CONTROL, 3, "Flatten|DontFlatten"}, + {SPV_OPERAND_TYPE_LOOP_CONTROL, 0, "None"}, + {SPV_OPERAND_TYPE_LOOP_CONTROL, 1, "Unroll"}, + {SPV_OPERAND_TYPE_LOOP_CONTROL, 2, "DontUnroll"}, + // Weirdly, you can specify to unroll and don't unroll a loop. + {SPV_OPERAND_TYPE_LOOP_CONTROL, 3, "Unroll|DontUnroll"}, + {SPV_OPERAND_TYPE_FUNCTION_CONTROL, 0, "None"}, + {SPV_OPERAND_TYPE_FUNCTION_CONTROL, 1, "Inline"}, + {SPV_OPERAND_TYPE_FUNCTION_CONTROL, 2, "DontInline"}, + {SPV_OPERAND_TYPE_FUNCTION_CONTROL, 4, "Pure"}, + {SPV_OPERAND_TYPE_FUNCTION_CONTROL, 8, "Const"}, + {SPV_OPERAND_TYPE_FUNCTION_CONTROL, 0xd, "Inline|Const|Pure"}, + }), ); + +using BadFPFastMathMaskParseTest = ::testing::TestWithParam; + +TEST_P(BadFPFastMathMaskParseTest, BadMaskExpressions) { + spv_context context = spvContextCreate(SPV_ENV_UNIVERSAL_1_0); + + uint32_t value; + EXPECT_NE(SPV_SUCCESS, + AssemblyGrammar(context).parseMaskOperand( + SPV_OPERAND_TYPE_FP_FAST_MATH_MODE, GetParam(), &value)); + + spvContextDestroy(context); +} + +INSTANTIATE_TEST_CASE_P(ParseMask, BadFPFastMathMaskParseTest, + ::testing::ValuesIn(std::vector{ + nullptr, "", "NotValidEnum", "|", "NotInf|", + "|NotInf", "NotInf||NotNaN", + "Unroll" // A good word, but for the wrong enum + }), ); + +TEST_F(TextToBinaryTest, InvalidText) { + ASSERT_EQ(SPV_ERROR_INVALID_TEXT, + spvTextToBinary(ScopedContext().context, nullptr, 0, &binary, + &diagnostic)); + EXPECT_NE(nullptr, diagnostic); + EXPECT_THAT(diagnostic->error, Eq(std::string("Missing assembly text."))); +} + +TEST_F(TextToBinaryTest, InvalidPointer) { + SetText( + "OpEntryPoint Kernel 0 \"\"\nOpExecutionMode 0 LocalSizeHint 1 1 1\n"); + ASSERT_EQ(SPV_ERROR_INVALID_POINTER, + spvTextToBinary(ScopedContext().context, text.str, text.length, + nullptr, &diagnostic)); +} + +TEST_F(TextToBinaryTest, InvalidPrefix) { + EXPECT_EQ( + "Expected or at the beginning of an instruction, " + "found 'Invalid'.", + CompileFailure("Invalid")); +} + +TEST_F(TextToBinaryTest, EmptyAssemblyString) { + // An empty assembly module is valid! + // It should produce a valid module with zero instructions. + EXPECT_THAT(CompiledInstructions(""), Eq(std::vector{})); +} + +TEST_F(TextToBinaryTest, StringSpace) { + const std::string code = ("OpSourceExtension \"string with spaces\"\n"); + EXPECT_EQ(code, EncodeAndDecodeSuccessfully(code)); +} + +TEST_F(TextToBinaryTest, UnknownBeginningOfInstruction) { + EXPECT_EQ( + "Expected or at the beginning of an instruction, " + "found 'Google'.", + CompileFailure( + "\nOpSource OpenCL_C 12\nOpMemoryModel Physical64 OpenCL\nGoogle\n")); + EXPECT_EQ(4u, diagnostic->position.line + 1); + EXPECT_EQ(1u, diagnostic->position.column + 1); +} + +TEST_F(TextToBinaryTest, NoEqualSign) { + EXPECT_EQ("Expected '=', found end of stream.", + CompileFailure("\nOpSource OpenCL_C 12\n" + "OpMemoryModel Physical64 OpenCL\n%2\n")); + EXPECT_EQ(5u, diagnostic->position.line + 1); + EXPECT_EQ(1u, diagnostic->position.column + 1); +} + +TEST_F(TextToBinaryTest, NoOpCode) { + EXPECT_EQ("Expected opcode, found end of stream.", + CompileFailure("\nOpSource OpenCL_C 12\n" + "OpMemoryModel Physical64 OpenCL\n%2 =\n")); + EXPECT_EQ(5u, diagnostic->position.line + 1); + EXPECT_EQ(1u, diagnostic->position.column + 1); +} + +TEST_F(TextToBinaryTest, WrongOpCode) { + EXPECT_EQ("Invalid Opcode prefix 'Wahahaha'.", + CompileFailure("\nOpSource OpenCL_C 12\n" + "OpMemoryModel Physical64 OpenCL\n%2 = Wahahaha\n")); + EXPECT_EQ(4u, diagnostic->position.line + 1); + EXPECT_EQ(6u, diagnostic->position.column + 1); +} + +TEST_F(TextToBinaryTest, CRLF) { + const std::string input = + "%i32 = OpTypeInt 32 1\r\n%c = OpConstant %i32 123\r\n"; + EXPECT_THAT(CompiledInstructions(input), + Eq(Concatenate({MakeInstruction(SpvOpTypeInt, {1, 32, 1}), + MakeInstruction(SpvOpConstant, {1, 2, 123})}))); +} + +using TextToBinaryFloatValueTest = spvtest::TextToBinaryTestBase< + ::testing::TestWithParam>>; + +TEST_P(TextToBinaryFloatValueTest, Samples) { + const std::string input = + "%1 = OpTypeFloat 32\n%2 = OpConstant %1 " + GetParam().first; + EXPECT_THAT(CompiledInstructions(input), + Eq(Concatenate({MakeInstruction(SpvOpTypeFloat, {1, 32}), + MakeInstruction(SpvOpConstant, + {1, 2, GetParam().second})}))); +} + +INSTANTIATE_TEST_CASE_P( + FloatValues, TextToBinaryFloatValueTest, + ::testing::ValuesIn(std::vector>{ + {"0.0", 0x00000000}, // +0 + {"!0x00000001", 0x00000001}, // +denorm + {"!0x00800000", 0x00800000}, // +norm + {"1.5", 0x3fc00000}, + {"!0x7f800000", 0x7f800000}, // +inf + {"!0x7f800001", 0x7f800001}, // NaN + + {"-0.0", 0x80000000}, // -0 + {"!0x80000001", 0x80000001}, // -denorm + {"!0x80800000", 0x80800000}, // -norm + {"-2.5", 0xc0200000}, + {"!0xff800000", 0xff800000}, // -inf + {"!0xff800001", 0xff800001}, // NaN + }), ); + +using TextToBinaryHalfValueTest = spvtest::TextToBinaryTestBase< + ::testing::TestWithParam>>; + +TEST_P(TextToBinaryHalfValueTest, Samples) { + const std::string input = + "%1 = OpTypeFloat 16\n%2 = OpConstant %1 " + GetParam().first; + EXPECT_THAT(CompiledInstructions(input), + Eq(Concatenate({MakeInstruction(SpvOpTypeFloat, {1, 16}), + MakeInstruction(SpvOpConstant, + {1, 2, GetParam().second})}))); +} + +INSTANTIATE_TEST_CASE_P( + HalfValues, TextToBinaryHalfValueTest, + ::testing::ValuesIn(std::vector>{ + {"0.0", 0x00000000}, + {"1.0", 0x00003c00}, + {"1.000844", 0x00003c00}, // Truncate to 1.0 + {"1.000977", 0x00003c01}, // Don't have to truncate + {"1.001465", 0x00003c01}, // Truncate to 1.0000977 + {"1.5", 0x00003e00}, + {"-1.0", 0x0000bc00}, + {"2.0", 0x00004000}, + {"-2.0", 0x0000c000}, + {"0x1p1", 0x00004000}, + {"-0x1p1", 0x0000c000}, + {"0x1.8p1", 0x00004200}, + {"0x1.8p4", 0x00004e00}, + {"0x1.801p4", 0x00004e00}, + {"0x1.804p4", 0x00004e01}, + }), ); + +TEST(CreateContext, InvalidEnvironment) { + spv_target_env env; + std::memset(&env, 99, sizeof(env)); + EXPECT_THAT(spvContextCreate(env), IsNull()); +} + +TEST(CreateContext, UniversalEnvironment) { + auto c = spvContextCreate(SPV_ENV_UNIVERSAL_1_0); + EXPECT_THAT(c, NotNull()); + spvContextDestroy(c); +} + +TEST(CreateContext, VulkanEnvironment) { + auto c = spvContextCreate(SPV_ENV_VULKAN_1_0); + EXPECT_THAT(c, NotNull()); + spvContextDestroy(c); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/text_word_get_test.cpp b/3rdparty/spirv-tools/test/text_word_get_test.cpp new file mode 100644 index 000000000..dccc7133a --- /dev/null +++ b/3rdparty/spirv-tools/test/text_word_get_test.cpp @@ -0,0 +1,251 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "unit_spirv.h" + +namespace { + +using libspirv::AssemblyContext; +using spvtest::AutoText; + +#define TAB "\t" +#define NEWLINE "\n" +#define BACKSLASH R"(\)" +#define QUOTE R"(")" + +TEST(TextWordGet, NullTerminator) { + std::string word; + spv_position_t endPosition = {}; + ASSERT_EQ( + SPV_SUCCESS, + AssemblyContext(AutoText("Word"), nullptr).getWord(&word, &endPosition)); + ASSERT_EQ(4u, endPosition.column); + ASSERT_EQ(0u, endPosition.line); + ASSERT_EQ(4u, endPosition.index); + ASSERT_STREQ("Word", word.c_str()); +} + +TEST(TextWordGet, TabTerminator) { + std::string word; + spv_position_t endPosition = {}; + ASSERT_EQ(SPV_SUCCESS, AssemblyContext(AutoText("Word\t"), nullptr) + .getWord(&word, &endPosition)); + ASSERT_EQ(4u, endPosition.column); + ASSERT_EQ(0u, endPosition.line); + ASSERT_EQ(4u, endPosition.index); + ASSERT_STREQ("Word", word.c_str()); +} + +TEST(TextWordGet, SpaceTerminator) { + std::string word; + spv_position_t endPosition = {}; + ASSERT_EQ( + SPV_SUCCESS, + AssemblyContext(AutoText("Word "), nullptr).getWord(&word, &endPosition)); + ASSERT_EQ(4u, endPosition.column); + ASSERT_EQ(0u, endPosition.line); + ASSERT_EQ(4u, endPosition.index); + ASSERT_STREQ("Word", word.c_str()); +} + +TEST(TextWordGet, SemicolonTerminator) { + std::string word; + spv_position_t endPosition = {}; + ASSERT_EQ( + SPV_SUCCESS, + AssemblyContext(AutoText("Wo;rd"), nullptr).getWord(&word, &endPosition)); + ASSERT_EQ(2u, endPosition.column); + ASSERT_EQ(0u, endPosition.line); + ASSERT_EQ(2u, endPosition.index); + ASSERT_STREQ("Wo", word.c_str()); +} + +TEST(TextWordGet, NoTerminator) { + const std::string full_text = "abcdefghijklmn"; + for (size_t len = 1; len <= full_text.size(); ++len) { + std::string word; + spv_text_t text = {full_text.data(), len}; + spv_position_t endPosition = {}; + ASSERT_EQ(SPV_SUCCESS, + AssemblyContext(&text, nullptr).getWord(&word, &endPosition)); + ASSERT_EQ(0u, endPosition.line); + ASSERT_EQ(len, endPosition.column); + ASSERT_EQ(len, endPosition.index); + ASSERT_EQ(full_text.substr(0, len), word); + } +} + +TEST(TextWordGet, MultipleWords) { + AutoText input("Words in a sentence"); + AssemblyContext data(input, nullptr); + + spv_position_t endPosition = {}; + const char* words[] = {"Words", "in", "a", "sentence"}; + + std::string word; + for (uint32_t wordIndex = 0; wordIndex < 4; ++wordIndex) { + ASSERT_EQ(SPV_SUCCESS, data.getWord(&word, &endPosition)); + ASSERT_EQ(strlen(words[wordIndex]), + endPosition.column - data.position().column); + ASSERT_EQ(0u, endPosition.line); + ASSERT_EQ(strlen(words[wordIndex]), + endPosition.index - data.position().index); + ASSERT_STREQ(words[wordIndex], word.c_str()); + + data.setPosition(endPosition); + if (3 != wordIndex) { + ASSERT_EQ(SPV_SUCCESS, data.advance()); + } else { + ASSERT_EQ(SPV_END_OF_STREAM, data.advance()); + } + } +} + +TEST(TextWordGet, QuotesAreKept) { + AutoText input(R"("quotes" "around words")"); + const char* expected[] = {R"("quotes")", R"("around words")"}; + AssemblyContext data(input, nullptr); + + std::string word; + spv_position_t endPosition = {}; + ASSERT_EQ(SPV_SUCCESS, data.getWord(&word, &endPosition)); + EXPECT_EQ(8u, endPosition.column); + EXPECT_EQ(0u, endPosition.line); + EXPECT_EQ(8u, endPosition.index); + EXPECT_STREQ(expected[0], word.c_str()); + + // Move to the next word. + data.setPosition(endPosition); + data.seekForward(1); + + ASSERT_EQ(SPV_SUCCESS, data.getWord(&word, &endPosition)); + EXPECT_EQ(23u, endPosition.column); + EXPECT_EQ(0u, endPosition.line); + EXPECT_EQ(23u, endPosition.index); + EXPECT_STREQ(expected[1], word.c_str()); +} + +TEST(TextWordGet, QuotesBetweenWordsActLikeGlue) { + AutoText input(R"(quotes" "between words)"); + const char* expected[] = {R"(quotes" "between)", "words"}; + AssemblyContext data(input, nullptr); + + std::string word; + spv_position_t endPosition = {}; + ASSERT_EQ(SPV_SUCCESS, data.getWord(&word, &endPosition)); + EXPECT_EQ(16u, endPosition.column); + EXPECT_EQ(0u, endPosition.line); + EXPECT_EQ(16u, endPosition.index); + EXPECT_STREQ(expected[0], word.c_str()); + + // Move to the next word. + data.setPosition(endPosition); + data.seekForward(1); + + ASSERT_EQ(SPV_SUCCESS, data.getWord(&word, &endPosition)); + EXPECT_EQ(22u, endPosition.column); + EXPECT_EQ(0u, endPosition.line); + EXPECT_EQ(22u, endPosition.index); + EXPECT_STREQ(expected[1], word.c_str()); +} + +TEST(TextWordGet, QuotingWhitespace) { + AutoText input(QUOTE "white " NEWLINE TAB " space" QUOTE); + // Whitespace surrounded by quotes acts like glue. + std::string word; + spv_position_t endPosition = {}; + ASSERT_EQ(SPV_SUCCESS, + AssemblyContext(input, nullptr).getWord(&word, &endPosition)); + EXPECT_EQ(input.str.length(), endPosition.column); + EXPECT_EQ(0u, endPosition.line); + EXPECT_EQ(input.str.length(), endPosition.index); + EXPECT_EQ(input.str, word); +} + +TEST(TextWordGet, QuoteAlone) { + AutoText input(QUOTE); + std::string word; + spv_position_t endPosition = {}; + ASSERT_EQ(SPV_SUCCESS, + AssemblyContext(input, nullptr).getWord(&word, &endPosition)); + ASSERT_EQ(1u, endPosition.column); + ASSERT_EQ(0u, endPosition.line); + ASSERT_EQ(1u, endPosition.index); + ASSERT_STREQ(QUOTE, word.c_str()); +} + +TEST(TextWordGet, EscapeAlone) { + AutoText input(BACKSLASH); + std::string word; + spv_position_t endPosition = {}; + ASSERT_EQ(SPV_SUCCESS, + AssemblyContext(input, nullptr).getWord(&word, &endPosition)); + ASSERT_EQ(1u, endPosition.column); + ASSERT_EQ(0u, endPosition.line); + ASSERT_EQ(1u, endPosition.index); + ASSERT_STREQ(BACKSLASH, word.c_str()); +} + +TEST(TextWordGet, EscapeAtEndOfInput) { + AutoText input("word" BACKSLASH); + std::string word; + spv_position_t endPosition = {}; + ASSERT_EQ(SPV_SUCCESS, + AssemblyContext(input, nullptr).getWord(&word, &endPosition)); + ASSERT_EQ(5u, endPosition.column); + ASSERT_EQ(0u, endPosition.line); + ASSERT_EQ(5u, endPosition.index); + ASSERT_STREQ("word" BACKSLASH, word.c_str()); +} + +TEST(TextWordGet, Escaping) { + AutoText input("w" BACKSLASH QUOTE "o" BACKSLASH NEWLINE "r" BACKSLASH ";d"); + std::string word; + spv_position_t endPosition = {}; + ASSERT_EQ(SPV_SUCCESS, + AssemblyContext(input, nullptr).getWord(&word, &endPosition)); + ASSERT_EQ(10u, endPosition.column); + ASSERT_EQ(0u, endPosition.line); + ASSERT_EQ(10u, endPosition.index); + ASSERT_EQ(input.str, word); +} + +TEST(TextWordGet, EscapingEscape) { + AutoText input("word" BACKSLASH BACKSLASH " abc"); + std::string word; + spv_position_t endPosition = {}; + ASSERT_EQ(SPV_SUCCESS, + AssemblyContext(input, nullptr).getWord(&word, &endPosition)); + ASSERT_EQ(6u, endPosition.column); + ASSERT_EQ(0u, endPosition.line); + ASSERT_EQ(6u, endPosition.index); + ASSERT_STREQ("word" BACKSLASH BACKSLASH, word.c_str()); +} + +TEST(TextWordGet, CRLF) { + AutoText input("abc\r\nd"); + AssemblyContext data(input, nullptr); + std::string word; + spv_position_t pos = {}; + ASSERT_EQ(SPV_SUCCESS, data.getWord(&word, &pos)); + EXPECT_EQ(3u, pos.column); + EXPECT_STREQ("abc", word.c_str()); + data.setPosition(pos); + data.advance(); + ASSERT_EQ(SPV_SUCCESS, data.getWord(&word, &pos)); + EXPECT_EQ(1u, pos.column); + EXPECT_STREQ("d", word.c_str()); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/timer_test.cpp b/3rdparty/spirv-tools/test/timer_test.cpp new file mode 100644 index 000000000..0002ae626 --- /dev/null +++ b/3rdparty/spirv-tools/test/timer_test.cpp @@ -0,0 +1,143 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "gtest/gtest.h" +#include "source/util/timer.h" + +namespace { + +using ::spvutils::CumulativeTimer; +using ::spvutils::PrintTimerDescription; +using ::spvutils::ScopedTimer; +using ::spvutils::Timer; + +// A mock class to mimic Timer class for a testing purpose. It has fixed +// CPU/WALL/USR/SYS time, RSS delta, and the delta of the number of page faults. +class MockTimer : public Timer { + public: + MockTimer(std::ostream* out, bool measure_mem_usage = false) + : Timer(out, measure_mem_usage) {} + double CPUTime() override { return 0.019123; } + double WallTime() override { return 0.019723; } + double UserTime() override { return 0.012723; } + double SystemTime() override { return 0.002723; } + long RSS() const override { return 360L; } + long PageFault() const override { return 3600L; } +}; + +// This unit test checks whether the actual output of MockTimer::Report() is the +// same as fixed CPU/WALL/USR/SYS time, RSS delta, and the delta of the number +// of page faults that are returned by MockTimer. +TEST(MockTimer, DoNothing) { + std::ostringstream buf; + + PrintTimerDescription(&buf); + MockTimer timer(&buf); + timer.Start(); + + // Do nothing. + + timer.Stop(); + timer.Report("TimerTest"); + + EXPECT_EQ(0.019123, timer.CPUTime()); + EXPECT_EQ(0.019723, timer.WallTime()); + EXPECT_EQ(0.012723, timer.UserTime()); + EXPECT_EQ(0.002723, timer.SystemTime()); + EXPECT_EQ( + " PASS name CPU time WALL time USR time" + " SYS time\n TimerTest 0.02 0.02" + " 0.01 0.00\n", + buf.str()); +} + +// This unit test checks whether the ScopedTimer correctly reports +// the fixed CPU/WALL/USR/SYS time, RSS delta, and the delta of the number of +// page faults that are returned by MockTimer. +TEST(MockTimer, TestScopedTimer) { + std::ostringstream buf; + + { + ScopedTimer scopedtimer(&buf, "ScopedTimerTest"); + // Do nothing. + } + + EXPECT_EQ( + " ScopedTimerTest 0.02 0.02 0.01" + " 0.00\n", + buf.str()); +} + +// A mock class to mimic CumulativeTimer class for a testing purpose. It has +// fixed CPU/WALL/USR/SYS time, RSS delta, and the delta of the number of page +// faults for each measurement (i.e., a pair of Start() and Stop()). If the +// number of measurements increases, it increases |count_stop_| by the number of +// calling Stop() and the amount of each resource usage is proportional to +// |count_stop_|. +class MockCumulativeTimer : public CumulativeTimer { + public: + MockCumulativeTimer(std::ostream* out, bool measure_mem_usage = false) + : CumulativeTimer(out, measure_mem_usage), count_stop_(0) {} + double CPUTime() override { return count_stop_ * 0.019123; } + double WallTime() override { return count_stop_ * 0.019723; } + double UserTime() override { return count_stop_ * 0.012723; } + double SystemTime() override { return count_stop_ * 0.002723; } + long RSS() const override { return count_stop_ * 360L; } + long PageFault() const override { return count_stop_ * 3600L; } + + // Calling Stop() does nothing but just increases |count_stop_| by 1. + void Stop() override { ++count_stop_; }; + + private: + unsigned int count_stop_; +}; + +// This unit test checks whether the MockCumulativeTimer correctly reports the +// cumulative CPU/WALL/USR/SYS time, RSS delta, and the delta of the number of +// page faults whose values are fixed for each measurement (i.e., a pair of +// Start() and Stop()). +TEST(MockCumulativeTimer, DoNothing) { + CumulativeTimer* ctimer; + std::ostringstream buf; + + { + ctimer = new MockCumulativeTimer(&buf); + ctimer->Start(); + + // Do nothing. + + ctimer->Stop(); + } + + { + ctimer->Start(); + + // Do nothing. + + ctimer->Stop(); + ctimer->Report("CumulativeTimerTest"); + } + + EXPECT_EQ( + " CumulativeTimerTest 0.04 0.04 0.03" + " 0.01\n", + buf.str()); + + if (ctimer) delete ctimer; +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/unit_spirv.cpp b/3rdparty/spirv-tools/test/unit_spirv.cpp new file mode 100644 index 000000000..c2a770a88 --- /dev/null +++ b/3rdparty/spirv-tools/test/unit_spirv.cpp @@ -0,0 +1,53 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "unit_spirv.h" + +#include "gmock/gmock.h" +#include "test_fixture.h" + +namespace { + +using spvtest::MakeVector; +using ::testing::Eq; +using Words = std::vector; + +TEST(MakeVector, Samples) { + EXPECT_THAT(MakeVector(""), Eq(Words{0})); + EXPECT_THAT(MakeVector("a"), Eq(Words{0x0061})); + EXPECT_THAT(MakeVector("ab"), Eq(Words{0x006261})); + EXPECT_THAT(MakeVector("abc"), Eq(Words{0x00636261})); + EXPECT_THAT(MakeVector("abcd"), Eq(Words{0x64636261, 0x00})); + EXPECT_THAT(MakeVector("abcde"), Eq(Words{0x64636261, 0x0065})); +} + +TEST(WordVectorPrintTo, PreservesFlagsAndFill) { + std::stringstream s; + s << std::setw(4) << std::oct << std::setfill('x') << 8 << " "; + spvtest::PrintTo(spvtest::WordVector({10, 16}), &s); + // The octal setting and fill character should be preserved + // from before the PrintTo. + // Width is reset after each emission of a regular scalar type. + // So set it explicitly again. + s << std::setw(4) << 9; + + EXPECT_THAT(s.str(), Eq("xx10 0x0000000a 0x00000010 xx11")); +} + +TEST_P(RoundTripTest, Sample) { + EXPECT_THAT(EncodeAndDecodeSuccessfully(GetParam()), Eq(GetParam())) + << GetParam(); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/unit_spirv.h b/3rdparty/spirv-tools/test/unit_spirv.h new file mode 100644 index 000000000..45e8c2379 --- /dev/null +++ b/3rdparty/spirv-tools/test/unit_spirv.h @@ -0,0 +1,234 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_TEST_UNITSPIRV_H_ +#define LIBSPIRV_TEST_UNITSPIRV_H_ + +#include + +#include +#include + +#include "source/assembly_grammar.h" +#include "source/binary.h" +#include "source/diagnostic.h" +#include "source/enum_set.h" +#include "source/opcode.h" +#include "source/spirv_endian.h" +#include "source/text.h" +#include "source/text_handler.h" +#include "source/validate.h" +#include "spirv-tools/libspirv.h" + +#include + +#ifdef __ANDROID__ +#include +namespace std { +template +std::string to_string(const T& val) { + std::ostringstream os; + os << val; + return os.str(); +} +} // namespace std +#endif + +// Determine endianness & predicate tests on it +enum { + I32_ENDIAN_LITTLE = 0x03020100ul, + I32_ENDIAN_BIG = 0x00010203ul, +}; + +static const union { + unsigned char bytes[4]; + uint32_t value; +} o32_host_order = {{0, 1, 2, 3}}; +#define I32_ENDIAN_HOST (o32_host_order.value) + +// A namespace for utilities used in SPIR-V Tools unit tests. +namespace spvtest { + +class WordVector; + +// Emits the given word vector to the given stream. +// This function can be used by the gtest value printer. +void PrintTo(const WordVector& words, ::std::ostream* os); + +// A proxy class to allow us to easily write out vectors of SPIR-V words. +class WordVector { + public: + explicit WordVector(const std::vector& val) : value_(val) {} + explicit WordVector(const spv_binary_t& binary) + : value_(binary.code, binary.code + binary.wordCount) {} + + // Returns the underlying vector. + const std::vector& value() const { return value_; } + + // Returns the string representation of this word vector. + std::string str() const { + std::ostringstream os; + PrintTo(*this, &os); + return os.str(); + } + + private: + const std::vector value_; +}; + +inline void PrintTo(const WordVector& words, ::std::ostream* os) { + size_t count = 0; + const auto saved_flags = os->flags(); + const auto saved_fill = os->fill(); + for (uint32_t value : words.value()) { + *os << "0x" << std::setw(8) << std::setfill('0') << std::hex << value + << " "; + if (count++ % 8 == 7) { + *os << std::endl; + } + } + os->flags(saved_flags); + os->fill(saved_fill); +} + +// Returns a vector of words representing a single instruction with the +// given opcode and operand words as a vector. +inline std::vector MakeInstruction( + SpvOp opcode, const std::vector& args) { + std::vector result{ + spvOpcodeMake(uint16_t(args.size() + 1), opcode)}; + result.insert(result.end(), args.begin(), args.end()); + return result; +} + +// Returns a vector of words representing a single instruction with the +// given opcode and whose operands are the concatenation of the two given +// argument lists. +inline std::vector MakeInstruction( + SpvOp opcode, std::vector args, + const std::vector& extra_args) { + args.insert(args.end(), extra_args.begin(), extra_args.end()); + return MakeInstruction(opcode, args); +} + +// Returns the vector of words representing the concatenation +// of all input vectors. +inline std::vector Concatenate( + const std::vector>& instructions) { + std::vector result; + for (const auto& instruction : instructions) { + result.insert(result.end(), instruction.begin(), instruction.end()); + } + return result; +} + +// Encodes a string as a sequence of words, using the SPIR-V encoding. +inline std::vector MakeVector(std::string input) { + std::vector result; + uint32_t word = 0; + size_t num_bytes = input.size(); + // SPIR-V strings are null-terminated. The byte_index == num_bytes + // case is used to push the terminating null byte. + for (size_t byte_index = 0; byte_index <= num_bytes; byte_index++) { + const auto new_byte = + (byte_index < num_bytes ? uint8_t(input[byte_index]) : uint8_t(0)); + word |= (new_byte << (8 * (byte_index % sizeof(uint32_t)))); + if (3 == (byte_index % sizeof(uint32_t))) { + result.push_back(word); + word = 0; + } + } + // Emit a trailing partial word. + if ((num_bytes + 1) % sizeof(uint32_t)) { + result.push_back(word); + } + return result; +} + +// A type for easily creating spv_text_t values, with an implicit conversion to +// spv_text. +struct AutoText { + explicit AutoText(const std::string& value) + : str(value), text({str.data(), str.size()}) {} + operator spv_text() { return &text; } + std::string str; + spv_text_t text; +}; + +// An example case for an enumerated value, optionally with operands. +template +class EnumCase { + public: + EnumCase() = default; // Required by ::testing::Combine(). + EnumCase(E val, std::string enum_name, std::vector ops = {}) + : enum_value_(val), name_(enum_name), operands_(ops) {} + // Returns the enum value as a uint32_t. + uint32_t value() const { return static_cast(enum_value_); } + // Returns the name of the enumerant. + const std::string& name() const { return name_; } + // Returns a reference to the operands. + const std::vector& operands() const { return operands_; } + + private: + E enum_value_; + std::string name_; + std::vector operands_; +}; + +// Returns a string with num_4_byte_chars Unicode characters, +// each of which has a 4-byte UTF-8 encoding. +inline std::string MakeLongUTF8String(size_t num_4_byte_chars) { + // An example of a longest valid UTF-8 character. + // Be explicit about the character type because Microsoft compilers can + // otherwise interpret the character string as being over wide (16-bit) + // characters. Ideally, we would just use a C++11 UTF-8 string literal, + // but we want to support older Microsoft compilers. + const std::basic_string earth_africa("\xF0\x9F\x8C\x8D"); + EXPECT_EQ(4u, earth_africa.size()); + + std::string result; + result.reserve(num_4_byte_chars * 4); + for (size_t i = 0; i < num_4_byte_chars; i++) { + result += earth_africa; + } + EXPECT_EQ(4 * num_4_byte_chars, result.size()); + return result; +} + +// Returns a vector of all valid target environment enums. +inline std::vector AllTargetEnvironments() { + return { + SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, + SPV_ENV_OPENCL_1_2, SPV_ENV_OPENCL_EMBEDDED_1_2, + SPV_ENV_OPENCL_2_0, SPV_ENV_OPENCL_EMBEDDED_2_0, + SPV_ENV_OPENCL_2_1, SPV_ENV_OPENCL_EMBEDDED_2_1, + SPV_ENV_OPENCL_2_2, SPV_ENV_OPENCL_EMBEDDED_2_2, + SPV_ENV_VULKAN_1_0, SPV_ENV_OPENGL_4_0, + SPV_ENV_OPENGL_4_1, SPV_ENV_OPENGL_4_2, + SPV_ENV_OPENGL_4_3, SPV_ENV_OPENGL_4_5, + SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_3, + SPV_ENV_VULKAN_1_1, + }; +} + +// Returns the capabilities in a CapabilitySet as an ordered vector. +inline std::vector ElementsIn( + const libspirv::CapabilitySet& capabilities) { + std::vector result; + capabilities.ForEach([&result](SpvCapability c) { result.push_back(c); }); + return result; +} + +} // namespace spvtest +#endif // LIBSPIRV_TEST_UNITSPIRV_H_ diff --git a/3rdparty/spirv-tools/test/util/CMakeLists.txt b/3rdparty/spirv-tools/test/util/CMakeLists.txt new file mode 100644 index 000000000..9b6ca2c1f --- /dev/null +++ b/3rdparty/spirv-tools/test/util/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (c) 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +add_spvtools_unittest(TARGET util_intrusive_list + SRCS ilist_test.cpp + LIBS SPIRV-Tools-opt +) diff --git a/3rdparty/spirv-tools/test/util/ilist_test.cpp b/3rdparty/spirv-tools/test/util/ilist_test.cpp new file mode 100644 index 000000000..5f36f391b --- /dev/null +++ b/3rdparty/spirv-tools/test/util/ilist_test.cpp @@ -0,0 +1,322 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "gmock/gmock.h" + +#include "util/ilist.h" + +namespace { + +using spvtools::utils::IntrusiveList; +using spvtools::utils::IntrusiveNodeBase; +using ::testing::ElementsAre; +using IListTest = ::testing::Test; + +class TestNode : public IntrusiveNodeBase { + public: + TestNode() : IntrusiveNodeBase() {} + int data_; +}; + +class TestList : public IntrusiveList { + public: + TestList() = default; + TestList(TestList&& that) : IntrusiveList(std::move(that)) {} + TestList& operator=(TestList&& that) { + static_cast&>(*this) = + static_cast&&>(that); + return *this; + } +}; + +// This test checks the push_back method, as well as using an iterator to +// traverse the list from begin() to end(). This implicitly test the +// PreviousNode and NextNode functions. +TEST(IListTest, PushBack) { + TestNode nodes[10]; + TestList list; + for (int i = 0; i < 10; i++) { + nodes[i].data_ = i; + list.push_back(&nodes[i]); + } + + std::vector output; + for (auto& i : list) output.push_back(i.data_); + + EXPECT_THAT(output, ElementsAre(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)); +} + +// Returns a list containing the values 0 to n-1 using the first n elements of +// nodes to build the list. +TestList BuildList(TestNode nodes[], int n) { + TestList list; + for (int i = 0; i < n; i++) { + nodes[i].data_ = i; + list.push_back(&nodes[i]); + } + return list; +} + +// Test decrementing begin() +TEST(IListTest, DecrementingBegin) { + TestNode nodes[10]; + TestList list = BuildList(nodes, 10); + EXPECT_EQ(--list.begin(), list.end()); +} + +// Test incrementing end() +TEST(IListTest, IncrementingEnd1) { + TestNode nodes[10]; + TestList list = BuildList(nodes, 10); + EXPECT_EQ((++list.end())->data_, 0); +} + +// Test incrementing end() should equal begin() +TEST(IListTest, IncrementingEnd2) { + TestNode nodes[10]; + TestList list = BuildList(nodes, 10); + EXPECT_EQ(++list.end(), list.begin()); +} + +// Test decrementing end() +TEST(IListTest, DecrementingEnd) { + TestNode nodes[10]; + TestList list = BuildList(nodes, 10); + EXPECT_EQ((--list.end())->data_, 9); +} + +// Test the move constructor for the list class. +TEST(IListTest, MoveConstructor) { + TestNode nodes[10]; + TestList list = BuildList(nodes, 10); + std::vector output; + for (auto& i : list) output.push_back(i.data_); + + EXPECT_THAT(output, ElementsAre(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)); +} + +// Using a const list so we can test the const_iterator. +TEST(IListTest, ConstIterator) { + TestNode nodes[10]; + const TestList list = BuildList(nodes, 10); + std::vector output; + for (auto& i : list) output.push_back(i.data_); + + EXPECT_THAT(output, ElementsAre(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)); +} + +// Uses the move assignement instead of the move constructor. +TEST(IListTest, MoveAssignment) { + TestNode nodes[10]; + TestList list; + list = BuildList(nodes, 10); + std::vector output; + for (auto& i : list) output.push_back(i.data_); + + EXPECT_THAT(output, ElementsAre(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)); +} + +// Test inserting a new element at the end of a list using the IntrusiveNodeBase +// "InsertAfter" function. +TEST(IListTest, InsertAfter1) { + TestNode nodes[10]; + TestList list = BuildList(nodes, 5); + + nodes[5].data_ = 5; + nodes[5].InsertAfter(&nodes[4]); + + std::vector output; + for (auto& i : list) output.push_back(i.data_); + + EXPECT_THAT(output, ElementsAre(0, 1, 2, 3, 4, 5)); +} + +// Test inserting a new element in the middle of a list using the +// IntrusiveNodeBase "InsertAfter" function. +TEST(IListTest, InsertAfter2) { + TestNode nodes[10]; + TestList list = BuildList(nodes, 5); + + nodes[5].data_ = 5; + nodes[5].InsertAfter(&nodes[2]); + + std::vector output; + for (auto& i : list) output.push_back(i.data_); + + EXPECT_THAT(output, ElementsAre(0, 1, 2, 5, 3, 4)); +} + +// Test moving an element already in the list in the middle of a list using the +// IntrusiveNodeBase "InsertAfter" function. +TEST(IListTest, MoveUsingInsertAfter1) { + TestNode nodes[10]; + TestList list = BuildList(nodes, 6); + + nodes[5].InsertAfter(&nodes[2]); + + std::vector output; + for (auto& i : list) output.push_back(i.data_); + + EXPECT_THAT(output, ElementsAre(0, 1, 2, 5, 3, 4)); +} + +// Move the element at the start of the list into the middle. +TEST(IListTest, MoveUsingInsertAfter2) { + TestNode nodes[10]; + TestList list = BuildList(nodes, 6); + + nodes[0].InsertAfter(&nodes[2]); + + std::vector output; + for (auto& i : list) output.push_back(i.data_); + + EXPECT_THAT(output, ElementsAre(1, 2, 0, 3, 4, 5)); +} + +// Move an element in the middle of the list to the end. +TEST(IListTest, MoveUsingInsertAfter3) { + TestNode nodes[10]; + TestList list = BuildList(nodes, 6); + + nodes[2].InsertAfter(&nodes[5]); + + std::vector output; + for (auto& i : list) output.push_back(i.data_); + + EXPECT_THAT(output, ElementsAre(0, 1, 3, 4, 5, 2)); +} + +// Removing an element from the middle of a list. +TEST(IListTest, Remove1) { + TestNode nodes[10]; + TestList list = BuildList(nodes, 6); + + nodes[2].RemoveFromList(); + + std::vector output; + for (auto& i : list) output.push_back(i.data_); + + EXPECT_THAT(output, ElementsAre(0, 1, 3, 4, 5)); +} + +// Removing an element from the beginning of the list. +TEST(IListTest, Remove2) { + TestNode nodes[10]; + TestList list = BuildList(nodes, 6); + + nodes[0].RemoveFromList(); + + std::vector output; + for (auto& i : list) output.push_back(i.data_); + + EXPECT_THAT(output, ElementsAre(1, 2, 3, 4, 5)); +} + +// Removing the last element of a list. +TEST(IListTest, Remove3) { + TestNode nodes[10]; + TestList list = BuildList(nodes, 6); + + nodes[5].RemoveFromList(); + + std::vector output; + for (auto& i : list) output.push_back(i.data_); + + EXPECT_THAT(output, ElementsAre(0, 1, 2, 3, 4)); +} + +// Test that operator== and operator!= work properly for the iterator class. +TEST(IListTest, IteratorEqual) { + TestNode nodes[10]; + TestList list = BuildList(nodes, 6); + + std::vector output; + for (auto i = list.begin(); i != list.end(); ++i) + for (auto j = list.begin(); j != list.end(); ++j) + if (i == j) output.push_back(i->data_); + + EXPECT_THAT(output, ElementsAre(0, 1, 2, 3, 4, 5)); +} + +// Test MoveBefore. Moving into middle of a list. +TEST(IListTest, MoveBefore1) { + TestNode nodes[10]; + TestList list1 = BuildList(nodes, 6); + TestList list2 = BuildList(nodes + 6, 3); + + TestList::iterator insertion_point = list1.begin(); + ++insertion_point; + insertion_point.MoveBefore(&list2); + + std::vector output; + for (auto i = list1.begin(); i != list1.end(); ++i) { + output.push_back(i->data_); + } + + EXPECT_THAT(output, ElementsAre(0, 0, 1, 2, 1, 2, 3, 4, 5)); +} + +// Test MoveBefore. Moving to the start of a list. +TEST(IListTest, MoveBefore2) { + TestNode nodes[10]; + TestList list1 = BuildList(nodes, 6); + TestList list2 = BuildList(nodes + 6, 3); + + TestList::iterator insertion_point = list1.begin(); + insertion_point.MoveBefore(&list2); + + std::vector output; + for (auto i = list1.begin(); i != list1.end(); ++i) { + output.push_back(i->data_); + } + + EXPECT_THAT(output, ElementsAre(0, 1, 2, 0, 1, 2, 3, 4, 5)); +} + +// Test MoveBefore. Moving to the end of a list. +TEST(IListTest, MoveBefore3) { + TestNode nodes[10]; + TestList list1 = BuildList(nodes, 6); + TestList list2 = BuildList(nodes + 6, 3); + + TestList::iterator insertion_point = list1.end(); + insertion_point.MoveBefore(&list2); + + std::vector output; + for (auto i = list1.begin(); i != list1.end(); ++i) { + output.push_back(i->data_); + } + + EXPECT_THAT(output, ElementsAre(0, 1, 2, 3, 4, 5, 0, 1, 2)); +} + +// Test MoveBefore. Moving an empty list. +TEST(IListTest, MoveBefore4) { + TestNode nodes[10]; + TestList list1 = BuildList(nodes, 6); + TestList list2; + + TestList::iterator insertion_point = list1.end(); + insertion_point.MoveBefore(&list2); + + std::vector output; + for (auto i = list1.begin(); i != list1.end(); ++i) { + output.push_back(i->data_); + } + + EXPECT_THAT(output, ElementsAre(0, 1, 2, 3, 4, 5)); +} +} // namespace diff --git a/3rdparty/spirv-tools/test/val/CMakeLists.txt b/3rdparty/spirv-tools/test/val/CMakeLists.txt new file mode 100644 index 000000000..093a04a4d --- /dev/null +++ b/3rdparty/spirv-tools/test/val/CMakeLists.txt @@ -0,0 +1,188 @@ +# Copyright (c) 2016 The Khronos Group Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(VAL_TEST_COMMON_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/../test_fixture.h + ${CMAKE_CURRENT_SOURCE_DIR}/../unit_spirv.h + ${CMAKE_CURRENT_SOURCE_DIR}/val_fixtures.h +) + + +add_spvtools_unittest(TARGET val_capability + SRCS val_capability_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) + +add_spvtools_unittest(TARGET val_cfg + SRCS val_cfg_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) + +add_spvtools_unittest(TARGET val_id + SRCS val_id_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) + +add_spvtools_unittest(TARGET val_layout + SRCS val_layout_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) + +add_spvtools_unittest(TARGET val_ssa + SRCS val_ssa_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) + +add_spvtools_unittest(TARGET val_storage + SRCS val_storage_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) + +add_spvtools_unittest(TARGET val_state + SRCS val_state_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) + +add_spvtools_unittest(TARGET val_data + SRCS val_data_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) + +add_spvtools_unittest(TARGET val_type_unique + SRCS val_type_unique_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) + +add_spvtools_unittest(TARGET val_arithmetics + SRCS val_arithmetics_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) + +add_spvtools_unittest(TARGET val_composites + SRCS val_composites_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) + +add_spvtools_unittest(TARGET val_conversion + SRCS val_conversion_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) + +add_spvtools_unittest(TARGET val_derivatives + SRCS val_derivatives_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) + +add_spvtools_unittest(TARGET val_logicals + SRCS val_logicals_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) + +add_spvtools_unittest(TARGET val_bitwise + SRCS val_bitwise_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) + +add_spvtools_unittest(TARGET val_builtins + SRCS val_builtins_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) + +add_spvtools_unittest(TARGET val_image + SRCS val_image_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) + +add_spvtools_unittest(TARGET val_atomics + SRCS val_atomics_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) + +add_spvtools_unittest(TARGET val_barriers + SRCS val_barriers_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) + +add_spvtools_unittest(TARGET val_primitives + SRCS val_primitives_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) + +add_spvtools_unittest(TARGET val_ext_inst + SRCS val_ext_inst_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) + +add_spvtools_unittest(TARGET val_limits + SRCS val_limits_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) + +add_spvtools_unittest(TARGET val_validation_state + SRCS val_validation_state_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) + +add_spvtools_unittest(TARGET val_decoration + SRCS val_decoration_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) + +add_spvtools_unittest(TARGET val_literals + SRCS val_literals_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) + +add_spvtools_unittest(TARGET val_extensions + SRCS val_extensions_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) + +add_spvtools_unittest(TARGET val_adjacency + SRCS val_adjacency_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) + +add_spvtools_unittest(TARGET val_version + SRCS val_version_test.cpp + ${VAL_TEST_COMMON_SRCS} + LIBS ${SPIRV_TOOLS} +) diff --git a/3rdparty/spirv-tools/test/val/val_adjacency_test.cpp b/3rdparty/spirv-tools/test/val/val_adjacency_test.cpp new file mode 100644 index 000000000..586cf7e8f --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_adjacency_test.cpp @@ -0,0 +1,285 @@ +// Copyright (c) 2018 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "gmock/gmock.h" +#include "unit_spirv.h" +#include "val_fixtures.h" + +namespace { + +using ::testing::HasSubstr; +using ::testing::Not; + +using ValidateAdjacency = spvtest::ValidateBase; + +TEST_F(ValidateAdjacency, OpPhiBeginsModuleFail) { + const std::string module = R"( +%result = OpPhi %bool %true %true_label %false %false_label +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +%void = OpTypeVoid +%bool = OpTypeBool +%true = OpConstantTrue %bool +%false = OpConstantFalse %bool +%func = OpTypeFunction %void +%main = OpFunction %void None %func +%main_entry = OpLabel +OpBranch %true_label +%true_label = OpLabel +OpBranch %false_label +%false_label = OpLabel +OpBranch %end_label +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(module); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 1 has not been defined")); +} + +TEST_F(ValidateAdjacency, OpLoopMergeEndsModuleFail) { + const std::string module = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +%void = OpTypeVoid +%func = OpTypeFunction %void +%main = OpFunction %void None %func +%main_entry = OpLabel +OpBranch %loop +%loop = OpLabel +OpLoopMerge %end %loop None +)"; + + CompileSuccessfully(module); + EXPECT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Missing OpFunctionEnd at end of module")); +} + +TEST_F(ValidateAdjacency, OpSelectionMergeEndsModuleFail) { + const std::string module = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +%void = OpTypeVoid +%func = OpTypeFunction %void +%main = OpFunction %void None %func +%main_entry = OpLabel +OpBranch %merge +%merge = OpLabel +OpSelectionMerge %merge None +)"; + + CompileSuccessfully(module); + EXPECT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Missing OpFunctionEnd at end of module")); +} + +std::string GenerateShaderCode( + const std::string& body, + const std::string& capabilities_and_extensions = "OpCapability Shader", + const std::string& execution_model = "Fragment") { + std::ostringstream ss; + ss << capabilities_and_extensions << "\n"; + ss << "OpMemoryModel Logical GLSL450\n"; + ss << "OpEntryPoint " << execution_model << " %main \"main\"\n"; + + ss << R"( +%string = OpString "" +%void = OpTypeVoid +%bool = OpTypeBool +%int = OpTypeInt 32 0 +%true = OpConstantTrue %bool +%false = OpConstantFalse %bool +%zero = OpConstant %int 0 +%func = OpTypeFunction %void +%main = OpFunction %void None %func +%main_entry = OpLabel +)"; + + ss << body; + + ss << R"( +OpReturn +OpFunctionEnd)"; + + return ss.str(); +} + +TEST_F(ValidateAdjacency, OpPhiPreceededByOpLabelSuccess) { + const std::string body = R"( +OpSelectionMerge %end_label None +OpBranchConditional %true %true_label %false_label +%true_label = OpLabel +OpBranch %end_label +%false_label = OpLabel +OpBranch %end_label +%end_label = OpLabel +%line = OpLine %string 0 0 +%result = OpPhi %bool %true %true_label %false %false_label +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateAdjacency, OpPhiPreceededByOpPhiSuccess) { + const std::string body = R"( +OpSelectionMerge %end_label None +OpBranchConditional %true %true_label %false_label +%true_label = OpLabel +OpBranch %end_label +%false_label = OpLabel +OpBranch %end_label +%end_label = OpLabel +%1 = OpPhi %bool %true %true_label %false %false_label +%2 = OpPhi %bool %true %true_label %false %false_label +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateAdjacency, OpPhiPreceededByOpLineSuccess) { + const std::string body = R"( +OpSelectionMerge %end_label None +OpBranchConditional %true %true_label %false_label +%true_label = OpLabel +OpBranch %end_label +%false_label = OpLabel +OpBranch %end_label +%end_label = OpLabel +%line = OpLine %string 0 0 +%result = OpPhi %bool %true %true_label %false %false_label +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateAdjacency, OpPhiPreceededByBadOpFail) { + const std::string body = R"( +OpSelectionMerge %end_label None +OpBranchConditional %true %true_label %false_label +%true_label = OpLabel +OpBranch %end_label +%false_label = OpLabel +OpBranch %end_label +%end_label = OpLabel +OpNop +%result = OpPhi %bool %true %true_label %false %false_label +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpPhi must appear before all non-OpPhi instructions")); +} + +TEST_F(ValidateAdjacency, OpLoopMergePreceedsOpBranchSuccess) { + const std::string body = R"( +OpBranch %loop +%loop = OpLabel +OpLoopMerge %end %loop None +OpBranch %loop +%end = OpLabel +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateAdjacency, OpLoopMergePreceedsOpBranchConditionalSuccess) { + const std::string body = R"( +OpBranch %loop +%loop = OpLabel +OpLoopMerge %end %loop None +OpBranchConditional %true %loop %end +%end = OpLabel +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateAdjacency, OpLoopMergePreceedsBadOpFail) { + const std::string body = R"( +OpBranch %loop +%loop = OpLabel +OpLoopMerge %end %loop None +OpNop +OpBranchConditional %true %loop %end +%end = OpLabel +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpLoopMerge must immediately precede either an " + "OpBranch or OpBranchConditional instruction.")); +} + +TEST_F(ValidateAdjacency, OpSelectionMergePreceedsOpBranchConditionalSuccess) { + const std::string body = R"( +OpSelectionMerge %end_label None +OpBranchConditional %true %true_label %false_label +%true_label = OpLabel +OpBranch %end_label +%false_label = OpLabel +OpBranch %end_label +%end_label = OpLabel +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateAdjacency, OpSelectionMergePreceedsOpSwitchSuccess) { + const std::string body = R"( +OpSelectionMerge %merge None +OpSwitch %zero %merge 0 %label +%label = OpLabel +OpBranch %merge +%merge = OpLabel +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateAdjacency, OpSelectionMergePreceedsBadOpFail) { + const std::string body = R"( +OpSelectionMerge %merge None +OpNop +OpSwitch %zero %merge 0 %label +%label = OpLabel +OpBranch %merge +%merge = OpLabel +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpSelectionMerge must immediately precede either an " + "OpBranchConditional or OpSwitch instruction")); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/val/val_arithmetics_test.cpp b/3rdparty/spirv-tools/test/val/val_arithmetics_test.cpp new file mode 100644 index 000000000..7c3172f92 --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_arithmetics_test.cpp @@ -0,0 +1,1276 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Tests for unique type declaration rules validator. + +#include + +#include "gmock/gmock.h" +#include "unit_spirv.h" +#include "val_fixtures.h" + +namespace { + +using ::testing::HasSubstr; +using ::testing::Not; + +using std::string; + +using ValidateArithmetics = spvtest::ValidateBase; + +std::string GenerateCode(const std::string& main_body) { + const std::string prefix = + R"( +OpCapability Shader +OpCapability Int64 +OpCapability Float64 +OpCapability Matrix +%ext_inst = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +%void = OpTypeVoid +%func = OpTypeFunction %void +%bool = OpTypeBool +%f32 = OpTypeFloat 32 +%u32 = OpTypeInt 32 0 +%s32 = OpTypeInt 32 1 +%f64 = OpTypeFloat 64 +%u64 = OpTypeInt 64 0 +%s64 = OpTypeInt 64 1 +%boolvec2 = OpTypeVector %bool 2 +%s32vec2 = OpTypeVector %s32 2 +%u32vec2 = OpTypeVector %u32 2 +%u64vec2 = OpTypeVector %u64 2 +%f32vec2 = OpTypeVector %f32 2 +%f64vec2 = OpTypeVector %f64 2 +%boolvec3 = OpTypeVector %bool 3 +%u32vec3 = OpTypeVector %u32 3 +%u64vec3 = OpTypeVector %u64 3 +%s32vec3 = OpTypeVector %s32 3 +%f32vec3 = OpTypeVector %f32 3 +%f64vec3 = OpTypeVector %f64 3 +%boolvec4 = OpTypeVector %bool 4 +%u32vec4 = OpTypeVector %u32 4 +%u64vec4 = OpTypeVector %u64 4 +%s32vec4 = OpTypeVector %s32 4 +%f32vec4 = OpTypeVector %f32 4 +%f64vec4 = OpTypeVector %f64 4 + +%f32mat22 = OpTypeMatrix %f32vec2 2 +%f32mat23 = OpTypeMatrix %f32vec2 3 +%f32mat32 = OpTypeMatrix %f32vec3 2 +%f32mat33 = OpTypeMatrix %f32vec3 3 +%f64mat22 = OpTypeMatrix %f64vec2 2 + +%struct_f32_f32 = OpTypeStruct %f32 %f32 +%struct_u32_u32 = OpTypeStruct %u32 %u32 +%struct_u32_u32_u32 = OpTypeStruct %u32 %u32 %u32 +%struct_s32_s32 = OpTypeStruct %s32 %s32 +%struct_s32_u32 = OpTypeStruct %s32 %u32 +%struct_u32vec2_u32vec2 = OpTypeStruct %u32vec2 %u32vec2 +%struct_s32vec2_s32vec2 = OpTypeStruct %s32vec2 %s32vec2 + +%f32_0 = OpConstant %f32 0 +%f32_1 = OpConstant %f32 1 +%f32_2 = OpConstant %f32 2 +%f32_3 = OpConstant %f32 3 +%f32_4 = OpConstant %f32 4 +%f32_pi = OpConstant %f32 3.14159 + +%s32_0 = OpConstant %s32 0 +%s32_1 = OpConstant %s32 1 +%s32_2 = OpConstant %s32 2 +%s32_3 = OpConstant %s32 3 +%s32_4 = OpConstant %s32 4 +%s32_m1 = OpConstant %s32 -1 + +%u32_0 = OpConstant %u32 0 +%u32_1 = OpConstant %u32 1 +%u32_2 = OpConstant %u32 2 +%u32_3 = OpConstant %u32 3 +%u32_4 = OpConstant %u32 4 + +%f64_0 = OpConstant %f64 0 +%f64_1 = OpConstant %f64 1 +%f64_2 = OpConstant %f64 2 +%f64_3 = OpConstant %f64 3 +%f64_4 = OpConstant %f64 4 + +%s64_0 = OpConstant %s64 0 +%s64_1 = OpConstant %s64 1 +%s64_2 = OpConstant %s64 2 +%s64_3 = OpConstant %s64 3 +%s64_4 = OpConstant %s64 4 +%s64_m1 = OpConstant %s64 -1 + +%u64_0 = OpConstant %u64 0 +%u64_1 = OpConstant %u64 1 +%u64_2 = OpConstant %u64 2 +%u64_3 = OpConstant %u64 3 +%u64_4 = OpConstant %u64 4 + +%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1 +%u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2 +%u32vec3_012 = OpConstantComposite %u32vec3 %u32_0 %u32_1 %u32_2 +%u32vec3_123 = OpConstantComposite %u32vec3 %u32_1 %u32_2 %u32_3 +%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3 +%u32vec4_1234 = OpConstantComposite %u32vec4 %u32_1 %u32_2 %u32_3 %u32_4 + +%s32vec2_01 = OpConstantComposite %s32vec2 %s32_0 %s32_1 +%s32vec2_12 = OpConstantComposite %s32vec2 %s32_1 %s32_2 +%s32vec3_012 = OpConstantComposite %s32vec3 %s32_0 %s32_1 %s32_2 +%s32vec3_123 = OpConstantComposite %s32vec3 %s32_1 %s32_2 %s32_3 +%s32vec4_0123 = OpConstantComposite %s32vec4 %s32_0 %s32_1 %s32_2 %s32_3 +%s32vec4_1234 = OpConstantComposite %s32vec4 %s32_1 %s32_2 %s32_3 %s32_4 + +%f32vec2_01 = OpConstantComposite %f32vec2 %f32_0 %f32_1 +%f32vec2_12 = OpConstantComposite %f32vec2 %f32_1 %f32_2 +%f32vec3_012 = OpConstantComposite %f32vec3 %f32_0 %f32_1 %f32_2 +%f32vec3_123 = OpConstantComposite %f32vec3 %f32_1 %f32_2 %f32_3 +%f32vec4_0123 = OpConstantComposite %f32vec4 %f32_0 %f32_1 %f32_2 %f32_3 +%f32vec4_1234 = OpConstantComposite %f32vec4 %f32_1 %f32_2 %f32_3 %f32_4 + +%f64vec2_01 = OpConstantComposite %f64vec2 %f64_0 %f64_1 +%f64vec2_12 = OpConstantComposite %f64vec2 %f64_1 %f64_2 +%f64vec3_012 = OpConstantComposite %f64vec3 %f64_0 %f64_1 %f64_2 +%f64vec3_123 = OpConstantComposite %f64vec3 %f64_1 %f64_2 %f64_3 +%f64vec4_0123 = OpConstantComposite %f64vec4 %f64_0 %f64_1 %f64_2 %f64_3 +%f64vec4_1234 = OpConstantComposite %f64vec4 %f64_1 %f64_2 %f64_3 %f64_4 + +%f32mat22_1212 = OpConstantComposite %f32mat22 %f32vec2_12 %f32vec2_12 +%f32mat23_121212 = OpConstantComposite %f32mat23 %f32vec2_12 %f32vec2_12 %f32vec2_12 +%f32mat32_123123 = OpConstantComposite %f32mat32 %f32vec3_123 %f32vec3_123 +%f32mat33_123123123 = OpConstantComposite %f32mat33 %f32vec3_123 %f32vec3_123 %f32vec3_123 + +%f64mat22_1212 = OpConstantComposite %f64mat22 %f64vec2_12 %f64vec2_12 + +%main = OpFunction %void None %func +%main_entry = OpLabel)"; + + const std::string suffix = + R"( +OpReturn +OpFunctionEnd)"; + + return prefix + main_body + suffix; +} + +TEST_F(ValidateArithmetics, F32Success) { + const std::string body = R"( +%val1 = OpFMul %f32 %f32_0 %f32_1 +%val2 = OpFSub %f32 %f32_2 %f32_0 +%val3 = OpFAdd %f32 %val1 %val2 +%val4 = OpFNegate %f32 %val3 +%val5 = OpFDiv %f32 %val4 %val1 +%val6 = OpFRem %f32 %val4 %f32_2 +%val7 = OpFMod %f32 %val4 %f32_2 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateArithmetics, F64Success) { + const std::string body = R"( +%val1 = OpFMul %f64 %f64_0 %f64_1 +%val2 = OpFSub %f64 %f64_2 %f64_0 +%val3 = OpFAdd %f64 %val1 %val2 +%val4 = OpFNegate %f64 %val3 +%val5 = OpFDiv %f64 %val4 %val1 +%val6 = OpFRem %f64 %val4 %f64_2 +%val7 = OpFMod %f64 %val4 %f64_2 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateArithmetics, Int32Success) { + const std::string body = R"( +%val1 = OpIMul %u32 %s32_0 %u32_1 +%val2 = OpIMul %s32 %s32_2 %u32_1 +%val3 = OpIAdd %u32 %val1 %val2 +%val4 = OpIAdd %s32 %val1 %val2 +%val5 = OpISub %u32 %val3 %val4 +%val6 = OpISub %s32 %val4 %val3 +%val7 = OpSDiv %s32 %val4 %val3 +%val8 = OpSNegate %s32 %val7 +%val9 = OpSRem %s32 %val4 %val3 +%val10 = OpSMod %s32 %val4 %val3 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateArithmetics, Int64Success) { + const std::string body = R"( +%val1 = OpIMul %u64 %s64_0 %u64_1 +%val2 = OpIMul %s64 %s64_2 %u64_1 +%val3 = OpIAdd %u64 %val1 %val2 +%val4 = OpIAdd %s64 %val1 %val2 +%val5 = OpISub %u64 %val3 %val4 +%val6 = OpISub %s64 %val4 %val3 +%val7 = OpSDiv %s64 %val4 %val3 +%val8 = OpSNegate %s64 %val7 +%val9 = OpSRem %s64 %val4 %val3 +%val10 = OpSMod %s64 %val4 %val3 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateArithmetics, F32Vec2Success) { + const std::string body = R"( +%val1 = OpFMul %f32vec2 %f32vec2_01 %f32vec2_12 +%val2 = OpFSub %f32vec2 %f32vec2_12 %f32vec2_01 +%val3 = OpFAdd %f32vec2 %val1 %val2 +%val4 = OpFNegate %f32vec2 %val3 +%val5 = OpFDiv %f32vec2 %val4 %val1 +%val6 = OpFRem %f32vec2 %val4 %f32vec2_12 +%val7 = OpFMod %f32vec2 %val4 %f32vec2_12 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateArithmetics, F64Vec2Success) { + const std::string body = R"( +%val1 = OpFMul %f64vec2 %f64vec2_01 %f64vec2_12 +%val2 = OpFSub %f64vec2 %f64vec2_12 %f64vec2_01 +%val3 = OpFAdd %f64vec2 %val1 %val2 +%val4 = OpFNegate %f64vec2 %val3 +%val5 = OpFDiv %f64vec2 %val4 %val1 +%val6 = OpFRem %f64vec2 %val4 %f64vec2_12 +%val7 = OpFMod %f64vec2 %val4 %f64vec2_12 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateArithmetics, U32Vec2Success) { + const std::string body = R"( +%val1 = OpIMul %u32vec2 %u32vec2_01 %u32vec2_12 +%val2 = OpISub %u32vec2 %u32vec2_12 %u32vec2_01 +%val3 = OpIAdd %u32vec2 %val1 %val2 +%val4 = OpSNegate %u32vec2 %val3 +%val5 = OpSDiv %u32vec2 %val4 %val1 +%val6 = OpSRem %u32vec2 %val4 %u32vec2_12 +%val7 = OpSMod %u32vec2 %val4 %u32vec2_12 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateArithmetics, FNegateTypeIdU32) { + const std::string body = R"( +%val = OpFNegate %u32 %u32_0 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected floating scalar or vector type as Result Type: FNegate")); +} + +TEST_F(ValidateArithmetics, FNegateTypeIdVec2U32) { + const std::string body = R"( +%val = OpFNegate %u32vec2 %u32vec2_01 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected floating scalar or vector type as Result Type: FNegate")); +} + +TEST_F(ValidateArithmetics, FNegateWrongOperand) { + const std::string body = R"( +%val = OpFNegate %f32 %u32_0 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected arithmetic operands to be of Result Type: " + "FNegate operand index 2")); +} + +TEST_F(ValidateArithmetics, FMulTypeIdU32) { + const std::string body = R"( +%val = OpFMul %u32 %u32_0 %u32_1 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected floating scalar or vector type as Result Type: FMul")); +} + +TEST_F(ValidateArithmetics, FMulTypeIdVec2U32) { + const std::string body = R"( +%val = OpFMul %u32vec2 %u32vec2_01 %u32vec2_12 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected floating scalar or vector type as Result Type: FMul")); +} + +TEST_F(ValidateArithmetics, FMulWrongOperand1) { + const std::string body = R"( +%val = OpFMul %f32 %u32_0 %f32_1 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected arithmetic operands to be of Result Type: " + "FMul operand index 2")); +} + +TEST_F(ValidateArithmetics, FMulWrongOperand2) { + const std::string body = R"( +%val = OpFMul %f32 %f32_0 %u32_1 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected arithmetic operands to be of Result Type: " + "FMul operand index 3")); +} + +TEST_F(ValidateArithmetics, FMulWrongVectorOperand1) { + const std::string body = R"( +%val = OpFMul %f64vec3 %f32vec3_123 %f64vec3_012 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected arithmetic operands to be of Result Type: " + "FMul operand index 2")); +} + +TEST_F(ValidateArithmetics, FMulWrongVectorOperand2) { + const std::string body = R"( +%val = OpFMul %f32vec3 %f32vec3_123 %f64vec3_012 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected arithmetic operands to be of Result Type: " + "FMul operand index 3")); +} + +TEST_F(ValidateArithmetics, IMulFloatTypeId) { + const std::string body = R"( +%val = OpIMul %f32 %u32_0 %s32_1 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected int scalar or vector type as Result Type: IMul")); +} + +TEST_F(ValidateArithmetics, IMulFloatOperand1) { + const std::string body = R"( +%val = OpIMul %u32 %f32_0 %s32_1 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected int scalar or vector type as operand: " + "IMul operand index 2")); +} + +TEST_F(ValidateArithmetics, IMulFloatOperand2) { + const std::string body = R"( +%val = OpIMul %u32 %s32_0 %f32_1 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected int scalar or vector type as operand: " + "IMul operand index 3")); +} + +TEST_F(ValidateArithmetics, IMulWrongBitWidthOperand1) { + const std::string body = R"( +%val = OpIMul %u64 %u32_0 %s64_1 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected arithmetic operands to have the same bit width " + "as Result Type: IMul operand index 2")); +} + +TEST_F(ValidateArithmetics, IMulWrongBitWidthOperand2) { + const std::string body = R"( +%val = OpIMul %u32 %u32_0 %s64_1 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected arithmetic operands to have the same bit width " + "as Result Type: IMul operand index 3")); +} + +TEST_F(ValidateArithmetics, IMulWrongBitWidthVector) { + const std::string body = R"( +%val = OpIMul %u64vec3 %u32vec3_012 %u32vec3_123 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected arithmetic operands to have the same bit width " + "as Result Type: IMul operand index 2")); +} + +TEST_F(ValidateArithmetics, IMulVectorScalarOperand1) { + const std::string body = R"( +%val = OpIMul %u32vec2 %u32_0 %u32vec2_01 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected arithmetic operands to have the same dimension " + "as Result Type: IMul operand index 2")); +} + +TEST_F(ValidateArithmetics, IMulVectorScalarOperand2) { + const std::string body = R"( +%val = OpIMul %u32vec2 %u32vec2_01 %u32_0 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected arithmetic operands to have the same dimension " + "as Result Type: IMul operand index 3")); +} + +TEST_F(ValidateArithmetics, IMulScalarVectorOperand1) { + const std::string body = R"( +%val = OpIMul %s32 %u32vec2_01 %u32_0 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected arithmetic operands to have the same dimension " + "as Result Type: IMul operand index 2")); +} + +TEST_F(ValidateArithmetics, IMulScalarVectorOperand2) { + const std::string body = R"( +%val = OpIMul %u32 %u32_0 %s32vec2_01 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected arithmetic operands to have the same dimension " + "as Result Type: IMul operand index 3")); +} + +TEST_F(ValidateArithmetics, SNegateFloat) { + const std::string body = R"( +%val = OpSNegate %s32 %f32_1 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected int scalar or vector type as operand: " + "SNegate operand index 2")); +} + +TEST_F(ValidateArithmetics, UDivFloatType) { + const std::string body = R"( +%val = OpUDiv %f32 %u32_2 %u32_1 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected unsigned int scalar or vector type as Result Type: UDiv")); +} + +TEST_F(ValidateArithmetics, UDivSignedIntType) { + const std::string body = R"( +%val = OpUDiv %s32 %u32_2 %u32_1 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected unsigned int scalar or vector type as Result Type: UDiv")); +} + +TEST_F(ValidateArithmetics, UDivWrongOperand1) { + const std::string body = R"( +%val = OpUDiv %u64 %f64_2 %u64_1 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected arithmetic operands to be of Result Type: " + "UDiv operand index 2")); +} + +TEST_F(ValidateArithmetics, UDivWrongOperand2) { + const std::string body = R"( +%val = OpUDiv %u64 %u64_2 %u32_1 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected arithmetic operands to be of Result Type: " + "UDiv operand index 3")); +} + +TEST_F(ValidateArithmetics, DotSuccess) { + const std::string body = R"( +%val = OpDot %f32 %f32vec2_01 %f32vec2_12 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateArithmetics, DotWrongTypeId) { + const std::string body = R"( +%val = OpDot %u32 %u32vec2_01 %u32vec2_12 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected float scalar type as Result Type: Dot")); +} + +TEST_F(ValidateArithmetics, DotNotVectorTypeOperand1) { + const std::string body = R"( +%val = OpDot %f32 %f32 %f32vec2_12 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected float vector as operand: Dot operand index 2")); +} + +TEST_F(ValidateArithmetics, DotNotVectorTypeOperand2) { + const std::string body = R"( +%val = OpDot %f32 %f32vec3_012 %f32_1 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected float vector as operand: Dot operand index 3")); +} + +TEST_F(ValidateArithmetics, DotWrongComponentOperand1) { + const std::string body = R"( +%val = OpDot %f64 %f32vec2_01 %f64vec2_12 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected component type to be equal to Result Type: " + "Dot operand index 2")); +} + +TEST_F(ValidateArithmetics, DotWrongComponentOperand2) { + const std::string body = R"( +%val = OpDot %f32 %f32vec2_01 %f64vec2_12 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected component type to be equal to Result Type: " + "Dot operand index 3")); +} + +TEST_F(ValidateArithmetics, DotDifferentVectorSize) { + const std::string body = R"( +%val = OpDot %f32 %f32vec2_01 %f32vec3_123 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected operands to have the same number of componenets: Dot")); +} + +TEST_F(ValidateArithmetics, VectorTimesScalarSuccess) { + const std::string body = R"( +%val = OpVectorTimesScalar %f32vec2 %f32vec2_01 %f32_2 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateArithmetics, VectorTimesScalarWrongTypeId) { + const std::string body = R"( +%val = OpVectorTimesScalar %u32vec2 %f32vec2_01 %f32_2 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected float vector type as Result Type: " + "VectorTimesScalar")); +} + +TEST_F(ValidateArithmetics, VectorTimesScalarWrongVector) { + const std::string body = R"( +%val = OpVectorTimesScalar %f32vec2 %f32vec3_012 %f32_2 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected vector operand type to be equal to Result Type: " + "VectorTimesScalar")); +} + +TEST_F(ValidateArithmetics, VectorTimesScalarWrongScalar) { + const std::string body = R"( +%val = OpVectorTimesScalar %f32vec2 %f32vec2_01 %f64_2 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected scalar operand type to be equal to the component " + "type of the vector operand: VectorTimesScalar")); +} + +TEST_F(ValidateArithmetics, MatrixTimesScalarSuccess) { + const std::string body = R"( +%val = OpMatrixTimesScalar %f32mat22 %f32mat22_1212 %f32_2 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateArithmetics, MatrixTimesScalarWrongTypeId) { + const std::string body = R"( +%val = OpMatrixTimesScalar %f32vec2 %f32mat22_1212 %f32_2 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected float matrix type as Result Type: " + "MatrixTimesScalar")); +} + +TEST_F(ValidateArithmetics, MatrixTimesScalarWrongMatrix) { + const std::string body = R"( +%val = OpMatrixTimesScalar %f32mat22 %f32vec2_01 %f32_2 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected matrix operand type to be equal to Result Type: " + "MatrixTimesScalar")); +} + +TEST_F(ValidateArithmetics, MatrixTimesScalarWrongScalar) { + const std::string body = R"( +%val = OpMatrixTimesScalar %f32mat22 %f32mat22_1212 %f64_2 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected scalar operand type to be equal to the component " + "type of the matrix operand: MatrixTimesScalar")); +} + +TEST_F(ValidateArithmetics, VectorTimesMatrix2x22Success) { + const std::string body = R"( +%val = OpVectorTimesMatrix %f32vec2 %f32vec2_12 %f32mat22_1212 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateArithmetics, VectorTimesMatrix3x32Success) { + const std::string body = R"( +%val = OpVectorTimesMatrix %f32vec2 %f32vec3_123 %f32mat32_123123 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateArithmetics, VectorTimesMatrixWrongTypeId) { + const std::string body = R"( +%val = OpVectorTimesMatrix %f32mat22 %f32vec2_12 %f32mat22_1212 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected float vector type as Result Type: " + "VectorTimesMatrix")); +} + +TEST_F(ValidateArithmetics, VectorTimesMatrixNotFloatVector) { + const std::string body = R"( +%val = OpVectorTimesMatrix %f32vec2 %u32vec2_12 %f32mat22_1212 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected float vector type as left operand: " + "VectorTimesMatrix")); +} + +TEST_F(ValidateArithmetics, VectorTimesMatrixWrongVectorComponent) { + const std::string body = R"( +%val = OpVectorTimesMatrix %f32vec2 %f64vec2_12 %f32mat22_1212 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected component types of Result Type and vector to be equal: " + "VectorTimesMatrix")); +} + +TEST_F(ValidateArithmetics, VectorTimesMatrixWrongMatrix) { + const std::string body = R"( +%val = OpVectorTimesMatrix %f32vec2 %f32vec2_12 %f32vec2_12 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected float matrix type as right operand: " + "VectorTimesMatrix")); +} + +TEST_F(ValidateArithmetics, VectorTimesMatrixWrongMatrixComponent) { + const std::string body = R"( +%val = OpVectorTimesMatrix %f32vec2 %f32vec2_12 %f64mat22_1212 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected component types of Result Type and matrix to be equal: " + "VectorTimesMatrix")); +} + +TEST_F(ValidateArithmetics, VectorTimesMatrix2eq2x23Fail) { + const std::string body = R"( +%val = OpVectorTimesMatrix %f32vec2 %f32vec2_12 %f32mat23_121212 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected number of columns of the matrix to be equal to Result Type " + "vector size: VectorTimesMatrix")); +} + +TEST_F(ValidateArithmetics, VectorTimesMatrix2x32Fail) { + const std::string body = R"( +%val = OpVectorTimesMatrix %f32vec2 %f32vec2_12 %f32mat32_123123 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected number of rows of the matrix to be equal to the vector " + "operand size: VectorTimesMatrix")); +} + +TEST_F(ValidateArithmetics, MatrixTimesVector22x2Success) { + const std::string body = R"( +%val = OpMatrixTimesVector %f32vec2 %f32mat22_1212 %f32vec2_12 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateArithmetics, MatrixTimesVector23x3Success) { + const std::string body = R"( +%val = OpMatrixTimesVector %f32vec2 %f32mat23_121212 %f32vec3_123 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateArithmetics, MatrixTimesVectorWrongTypeId) { + const std::string body = R"( +%val = OpMatrixTimesVector %f32mat22 %f32mat22_1212 %f32vec2_12 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected float vector type as Result Type: " + "MatrixTimesVector")); +} + +TEST_F(ValidateArithmetics, MatrixTimesVectorWrongMatrix) { + const std::string body = R"( +%val = OpMatrixTimesVector %f32vec3 %f32vec3_123 %f32vec3_123 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected float matrix type as left operand: " + "MatrixTimesVector")); +} + +TEST_F(ValidateArithmetics, MatrixTimesVectorWrongMatrixCol) { + const std::string body = R"( +%val = OpMatrixTimesVector %f32vec3 %f32mat23_121212 %f32vec3_123 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected column type of the matrix to be equal to Result Type: " + "MatrixTimesVector")); +} + +TEST_F(ValidateArithmetics, MatrixTimesVectorWrongVector) { + const std::string body = R"( +%val = OpMatrixTimesVector %f32vec2 %f32mat22_1212 %u32vec2_12 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected float vector type as right operand: " + "MatrixTimesVector")); +} + +TEST_F(ValidateArithmetics, MatrixTimesVectorDifferentComponents) { + const std::string body = R"( +%val = OpMatrixTimesVector %f32vec2 %f32mat22_1212 %f64vec2_12 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected component types of the operands to be equal: " + "MatrixTimesVector")); +} + +TEST_F(ValidateArithmetics, MatrixTimesVector22x3Fail) { + const std::string body = R"( +%val = OpMatrixTimesVector %f32vec2 %f32mat22_1212 %f32vec3_123 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected number of columns of the matrix to be equal to the vector " + "size: MatrixTimesVector")); +} + +TEST_F(ValidateArithmetics, MatrixTimesMatrix22x22Success) { + const std::string body = R"( +%val = OpMatrixTimesMatrix %f32mat22 %f32mat22_1212 %f32mat22_1212 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateArithmetics, MatrixTimesMatrix23x32Success) { + const std::string body = R"( +%val = OpMatrixTimesMatrix %f32mat22 %f32mat23_121212 %f32mat32_123123 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateArithmetics, MatrixTimesMatrix33x33Success) { + const std::string body = R"( +%val = OpMatrixTimesMatrix %f32mat33 %f32mat33_123123123 %f32mat33_123123123 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateArithmetics, MatrixTimesMatrixWrongTypeId) { + const std::string body = R"( +%val = OpMatrixTimesMatrix %f32vec2 %f32mat22_1212 %f32mat22_1212 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected float matrix type as Result Type: MatrixTimesMatrix")); +} + +TEST_F(ValidateArithmetics, MatrixTimesMatrixWrongLeftOperand) { + const std::string body = R"( +%val = OpMatrixTimesMatrix %f32mat22 %f32vec2_12 %f32mat22_1212 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected float matrix type as left operand: MatrixTimesMatrix")); +} + +TEST_F(ValidateArithmetics, MatrixTimesMatrixWrongRightOperand) { + const std::string body = R"( +%val = OpMatrixTimesMatrix %f32mat22 %f32mat22_1212 %f32vec2_12 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected float matrix type as right operand: MatrixTimesMatrix")); +} + +TEST_F(ValidateArithmetics, MatrixTimesMatrix32x23Fail) { + const std::string body = R"( +%val = OpMatrixTimesMatrix %f32mat22 %f32mat32_123123 %f32mat23_121212 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected column types of Result Type and left matrix to be equal: " + "MatrixTimesMatrix")); +} + +TEST_F(ValidateArithmetics, MatrixTimesMatrixDifferentComponents) { + const std::string body = R"( +%val = OpMatrixTimesMatrix %f32mat22 %f32mat22_1212 %f64mat22_1212 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected component types of Result Type and right " + "matrix to be equal: " + "MatrixTimesMatrix")); +} + +TEST_F(ValidateArithmetics, MatrixTimesMatrix23x23Fail) { + const std::string body = R"( +%val = OpMatrixTimesMatrix %f32mat22 %f32mat23_121212 %f32mat23_121212 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected number of columns of Result Type and right " + "matrix to be equal: " + "MatrixTimesMatrix")); +} + +TEST_F(ValidateArithmetics, MatrixTimesMatrix23x22Fail) { + const std::string body = R"( +%val = OpMatrixTimesMatrix %f32mat22 %f32mat23_121212 %f32mat22_1212 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected number of columns of left matrix and number " + "of rows of right " + "matrix to be equal: MatrixTimesMatrix")); +} + +TEST_F(ValidateArithmetics, OuterProduct2x2Success) { + const std::string body = R"( +%val = OpOuterProduct %f32mat22 %f32vec2_12 %f32vec2_01 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateArithmetics, OuterProduct3x2Success) { + const std::string body = R"( +%val = OpOuterProduct %f32mat32 %f32vec3_123 %f32vec2_01 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateArithmetics, OuterProduct2x3Success) { + const std::string body = R"( +%val = OpOuterProduct %f32mat23 %f32vec2_01 %f32vec3_123 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateArithmetics, OuterProductWrongTypeId) { + const std::string body = R"( +%val = OpOuterProduct %f32vec2 %f32vec2_01 %f32vec3_123 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected float matrix type as Result Type: " + "OuterProduct")); +} + +TEST_F(ValidateArithmetics, OuterProductWrongLeftOperand) { + const std::string body = R"( +%val = OpOuterProduct %f32mat22 %f32vec3_123 %f32vec2_01 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected column type of Result Type to be equal to the type " + "of the left operand: OuterProduct")); +} + +TEST_F(ValidateArithmetics, OuterProductRightOperandNotFloatVector) { + const std::string body = R"( +%val = OpOuterProduct %f32mat22 %f32vec2_12 %u32vec2_01 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected float vector type as right operand: OuterProduct")); +} + +TEST_F(ValidateArithmetics, OuterProductRightOperandWrongComponent) { + const std::string body = R"( +%val = OpOuterProduct %f32mat22 %f32vec2_12 %f64vec2_01 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected component types of the operands to be equal: " + "OuterProduct")); +} + +TEST_F(ValidateArithmetics, OuterProductRightOperandWrongDimension) { + const std::string body = R"( +%val = OpOuterProduct %f32mat22 %f32vec2_12 %f32vec3_123 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected number of columns of the matrix to be equal to the " + "vector size of the right operand: OuterProduct")); +} + +TEST_F(ValidateArithmetics, IAddCarrySuccess) { + const std::string body = R"( +%val1 = OpIAddCarry %struct_u32_u32 %u32_0 %u32_1 +%val2 = OpIAddCarry %struct_u32vec2_u32vec2 %u32vec2_01 %u32vec2_12 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateArithmetics, IAddCarryResultTypeNotStruct) { + const std::string body = R"( +%val = OpIAddCarry %u32 %u32_0 %u32_1 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected a struct as Result Type: IAddCarry")); +} + +TEST_F(ValidateArithmetics, IAddCarryResultTypeNotTwoMembers) { + const std::string body = R"( +%val = OpIAddCarry %struct_u32_u32_u32 %u32_0 %u32_1 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Result Type struct to have two members: IAddCarry")); +} + +TEST_F(ValidateArithmetics, IAddCarryResultTypeMemberNotUnsignedInt) { + const std::string body = R"( +%val = OpIAddCarry %struct_s32_s32 %s32_0 %s32_1 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type struct member types to be " + "unsigned integer scalar " + "or vector: IAddCarry")); +} + +TEST_F(ValidateArithmetics, IAddCarryWrongLeftOperand) { + const std::string body = R"( +%val = OpIAddCarry %struct_u32_u32 %s32_0 %u32_1 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected both operands to be of Result Type member " + "type: IAddCarry")); +} + +TEST_F(ValidateArithmetics, IAddCarryWrongRightOperand) { + const std::string body = R"( +%val = OpIAddCarry %struct_u32_u32 %u32_0 %s32_1 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected both operands to be of Result Type member " + "type: IAddCarry")); +} + +TEST_F(ValidateArithmetics, OpSMulExtendedSuccess) { + const std::string body = R"( +%val1 = OpSMulExtended %struct_u32_u32 %u32_0 %u32_1 +%val2 = OpSMulExtended %struct_s32_s32 %s32_0 %s32_1 +%val3 = OpSMulExtended %struct_u32vec2_u32vec2 %u32vec2_01 %u32vec2_12 +%val4 = OpSMulExtended %struct_s32vec2_s32vec2 %s32vec2_01 %s32vec2_12 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateArithmetics, SMulExtendedResultTypeMemberNotInt) { + const std::string body = R"( +%val = OpSMulExtended %struct_f32_f32 %f32_0 %f32_1 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Result Type struct member types to be integer scalar " + "or vector: SMulExtended")); +} + +TEST_F(ValidateArithmetics, SMulExtendedResultTypeMembersNotIdentical) { + const std::string body = R"( +%val = OpSMulExtended %struct_s32_u32 %s32_0 %s32_1 +)"; + + CompileSuccessfully(GenerateCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Result Type struct member types to be identical: " + "SMulExtended")); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/val/val_atomics_test.cpp b/3rdparty/spirv-tools/test/val/val_atomics_test.cpp new file mode 100644 index 000000000..8d98fd371 --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_atomics_test.cpp @@ -0,0 +1,1012 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "gmock/gmock.h" +#include "unit_spirv.h" +#include "val_fixtures.h" + +namespace { + +using ::testing::HasSubstr; +using ::testing::Not; + +using ValidateAtomics = spvtest::ValidateBase; + +std::string GenerateShaderCode( + const std::string& body, + const std::string& capabilities_and_extensions = "") { + std::ostringstream ss; + ss << R"( +OpCapability Shader +OpCapability Int64 +)"; + + ss << capabilities_and_extensions; + ss << R"( +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +%void = OpTypeVoid +%func = OpTypeFunction %void +%bool = OpTypeBool +%f32 = OpTypeFloat 32 +%u32 = OpTypeInt 32 0 +%u64 = OpTypeInt 64 0 +%f32vec4 = OpTypeVector %f32 4 + +%f32_0 = OpConstant %f32 0 +%f32_1 = OpConstant %f32 1 +%u32_0 = OpConstant %u32 0 +%u32_1 = OpConstant %u32 1 +%u64_1 = OpConstant %u64 1 +%f32vec4_0000 = OpConstantComposite %f32vec4 %f32_0 %f32_0 %f32_0 %f32_0 + +%cross_device = OpConstant %u32 0 +%device = OpConstant %u32 1 +%workgroup = OpConstant %u32 2 +%subgroup = OpConstant %u32 3 +%invocation = OpConstant %u32 4 + +%relaxed = OpConstant %u32 0 +%acquire = OpConstant %u32 2 +%release = OpConstant %u32 4 +%acquire_release = OpConstant %u32 8 +%acquire_and_release = OpConstant %u32 6 +%sequentially_consistent = OpConstant %u32 16 +%acquire_release_uniform_workgroup = OpConstant %u32 328 + +%f32_ptr = OpTypePointer Workgroup %f32 +%f32_var = OpVariable %f32_ptr Workgroup + +%u32_ptr = OpTypePointer Workgroup %u32 +%u32_var = OpVariable %u32_ptr Workgroup + +%u64_ptr = OpTypePointer Workgroup %u64 +%u64_var = OpVariable %u64_ptr Workgroup + +%f32vec4_ptr = OpTypePointer Workgroup %f32vec4 +%f32vec4_var = OpVariable %f32vec4_ptr Workgroup + +%f32_ptr_function = OpTypePointer Function %f32 + +%main = OpFunction %void None %func +%main_entry = OpLabel +)"; + + ss << body; + + ss << R"( +OpReturn +OpFunctionEnd)"; + + return ss.str(); +} + +std::string GenerateKernelCode( + const std::string& body, + const std::string& capabilities_and_extensions = "") { + std::ostringstream ss; + ss << R"( +OpCapability Addresses +OpCapability Kernel +OpCapability Linkage +OpCapability Int64 +)"; + + ss << capabilities_and_extensions; + ss << R"( +OpMemoryModel Physical32 OpenCL +%void = OpTypeVoid +%func = OpTypeFunction %void +%bool = OpTypeBool +%f32 = OpTypeFloat 32 +%u32 = OpTypeInt 32 0 +%u64 = OpTypeInt 64 0 +%f32vec4 = OpTypeVector %f32 4 + +%f32_0 = OpConstant %f32 0 +%f32_1 = OpConstant %f32 1 +%u32_0 = OpConstant %u32 0 +%u32_1 = OpConstant %u32 1 +%u64_1 = OpConstant %u64 1 +%f32vec4_0000 = OpConstantComposite %f32vec4 %f32_0 %f32_0 %f32_0 %f32_0 + +%cross_device = OpConstant %u32 0 +%device = OpConstant %u32 1 +%workgroup = OpConstant %u32 2 +%subgroup = OpConstant %u32 3 +%invocation = OpConstant %u32 4 + +%relaxed = OpConstant %u32 0 +%acquire = OpConstant %u32 2 +%release = OpConstant %u32 4 +%acquire_release = OpConstant %u32 8 +%acquire_and_release = OpConstant %u32 6 +%sequentially_consistent = OpConstant %u32 16 +%acquire_release_uniform_workgroup = OpConstant %u32 328 +%acquire_release_atomic_counter_workgroup = OpConstant %u32 1288 + +%f32_ptr = OpTypePointer Workgroup %f32 +%f32_var = OpVariable %f32_ptr Workgroup + +%u32_ptr = OpTypePointer Workgroup %u32 +%u32_var = OpVariable %u32_ptr Workgroup + +%u64_ptr = OpTypePointer Workgroup %u64 +%u64_var = OpVariable %u64_ptr Workgroup + +%f32vec4_ptr = OpTypePointer Workgroup %f32vec4 +%f32vec4_var = OpVariable %f32vec4_ptr Workgroup + +%f32_ptr_function = OpTypePointer Function %f32 + +%main = OpFunction %void None %func +%main_entry = OpLabel +)"; + + ss << body; + + ss << R"( +OpReturn +OpFunctionEnd)"; + + return ss.str(); +} + +TEST_F(ValidateAtomics, AtomicLoadShaderSuccess) { + const std::string body = R"( +%val1 = OpAtomicLoad %u32 %u32_var %device %relaxed +%val2 = OpAtomicLoad %u32 %u32_var %workgroup %acquire +%val3 = OpAtomicLoad %u64 %u64_var %subgroup %sequentially_consistent +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateAtomics, AtomicLoadKernelSuccess) { + const std::string body = R"( +%val1 = OpAtomicLoad %f32 %f32_var %device %relaxed +%val2 = OpAtomicLoad %u32 %u32_var %workgroup %sequentially_consistent +%val3 = OpAtomicLoad %u64 %u64_var %subgroup %acquire +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateAtomics, AtomicLoadVulkanSuccess) { + const std::string body = R"( +%val1 = OpAtomicLoad %u32 %u32_var %device %relaxed +%val2 = OpAtomicLoad %u32 %u32_var %workgroup %acquire +)"; + + CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); +} + +// TODO(atgoo@github.com): the corresponding check fails Vulkan CTS, +// reenable once fixed. +TEST_F(ValidateAtomics, DISABLED_AtomicLoadVulkanSubgroup) { + const std::string body = R"( +%val1 = OpAtomicLoad %u32 %u32_var %subgroup %acquire +)"; + + CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("AtomicLoad: in Vulkan environment memory scope is " + "limited to Device, Workgroup and Invocation")); +} + +TEST_F(ValidateAtomics, AtomicLoadVulkanRelease) { + const std::string body = R"( +%val1 = OpAtomicLoad %u32 %u32_var %workgroup %release +)"; + + CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Vulkan spec disallows OpAtomicLoad with Memory Semantics " + "Release, AcquireRelease and SequentiallyConsistent")); +} + +TEST_F(ValidateAtomics, AtomicLoadVulkanAcquireRelease) { + const std::string body = R"( +%val1 = OpAtomicLoad %u32 %u32_var %workgroup %acquire_release +)"; + + CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Vulkan spec disallows OpAtomicLoad with Memory Semantics " + "Release, AcquireRelease and SequentiallyConsistent")); +} + +TEST_F(ValidateAtomics, AtomicLoadVulkanSequentiallyConsistent) { + const std::string body = R"( +%val1 = OpAtomicLoad %u32 %u32_var %workgroup %sequentially_consistent +)"; + + CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Vulkan spec disallows OpAtomicLoad with Memory Semantics " + "Release, AcquireRelease and SequentiallyConsistent")); +} + +TEST_F(ValidateAtomics, AtomicLoadShaderFloat) { + const std::string body = R"( +%val1 = OpAtomicLoad %f32 %f32_var %device %relaxed +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("AtomicLoad: " + "expected Result Type to be int scalar type")); +} + +TEST_F(ValidateAtomics, AtomicLoadVulkanInt64) { + const std::string body = R"( +%val1 = OpAtomicLoad %u64 %u64_var %device %relaxed +)"; + + CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("AtomicLoad: according to the Vulkan spec atomic " + "Result Type needs to be a 32-bit int scalar type")); +} + +TEST_F(ValidateAtomics, AtomicLoadWrongResultType) { + const std::string body = R"( +%val1 = OpAtomicLoad %f32vec4 %f32vec4_var %device %relaxed +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("AtomicLoad: " + "expected Result Type to be int or float scalar type")); +} + +TEST_F(ValidateAtomics, AtomicLoadWrongPointerType) { + const std::string body = R"( +%val1 = OpAtomicLoad %f32 %f32_ptr %device %relaxed +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("AtomicLoad: expected Pointer to be of type OpTypePointer")); +} + +TEST_F(ValidateAtomics, AtomicLoadWrongPointerDataType) { + const std::string body = R"( +%val1 = OpAtomicLoad %u32 %f32_var %device %relaxed +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("AtomicLoad: " + "expected Pointer to point to a value of type Result Type")); +} + +TEST_F(ValidateAtomics, AtomicLoadWrongScopeType) { + const std::string body = R"( +%val1 = OpAtomicLoad %f32 %f32_var %f32_1 %relaxed +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("AtomicLoad: expected Scope to be 32-bit int")); +} + +TEST_F(ValidateAtomics, AtomicLoadWrongMemorySemanticsType) { + const std::string body = R"( +%val1 = OpAtomicLoad %f32 %f32_var %device %u64_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("AtomicLoad: expected Memory Semantics to be 32-bit int")); +} + +TEST_F(ValidateAtomics, AtomicStoreKernelSuccess) { + const std::string body = R"( +OpAtomicStore %f32_var %device %relaxed %f32_1 +OpAtomicStore %u32_var %subgroup %release %u32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateAtomics, AtomicStoreShaderSuccess) { + const std::string body = R"( +OpAtomicStore %u32_var %device %release %u32_1 +OpAtomicStore %u32_var %subgroup %sequentially_consistent %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateAtomics, AtomicStoreVulkanSuccess) { + const std::string body = R"( +OpAtomicStore %u32_var %device %release %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); +} + +TEST_F(ValidateAtomics, AtomicStoreVulkanAcquire) { + const std::string body = R"( +OpAtomicStore %u32_var %device %acquire %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Vulkan spec disallows OpAtomicStore with Memory Semantics " + "Acquire, AcquireRelease and SequentiallyConsistent")); +} + +TEST_F(ValidateAtomics, AtomicStoreVulkanAcquireRelease) { + const std::string body = R"( +OpAtomicStore %u32_var %device %acquire_release %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Vulkan spec disallows OpAtomicStore with Memory Semantics " + "Acquire, AcquireRelease and SequentiallyConsistent")); +} + +TEST_F(ValidateAtomics, AtomicStoreVulkanSequentiallyConsistent) { + const std::string body = R"( +OpAtomicStore %u32_var %device %sequentially_consistent %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Vulkan spec disallows OpAtomicStore with Memory Semantics " + "Acquire, AcquireRelease and SequentiallyConsistent")); +} + +TEST_F(ValidateAtomics, AtomicStoreWrongPointerType) { + const std::string body = R"( +OpAtomicStore %f32_1 %device %relaxed %f32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("AtomicStore: expected Pointer to be of type OpTypePointer")); +} + +TEST_F(ValidateAtomics, AtomicStoreWrongPointerDataType) { + const std::string body = R"( +OpAtomicStore %f32vec4_var %device %relaxed %f32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("AtomicStore: " + "expected Pointer to be a pointer to int or float scalar " + "type")); +} + +TEST_F(ValidateAtomics, AtomicStoreWrongPointerStorageType) { + const std::string body = R"( +%f32_var_function = OpVariable %f32_ptr_function Function +OpAtomicStore %f32_var_function %device %relaxed %f32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("AtomicStore: expected Pointer Storage Class to be Uniform, " + "Workgroup, CrossWorkgroup, Generic, AtomicCounter, Image or " + "StorageBuffer")); +} + +TEST_F(ValidateAtomics, AtomicStoreWrongScopeType) { + const std::string body = R"( +OpAtomicStore %f32_var %f32_1 %relaxed %f32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("AtomicStore: expected Scope to be 32-bit int")); +} + +TEST_F(ValidateAtomics, AtomicStoreWrongMemorySemanticsType) { + const std::string body = R"( +OpAtomicStore %f32_var %device %f32_1 %f32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("AtomicStore: expected Memory Semantics to be 32-bit int")); +} + +TEST_F(ValidateAtomics, AtomicStoreWrongValueType) { + const std::string body = R"( +OpAtomicStore %f32_var %device %relaxed %u32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("AtomicStore: " + "expected Value type and the type pointed to by Pointer to " + "be the same")); +} + +TEST_F(ValidateAtomics, AtomicExchangeShaderSuccess) { + const std::string body = R"( +%val1 = OpAtomicStore %u32_var %device %relaxed %u32_1 +%val2 = OpAtomicExchange %u32 %u32_var %device %relaxed %u32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateAtomics, AtomicExchangeKernelSuccess) { + const std::string body = R"( +OpAtomicStore %f32_var %device %relaxed %f32_1 +%val2 = OpAtomicExchange %f32 %f32_var %device %relaxed %f32_0 +%val3 = OpAtomicStore %u32_var %device %relaxed %u32_1 +%val4 = OpAtomicExchange %u32 %u32_var %device %relaxed %u32_0 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateAtomics, AtomicExchangeShaderFloat) { + const std::string body = R"( +OpAtomicStore %f32_var %device %relaxed %f32_1 +%val2 = OpAtomicExchange %f32 %f32_var %device %relaxed %f32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("AtomicExchange: " + "expected Result Type to be int scalar type")); +} + +TEST_F(ValidateAtomics, AtomicExchangeWrongResultType) { + const std::string body = R"( +%val1 = OpStore %f32vec4_var %f32vec4_0000 +%val2 = OpAtomicExchange %f32vec4 %f32vec4_var %device %relaxed %f32vec4_0000 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("AtomicExchange: " + "expected Result Type to be int or float scalar type")); +} + +TEST_F(ValidateAtomics, AtomicExchangeWrongPointerType) { + const std::string body = R"( +%val2 = OpAtomicExchange %f32 %f32vec4_ptr %device %relaxed %f32vec4_0000 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "AtomicExchange: expected Pointer to be of type OpTypePointer")); +} + +TEST_F(ValidateAtomics, AtomicExchangeWrongPointerDataType) { + const std::string body = R"( +%val1 = OpStore %f32vec4_var %f32vec4_0000 +%val2 = OpAtomicExchange %f32 %f32vec4_var %device %relaxed %f32vec4_0000 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("AtomicExchange: " + "expected Pointer to point to a value of type Result Type")); +} + +TEST_F(ValidateAtomics, AtomicExchangeWrongScopeType) { + const std::string body = R"( +OpAtomicStore %f32_var %device %relaxed %f32_1 +%val2 = OpAtomicExchange %f32 %f32_var %f32_1 %relaxed %f32_0 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("AtomicExchange: expected Scope to be 32-bit int")); +} + +TEST_F(ValidateAtomics, AtomicExchangeWrongMemorySemanticsType) { + const std::string body = R"( +OpAtomicStore %f32_var %device %relaxed %f32_1 +%val2 = OpAtomicExchange %f32 %f32_var %device %f32_1 %f32_0 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("AtomicExchange: expected Memory Semantics to be 32-bit int")); +} + +TEST_F(ValidateAtomics, AtomicExchangeWrongValueType) { + const std::string body = R"( +OpAtomicStore %f32_var %device %relaxed %f32_1 +%val2 = OpAtomicExchange %f32 %f32_var %device %relaxed %u32_0 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("AtomicExchange: " + "expected Value to be of type Result Type")); +} + +TEST_F(ValidateAtomics, AtomicCompareExchangeShaderSuccess) { + const std::string body = R"( +%val1 = OpAtomicStore %u32_var %device %relaxed %u32_1 +%val2 = OpAtomicCompareExchange %u32 %u32_var %device %relaxed %relaxed %u32_0 %u32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateAtomics, AtomicCompareExchangeKernelSuccess) { + const std::string body = R"( +OpAtomicStore %f32_var %device %relaxed %f32_1 +%val2 = OpAtomicCompareExchange %f32 %f32_var %device %relaxed %relaxed %f32_0 %f32_1 +%val3 = OpAtomicStore %u32_var %device %relaxed %u32_1 +%val4 = OpAtomicCompareExchange %u32 %u32_var %device %relaxed %relaxed %u32_0 %u32_0 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateAtomics, AtomicCompareExchangeShaderFloat) { + const std::string body = R"( +OpAtomicStore %f32_var %device %relaxed %f32_1 +%val1 = OpAtomicCompareExchange %f32 %f32_var %device %relaxed %relaxed %f32_0 %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("AtomicCompareExchange: " + "expected Result Type to be int scalar type")); +} + +TEST_F(ValidateAtomics, AtomicCompareExchangeWrongResultType) { + const std::string body = R"( +%val1 = OpStore %f32vec4_var %f32vec4_0000 +%val2 = OpAtomicCompareExchange %f32vec4 %f32vec4_var %device %relaxed %relaxed %f32vec4_0000 %f32vec4_0000 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("AtomicCompareExchange: " + "expected Result Type to be int or float scalar type")); +} + +TEST_F(ValidateAtomics, AtomicCompareExchangeWrongPointerType) { + const std::string body = R"( +%val2 = OpAtomicCompareExchange %f32 %f32vec4_ptr %device %relaxed %relaxed %f32vec4_0000 %f32vec4_0000 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("AtomicCompareExchange: expected Pointer to be of type " + "OpTypePointer")); +} + +TEST_F(ValidateAtomics, AtomicCompareExchangeWrongPointerDataType) { + const std::string body = R"( +%val1 = OpStore %f32vec4_var %f32vec4_0000 +%val2 = OpAtomicCompareExchange %f32 %f32vec4_var %device %relaxed %relaxed %f32_0 %f32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("AtomicCompareExchange: " + "expected Pointer to point to a value of type Result Type")); +} + +TEST_F(ValidateAtomics, AtomicCompareExchangeWrongScopeType) { + const std::string body = R"( +OpAtomicStore %f32_var %device %relaxed %f32_1 +%val2 = OpAtomicCompareExchange %f32 %f32_var %f32_1 %relaxed %relaxed %f32_0 %f32_0 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("AtomicCompareExchange: expected Scope to be 32-bit int")); +} + +TEST_F(ValidateAtomics, AtomicCompareExchangeWrongMemorySemanticsType1) { + const std::string body = R"( +OpAtomicStore %f32_var %device %relaxed %f32_1 +%val2 = OpAtomicCompareExchange %f32 %f32_var %device %f32_1 %relaxed %f32_0 %f32_0 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "AtomicCompareExchange: expected Memory Semantics to be 32-bit int")); +} + +TEST_F(ValidateAtomics, AtomicCompareExchangeWrongMemorySemanticsType2) { + const std::string body = R"( +OpAtomicStore %f32_var %device %relaxed %f32_1 +%val2 = OpAtomicCompareExchange %f32 %f32_var %device %relaxed %f32_1 %f32_0 %f32_0 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "AtomicCompareExchange: expected Memory Semantics to be 32-bit int")); +} + +TEST_F(ValidateAtomics, AtomicCompareExchangeUnequalRelease) { + const std::string body = R"( +OpAtomicStore %f32_var %device %relaxed %f32_1 +%val2 = OpAtomicCompareExchange %f32 %f32_var %device %relaxed %release %f32_0 %f32_0 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("AtomicCompareExchange: Memory Semantics Release and " + "AcquireRelease cannot be used for operand Unequal")); +} + +TEST_F(ValidateAtomics, AtomicCompareExchangeWrongValueType) { + const std::string body = R"( +OpAtomicStore %f32_var %device %relaxed %f32_1 +%val2 = OpAtomicCompareExchange %f32 %f32_var %device %relaxed %relaxed %u32_0 %f32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("AtomicCompareExchange: " + "expected Value to be of type Result Type")); +} + +TEST_F(ValidateAtomics, AtomicCompareExchangeWrongComparatorType) { + const std::string body = R"( +OpAtomicStore %f32_var %device %relaxed %f32_1 +%val2 = OpAtomicCompareExchange %f32 %f32_var %device %relaxed %relaxed %f32_0 %u32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("AtomicCompareExchange: " + "expected Comparator to be of type Result Type")); +} + +TEST_F(ValidateAtomics, AtomicCompareExchangeWeakSuccess) { + const std::string body = R"( +%val3 = OpAtomicStore %u32_var %device %relaxed %u32_1 +%val4 = OpAtomicCompareExchangeWeak %u32 %u32_var %device %relaxed %relaxed %u32_0 %u32_0 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateAtomics, AtomicCompareExchangeWeakWrongResultType) { + const std::string body = R"( +OpAtomicStore %f32_var %device %relaxed %f32_1 +%val2 = OpAtomicCompareExchangeWeak %f32 %f32_var %device %relaxed %relaxed %f32_0 %f32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("AtomicCompareExchangeWeak: " + "expected Result Type to be int scalar type")); +} + +TEST_F(ValidateAtomics, AtomicArithmeticsSuccess) { + const std::string body = R"( +OpAtomicStore %u32_var %device %relaxed %u32_1 +%val1 = OpAtomicIIncrement %u32 %u32_var %device %acquire_release +%val2 = OpAtomicIDecrement %u32 %u32_var %device %acquire_release +%val3 = OpAtomicIAdd %u32 %u32_var %device %acquire_release %u32_1 +%val4 = OpAtomicISub %u32 %u32_var %device %acquire_release %u32_1 +%val5 = OpAtomicUMin %u32 %u32_var %device %acquire_release %u32_1 +%val6 = OpAtomicUMax %u32 %u32_var %device %acquire_release %u32_1 +%val7 = OpAtomicSMin %u32 %u32_var %device %sequentially_consistent %u32_1 +%val8 = OpAtomicSMax %u32 %u32_var %device %sequentially_consistent %u32_1 +%val9 = OpAtomicAnd %u32 %u32_var %device %sequentially_consistent %u32_1 +%val10 = OpAtomicOr %u32 %u32_var %device %sequentially_consistent %u32_1 +%val11 = OpAtomicXor %u32 %u32_var %device %sequentially_consistent %u32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateAtomics, AtomicFlagsSuccess) { + const std::string body = R"( +OpAtomicFlagClear %u32_var %device %release +%val1 = OpAtomicFlagTestAndSet %bool %u32_var %device %relaxed +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateAtomics, AtomicFlagTestAndSetWrongResultType) { + const std::string body = R"( +%val1 = OpAtomicFlagTestAndSet %u32 %u32_var %device %relaxed +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("AtomicFlagTestAndSet: " + "expected Result Type to be bool scalar type")); +} + +TEST_F(ValidateAtomics, AtomicFlagTestAndSetNotPointer) { + const std::string body = R"( +%val1 = OpAtomicFlagTestAndSet %bool %u32_1 %device %relaxed +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("AtomicFlagTestAndSet: " + "expected Pointer to be of type OpTypePointer")); +} + +TEST_F(ValidateAtomics, AtomicFlagTestAndSetNotIntPointer) { + const std::string body = R"( +%val1 = OpAtomicFlagTestAndSet %bool %f32_var %device %relaxed +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("AtomicFlagTestAndSet: " + "expected Pointer to point to a value of 32-bit int type")); +} + +TEST_F(ValidateAtomics, AtomicFlagTestAndSetNotInt32Pointer) { + const std::string body = R"( +%val1 = OpAtomicFlagTestAndSet %bool %u64_var %device %relaxed +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("AtomicFlagTestAndSet: " + "expected Pointer to point to a value of 32-bit int type")); +} + +TEST_F(ValidateAtomics, AtomicFlagTestAndSetWrongScopeType) { + const std::string body = R"( +%val1 = OpAtomicFlagTestAndSet %bool %u32_var %u64_1 %relaxed +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("AtomicFlagTestAndSet: " + "expected Scope to be 32-bit int")); +} + +TEST_F(ValidateAtomics, AtomicFlagTestAndSetWrongMemorySemanticsType) { + const std::string body = R"( +%val1 = OpAtomicFlagTestAndSet %bool %u32_var %device %u64_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("AtomicFlagTestAndSet: " + "expected Memory Semantics to be 32-bit int")); +} + +TEST_F(ValidateAtomics, AtomicFlagClearAcquire) { + const std::string body = R"( +OpAtomicFlagClear %u32_var %device %acquire +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Memory Semantics Acquire and AcquireRelease cannot be " + "used with AtomicFlagClear")); +} + +TEST_F(ValidateAtomics, AtomicFlagClearNotPointer) { + const std::string body = R"( +OpAtomicFlagClear %u32_1 %device %relaxed +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("AtomicFlagClear: " + "expected Pointer to be of type OpTypePointer")); +} + +TEST_F(ValidateAtomics, AtomicFlagClearNotIntPointer) { + const std::string body = R"( +OpAtomicFlagClear %f32_var %device %relaxed +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("AtomicFlagClear: " + "expected Pointer to point to a value of 32-bit int type")); +} + +TEST_F(ValidateAtomics, AtomicFlagClearNotInt32Pointer) { + const std::string body = R"( +OpAtomicFlagClear %u64_var %device %relaxed +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("AtomicFlagClear: " + "expected Pointer to point to a value of 32-bit int type")); +} + +TEST_F(ValidateAtomics, AtomicFlagClearWrongScopeType) { + const std::string body = R"( +OpAtomicFlagClear %u32_var %u64_1 %relaxed +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("AtomicFlagClear: expected Scope to be 32-bit int")); +} + +TEST_F(ValidateAtomics, AtomicFlagClearWrongMemorySemanticsType) { + const std::string body = R"( +OpAtomicFlagClear %u32_var %device %u64_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("AtomicFlagClear: expected Memory Semantics to be 32-bit int")); +} + +TEST_F(ValidateAtomics, AtomicIIncrementAcquireAndRelease) { + const std::string body = R"( +OpAtomicStore %u32_var %device %relaxed %u32_1 +%val1 = OpAtomicIIncrement %u32 %u32_var %device %acquire_and_release +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("AtomicIIncrement: no more than one of the following Memory " + "Semantics bits can be set at the same time: Acquire, Release, " + "AcquireRelease or SequentiallyConsistent")); +} + +TEST_F(ValidateAtomics, AtomicUniformMemorySemanticsShader) { + const std::string body = R"( +OpAtomicStore %u32_var %device %relaxed %u32_1 +%val1 = OpAtomicIIncrement %u32 %u32_var %device %acquire_release_uniform_workgroup +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateAtomics, AtomicUniformMemorySemanticsKernel) { + const std::string body = R"( +OpAtomicStore %u32_var %device %relaxed %u32_1 +%val1 = OpAtomicIIncrement %u32 %u32_var %device %acquire_release_uniform_workgroup +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("AtomicIIncrement: Memory Semantics UniformMemory " + "requires capability Shader")); +} + +TEST_F(ValidateAtomics, AtomicCounterMemorySemanticsNoCapability) { + const std::string body = R"( +OpAtomicStore %u32_var %device %relaxed %u32_1 +%val1 = OpAtomicIIncrement %u32 %u32_var %device %acquire_release_atomic_counter_workgroup +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("AtomicIIncrement: Memory Semantics UniformMemory " + "requires capability AtomicStorage")); +} + +TEST_F(ValidateAtomics, AtomicCounterMemorySemanticsWithCapability) { + const std::string body = R"( +OpAtomicStore %u32_var %device %relaxed %u32_1 +%val1 = OpAtomicIIncrement %u32 %u32_var %device %acquire_release_atomic_counter_workgroup +)"; + + CompileSuccessfully(GenerateKernelCode(body, "OpCapability AtomicStorage\n")); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/val/val_barriers_test.cpp b/3rdparty/spirv-tools/test/val/val_barriers_test.cpp new file mode 100644 index 000000000..e1d510592 --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_barriers_test.cpp @@ -0,0 +1,612 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "gmock/gmock.h" +#include "unit_spirv.h" +#include "val_fixtures.h" + +namespace { + +using ::testing::HasSubstr; +using ::testing::Not; + +using ValidateBarriers = spvtest::ValidateBase; + +std::string GenerateShaderCode( + const std::string& body, + const std::string& capabilities_and_extensions = "", + const std::string& execution_model = "GLCompute") { + std::ostringstream ss; + ss << R"( +OpCapability Shader +OpCapability Int64 +)"; + + ss << capabilities_and_extensions; + ss << "OpMemoryModel Logical GLSL450\n"; + ss << "OpEntryPoint " << execution_model << " %main \"main\"\n"; + + ss << R"( +%void = OpTypeVoid +%func = OpTypeFunction %void +%bool = OpTypeBool +%f32 = OpTypeFloat 32 +%u32 = OpTypeInt 32 0 +%u64 = OpTypeInt 64 0 + +%f32_0 = OpConstant %f32 0 +%f32_1 = OpConstant %f32 1 +%u32_0 = OpConstant %u32 0 +%u32_1 = OpConstant %u32 1 +%u32_4 = OpConstant %u32 4 +%u64_0 = OpConstant %u64 0 +%u64_1 = OpConstant %u64 1 + +%cross_device = OpConstant %u32 0 +%device = OpConstant %u32 1 +%workgroup = OpConstant %u32 2 +%subgroup = OpConstant %u32 3 +%invocation = OpConstant %u32 4 + +%none = OpConstant %u32 0 +%acquire = OpConstant %u32 2 +%release = OpConstant %u32 4 +%acquire_release = OpConstant %u32 8 +%acquire_and_release = OpConstant %u32 6 +%sequentially_consistent = OpConstant %u32 16 +%acquire_release_uniform_workgroup = OpConstant %u32 328 +%acquire_and_release_uniform = OpConstant %u32 70 +%acquire_release_subgroup = OpConstant %u32 136 +%uniform = OpConstant %u32 64 + +%main = OpFunction %void None %func +%main_entry = OpLabel +)"; + + ss << body; + + ss << R"( +OpReturn +OpFunctionEnd)"; + + return ss.str(); +} + +std::string GenerateKernelCode( + const std::string& body, + const std::string& capabilities_and_extensions = "") { + std::ostringstream ss; + ss << R"( +OpCapability Addresses +OpCapability Kernel +OpCapability Linkage +OpCapability Int64 +OpCapability NamedBarrier +)"; + + ss << capabilities_and_extensions; + ss << R"( +OpMemoryModel Physical32 OpenCL +%void = OpTypeVoid +%func = OpTypeFunction %void +%bool = OpTypeBool +%f32 = OpTypeFloat 32 +%u32 = OpTypeInt 32 0 +%u64 = OpTypeInt 64 0 + +%f32_0 = OpConstant %f32 0 +%f32_1 = OpConstant %f32 1 +%f32_4 = OpConstant %f32 4 +%u32_0 = OpConstant %u32 0 +%u32_1 = OpConstant %u32 1 +%u32_4 = OpConstant %u32 4 +%u64_0 = OpConstant %u64 0 +%u64_1 = OpConstant %u64 1 +%u64_4 = OpConstant %u64 4 + +%cross_device = OpConstant %u32 0 +%device = OpConstant %u32 1 +%workgroup = OpConstant %u32 2 +%subgroup = OpConstant %u32 3 +%invocation = OpConstant %u32 4 + +%none = OpConstant %u32 0 +%acquire = OpConstant %u32 2 +%release = OpConstant %u32 4 +%acquire_release = OpConstant %u32 8 +%acquire_and_release = OpConstant %u32 6 +%sequentially_consistent = OpConstant %u32 16 +%acquire_release_uniform_workgroup = OpConstant %u32 328 +%acquire_and_release_uniform = OpConstant %u32 70 +%uniform = OpConstant %u32 64 + +%named_barrier = OpTypeNamedBarrier + +%main = OpFunction %void None %func +%main_entry = OpLabel +)"; + + ss << body; + + ss << R"( +OpReturn +OpFunctionEnd)"; + + return ss.str(); +} + +TEST_F(ValidateBarriers, OpControlBarrierGLComputeSuccess) { + const std::string body = R"( +OpControlBarrier %device %device %none +OpControlBarrier %workgroup %workgroup %acquire +OpControlBarrier %workgroup %device %release +OpControlBarrier %cross_device %cross_device %acquire_release +OpControlBarrier %cross_device %cross_device %sequentially_consistent +OpControlBarrier %cross_device %cross_device %acquire_release_uniform_workgroup +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateBarriers, OpControlBarrierKernelSuccess) { + const std::string body = R"( +OpControlBarrier %device %device %none +OpControlBarrier %workgroup %workgroup %acquire +OpControlBarrier %workgroup %device %release +OpControlBarrier %cross_device %cross_device %acquire_release +OpControlBarrier %cross_device %cross_device %sequentially_consistent +OpControlBarrier %cross_device %cross_device %acquire_release_uniform_workgroup +)"; + + CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); +} + +TEST_F(ValidateBarriers, OpControlBarrierTesselationControlSuccess) { + const std::string body = R"( +OpControlBarrier %device %device %none +OpControlBarrier %workgroup %workgroup %acquire +OpControlBarrier %workgroup %device %release +OpControlBarrier %cross_device %cross_device %acquire_release +OpControlBarrier %cross_device %cross_device %sequentially_consistent +OpControlBarrier %cross_device %cross_device %acquire_release_uniform_workgroup +)"; + + CompileSuccessfully(GenerateShaderCode(body, "OpCapability Tessellation\n", + "TessellationControl")); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateBarriers, OpControlBarrierVulkanSuccess) { + const std::string body = R"( +OpControlBarrier %workgroup %device %none +OpControlBarrier %workgroup %workgroup %acquire_release_uniform_workgroup +)"; + + CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); +} + +TEST_F(ValidateBarriers, OpControlBarrierExecutionModelFragmentSpirv12) { + const std::string body = R"( +OpControlBarrier %device %device %none +)"; + + CompileSuccessfully(GenerateShaderCode(body, "", "Fragment"), + SPV_ENV_UNIVERSAL_1_2); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_2)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpControlBarrier requires one of the following Execution " + "Models: TessellationControl, GLCompute or Kernel")); +} + +TEST_F(ValidateBarriers, OpControlBarrierExecutionModelFragmentSpirv13) { + const std::string body = R"( +OpControlBarrier %device %device %none +)"; + + CompileSuccessfully(GenerateShaderCode(body, "", "Fragment"), + SPV_ENV_UNIVERSAL_1_3); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); +} + +TEST_F(ValidateBarriers, OpControlBarrierFloatExecutionScope) { + const std::string body = R"( +OpControlBarrier %f32_1 %device %none +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("ControlBarrier: expected Execution Scope to be a 32-bit int")); +} + +TEST_F(ValidateBarriers, OpControlBarrierU64ExecutionScope) { + const std::string body = R"( +OpControlBarrier %u64_1 %device %none +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("ControlBarrier: expected Execution Scope to be a 32-bit int")); +} + +TEST_F(ValidateBarriers, OpControlBarrierFloatMemoryScope) { + const std::string body = R"( +OpControlBarrier %device %f32_1 %none +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("ControlBarrier: expected Memory Scope to be a 32-bit int")); +} + +TEST_F(ValidateBarriers, OpControlBarrierU64MemoryScope) { + const std::string body = R"( +OpControlBarrier %device %u64_1 %none +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("ControlBarrier: expected Memory Scope to be a 32-bit int")); +} + +TEST_F(ValidateBarriers, OpControlBarrierFloatMemorySemantics) { + const std::string body = R"( +OpControlBarrier %device %device %f32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "ControlBarrier: expected Memory Semantics to be a 32-bit int")); +} + +TEST_F(ValidateBarriers, OpControlBarrierU64MemorySemantics) { + const std::string body = R"( +OpControlBarrier %device %device %u64_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "ControlBarrier: expected Memory Semantics to be a 32-bit int")); +} + +TEST_F(ValidateBarriers, OpControlBarrierVulkanExecutionScopeDevice) { + const std::string body = R"( +OpControlBarrier %device %workgroup %none +)"; + + CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ControlBarrier: in Vulkan environment Execution Scope " + "is limited to Workgroup and Subgroup")); +} + +TEST_F(ValidateBarriers, OpControlBarrierVulkanMemoryScopeSubgroup) { + const std::string body = R"( +OpControlBarrier %subgroup %subgroup %none +)"; + + CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ControlBarrier: in Vulkan environment Memory Scope is " + "limited to Device, Workgroup and Invocation")); +} + +TEST_F(ValidateBarriers, OpControlBarrierAcquireAndRelease) { + const std::string body = R"( +OpControlBarrier %device %device %acquire_and_release_uniform +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ControlBarrier: Memory Semantics can have at most one " + "of the following bits set: Acquire, Release, " + "AcquireRelease or SequentiallyConsistent")); +} + +// TODO(atgoo@github.com): the corresponding check fails Vulkan CTS, +// reenable once fixed. +TEST_F(ValidateBarriers, DISABLED_OpControlBarrierVulkanSubgroupStorageClass) { + const std::string body = R"( +OpControlBarrier %workgroup %device %acquire_release_subgroup +)"; + + CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "ControlBarrier: expected Memory Semantics to include a " + "Vulkan-supported storage class if Memory Semantics is not None")); +} + +TEST_F(ValidateBarriers, OpMemoryBarrierSuccess) { + const std::string body = R"( +OpMemoryBarrier %cross_device %acquire_release_uniform_workgroup +OpMemoryBarrier %device %uniform +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateBarriers, OpMemoryBarrierKernelSuccess) { + const std::string body = R"( +OpMemoryBarrier %cross_device %acquire_release_uniform_workgroup +OpMemoryBarrier %device %uniform +)"; + + CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); +} + +TEST_F(ValidateBarriers, OpMemoryBarrierVulkanSuccess) { + const std::string body = R"( +OpMemoryBarrier %workgroup %acquire_release_uniform_workgroup +)"; + + CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); +} + +TEST_F(ValidateBarriers, OpMemoryBarrierFloatMemoryScope) { + const std::string body = R"( +OpMemoryBarrier %f32_1 %acquire_release_uniform_workgroup +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("MemoryBarrier: expected Memory Scope to be a 32-bit int")); +} + +TEST_F(ValidateBarriers, OpMemoryBarrierU64MemoryScope) { + const std::string body = R"( +OpMemoryBarrier %u64_1 %acquire_release_uniform_workgroup +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("MemoryBarrier: expected Memory Scope to be a 32-bit int")); +} + +TEST_F(ValidateBarriers, OpMemoryBarrierFloatMemorySemantics) { + const std::string body = R"( +OpMemoryBarrier %device %f32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("MemoryBarrier: expected Memory Semantics to be a 32-bit int")); +} + +TEST_F(ValidateBarriers, OpMemoryBarrierU64MemorySemantics) { + const std::string body = R"( +OpMemoryBarrier %device %u64_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("MemoryBarrier: expected Memory Semantics to be a 32-bit int")); +} + +TEST_F(ValidateBarriers, OpMemoryBarrierVulkanMemoryScopeSubgroup) { + const std::string body = R"( +OpMemoryBarrier %subgroup %acquire_release_uniform_workgroup +)"; + + CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("MemoryBarrier: in Vulkan environment Memory Scope is " + "limited to Device, Workgroup and Invocation")); +} + +TEST_F(ValidateBarriers, OpMemoryBarrierAcquireAndRelease) { + const std::string body = R"( +OpMemoryBarrier %device %acquire_and_release_uniform +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("MemoryBarrier: Memory Semantics can have at most one " + "of the following bits set: Acquire, Release, " + "AcquireRelease or SequentiallyConsistent")); +} + +TEST_F(ValidateBarriers, OpMemoryBarrierVulkanMemorySemanticsNone) { + const std::string body = R"( +OpMemoryBarrier %device %none +)"; + + CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("MemoryBarrier: Vulkan specification requires Memory Semantics " + "to have one of the following bits set: Acquire, Release, " + "AcquireRelease or SequentiallyConsistent")); +} + +TEST_F(ValidateBarriers, OpMemoryBarrierVulkanMemorySemanticsAcquire) { + const std::string body = R"( +OpMemoryBarrier %device %acquire +)"; + + CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("MemoryBarrier: expected Memory Semantics to include a " + "Vulkan-supported storage class")); +} + +TEST_F(ValidateBarriers, OpMemoryBarrierVulkanSubgroupStorageClass) { + const std::string body = R"( +OpMemoryBarrier %device %acquire_release_subgroup +)"; + + CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("MemoryBarrier: expected Memory Semantics to include a " + "Vulkan-supported storage class")); +} + +TEST_F(ValidateBarriers, OpNamedBarrierInitializeSuccess) { + const std::string body = R"( +%barrier = OpNamedBarrierInitialize %named_barrier %u32_4 +)"; + + CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); +} + +TEST_F(ValidateBarriers, OpNamedBarrierInitializeWrongResultType) { + const std::string body = R"( +%barrier = OpNamedBarrierInitialize %u32 %u32_4 +)"; + + CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("NamedBarrierInitialize: expected Result Type to be " + "OpTypeNamedBarrier")); +} + +TEST_F(ValidateBarriers, OpNamedBarrierInitializeFloatSubgroupCount) { + const std::string body = R"( +%barrier = OpNamedBarrierInitialize %named_barrier %f32_4 +)"; + + CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("NamedBarrierInitialize: expected Subgroup Count to be " + "a 32-bit int")); +} + +TEST_F(ValidateBarriers, OpNamedBarrierInitializeU64SubgroupCount) { + const std::string body = R"( +%barrier = OpNamedBarrierInitialize %named_barrier %u64_4 +)"; + + CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("NamedBarrierInitialize: expected Subgroup Count to be " + "a 32-bit int")); +} + +TEST_F(ValidateBarriers, OpMemoryNamedBarrierSuccess) { + const std::string body = R"( +%barrier = OpNamedBarrierInitialize %named_barrier %u32_4 +OpMemoryNamedBarrier %barrier %workgroup %acquire_release_uniform_workgroup +)"; + + CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); +} + +TEST_F(ValidateBarriers, OpMemoryNamedBarrierNotNamedBarrier) { + const std::string body = R"( +OpMemoryNamedBarrier %u32_1 %workgroup %acquire_release_uniform_workgroup +)"; + + CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("MemoryNamedBarrier: expected Named Barrier to be of " + "type OpTypeNamedBarrier")); +} + +TEST_F(ValidateBarriers, OpMemoryNamedBarrierFloatMemoryScope) { + const std::string body = R"( +%barrier = OpNamedBarrierInitialize %named_barrier %u32_4 +OpMemoryNamedBarrier %barrier %f32_1 %acquire_release_uniform_workgroup +)"; + + CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "MemoryNamedBarrier: expected Memory Scope to be a 32-bit int")); +} + +TEST_F(ValidateBarriers, OpMemoryNamedBarrierFloatMemorySemantics) { + const std::string body = R"( +%barrier = OpNamedBarrierInitialize %named_barrier %u32_4 +OpMemoryNamedBarrier %barrier %workgroup %f32_0 +)"; + + CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "MemoryNamedBarrier: expected Memory Semantics to be a 32-bit int")); +} + +TEST_F(ValidateBarriers, OpMemoryNamedBarrierAcquireAndRelease) { + const std::string body = R"( +%barrier = OpNamedBarrierInitialize %named_barrier %u32_4 +OpMemoryNamedBarrier %barrier %workgroup %acquire_and_release_uniform +)"; + + CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("MemoryNamedBarrier: Memory Semantics can have at most " + "one of the following bits set: Acquire, Release, " + "AcquireRelease or SequentiallyConsistent")); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/val/val_bitwise_test.cpp b/3rdparty/spirv-tools/test/val/val_bitwise_test.cpp new file mode 100644 index 000000000..65ce0acb1 --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_bitwise_test.cpp @@ -0,0 +1,544 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Tests for unique type declaration rules validator. + +#include + +#include "gmock/gmock.h" +#include "unit_spirv.h" +#include "val_fixtures.h" + +namespace { + +using ::testing::HasSubstr; +using ::testing::Not; + +using ValidateBitwise = spvtest::ValidateBase; + +std::string GenerateShaderCode( + const std::string& body, + const std::string& capabilities_and_extensions = "") { + const std::string capabilities = + R"( +OpCapability Shader +OpCapability Int64 +OpCapability Float64)"; + + const std::string after_extension_before_body = + R"( +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +%void = OpTypeVoid +%func = OpTypeFunction %void +%bool = OpTypeBool +%f32 = OpTypeFloat 32 +%u32 = OpTypeInt 32 0 +%s32 = OpTypeInt 32 1 +%f64 = OpTypeFloat 64 +%u64 = OpTypeInt 64 0 +%s64 = OpTypeInt 64 1 +%boolvec2 = OpTypeVector %bool 2 +%s32vec2 = OpTypeVector %s32 2 +%u32vec2 = OpTypeVector %u32 2 +%u64vec2 = OpTypeVector %u64 2 +%f32vec2 = OpTypeVector %f32 2 +%f64vec2 = OpTypeVector %f64 2 +%boolvec3 = OpTypeVector %bool 3 +%u32vec3 = OpTypeVector %u32 3 +%u64vec3 = OpTypeVector %u64 3 +%s32vec3 = OpTypeVector %s32 3 +%f32vec3 = OpTypeVector %f32 3 +%f64vec3 = OpTypeVector %f64 3 +%boolvec4 = OpTypeVector %bool 4 +%u32vec4 = OpTypeVector %u32 4 +%u64vec4 = OpTypeVector %u64 4 +%s32vec4 = OpTypeVector %s32 4 +%f32vec4 = OpTypeVector %f32 4 +%f64vec4 = OpTypeVector %f64 4 + +%f32_0 = OpConstant %f32 0 +%f32_1 = OpConstant %f32 1 +%f32_2 = OpConstant %f32 2 +%f32_3 = OpConstant %f32 3 +%f32_4 = OpConstant %f32 4 + +%s32_0 = OpConstant %s32 0 +%s32_1 = OpConstant %s32 1 +%s32_2 = OpConstant %s32 2 +%s32_3 = OpConstant %s32 3 +%s32_4 = OpConstant %s32 4 +%s32_m1 = OpConstant %s32 -1 + +%u32_0 = OpConstant %u32 0 +%u32_1 = OpConstant %u32 1 +%u32_2 = OpConstant %u32 2 +%u32_3 = OpConstant %u32 3 +%u32_4 = OpConstant %u32 4 + +%f64_0 = OpConstant %f64 0 +%f64_1 = OpConstant %f64 1 +%f64_2 = OpConstant %f64 2 +%f64_3 = OpConstant %f64 3 +%f64_4 = OpConstant %f64 4 + +%s64_0 = OpConstant %s64 0 +%s64_1 = OpConstant %s64 1 +%s64_2 = OpConstant %s64 2 +%s64_3 = OpConstant %s64 3 +%s64_4 = OpConstant %s64 4 +%s64_m1 = OpConstant %s64 -1 + +%u64_0 = OpConstant %u64 0 +%u64_1 = OpConstant %u64 1 +%u64_2 = OpConstant %u64 2 +%u64_3 = OpConstant %u64 3 +%u64_4 = OpConstant %u64 4 + +%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1 +%u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2 +%u32vec3_012 = OpConstantComposite %u32vec3 %u32_0 %u32_1 %u32_2 +%u32vec3_123 = OpConstantComposite %u32vec3 %u32_1 %u32_2 %u32_3 +%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3 +%u32vec4_1234 = OpConstantComposite %u32vec4 %u32_1 %u32_2 %u32_3 %u32_4 + +%s32vec2_01 = OpConstantComposite %s32vec2 %s32_0 %s32_1 +%s32vec2_12 = OpConstantComposite %s32vec2 %s32_1 %s32_2 +%s32vec3_012 = OpConstantComposite %s32vec3 %s32_0 %s32_1 %s32_2 +%s32vec3_123 = OpConstantComposite %s32vec3 %s32_1 %s32_2 %s32_3 +%s32vec4_0123 = OpConstantComposite %s32vec4 %s32_0 %s32_1 %s32_2 %s32_3 +%s32vec4_1234 = OpConstantComposite %s32vec4 %s32_1 %s32_2 %s32_3 %s32_4 + +%f32vec2_01 = OpConstantComposite %f32vec2 %f32_0 %f32_1 +%f32vec2_12 = OpConstantComposite %f32vec2 %f32_1 %f32_2 +%f32vec3_012 = OpConstantComposite %f32vec3 %f32_0 %f32_1 %f32_2 +%f32vec3_123 = OpConstantComposite %f32vec3 %f32_1 %f32_2 %f32_3 +%f32vec4_0123 = OpConstantComposite %f32vec4 %f32_0 %f32_1 %f32_2 %f32_3 +%f32vec4_1234 = OpConstantComposite %f32vec4 %f32_1 %f32_2 %f32_3 %f32_4 + +%main = OpFunction %void None %func +%main_entry = OpLabel)"; + + const std::string after_body = + R"( +OpReturn +OpFunctionEnd)"; + + return capabilities + capabilities_and_extensions + + after_extension_before_body + body + after_body; +} + +TEST_F(ValidateBitwise, ShiftAllSuccess) { + const std::string body = R"( +%val1 = OpShiftRightLogical %u64 %u64_1 %s32_2 +%val2 = OpShiftRightArithmetic %s32vec2 %s32vec2_12 %s32vec2_12 +%val3 = OpShiftLeftLogical %u32vec2 %s32vec2_12 %u32vec2_12 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateBitwise, OpShiftRightLogicalWrongResultType) { + const std::string body = R"( +%val1 = OpShiftRightLogical %bool %u64_1 %s32_2 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected int scalar or vector type as Result Type: " + "ShiftRightLogical")); +} + +TEST_F(ValidateBitwise, OpShiftRightLogicalBaseNotInt) { + const std::string body = R"( +%val1 = OpShiftRightLogical %u32 %f32_1 %s32_2 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Base to be int scalar or vector: ShiftRightLogical")); +} + +TEST_F(ValidateBitwise, OpShiftRightLogicalBaseWrongDimension) { + const std::string body = R"( +%val1 = OpShiftRightLogical %u32 %u32vec2_12 %s32_2 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Base to have the same dimension as Result Type: " + "ShiftRightLogical")); +} + +TEST_F(ValidateBitwise, OpShiftRightLogicalBaseWrongBitWidth) { + const std::string body = R"( +%val1 = OpShiftRightLogical %u64 %u32_1 %s32_2 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Base to have the same bit width as Result Type: " + "ShiftRightLogical")); +} + +TEST_F(ValidateBitwise, OpShiftRightLogicalShiftNotInt) { + const std::string body = R"( +%val1 = OpShiftRightLogical %u32 %u32_1 %f32_2 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected Shift to be int scalar or vector: ShiftRightLogical")); +} + +TEST_F(ValidateBitwise, OpShiftRightLogicalShiftWrongDimension) { + const std::string body = R"( +%val1 = OpShiftRightLogical %u32 %u32_1 %s32vec2_12 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Shift to have the same dimension as Result Type: " + "ShiftRightLogical")); +} + +TEST_F(ValidateBitwise, LogicAllSuccess) { + const std::string body = R"( +%val1 = OpBitwiseOr %u64 %u64_1 %s64_0 +%val2 = OpBitwiseAnd %s64 %s64_1 %u64_0 +%val3 = OpBitwiseXor %s32vec2 %s32vec2_12 %u32vec2_01 +%val4 = OpNot %s32vec2 %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateBitwise, OpBitwiseAndWrongResultType) { + const std::string body = R"( +%val1 = OpBitwiseAnd %bool %u64_1 %s32_2 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected int scalar or vector type as Result Type: BitwiseAnd")); +} + +TEST_F(ValidateBitwise, OpBitwiseAndLeftNotInt) { + const std::string body = R"( +%val1 = OpBitwiseAnd %u32 %f32_1 %s32_2 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected int scalar or vector as operand: BitwiseAnd " + "operand index 2")); +} + +TEST_F(ValidateBitwise, OpBitwiseAndRightNotInt) { + const std::string body = R"( +%val1 = OpBitwiseAnd %u32 %u32_1 %f32_2 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected int scalar or vector as operand: BitwiseAnd " + "operand index 3")); +} + +TEST_F(ValidateBitwise, OpBitwiseAndLeftWrongDimension) { + const std::string body = R"( +%val1 = OpBitwiseAnd %u32 %u32vec2_12 %s32_2 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected operands to have the same dimension as Result Type: " + "BitwiseAnd operand index 2")); +} + +TEST_F(ValidateBitwise, OpBitwiseAndRightWrongDimension) { + const std::string body = R"( +%val1 = OpBitwiseAnd %u32 %s32_2 %u32vec2_12 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected operands to have the same dimension as Result Type: " + "BitwiseAnd operand index 3")); +} + +TEST_F(ValidateBitwise, OpBitwiseAndLeftWrongBitWidth) { + const std::string body = R"( +%val1 = OpBitwiseAnd %u64 %u32_1 %s64_2 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected operands to have the same bit width as Result Type: " + "BitwiseAnd operand index 2")); +} + +TEST_F(ValidateBitwise, OpBitwiseAndRightWrongBitWidth) { + const std::string body = R"( +%val1 = OpBitwiseAnd %u64 %u64_1 %s32_2 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected operands to have the same bit width as Result Type: " + "BitwiseAnd operand index 3")); +} + +TEST_F(ValidateBitwise, OpBitFieldInsertSuccess) { + const std::string body = R"( +%val1 = OpBitFieldInsert %u64 %u64_1 %u64_2 %s32_1 %s32_2 +%val2 = OpBitFieldInsert %s32vec2 %s32vec2_12 %s32vec2_12 %s32_1 %u32_2 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateBitwise, OpBitFieldInsertWrongResultType) { + const std::string body = R"( +%val1 = OpBitFieldInsert %bool %u64_1 %u64_2 %s32_1 %s32_2 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected int scalar or vector type as Result Type: BitFieldInsert")); +} + +TEST_F(ValidateBitwise, OpBitFieldInsertWrongBaseType) { + const std::string body = R"( +%val1 = OpBitFieldInsert %u64 %s64_1 %u64_2 %s32_1 %s32_2 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected Base Type to be equal to Result Type: BitFieldInsert")); +} + +TEST_F(ValidateBitwise, OpBitFieldInsertWrongInsertType) { + const std::string body = R"( +%val1 = OpBitFieldInsert %u64 %u64_1 %s64_2 %s32_1 %s32_2 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected Insert Type to be equal to Result Type: BitFieldInsert")); +} + +TEST_F(ValidateBitwise, OpBitFieldInsertOffsetNotInt) { + const std::string body = R"( +%val1 = OpBitFieldInsert %u64 %u64_1 %u64_2 %f32_1 %s32_2 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Offset Type to be int scalar: BitFieldInsert")); +} + +TEST_F(ValidateBitwise, OpBitFieldInsertCountNotInt) { + const std::string body = R"( +%val1 = OpBitFieldInsert %u64 %u64_1 %u64_2 %u32_1 %f32_2 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Count Type to be int scalar: BitFieldInsert")); +} + +TEST_F(ValidateBitwise, OpBitFieldSExtractSuccess) { + const std::string body = R"( +%val1 = OpBitFieldSExtract %u64 %u64_1 %s32_1 %s32_2 +%val2 = OpBitFieldSExtract %s32vec2 %s32vec2_12 %s32_1 %u32_2 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateBitwise, OpBitFieldSExtractWrongResultType) { + const std::string body = R"( +%val1 = OpBitFieldSExtract %bool %u64_1 %s32_1 %s32_2 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected int scalar or vector type as Result Type: " + "BitFieldSExtract")); +} + +TEST_F(ValidateBitwise, OpBitFieldSExtractWrongBaseType) { + const std::string body = R"( +%val1 = OpBitFieldSExtract %u64 %s64_1 %s32_1 %s32_2 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected Base Type to be equal to Result Type: BitFieldSExtract")); +} + +TEST_F(ValidateBitwise, OpBitFieldSExtractOffsetNotInt) { + const std::string body = R"( +%val1 = OpBitFieldSExtract %u64 %u64_1 %f32_1 %s32_2 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Offset Type to be int scalar: BitFieldSExtract")); +} + +TEST_F(ValidateBitwise, OpBitFieldSExtractCountNotInt) { + const std::string body = R"( +%val1 = OpBitFieldSExtract %u64 %u64_1 %u32_1 %f32_2 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Count Type to be int scalar: BitFieldSExtract")); +} + +TEST_F(ValidateBitwise, OpBitReverseSuccess) { + const std::string body = R"( +%val1 = OpBitReverse %u64 %u64_1 +%val2 = OpBitReverse %s32vec2 %s32vec2_12 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateBitwise, OpBitReverseWrongResultType) { + const std::string body = R"( +%val1 = OpBitReverse %bool %u64_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected int scalar or vector type as Result Type: BitReverse")); +} + +TEST_F(ValidateBitwise, OpBitReverseWrongBaseType) { + const std::string body = R"( +%val1 = OpBitReverse %u64 %s64_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Base Type to be equal to Result Type: BitReverse")); +} + +TEST_F(ValidateBitwise, OpBitCountSuccess) { + const std::string body = R"( +%val1 = OpBitCount %s32 %u64_1 +%val2 = OpBitCount %u32vec2 %s32vec2_12 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateBitwise, OpBitCountWrongResultType) { + const std::string body = R"( +%val1 = OpBitCount %bool %u64_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected int scalar or vector type as Result Type: BitCount")); +} + +TEST_F(ValidateBitwise, OpBitCountBaseNotInt) { + const std::string body = R"( +%val1 = OpBitCount %u32 %f64_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Base Type to be int scalar or vector: BitCount")); +} + +TEST_F(ValidateBitwise, OpBitCountBaseWrongDimension) { + const std::string body = R"( +%val1 = OpBitCount %u32 %u32vec2_12 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Base dimension to be equal to Result Type dimension: " + "BitCount")); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/val/val_builtins_test.cpp b/3rdparty/spirv-tools/test/val/val_builtins_test.cpp new file mode 100644 index 000000000..78e429ddf --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_builtins_test.cpp @@ -0,0 +1,1924 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Tests validation rules of GLSL.450.std and OpenCL.std extended instructions. +// Doesn't test OpenCL.std vector size 2, 3, 4, 8 or 16 rules (not supported +// by standard SPIR-V). + +#include +#include +#include +#include + +#include "gmock/gmock.h" +#include "unit_spirv.h" +#include "val_fixtures.h" + +namespace { + +struct TestResult { + TestResult(spv_result_t in_validation_result = SPV_SUCCESS, + const char* in_error_str = nullptr, + const char* in_error_str2 = nullptr) + : validation_result(in_validation_result), + error_str(in_error_str), + error_str2(in_error_str2) {} + spv_result_t validation_result; + const char* error_str; + const char* error_str2; +}; + +using ::testing::Combine; +using ::testing::HasSubstr; +using ::testing::Not; +using ::testing::Values; +using ::testing::ValuesIn; + +using ValidateBuiltIns = spvtest::ValidateBase; +using ValidateVulkanCombineBuiltInExecutionModelDataTypeResult = + spvtest::ValidateBase>; + +struct EntryPoint { + std::string name; + std::string execution_model; + std::string execution_modes; + std::string body; +}; + +class CodeGenerator { + public: + std::string Build() const; + + std::vector entry_points_; + std::string capabilities_; + std::string extensions_; + std::string memory_model_; + std::string before_types_; + std::string types_; + std::string after_types_; + std::string add_at_the_end_; +}; + +std::string CodeGenerator::Build() const { + std::ostringstream ss; + + ss << capabilities_; + ss << extensions_; + ss << memory_model_; + + for (const EntryPoint& entry_point : entry_points_) { + ss << "OpEntryPoint " << entry_point.execution_model << " %" + << entry_point.name << " \"" << entry_point.name << "\"\n"; + } + + for (const EntryPoint& entry_point : entry_points_) { + ss << entry_point.execution_modes << "\n"; + } + + ss << before_types_; + ss << types_; + ss << after_types_; + + for (const EntryPoint& entry_point : entry_points_) { + ss << "\n"; + ss << "%" << entry_point.name << " = OpFunction %void None %func\n"; + ss << "%" << entry_point.name << "_entry = OpLabel\n"; + ss << entry_point.body; + ss << "\nOpReturn\nOpFunctionEnd\n"; + } + + ss << add_at_the_end_; + + return ss.str(); +} + +std::string GetDefaultShaderCapabilities() { + return R"( +OpCapability Shader +OpCapability Geometry +OpCapability Tessellation +OpCapability Float64 +OpCapability Int64 +OpCapability MultiViewport +OpCapability SampleRateShading +)"; +} + +std::string GetDefaultShaderTypes() { + return R"( +%void = OpTypeVoid +%func = OpTypeFunction %void +%bool = OpTypeBool +%f32 = OpTypeFloat 32 +%f64 = OpTypeFloat 64 +%u32 = OpTypeInt 32 0 +%u64 = OpTypeInt 64 0 +%f32vec2 = OpTypeVector %f32 2 +%f32vec3 = OpTypeVector %f32 3 +%f32vec4 = OpTypeVector %f32 4 +%f64vec2 = OpTypeVector %f64 2 +%f64vec3 = OpTypeVector %f64 3 +%f64vec4 = OpTypeVector %f64 4 +%u32vec2 = OpTypeVector %u32 2 +%u32vec3 = OpTypeVector %u32 3 +%u64vec3 = OpTypeVector %u64 3 +%u32vec4 = OpTypeVector %u32 4 +%u64vec2 = OpTypeVector %u64 2 + +%f32_0 = OpConstant %f32 0 +%f32_1 = OpConstant %f32 1 +%f32_2 = OpConstant %f32 2 +%f32_3 = OpConstant %f32 3 +%f32_4 = OpConstant %f32 4 +%f32_h = OpConstant %f32 0.5 +%f32vec2_01 = OpConstantComposite %f32vec2 %f32_0 %f32_1 +%f32vec2_12 = OpConstantComposite %f32vec2 %f32_1 %f32_2 +%f32vec3_012 = OpConstantComposite %f32vec3 %f32_0 %f32_1 %f32_2 +%f32vec3_123 = OpConstantComposite %f32vec3 %f32_1 %f32_2 %f32_3 +%f32vec4_0123 = OpConstantComposite %f32vec4 %f32_0 %f32_1 %f32_2 %f32_3 +%f32vec4_1234 = OpConstantComposite %f32vec4 %f32_1 %f32_2 %f32_3 %f32_4 + +%f64_0 = OpConstant %f64 0 +%f64_1 = OpConstant %f64 1 +%f64_2 = OpConstant %f64 2 +%f64_3 = OpConstant %f64 3 +%f64vec2_01 = OpConstantComposite %f64vec2 %f64_0 %f64_1 +%f64vec3_012 = OpConstantComposite %f64vec3 %f64_0 %f64_1 %f64_2 +%f64vec4_0123 = OpConstantComposite %f64vec4 %f64_0 %f64_1 %f64_2 %f64_3 + +%u32_0 = OpConstant %u32 0 +%u32_1 = OpConstant %u32 1 +%u32_2 = OpConstant %u32 2 +%u32_3 = OpConstant %u32 3 +%u32_4 = OpConstant %u32 4 + +%u64_0 = OpConstant %u64 0 +%u64_1 = OpConstant %u64 1 +%u64_2 = OpConstant %u64 2 +%u64_3 = OpConstant %u64 3 + +%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1 +%u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2 +%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3 +%u64vec2_01 = OpConstantComposite %u64vec2 %u64_0 %u64_1 + +%u32arr2 = OpTypeArray %u32 %u32_2 +%u32arr3 = OpTypeArray %u32 %u32_3 +%u32arr4 = OpTypeArray %u32 %u32_4 +%u64arr2 = OpTypeArray %u64 %u32_2 +%u64arr3 = OpTypeArray %u64 %u32_3 +%u64arr4 = OpTypeArray %u64 %u32_4 +%f32arr2 = OpTypeArray %f32 %u32_2 +%f32arr3 = OpTypeArray %f32 %u32_3 +%f32arr4 = OpTypeArray %f32 %u32_4 +%f64arr2 = OpTypeArray %f64 %u32_2 +%f64arr3 = OpTypeArray %f64 %u32_3 +%f64arr4 = OpTypeArray %f64 %u32_4 +)"; +} + +CodeGenerator GetDefaultShaderCodeGenerator() { + CodeGenerator generator; + generator.capabilities_ = GetDefaultShaderCapabilities(); + generator.memory_model_ = "OpMemoryModel Logical GLSL450\n"; + generator.types_ = GetDefaultShaderTypes(); + return generator; +} + +TEST_P(ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, InMain) { + const char* const built_in = std::get<0>(GetParam()); + const char* const execution_model = std::get<1>(GetParam()); + const char* const storage_class = std::get<2>(GetParam()); + const char* const data_type = std::get<3>(GetParam()); + const TestResult& test_result = std::get<4>(GetParam()); + + CodeGenerator generator = GetDefaultShaderCodeGenerator(); + generator.before_types_ = "OpMemberDecorate %built_in_type 0 BuiltIn "; + generator.before_types_ += built_in; + generator.before_types_ += "\n"; + + std::ostringstream after_types; + after_types << "%built_in_type = OpTypeStruct " << data_type << "\n"; + after_types << "%built_in_ptr = OpTypePointer " << storage_class + << " %built_in_type\n"; + after_types << "%built_in_var = OpVariable %built_in_ptr " << storage_class + << "\n"; + after_types << "%data_ptr = OpTypePointer " << storage_class << " " + << data_type << "\n"; + generator.after_types_ = after_types.str(); + + EntryPoint entry_point; + entry_point.name = "main"; + entry_point.execution_model = execution_model; + + std::ostringstream execution_modes; + if (0 == std::strcmp(execution_model, "Fragment")) { + execution_modes << "OpExecutionMode %" << entry_point.name + << " OriginUpperLeft\n"; + } + if (0 == std::strcmp(built_in, "FragDepth")) { + execution_modes << "OpExecutionMode %" << entry_point.name + << " DepthReplacing\n"; + } + entry_point.execution_modes = execution_modes.str(); + + entry_point.body = R"( +%ptr = OpAccessChain %data_ptr %built_in_var %u32_0 +)"; + generator.entry_points_.push_back(std::move(entry_point)); + + CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(test_result.validation_result, + ValidateInstructions(SPV_ENV_VULKAN_1_0)); + if (test_result.error_str) { + EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str)); + } + if (test_result.error_str2) { + EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2)); + } +} + +TEST_P(ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, InFunction) { + const char* const built_in = std::get<0>(GetParam()); + const char* const execution_model = std::get<1>(GetParam()); + const char* const storage_class = std::get<2>(GetParam()); + const char* const data_type = std::get<3>(GetParam()); + const TestResult& test_result = std::get<4>(GetParam()); + + CodeGenerator generator = GetDefaultShaderCodeGenerator(); + generator.before_types_ = "OpMemberDecorate %built_in_type 0 BuiltIn "; + generator.before_types_ += built_in; + generator.before_types_ += "\n"; + + std::ostringstream after_types; + after_types << "%built_in_type = OpTypeStruct " << data_type << "\n"; + after_types << "%built_in_ptr = OpTypePointer " << storage_class + << " %built_in_type\n"; + after_types << "%built_in_var = OpVariable %built_in_ptr " << storage_class + << "\n"; + after_types << "%data_ptr = OpTypePointer " << storage_class << " " + << data_type << "\n"; + generator.after_types_ = after_types.str(); + + EntryPoint entry_point; + entry_point.name = "main"; + entry_point.execution_model = execution_model; + + std::ostringstream execution_modes; + if (0 == std::strcmp(execution_model, "Fragment")) { + execution_modes << "OpExecutionMode %" << entry_point.name + << " OriginUpperLeft\n"; + } + if (0 == std::strcmp(built_in, "FragDepth")) { + execution_modes << "OpExecutionMode %" << entry_point.name + << " DepthReplacing\n"; + } + entry_point.execution_modes = execution_modes.str(); + + entry_point.body = R"( +%val2 = OpFunctionCall %void %foo +)"; + + generator.add_at_the_end_ = R"( +%foo = OpFunction %void None %func +%foo_entry = OpLabel +%ptr = OpAccessChain %data_ptr %built_in_var %u32_0 +OpReturn +OpFunctionEnd +)"; + generator.entry_points_.push_back(std::move(entry_point)); + + CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(test_result.validation_result, + ValidateInstructions(SPV_ENV_VULKAN_1_0)); + if (test_result.error_str) { + EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str)); + } + if (test_result.error_str2) { + EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2)); + } +} + +TEST_P(ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, Variable) { + const char* const built_in = std::get<0>(GetParam()); + const char* const execution_model = std::get<1>(GetParam()); + const char* const storage_class = std::get<2>(GetParam()); + const char* const data_type = std::get<3>(GetParam()); + const TestResult& test_result = std::get<4>(GetParam()); + + CodeGenerator generator = GetDefaultShaderCodeGenerator(); + generator.before_types_ = "OpDecorate %built_in_var BuiltIn "; + generator.before_types_ += built_in; + generator.before_types_ += "\n"; + + std::ostringstream after_types; + after_types << "%built_in_ptr = OpTypePointer " << storage_class << " " + << data_type << "\n"; + after_types << "%built_in_var = OpVariable %built_in_ptr " << storage_class + << "\n"; + generator.after_types_ = after_types.str(); + + EntryPoint entry_point; + entry_point.name = "main"; + entry_point.execution_model = execution_model; + // Any kind of reference would do. + entry_point.body = R"( +%val = OpBitcast %u64 %built_in_var +)"; + + std::ostringstream execution_modes; + if (0 == std::strcmp(execution_model, "Fragment")) { + execution_modes << "OpExecutionMode %" << entry_point.name + << " OriginUpperLeft\n"; + } + if (0 == std::strcmp(built_in, "FragDepth")) { + execution_modes << "OpExecutionMode %" << entry_point.name + << " DepthReplacing\n"; + } + entry_point.execution_modes = execution_modes.str(); + + generator.entry_points_.push_back(std::move(entry_point)); + + CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(test_result.validation_result, + ValidateInstructions(SPV_ENV_VULKAN_1_0)); + if (test_result.error_str) { + EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str)); + } + if (test_result.error_str2) { + EXPECT_THAT(getDiagnosticString(), HasSubstr(test_result.error_str2)); + } +} + +INSTANTIATE_TEST_CASE_P( + ClipAndCullDistanceOutputSuccess, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("ClipDistance", "CullDistance"), + Values("Vertex", "Geometry", "TessellationControl", + "TessellationEvaluation"), + Values("Output"), Values("%f32arr2", "%f32arr4"), + Values(TestResult())), ); + +INSTANTIATE_TEST_CASE_P( + ClipAndCullDistanceInputSuccess, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("ClipDistance", "CullDistance"), + Values("Fragment", "Geometry", "TessellationControl", + "TessellationEvaluation"), + Values("Input"), Values("%f32arr2", "%f32arr4"), + Values(TestResult())), ); + +INSTANTIATE_TEST_CASE_P( + ClipAndCullDistanceFragmentOutput, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("ClipDistance", "CullDistance"), Values("Fragment"), + Values("Output"), Values("%f32arr4"), + Values(TestResult( + SPV_ERROR_INVALID_DATA, + "Vulkan spec doesn't allow BuiltIn ClipDistance/CullDistance " + "to be used for variables with Output storage class if " + "execution model is Fragment.", + "which is called with execution model Fragment."))), ); + +INSTANTIATE_TEST_CASE_P( + ClipAndCullDistanceVertexInput, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("ClipDistance", "CullDistance"), Values("Vertex"), + Values("Input"), Values("%f32arr4"), + Values(TestResult( + SPV_ERROR_INVALID_DATA, + "Vulkan spec doesn't allow BuiltIn ClipDistance/CullDistance " + "to be used for variables with Input storage class if " + "execution model is Vertex.", + "which is called with execution model Vertex."))), ); + +INSTANTIATE_TEST_CASE_P( + ClipAndCullInvalidExecutionModel, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("ClipDistance", "CullDistance"), Values("GLCompute"), + Values("Input", "Output"), Values("%f32arr4"), + Values(TestResult( + SPV_ERROR_INVALID_DATA, + "to be used only with Fragment, Vertex, TessellationControl, " + "TessellationEvaluation or Geometry execution models"))), ); + +INSTANTIATE_TEST_CASE_P( + ClipAndCullDistanceNotArray, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("ClipDistance", "CullDistance"), Values("Fragment"), + Values("Input"), Values("%f32vec2", "%f32vec4", "%f32"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit float array", + "is not an array"))), ); + +INSTANTIATE_TEST_CASE_P( + ClipAndCullDistanceNotFloatArray, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("ClipDistance", "CullDistance"), Values("Fragment"), + Values("Input"), Values("%u32arr2", "%u64arr4"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit float array", + "components are not float scalar"))), ); + +INSTANTIATE_TEST_CASE_P( + ClipAndCullDistanceNotF32Array, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("ClipDistance", "CullDistance"), Values("Fragment"), + Values("Input"), Values("%f64arr2", "%f64arr4"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit float array", + "has components with bit width 64"))), ); + +INSTANTIATE_TEST_CASE_P( + FragCoordSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("FragCoord"), Values("Fragment"), Values("Input"), + Values("%f32vec4"), Values(TestResult())), ); + +INSTANTIATE_TEST_CASE_P( + FragCoordNotFragment, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine( + Values("FragCoord"), + Values("Vertex", "GLCompute", "Geometry", "TessellationControl", + "TessellationEvaluation"), + Values("Input"), Values("%f32vec4"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "to be used only with Fragment execution model"))), ); + +INSTANTIATE_TEST_CASE_P( + FragCoordNotInput, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("FragCoord"), Values("Fragment"), Values("Output"), + Values("%f32vec4"), + Values(TestResult( + SPV_ERROR_INVALID_DATA, + "to be only used for variables with Input storage class", + "uses storage class Output"))), ); + +INSTANTIATE_TEST_CASE_P( + FragCoordNotFloatVector, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("FragCoord"), Values("Fragment"), Values("Input"), + Values("%f32arr4", "%u32vec4"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 4-component 32-bit float vector", + "is not a float vector"))), ); + +INSTANTIATE_TEST_CASE_P( + FragCoordNotFloatVec4, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("FragCoord"), Values("Fragment"), Values("Input"), + Values("%f32vec3"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 4-component 32-bit float vector", + "has 3 components"))), ); + +INSTANTIATE_TEST_CASE_P( + FragCoordNotF32Vec4, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("FragCoord"), Values("Fragment"), Values("Input"), + Values("%f64vec4"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 4-component 32-bit float vector", + "has components with bit width 64"))), ); + +INSTANTIATE_TEST_CASE_P( + FragDepthSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("FragDepth"), Values("Fragment"), Values("Output"), + Values("%f32"), Values(TestResult())), ); + +INSTANTIATE_TEST_CASE_P( + FragDepthNotFragment, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine( + Values("FragDepth"), + Values("Vertex", "GLCompute", "Geometry", "TessellationControl", + "TessellationEvaluation"), + Values("Output"), Values("%f32"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "to be used only with Fragment execution model"))), ); + +INSTANTIATE_TEST_CASE_P( + FragDepthNotOutput, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("FragDepth"), Values("Fragment"), Values("Input"), + Values("%f32"), + Values(TestResult( + SPV_ERROR_INVALID_DATA, + "to be only used for variables with Output storage class", + "uses storage class Input"))), ); + +INSTANTIATE_TEST_CASE_P( + FragDepthNotFloatScalar, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("FragDepth"), Values("Fragment"), Values("Output"), + Values("%f32vec4", "%u32"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit float scalar", + "is not a float scalar"))), ); + +INSTANTIATE_TEST_CASE_P( + FragDepthNotF32, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("FragDepth"), Values("Fragment"), Values("Output"), + Values("%f64"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit float scalar", + "has bit width 64"))), ); + +INSTANTIATE_TEST_CASE_P( + FrontFacingAndHelperInvocationSuccess, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("FrontFacing", "HelperInvocation"), Values("Fragment"), + Values("Input"), Values("%bool"), Values(TestResult())), ); + +INSTANTIATE_TEST_CASE_P( + FrontFacingAndHelperInvocationNotFragment, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine( + Values("FrontFacing", "HelperInvocation"), + Values("Vertex", "GLCompute", "Geometry", "TessellationControl", + "TessellationEvaluation"), + Values("Input"), Values("%bool"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "to be used only with Fragment execution model"))), ); + +INSTANTIATE_TEST_CASE_P( + FrontFacingAndHelperInvocationNotInput, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("FrontFacing", "HelperInvocation"), Values("Fragment"), + Values("Output"), Values("%bool"), + Values(TestResult( + SPV_ERROR_INVALID_DATA, + "to be only used for variables with Input storage class", + "uses storage class Output"))), ); + +INSTANTIATE_TEST_CASE_P( + FrontFacingAndHelperInvocationNotBool, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("FrontFacing", "HelperInvocation"), Values("Fragment"), + Values("Input"), Values("%f32", "%u32"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a bool scalar", + "is not a bool scalar"))), ); + +INSTANTIATE_TEST_CASE_P( + ComputeShaderInputInt32Vec3Success, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups", + "WorkgroupId"), + Values("GLCompute"), Values("Input"), Values("%u32vec3"), + Values(TestResult())), ); + +INSTANTIATE_TEST_CASE_P( + ComputeShaderInputInt32Vec3NotGLCompute, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups", + "WorkgroupId"), + Values("Vertex", "Fragment", "Geometry", "TessellationControl", + "TessellationEvaluation"), + Values("Input"), Values("%u32vec3"), + Values(TestResult( + SPV_ERROR_INVALID_DATA, + "to be used only with GLCompute execution model"))), ); + +INSTANTIATE_TEST_CASE_P( + ComputeShaderInputInt32Vec3NotInput, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups", + "WorkgroupId"), + Values("GLCompute"), Values("Output"), Values("%u32vec3"), + Values(TestResult( + SPV_ERROR_INVALID_DATA, + "to be only used for variables with Input storage class", + "uses storage class Output"))), ); + +INSTANTIATE_TEST_CASE_P( + ComputeShaderInputInt32Vec3NotIntVector, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups", + "WorkgroupId"), + Values("GLCompute"), Values("Input"), + Values("%u32arr3", "%f32vec3"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 3-component 32-bit int vector", + "is not an int vector"))), ); + +INSTANTIATE_TEST_CASE_P( + ComputeShaderInputInt32Vec3NotIntVec3, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups", + "WorkgroupId"), + Values("GLCompute"), Values("Input"), Values("%u32vec4"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 3-component 32-bit int vector", + "has 4 components"))), ); + +INSTANTIATE_TEST_CASE_P( + ComputeShaderInputInt32Vec3NotInt32Vec, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("GlobalInvocationId", "LocalInvocationId", "NumWorkgroups", + "WorkgroupId"), + Values("GLCompute"), Values("Input"), Values("%u64vec3"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 3-component 32-bit int vector", + "has components with bit width 64"))), ); + +INSTANTIATE_TEST_CASE_P( + InvocationIdSuccess, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("InvocationId"), Values("Geometry", "TessellationControl"), + Values("Input"), Values("%u32"), Values(TestResult())), ); + +INSTANTIATE_TEST_CASE_P( + InvocationIdInvalidExecutionModel, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("InvocationId"), + Values("Vertex", "Fragment", "GLCompute", "TessellationEvaluation"), + Values("Input"), Values("%u32"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "to be used only with TessellationControl or " + "Geometry execution models"))), ); + +INSTANTIATE_TEST_CASE_P( + InvocationIdNotInput, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("InvocationId"), Values("Geometry", "TessellationControl"), + Values("Output"), Values("%u32"), + Values(TestResult( + SPV_ERROR_INVALID_DATA, + "to be only used for variables with Input storage class", + "uses storage class Output"))), ); + +INSTANTIATE_TEST_CASE_P( + InvocationIdNotIntScalar, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("InvocationId"), Values("Geometry", "TessellationControl"), + Values("Input"), Values("%f32", "%u32vec3"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit int scalar", + "is not an int scalar"))), ); + +INSTANTIATE_TEST_CASE_P( + InvocationIdNotInt32, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("InvocationId"), Values("Geometry", "TessellationControl"), + Values("Input"), Values("%u64"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit int scalar", + "has bit width 64"))), ); + +INSTANTIATE_TEST_CASE_P( + InstanceIndexSuccess, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("InstanceIndex"), Values("Vertex"), Values("Input"), + Values("%u32"), Values(TestResult())), ); + +INSTANTIATE_TEST_CASE_P( + InstanceIndexInvalidExecutionModel, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine( + Values("InstanceIndex"), + Values("Geometry", "Fragment", "GLCompute", "TessellationControl", + "TessellationEvaluation"), + Values("Input"), Values("%u32"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "to be used only with Vertex execution model"))), ); + +INSTANTIATE_TEST_CASE_P( + InstanceIndexNotInput, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("InstanceIndex"), Values("Vertex"), Values("Output"), + Values("%u32"), + Values(TestResult( + SPV_ERROR_INVALID_DATA, + "to be only used for variables with Input storage class", + "uses storage class Output"))), ); + +INSTANTIATE_TEST_CASE_P( + InstanceIndexNotIntScalar, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("InstanceIndex"), Values("Vertex"), Values("Input"), + Values("%f32", "%u32vec3"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit int scalar", + "is not an int scalar"))), ); + +INSTANTIATE_TEST_CASE_P( + InstanceIndexNotInt32, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("InstanceIndex"), Values("Vertex"), Values("Input"), + Values("%u64"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit int scalar", + "has bit width 64"))), ); + +INSTANTIATE_TEST_CASE_P( + LayerAndViewportIndexInputSuccess, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("Layer", "ViewportIndex"), Values("Fragment"), + Values("Input"), Values("%u32"), Values(TestResult())), ); + +INSTANTIATE_TEST_CASE_P( + LayerAndViewportIndexOutputSuccess, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("Layer", "ViewportIndex"), Values("Geometry"), + Values("Output"), Values("%u32"), Values(TestResult())), ); + +INSTANTIATE_TEST_CASE_P( + LayerAndViewportIndexInvalidExecutionModel, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine( + Values("Layer", "ViewportIndex"), + Values("Vertex", "GLCompute", "TessellationControl", + "TessellationEvaluation"), + Values("Input"), Values("%u32"), + Values(TestResult( + SPV_ERROR_INVALID_DATA, + "to be used only with Fragment or Geometry execution models"))), ); + +INSTANTIATE_TEST_CASE_P( + LayerAndViewportIndexFragmentNotInput, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine( + Values("Layer", "ViewportIndex"), Values("Fragment"), Values("Output"), + Values("%u32"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "Output storage class if execution model is Fragment", + "which is called with execution model Fragment"))), ); + +INSTANTIATE_TEST_CASE_P( + LayerAndViewportIndexGeometryNotOutput, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine( + Values("Layer", "ViewportIndex"), Values("Geometry"), Values("Input"), + Values("%u32"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "Input storage class if execution model is Geometry", + "which is called with execution model Geometry"))), ); + +INSTANTIATE_TEST_CASE_P( + LayerAndViewportIndexNotIntScalar, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("Layer", "ViewportIndex"), Values("Fragment"), + Values("Input"), Values("%f32", "%u32vec3"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit int scalar", + "is not an int scalar"))), ); + +INSTANTIATE_TEST_CASE_P( + LayerAndViewportIndexNotInt32, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("Layer", "ViewportIndex"), Values("Fragment"), + Values("Input"), Values("%u64"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit int scalar", + "has bit width 64"))), ); + +INSTANTIATE_TEST_CASE_P( + PatchVerticesSuccess, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("PatchVertices"), + Values("TessellationEvaluation", "TessellationControl"), + Values("Input"), Values("%u32"), Values(TestResult())), ); + +INSTANTIATE_TEST_CASE_P( + PatchVerticesInvalidExecutionModel, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("PatchVertices"), + Values("Vertex", "Fragment", "GLCompute", "Geometry"), + Values("Input"), Values("%u32"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "to be used only with TessellationControl or " + "TessellationEvaluation execution models"))), ); + +INSTANTIATE_TEST_CASE_P( + PatchVerticesNotInput, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("PatchVertices"), + Values("TessellationEvaluation", "TessellationControl"), + Values("Output"), Values("%u32"), + Values(TestResult( + SPV_ERROR_INVALID_DATA, + "to be only used for variables with Input storage class", + "uses storage class Output"))), ); + +INSTANTIATE_TEST_CASE_P( + PatchVerticesNotIntScalar, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("PatchVertices"), + Values("TessellationEvaluation", "TessellationControl"), + Values("Input"), Values("%f32", "%u32vec3"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit int scalar", + "is not an int scalar"))), ); + +INSTANTIATE_TEST_CASE_P( + PatchVerticesNotInt32, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("PatchVertices"), + Values("TessellationEvaluation", "TessellationControl"), + Values("Input"), Values("%u64"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit int scalar", + "has bit width 64"))), ); + +INSTANTIATE_TEST_CASE_P( + PointCoordSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("PointCoord"), Values("Fragment"), Values("Input"), + Values("%f32vec2"), Values(TestResult())), ); + +INSTANTIATE_TEST_CASE_P( + PointCoordNotFragment, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine( + Values("PointCoord"), + Values("Vertex", "GLCompute", "Geometry", "TessellationControl", + "TessellationEvaluation"), + Values("Input"), Values("%f32vec2"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "to be used only with Fragment execution model"))), ); + +INSTANTIATE_TEST_CASE_P( + PointCoordNotInput, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("PointCoord"), Values("Fragment"), Values("Output"), + Values("%f32vec2"), + Values(TestResult( + SPV_ERROR_INVALID_DATA, + "to be only used for variables with Input storage class", + "uses storage class Output"))), ); + +INSTANTIATE_TEST_CASE_P( + PointCoordNotFloatVector, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("PointCoord"), Values("Fragment"), Values("Input"), + Values("%f32arr2", "%u32vec2"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 2-component 32-bit float vector", + "is not a float vector"))), ); + +INSTANTIATE_TEST_CASE_P( + PointCoordNotFloatVec3, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("PointCoord"), Values("Fragment"), Values("Input"), + Values("%f32vec3"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 2-component 32-bit float vector", + "has 3 components"))), ); + +INSTANTIATE_TEST_CASE_P( + PointCoordNotF32Vec4, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("PointCoord"), Values("Fragment"), Values("Input"), + Values("%f64vec2"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 2-component 32-bit float vector", + "has components with bit width 64"))), ); + +INSTANTIATE_TEST_CASE_P( + PointSizeOutputSuccess, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("PointSize"), + Values("Vertex", "Geometry", "TessellationControl", + "TessellationEvaluation"), + Values("Output"), Values("%f32"), Values(TestResult())), ); + +INSTANTIATE_TEST_CASE_P( + PointSizeInputSuccess, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("PointSize"), + Values("Geometry", "TessellationControl", "TessellationEvaluation"), + Values("Input"), Values("%f32"), Values(TestResult())), ); + +INSTANTIATE_TEST_CASE_P( + PointSizeVertexInput, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("PointSize"), Values("Vertex"), Values("Input"), + Values("%f32"), + Values(TestResult( + SPV_ERROR_INVALID_DATA, + "Vulkan spec doesn't allow BuiltIn PointSize " + "to be used for variables with Input storage class if " + "execution model is Vertex.", + "which is called with execution model Vertex."))), ); + +INSTANTIATE_TEST_CASE_P( + PointSizeInvalidExecutionModel, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("PointSize"), Values("GLCompute", "Fragment"), + Values("Input", "Output"), Values("%f32"), + Values(TestResult( + SPV_ERROR_INVALID_DATA, + "to be used only with Vertex, TessellationControl, " + "TessellationEvaluation or Geometry execution models"))), ); + +INSTANTIATE_TEST_CASE_P( + PointSizeNotFloatScalar, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("PointSize"), Values("Vertex"), Values("Output"), + Values("%f32vec4", "%u32"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit float scalar", + "is not a float scalar"))), ); + +INSTANTIATE_TEST_CASE_P( + PointSizeNotF32, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("PointSize"), Values("Vertex"), Values("Output"), + Values("%f64"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit float scalar", + "has bit width 64"))), ); + +INSTANTIATE_TEST_CASE_P( + PositionOutputSuccess, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("Position"), + Values("Vertex", "Geometry", "TessellationControl", + "TessellationEvaluation"), + Values("Output"), Values("%f32vec4"), Values(TestResult())), ); + +INSTANTIATE_TEST_CASE_P( + PositionInputSuccess, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("Position"), + Values("Geometry", "TessellationControl", "TessellationEvaluation"), + Values("Input"), Values("%f32vec4"), Values(TestResult())), ); + +INSTANTIATE_TEST_CASE_P( + PositionVertexInput, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("Position"), Values("Vertex"), Values("Input"), + Values("%f32vec4"), + Values(TestResult( + SPV_ERROR_INVALID_DATA, + "Vulkan spec doesn't allow BuiltIn Position " + "to be used for variables with Input storage class if " + "execution model is Vertex.", + "which is called with execution model Vertex."))), ); + +INSTANTIATE_TEST_CASE_P( + PositionInvalidExecutionModel, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("Position"), Values("GLCompute", "Fragment"), + Values("Input", "Output"), Values("%f32vec4"), + Values(TestResult( + SPV_ERROR_INVALID_DATA, + "to be used only with Vertex, TessellationControl, " + "TessellationEvaluation or Geometry execution models"))), ); + +INSTANTIATE_TEST_CASE_P( + PositionNotFloatVector, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("Position"), Values("Geometry"), Values("Input"), + Values("%f32arr4", "%u32vec4"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 4-component 32-bit float vector", + "is not a float vector"))), ); + +INSTANTIATE_TEST_CASE_P( + PositionNotFloatVec4, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("Position"), Values("Geometry"), Values("Input"), + Values("%f32vec3"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 4-component 32-bit float vector", + "has 3 components"))), ); + +INSTANTIATE_TEST_CASE_P( + PositionNotF32Vec4, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("Position"), Values("Geometry"), Values("Input"), + Values("%f64vec4"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 4-component 32-bit float vector", + "has components with bit width 64"))), ); + +INSTANTIATE_TEST_CASE_P( + PrimitiveIdInputSuccess, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("PrimitiveId"), + Values("Fragment", "TessellationControl", "TessellationEvaluation", + "Geometry"), + Values("Input"), Values("%u32"), Values(TestResult())), ); + +INSTANTIATE_TEST_CASE_P( + PrimitiveIdOutputSuccess, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("PrimitiveId"), Values("Geometry"), Values("Output"), + Values("%u32"), Values(TestResult())), ); + +INSTANTIATE_TEST_CASE_P( + PrimitiveIdInvalidExecutionModel, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("PrimitiveId"), Values("Vertex", "GLCompute"), + Values("Input"), Values("%u32"), + Values(TestResult( + SPV_ERROR_INVALID_DATA, + "to be used only with Fragment, TessellationControl, " + "TessellationEvaluation or Geometry execution models"))), ); + +INSTANTIATE_TEST_CASE_P( + PrimitiveIdFragmentNotInput, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine( + Values("PrimitiveId"), Values("Fragment"), Values("Output"), + Values("%u32"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "Output storage class if execution model is Fragment", + "which is called with execution model Fragment"))), ); + +INSTANTIATE_TEST_CASE_P( + PrimitiveIdGeometryNotInput, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("PrimitiveId"), + Values("TessellationControl", "TessellationEvaluation"), + Values("Output"), Values("%u32"), + Values(TestResult( + SPV_ERROR_INVALID_DATA, + "Output storage class if execution model is Tessellation", + "which is called with execution model Tessellation"))), ); + +INSTANTIATE_TEST_CASE_P( + PrimitiveIdNotIntScalar, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("PrimitiveId"), Values("Fragment"), Values("Input"), + Values("%f32", "%u32vec3"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit int scalar", + "is not an int scalar"))), ); + +INSTANTIATE_TEST_CASE_P( + PrimitiveIdNotInt32, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("PrimitiveId"), Values("Fragment"), Values("Input"), + Values("%u64"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit int scalar", + "has bit width 64"))), ); + +INSTANTIATE_TEST_CASE_P( + SampleIdSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("SampleId"), Values("Fragment"), Values("Input"), + Values("%u32"), Values(TestResult())), ); + +INSTANTIATE_TEST_CASE_P( + SampleIdInvalidExecutionModel, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine( + Values("SampleId"), + Values("Vertex", "GLCompute", "Geometry", "TessellationControl", + "TessellationEvaluation"), + Values("Input"), Values("%u32"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "to be used only with Fragment execution model"))), ); + +INSTANTIATE_TEST_CASE_P( + SampleIdNotInput, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine( + Values("SampleId"), Values("Fragment"), Values("Output"), + Values("%u32"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "Vulkan spec allows BuiltIn SampleId to be only used " + "for variables with Input storage class"))), ); + +INSTANTIATE_TEST_CASE_P( + SampleIdNotIntScalar, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("SampleId"), Values("Fragment"), Values("Input"), + Values("%f32", "%u32vec3"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit int scalar", + "is not an int scalar"))), ); + +INSTANTIATE_TEST_CASE_P( + SampleIdNotInt32, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("SampleId"), Values("Fragment"), Values("Input"), + Values("%u64"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit int scalar", + "has bit width 64"))), ); + +INSTANTIATE_TEST_CASE_P( + SampleMaskSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("SampleMask"), Values("Fragment"), Values("Input", "Output"), + Values("%u32arr2", "%u32arr4"), Values(TestResult())), ); + +INSTANTIATE_TEST_CASE_P( + SampleMaskInvalidExecutionModel, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine( + Values("SampleMask"), + Values("Vertex", "GLCompute", "Geometry", "TessellationControl", + "TessellationEvaluation"), + Values("Input"), Values("%u32arr2"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "to be used only with Fragment execution model"))), ); + +INSTANTIATE_TEST_CASE_P( + SampleMaskWrongStorageClass, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("SampleMask"), Values("Fragment"), Values("UniformConstant"), + Values("%u32arr2"), + Values(TestResult( + SPV_ERROR_INVALID_DATA, + "Vulkan spec allows BuiltIn SampleMask to be only used for " + "variables with Input or Output storage class"))), ); + +INSTANTIATE_TEST_CASE_P( + SampleMaskNotArray, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("SampleMask"), Values("Fragment"), Values("Input"), + Values("%f32", "%u32vec3"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit int array", + "is not an array"))), ); + +INSTANTIATE_TEST_CASE_P( + SampleMaskNotIntArray, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("SampleMask"), Values("Fragment"), Values("Input"), + Values("%f32arr2"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit int array", + "components are not int scalar"))), ); + +INSTANTIATE_TEST_CASE_P( + SampleMaskNotInt32Array, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("SampleMask"), Values("Fragment"), Values("Input"), + Values("%u64arr2"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit int array", + "has components with bit width 64"))), ); + +INSTANTIATE_TEST_CASE_P( + SamplePositionSuccess, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("SamplePosition"), Values("Fragment"), Values("Input"), + Values("%f32vec2"), Values(TestResult())), ); + +INSTANTIATE_TEST_CASE_P( + SamplePositionNotFragment, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine( + Values("SamplePosition"), + Values("Vertex", "GLCompute", "Geometry", "TessellationControl", + "TessellationEvaluation"), + Values("Input"), Values("%f32vec2"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "to be used only with Fragment execution model"))), ); + +INSTANTIATE_TEST_CASE_P( + SamplePositionNotInput, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("SamplePosition"), Values("Fragment"), Values("Output"), + Values("%f32vec2"), + Values(TestResult( + SPV_ERROR_INVALID_DATA, + "to be only used for variables with Input storage class", + "uses storage class Output"))), ); + +INSTANTIATE_TEST_CASE_P( + SamplePositionNotFloatVector, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("SamplePosition"), Values("Fragment"), Values("Input"), + Values("%f32arr2", "%u32vec4"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 2-component 32-bit float vector", + "is not a float vector"))), ); + +INSTANTIATE_TEST_CASE_P( + SamplePositionNotFloatVec2, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("SamplePosition"), Values("Fragment"), Values("Input"), + Values("%f32vec3"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 2-component 32-bit float vector", + "has 3 components"))), ); + +INSTANTIATE_TEST_CASE_P( + SamplePositionNotF32Vec2, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("SamplePosition"), Values("Fragment"), Values("Input"), + Values("%f64vec2"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 2-component 32-bit float vector", + "has components with bit width 64"))), ); + +INSTANTIATE_TEST_CASE_P( + TessCoordSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("TessCoord"), Values("TessellationEvaluation"), + Values("Input"), Values("%f32vec3"), Values(TestResult())), ); + +INSTANTIATE_TEST_CASE_P( + TessCoordNotFragment, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine( + Values("TessCoord"), + Values("Vertex", "GLCompute", "Geometry", "TessellationControl", + "Fragment"), + Values("Input"), Values("%f32vec3"), + Values(TestResult( + SPV_ERROR_INVALID_DATA, + "to be used only with TessellationEvaluation execution model"))), ); + +INSTANTIATE_TEST_CASE_P( + TessCoordNotInput, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("TessCoord"), Values("Fragment"), Values("Output"), + Values("%f32vec3"), + Values(TestResult( + SPV_ERROR_INVALID_DATA, + "to be only used for variables with Input storage class", + "uses storage class Output"))), ); + +INSTANTIATE_TEST_CASE_P( + TessCoordNotFloatVector, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("TessCoord"), Values("Fragment"), Values("Input"), + Values("%f32arr3", "%u32vec4"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 3-component 32-bit float vector", + "is not a float vector"))), ); + +INSTANTIATE_TEST_CASE_P( + TessCoordNotFloatVec3, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("TessCoord"), Values("Fragment"), Values("Input"), + Values("%f32vec2"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 3-component 32-bit float vector", + "has 2 components"))), ); + +INSTANTIATE_TEST_CASE_P( + TessCoordNotF32Vec3, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("TessCoord"), Values("Fragment"), Values("Input"), + Values("%f64vec3"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 3-component 32-bit float vector", + "has components with bit width 64"))), ); + +INSTANTIATE_TEST_CASE_P( + TessLevelOuterTeseInputSuccess, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"), + Values("Input"), Values("%f32arr4"), Values(TestResult())), ); + +INSTANTIATE_TEST_CASE_P( + TessLevelOuterTescOutputSuccess, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("TessLevelOuter"), Values("TessellationControl"), + Values("Output"), Values("%f32arr4"), Values(TestResult())), ); + +INSTANTIATE_TEST_CASE_P( + TessLevelOuterInvalidExecutionModel, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("TessLevelOuter"), + Values("Vertex", "GLCompute", "Geometry", "Fragment"), + Values("Input"), Values("%f32arr4"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "to be used only with TessellationControl or " + "TessellationEvaluation execution models."))), ); + +INSTANTIATE_TEST_CASE_P( + TessLevelOuterOutputTese, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"), + Values("Output"), Values("%f32arr4"), + Values(TestResult( + SPV_ERROR_INVALID_DATA, + "Vulkan spec doesn't allow TessLevelOuter/TessLevelInner to be " + "used for variables with Output storage class if execution " + "model is TessellationEvaluation."))), ); + +INSTANTIATE_TEST_CASE_P( + TessLevelOuterInputTesc, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("TessLevelOuter"), Values("TessellationControl"), + Values("Input"), Values("%f32arr4"), + Values(TestResult( + SPV_ERROR_INVALID_DATA, + "Vulkan spec doesn't allow TessLevelOuter/TessLevelInner to be " + "used for variables with Input storage class if execution " + "model is TessellationControl."))), ); + +INSTANTIATE_TEST_CASE_P( + TessLevelOuterNotArray, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"), + Values("Input"), Values("%f32vec4", "%f32"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 4-component 32-bit float array", + "is not an array"))), ); + +INSTANTIATE_TEST_CASE_P( + TessLevelOuterNotFloatArray, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"), + Values("Input"), Values("%u32arr4"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 4-component 32-bit float array", + "components are not float scalar"))), ); + +INSTANTIATE_TEST_CASE_P( + TessLevelOuterNotFloatArr4, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"), + Values("Input"), Values("%f32arr3"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 4-component 32-bit float array", + "has 3 components"))), ); + +INSTANTIATE_TEST_CASE_P( + TessLevelOuterNotF32Arr4, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("TessLevelOuter"), Values("TessellationEvaluation"), + Values("Input"), Values("%f64arr4"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 4-component 32-bit float array", + "has components with bit width 64"))), ); + +INSTANTIATE_TEST_CASE_P( + TessLevelInnerTeseInputSuccess, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("TessLevelInner"), Values("TessellationEvaluation"), + Values("Input"), Values("%f32arr2"), Values(TestResult())), ); + +INSTANTIATE_TEST_CASE_P( + TessLevelInnerTescOutputSuccess, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("TessLevelInner"), Values("TessellationControl"), + Values("Output"), Values("%f32arr2"), Values(TestResult())), ); + +INSTANTIATE_TEST_CASE_P( + TessLevelInnerInvalidExecutionModel, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("TessLevelInner"), + Values("Vertex", "GLCompute", "Geometry", "Fragment"), + Values("Input"), Values("%f32arr2"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "to be used only with TessellationControl or " + "TessellationEvaluation execution models."))), ); + +INSTANTIATE_TEST_CASE_P( + TessLevelInnerOutputTese, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("TessLevelInner"), Values("TessellationEvaluation"), + Values("Output"), Values("%f32arr2"), + Values(TestResult( + SPV_ERROR_INVALID_DATA, + "Vulkan spec doesn't allow TessLevelOuter/TessLevelInner to be " + "used for variables with Output storage class if execution " + "model is TessellationEvaluation."))), ); + +INSTANTIATE_TEST_CASE_P( + TessLevelInnerInputTesc, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("TessLevelInner"), Values("TessellationControl"), + Values("Input"), Values("%f32arr2"), + Values(TestResult( + SPV_ERROR_INVALID_DATA, + "Vulkan spec doesn't allow TessLevelOuter/TessLevelInner to be " + "used for variables with Input storage class if execution " + "model is TessellationControl."))), ); + +INSTANTIATE_TEST_CASE_P( + TessLevelInnerNotArray, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("TessLevelInner"), Values("TessellationEvaluation"), + Values("Input"), Values("%f32vec2", "%f32"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 2-component 32-bit float array", + "is not an array"))), ); + +INSTANTIATE_TEST_CASE_P( + TessLevelInnerNotFloatArray, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("TessLevelInner"), Values("TessellationEvaluation"), + Values("Input"), Values("%u32arr2"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 2-component 32-bit float array", + "components are not float scalar"))), ); + +INSTANTIATE_TEST_CASE_P( + TessLevelInnerNotFloatArr2, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("TessLevelInner"), Values("TessellationEvaluation"), + Values("Input"), Values("%f32arr3"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 2-component 32-bit float array", + "has 3 components"))), ); + +INSTANTIATE_TEST_CASE_P( + TessLevelInnerNotF32Arr2, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("TessLevelInner"), Values("TessellationEvaluation"), + Values("Input"), Values("%f64arr2"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 2-component 32-bit float array", + "has components with bit width 64"))), ); + +INSTANTIATE_TEST_CASE_P( + VertexIndexSuccess, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("VertexIndex"), Values("Vertex"), Values("Input"), + Values("%u32"), Values(TestResult())), ); + +INSTANTIATE_TEST_CASE_P( + VertexIndexInvalidExecutionModel, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine( + Values("VertexIndex"), + Values("Fragment", "GLCompute", "Geometry", "TessellationControl", + "TessellationEvaluation"), + Values("Input"), Values("%u32"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "to be used only with Vertex execution model"))), ); + +INSTANTIATE_TEST_CASE_P( + VertexIndexNotInput, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine( + Values("VertexIndex"), Values("Vertex"), Values("Output"), + Values("%u32"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "Vulkan spec allows BuiltIn VertexIndex to be only " + "used for variables with Input storage class"))), ); + +INSTANTIATE_TEST_CASE_P( + VertexIndexNotIntScalar, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("VertexIndex"), Values("Vertex"), Values("Input"), + Values("%f32", "%u32vec3"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit int scalar", + "is not an int scalar"))), ); + +INSTANTIATE_TEST_CASE_P( + VertexIndexNotInt32, + ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, + Combine(Values("VertexIndex"), Values("Vertex"), Values("Input"), + Values("%u64"), + Values(TestResult(SPV_ERROR_INVALID_DATA, + "needs to be a 32-bit int scalar", + "has bit width 64"))), ); + +TEST_F(ValidateBuiltIns, WorkgroupSizeSuccess) { + CodeGenerator generator = GetDefaultShaderCodeGenerator(); + generator.before_types_ = R"( +OpDecorate %workgroup_size BuiltIn WorkgroupSize +)"; + + generator.after_types_ = R"( +%workgroup_size = OpConstantComposite %u32vec3 %u32_1 %u32_1 %u32_1 +)"; + + EntryPoint entry_point; + entry_point.name = "main"; + entry_point.execution_model = "GLCompute"; + entry_point.body = R"( +%copy = OpCopyObject %u32vec3 %workgroup_size +)"; + generator.entry_points_.push_back(std::move(entry_point)); + + CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); +} + +TEST_F(ValidateBuiltIns, WorkgroupSizeFragment) { + CodeGenerator generator = GetDefaultShaderCodeGenerator(); + generator.before_types_ = R"( +OpDecorate %workgroup_size BuiltIn WorkgroupSize +)"; + + generator.after_types_ = R"( +%workgroup_size = OpConstantComposite %u32vec3 %u32_1 %u32_1 %u32_1 +)"; + + EntryPoint entry_point; + entry_point.name = "main"; + entry_point.execution_model = "Fragment"; + entry_point.execution_modes = "OpExecutionMode %main OriginUpperLeft"; + entry_point.body = R"( +%copy = OpCopyObject %u32vec3 %workgroup_size +)"; + generator.entry_points_.push_back(std::move(entry_point)); + + CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Vulkan spec allows BuiltIn WorkgroupSize to be used " + "only with GLCompute execution model")); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("is referencing ID <2> (OpConstantComposite) which is " + "decorated with BuiltIn WorkgroupSize in function <1> " + "called with execution model Fragment")); +} + +TEST_F(ValidateBuiltIns, WorkgroupSizeNotConstant) { + CodeGenerator generator = GetDefaultShaderCodeGenerator(); + generator.before_types_ = R"( +OpDecorate %copy BuiltIn WorkgroupSize +)"; + + generator.after_types_ = R"( +%workgroup_size = OpConstantComposite %u32vec3 %u32_1 %u32_1 %u32_1 +)"; + + EntryPoint entry_point; + entry_point.name = "main"; + entry_point.execution_model = "GLCompute"; + entry_point.body = R"( +%copy = OpCopyObject %u32vec3 %workgroup_size +)"; + generator.entry_points_.push_back(std::move(entry_point)); + + CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Vulkan spec requires BuiltIn WorkgroupSize to be a " + "constant. ID <2> (OpCopyObject) is not a constant")); +} + +TEST_F(ValidateBuiltIns, WorkgroupSizeNotVector) { + CodeGenerator generator = GetDefaultShaderCodeGenerator(); + generator.before_types_ = R"( +OpDecorate %workgroup_size BuiltIn WorkgroupSize +)"; + + generator.after_types_ = R"( +%workgroup_size = OpConstant %u32 16 +)"; + + EntryPoint entry_point; + entry_point.name = "main"; + entry_point.execution_model = "GLCompute"; + entry_point.body = R"( +%copy = OpCopyObject %u32 %workgroup_size +)"; + generator.entry_points_.push_back(std::move(entry_point)); + + CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("According to the Vulkan spec BuiltIn WorkgroupSize " + "variable needs to be a 3-component 32-bit int vector. " + "ID <2> (OpConstant) is not an int vector.")); +} + +TEST_F(ValidateBuiltIns, WorkgroupSizeNotIntVector) { + CodeGenerator generator = GetDefaultShaderCodeGenerator(); + generator.before_types_ = R"( +OpDecorate %workgroup_size BuiltIn WorkgroupSize +)"; + + generator.after_types_ = R"( +%workgroup_size = OpConstantComposite %f32vec3 %f32_1 %f32_1 %f32_1 +)"; + + EntryPoint entry_point; + entry_point.name = "main"; + entry_point.execution_model = "GLCompute"; + entry_point.body = R"( +%copy = OpCopyObject %f32vec3 %workgroup_size +)"; + generator.entry_points_.push_back(std::move(entry_point)); + + CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("According to the Vulkan spec BuiltIn WorkgroupSize " + "variable needs to be a 3-component 32-bit int vector. " + "ID <2> (OpConstantComposite) is not an int vector.")); +} + +TEST_F(ValidateBuiltIns, WorkgroupSizeNotVec3) { + CodeGenerator generator = GetDefaultShaderCodeGenerator(); + generator.before_types_ = R"( +OpDecorate %workgroup_size BuiltIn WorkgroupSize +)"; + + generator.after_types_ = R"( +%workgroup_size = OpConstantComposite %u32vec2 %u32_1 %u32_1 +)"; + + EntryPoint entry_point; + entry_point.name = "main"; + entry_point.execution_model = "GLCompute"; + entry_point.body = R"( +%copy = OpCopyObject %u32vec2 %workgroup_size +)"; + generator.entry_points_.push_back(std::move(entry_point)); + + CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("According to the Vulkan spec BuiltIn WorkgroupSize " + "variable needs to be a 3-component 32-bit int vector. " + "ID <2> (OpConstantComposite) has 2 components.")); +} + +TEST_F(ValidateBuiltIns, WorkgroupSizeNotInt32Vec) { + CodeGenerator generator = GetDefaultShaderCodeGenerator(); + generator.before_types_ = R"( +OpDecorate %workgroup_size BuiltIn WorkgroupSize +)"; + + generator.after_types_ = R"( +%workgroup_size = OpConstantComposite %u64vec3 %u64_1 %u64_1 %u64_1 +)"; + + EntryPoint entry_point; + entry_point.name = "main"; + entry_point.execution_model = "GLCompute"; + entry_point.body = R"( +%copy = OpCopyObject %u64vec3 %workgroup_size +)"; + generator.entry_points_.push_back(std::move(entry_point)); + + CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("According to the Vulkan spec BuiltIn WorkgroupSize variable " + "needs to be a 3-component 32-bit int vector. ID <2> " + "(OpConstantComposite) has components with bit width 64.")); +} + +TEST_F(ValidateBuiltIns, GeometryPositionInOutSuccess) { + CodeGenerator generator = GetDefaultShaderCodeGenerator(); + + generator.before_types_ = R"( +OpMemberDecorate %input_type 0 BuiltIn Position +OpMemberDecorate %output_type 0 BuiltIn Position +)"; + + generator.after_types_ = R"( +%input_type = OpTypeStruct %f32vec4 +%input_ptr = OpTypePointer Input %input_type +%input = OpVariable %input_ptr Input +%input_f32vec4_ptr = OpTypePointer Input %f32vec4 +%output_type = OpTypeStruct %f32vec4 +%output_ptr = OpTypePointer Output %output_type +%output = OpVariable %output_ptr Output +%output_f32vec4_ptr = OpTypePointer Output %f32vec4 +)"; + + EntryPoint entry_point; + entry_point.name = "main"; + entry_point.execution_model = "Geometry"; + entry_point.body = R"( +%input_pos = OpAccessChain %input_f32vec4_ptr %input %u32_0 +%output_pos = OpAccessChain %output_f32vec4_ptr %output %u32_0 +%pos = OpLoad %f32vec4 %input_pos +OpStore %output_pos %pos +)"; + generator.entry_points_.push_back(std::move(entry_point)); + + CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); +} + +TEST_F(ValidateBuiltIns, TwoBuiltInsFirstFails) { + CodeGenerator generator = GetDefaultShaderCodeGenerator(); + + generator.before_types_ = R"( +OpMemberDecorate %input_type 0 BuiltIn FragCoord +OpMemberDecorate %output_type 0 BuiltIn Position +)"; + + generator.after_types_ = R"( +%input_type = OpTypeStruct %f32vec4 +%input_ptr = OpTypePointer Input %input_type +%input = OpVariable %input_ptr Input +%input_f32vec4_ptr = OpTypePointer Input %f32vec4 +%output_type = OpTypeStruct %f32vec4 +%output_ptr = OpTypePointer Output %output_type +%output = OpVariable %output_ptr Output +%output_f32vec4_ptr = OpTypePointer Output %f32vec4 +)"; + + EntryPoint entry_point; + entry_point.name = "main"; + entry_point.execution_model = "Geometry"; + entry_point.body = R"( +%input_pos = OpAccessChain %input_f32vec4_ptr %input %u32_0 +%output_pos = OpAccessChain %output_f32vec4_ptr %output %u32_0 +%pos = OpLoad %f32vec4 %input_pos +OpStore %output_pos %pos +)"; + generator.entry_points_.push_back(std::move(entry_point)); + + CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Vulkan spec allows BuiltIn FragCoord to be used only " + "with Fragment execution model")); +} + +TEST_F(ValidateBuiltIns, TwoBuiltInsSecondFails) { + CodeGenerator generator = GetDefaultShaderCodeGenerator(); + + generator.before_types_ = R"( +OpMemberDecorate %input_type 0 BuiltIn Position +OpMemberDecorate %output_type 0 BuiltIn FragCoord +)"; + + generator.after_types_ = R"( +%input_type = OpTypeStruct %f32vec4 +%input_ptr = OpTypePointer Input %input_type +%input = OpVariable %input_ptr Input +%input_f32vec4_ptr = OpTypePointer Input %f32vec4 +%output_type = OpTypeStruct %f32vec4 +%output_ptr = OpTypePointer Output %output_type +%output = OpVariable %output_ptr Output +%output_f32vec4_ptr = OpTypePointer Output %f32vec4 +)"; + + EntryPoint entry_point; + entry_point.name = "main"; + entry_point.execution_model = "Geometry"; + entry_point.body = R"( +%input_pos = OpAccessChain %input_f32vec4_ptr %input %u32_0 +%output_pos = OpAccessChain %output_f32vec4_ptr %output %u32_0 +%pos = OpLoad %f32vec4 %input_pos +OpStore %output_pos %pos +)"; + generator.entry_points_.push_back(std::move(entry_point)); + + CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Vulkan spec allows BuiltIn FragCoord to be only used " + "for variables with Input storage class")); +} + +TEST_F(ValidateBuiltIns, VertexPositionVariableSuccess) { + CodeGenerator generator = GetDefaultShaderCodeGenerator(); + generator.before_types_ = R"( +OpDecorate %position BuiltIn Position +)"; + + generator.after_types_ = R"( +%f32vec4_ptr_output = OpTypePointer Output %f32vec4 +%position = OpVariable %f32vec4_ptr_output Output +)"; + + EntryPoint entry_point; + entry_point.name = "main"; + entry_point.execution_model = "Vertex"; + entry_point.body = R"( +OpStore %position %f32vec4_0123 +)"; + generator.entry_points_.push_back(std::move(entry_point)); + + CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); +} + +TEST_F(ValidateBuiltIns, FragmentPositionTwoEntryPoints) { + CodeGenerator generator = GetDefaultShaderCodeGenerator(); + generator.before_types_ = R"( +OpMemberDecorate %output_type 0 BuiltIn Position +)"; + + generator.after_types_ = R"( +%output_type = OpTypeStruct %f32vec4 +%output_ptr = OpTypePointer Output %output_type +%output = OpVariable %output_ptr Output +%output_f32vec4_ptr = OpTypePointer Output %f32vec4 +)"; + + EntryPoint entry_point; + entry_point.name = "vmain"; + entry_point.execution_model = "Vertex"; + entry_point.body = R"( +%val1 = OpFunctionCall %void %foo +)"; + generator.entry_points_.push_back(std::move(entry_point)); + + entry_point.name = "fmain"; + entry_point.execution_model = "Fragment"; + entry_point.execution_modes = "OpExecutionMode %fmain OriginUpperLeft"; + entry_point.body = R"( +%val2 = OpFunctionCall %void %foo +)"; + generator.entry_points_.push_back(std::move(entry_point)); + + generator.add_at_the_end_ = R"( +%foo = OpFunction %void None %func +%foo_entry = OpLabel +%position = OpAccessChain %output_f32vec4_ptr %output %u32_0 +OpStore %position %f32vec4_0123 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Vulkan spec allows BuiltIn Position to be used only " + "with Vertex, TessellationControl, " + "TessellationEvaluation or Geometry execution models")); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("called with execution model Fragment")); +} + +TEST_F(ValidateBuiltIns, FragmentFragDepthNoDepthReplacing) { + CodeGenerator generator = GetDefaultShaderCodeGenerator(); + generator.before_types_ = R"( +OpMemberDecorate %output_type 0 BuiltIn FragDepth +)"; + + generator.after_types_ = R"( +%output_type = OpTypeStruct %f32 +%output_ptr = OpTypePointer Output %output_type +%output = OpVariable %output_ptr Output +%output_f32_ptr = OpTypePointer Output %f32 +)"; + + EntryPoint entry_point; + entry_point.name = "main"; + entry_point.execution_model = "Fragment"; + entry_point.execution_modes = "OpExecutionMode %main OriginUpperLeft"; + entry_point.body = R"( +%val2 = OpFunctionCall %void %foo +)"; + generator.entry_points_.push_back(std::move(entry_point)); + + generator.add_at_the_end_ = R"( +%foo = OpFunction %void None %func +%foo_entry = OpLabel +%frag_depth = OpAccessChain %output_f32_ptr %output %u32_0 +OpStore %frag_depth %f32_1 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Vulkan spec requires DepthReplacing execution mode to " + "be declared when using BuiltIn FragDepth")); +} + +TEST_F(ValidateBuiltIns, FragmentFragDepthOneMainHasDepthReplacingOtherHasnt) { + CodeGenerator generator = GetDefaultShaderCodeGenerator(); + generator.before_types_ = R"( +OpMemberDecorate %output_type 0 BuiltIn FragDepth +)"; + + generator.after_types_ = R"( +%output_type = OpTypeStruct %f32 +%output_ptr = OpTypePointer Output %output_type +%output = OpVariable %output_ptr Output +%output_f32_ptr = OpTypePointer Output %f32 +)"; + + EntryPoint entry_point; + entry_point.name = "main_d_r"; + entry_point.execution_model = "Fragment"; + entry_point.execution_modes = + "OpExecutionMode %main_d_r OriginUpperLeft\n" + "OpExecutionMode %main_d_r DepthReplacing"; + entry_point.body = R"( +%val2 = OpFunctionCall %void %foo +)"; + generator.entry_points_.push_back(std::move(entry_point)); + + entry_point.name = "main_no_d_r"; + entry_point.execution_model = "Fragment"; + entry_point.execution_modes = "OpExecutionMode %main_no_d_r OriginUpperLeft"; + entry_point.body = R"( +%val3 = OpFunctionCall %void %foo +)"; + generator.entry_points_.push_back(std::move(entry_point)); + + generator.add_at_the_end_ = R"( +%foo = OpFunction %void None %func +%foo_entry = OpLabel +%frag_depth = OpAccessChain %output_f32_ptr %output %u32_0 +OpStore %frag_depth %f32_1 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Vulkan spec requires DepthReplacing execution mode to " + "be declared when using BuiltIn FragDepth")); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/val/val_capability_test.cpp b/3rdparty/spirv-tools/test/val/val_capability_test.cpp new file mode 100644 index 000000000..1e5b1f03e --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_capability_test.cpp @@ -0,0 +1,1969 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Validation tests for Logical Layout + +#include +#include +#include +#include + +#include + +#include "source/assembly_grammar.h" +#include "test_fixture.h" +#include "unit_spirv.h" +#include "val_fixtures.h" + +namespace { + +using spvtest::ScopedContext; +using std::get; +using std::make_pair; +using std::ostringstream; +using std::pair; +using std::string; +using std::tuple; +using std::vector; +using testing::Combine; +using testing::HasSubstr; +using testing::Values; +using testing::ValuesIn; + +// Parameter for validation test fixtures. The first string is a capability +// name that will begin the assembly under test, the second the remainder +// assembly, and the vector at the end determines whether the test expects +// success or failure. See below for details and convenience methods to access +// each one. +// +// The assembly to test is composed from a variable top line and a fixed +// remainder. The top line will be an OpCapability instruction, while the +// remainder will be some assembly text that succeeds or fails to assemble +// depending on which capability was chosen. For instance, the following will +// succeed: +// +// OpCapability Pipes ; implies Kernel +// OpLifetimeStop %1 0 ; requires Kernel +// +// and the following will fail: +// +// OpCapability Kernel +// %1 = OpTypeNamedBarrier ; requires NamedBarrier +// +// So how does the test parameter capture which capabilities should cause +// success and which shouldn't? The answer is in the last element: it's a +// vector of capabilities that make the remainder assembly succeed. So if the +// first-line capability exists in that vector, success is expected; otherwise, +// failure is expected in the tests. +// +// We will use testing::Combine() to vary the first line: when we combine +// AllCapabilities() with a single remainder assembly, we generate enough test +// cases to try the assembly with every possible capability that could be +// declared. However, Combine() only produces tuples -- it cannot produce, say, +// a struct. Therefore, this type must be a tuple. +using CapTestParameter = tuple>>; + +const string& Capability(const CapTestParameter& p) { return get<0>(p); } +const string& Remainder(const CapTestParameter& p) { return get<1>(p).first; } +const vector& MustSucceed(const CapTestParameter& p) { + return get<1>(p).second; +} + +// Creates assembly to test from p. +string MakeAssembly(const CapTestParameter& p) { + ostringstream ss; + const string& capability = Capability(p); + if (!capability.empty()) { + ss << "OpCapability " << capability << "\n"; + } + ss << Remainder(p); + return ss.str(); +} + +// Expected validation result for p. +spv_result_t ExpectedResult(const CapTestParameter& p) { + const auto& caps = MustSucceed(p); + auto found = find(begin(caps), end(caps), Capability(p)); + return (found == end(caps)) ? SPV_ERROR_INVALID_CAPABILITY : SPV_SUCCESS; +} + +// Assembles using v1.0, unless the parameter's capability requires v1.1. +using ValidateCapability = spvtest::ValidateBase; + +// Always assembles using v1.1. +using ValidateCapabilityV11 = spvtest::ValidateBase; + +// Always assembles using Vulkan 1.0. +// TODO(dneto): Refactor all these tests to scale better across environments. +using ValidateCapabilityVulkan10 = spvtest::ValidateBase; +// Always assembles using OpenGL 4.0. +using ValidateCapabilityOpenGL40 = spvtest::ValidateBase; + +TEST_F(ValidateCapability, Default) { + const char str[] = R"( + OpCapability Kernel + OpCapability Linkage + OpCapability Matrix + OpMemoryModel Logical OpenCL +%f32 = OpTypeFloat 32 +%vec3 = OpTypeVector %f32 3 +%mat33 = OpTypeMatrix %vec3 3 +)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// clang-format off +const vector& AllCapabilities() { + static const auto r = new vector{ + "", + "Matrix", + "Shader", + "Geometry", + "Tessellation", + "Addresses", + "Linkage", + "Kernel", + "Vector16", + "Float16Buffer", + "Float16", + "Float64", + "Int64", + "Int64Atomics", + "ImageBasic", + "ImageReadWrite", + "ImageMipmap", + "Pipes", + "Groups", + "DeviceEnqueue", + "LiteralSampler", + "AtomicStorage", + "Int16", + "TessellationPointSize", + "GeometryPointSize", + "ImageGatherExtended", + "StorageImageMultisample", + "UniformBufferArrayDynamicIndexing", + "SampledImageArrayDynamicIndexing", + "StorageBufferArrayDynamicIndexing", + "StorageImageArrayDynamicIndexing", + "ClipDistance", + "CullDistance", + "ImageCubeArray", + "SampleRateShading", + "ImageRect", + "SampledRect", + "GenericPointer", + "Int8", + "InputAttachment", + "SparseResidency", + "MinLod", + "Sampled1D", + "Image1D", + "SampledCubeArray", + "SampledBuffer", + "ImageBuffer", + "ImageMSArray", + "StorageImageExtendedFormats", + "ImageQuery", + "DerivativeControl", + "InterpolationFunction", + "TransformFeedback", + "GeometryStreams", + "StorageImageReadWithoutFormat", + "StorageImageWriteWithoutFormat", + "MultiViewport", + "SubgroupDispatch", + "NamedBarrier", + "PipeStorage"}; + return *r; +} + +const vector& AllSpirV10Capabilities() { + static const auto r = new vector{ + "", + "Matrix", + "Shader", + "Geometry", + "Tessellation", + "Addresses", + "Linkage", + "Kernel", + "Vector16", + "Float16Buffer", + "Float16", + "Float64", + "Int64", + "Int64Atomics", + "ImageBasic", + "ImageReadWrite", + "ImageMipmap", + "Pipes", + "Groups", + "DeviceEnqueue", + "LiteralSampler", + "AtomicStorage", + "Int16", + "TessellationPointSize", + "GeometryPointSize", + "ImageGatherExtended", + "StorageImageMultisample", + "UniformBufferArrayDynamicIndexing", + "SampledImageArrayDynamicIndexing", + "StorageBufferArrayDynamicIndexing", + "StorageImageArrayDynamicIndexing", + "ClipDistance", + "CullDistance", + "ImageCubeArray", + "SampleRateShading", + "ImageRect", + "SampledRect", + "GenericPointer", + "Int8", + "InputAttachment", + "SparseResidency", + "MinLod", + "Sampled1D", + "Image1D", + "SampledCubeArray", + "SampledBuffer", + "ImageBuffer", + "ImageMSArray", + "StorageImageExtendedFormats", + "ImageQuery", + "DerivativeControl", + "InterpolationFunction", + "TransformFeedback", + "GeometryStreams", + "StorageImageReadWithoutFormat", + "StorageImageWriteWithoutFormat", + "MultiViewport"}; + return *r; +} + +const vector& AllVulkan10Capabilities() { + static const auto r = new vector{ + "", + "Matrix", + "Shader", + "InputAttachment", + "Sampled1D", + "Image1D", + "SampledBuffer", + "ImageBuffer", + "ImageQuery", + "DerivativeControl", + "Geometry", + "Tessellation", + "Float64", + "Int64", + "Int16", + "TessellationPointSize", + "GeometryPointSize", + "ImageGatherExtended", + "StorageImageMultisample", + "UniformBufferArrayDynamicIndexing", + "SampledImageArrayDynamicIndexing", + "StorageBufferArrayDynamicIndexing", + "StorageImageArrayDynamicIndexing", + "ClipDistance", + "CullDistance", + "ImageCubeArray", + "SampleRateShading", + "SparseResidency", + "MinLod", + "SampledCubeArray", + "ImageMSArray", + "StorageImageExtendedFormats", + "InterpolationFunction", + "StorageImageReadWithoutFormat", + "StorageImageWriteWithoutFormat", + "MultiViewport"}; + return *r; +} + +const vector& MatrixDependencies() { + static const auto r = new vector{ + "Matrix", + "Shader", + "Geometry", + "Tessellation", + "AtomicStorage", + "TessellationPointSize", + "GeometryPointSize", + "ImageGatherExtended", + "StorageImageMultisample", + "UniformBufferArrayDynamicIndexing", + "SampledImageArrayDynamicIndexing", + "StorageBufferArrayDynamicIndexing", + "StorageImageArrayDynamicIndexing", + "ClipDistance", + "CullDistance", + "ImageCubeArray", + "SampleRateShading", + "ImageRect", + "SampledRect", + "InputAttachment", + "SparseResidency", + "MinLod", + "SampledCubeArray", + "ImageMSArray", + "StorageImageExtendedFormats", + "ImageQuery", + "DerivativeControl", + "InterpolationFunction", + "TransformFeedback", + "GeometryStreams", + "StorageImageReadWithoutFormat", + "StorageImageWriteWithoutFormat", + "MultiViewport"}; + return *r; +} + +const vector& ShaderDependencies() { + static const auto r = new vector{ + "Shader", + "Geometry", + "Tessellation", + "AtomicStorage", + "TessellationPointSize", + "GeometryPointSize", + "ImageGatherExtended", + "StorageImageMultisample", + "UniformBufferArrayDynamicIndexing", + "SampledImageArrayDynamicIndexing", + "StorageBufferArrayDynamicIndexing", + "StorageImageArrayDynamicIndexing", + "ClipDistance", + "CullDistance", + "ImageCubeArray", + "SampleRateShading", + "ImageRect", + "SampledRect", + "InputAttachment", + "SparseResidency", + "MinLod", + "SampledCubeArray", + "ImageMSArray", + "StorageImageExtendedFormats", + "ImageQuery", + "DerivativeControl", + "InterpolationFunction", + "TransformFeedback", + "GeometryStreams", + "StorageImageReadWithoutFormat", + "StorageImageWriteWithoutFormat", + "MultiViewport"}; + return *r; +} + +const vector& TessellationDependencies() { + static const auto r = new vector{ + "Tessellation", + "TessellationPointSize"}; + return *r; +} + +const vector& GeometryDependencies() { + static const auto r = new vector{ + "Geometry", + "GeometryPointSize", + "GeometryStreams", + "MultiViewport"}; + return *r; +} + +const vector& GeometryTessellationDependencies() { + static const auto r = new vector{ + "Tessellation", + "TessellationPointSize", + "Geometry", + "GeometryPointSize", + "GeometryStreams", + "MultiViewport"}; + return *r; +} + +// Returns the names of capabilities that directly depend on Kernel, +// plus itself. +const vector& KernelDependencies() { + static const auto r = new vector{ + "Kernel", + "Vector16", + "Float16Buffer", + "ImageBasic", + "ImageReadWrite", + "ImageMipmap", + "Pipes", + "DeviceEnqueue", + "LiteralSampler", + "Int8", + "SubgroupDispatch", + "NamedBarrier", + "PipeStorage"}; + return *r; +} + +const vector& AddressesDependencies() { + static const auto r = new vector{ + "Addresses", + "GenericPointer"}; + return *r; +} + +const vector& Sampled1DDependencies() { + static const auto r = new vector{ + "Sampled1D", + "Image1D"}; + return *r; +} + +const vector& SampledRectDependencies() { + static const auto r = new vector{ + "SampledRect", + "ImageRect"}; + return *r; +} + +const vector& SampledBufferDependencies() { + static const auto r = new vector{ + "SampledBuffer", + "ImageBuffer"}; + return *r; +} + +const char kOpenCLMemoryModel[] = \ + " OpCapability Kernel" + " OpMemoryModel Logical OpenCL "; + +const char kGLSL450MemoryModel[] = \ + " OpCapability Shader" + " OpMemoryModel Logical GLSL450 "; + +const char kVoidFVoid[] = \ + " %void = OpTypeVoid" + " %void_f = OpTypeFunction %void" + " %func = OpFunction %void None %void_f" + " %label = OpLabel" + " OpReturn" + " OpFunctionEnd "; + +const char kVoidFVoid2[] = \ + " %void_f = OpTypeFunction %voidt" + " %func = OpFunction %voidt None %void_f" + " %label = OpLabel" + " OpReturn" + " OpFunctionEnd "; + +INSTANTIATE_TEST_CASE_P(ExecutionModel, ValidateCapability, + Combine( + ValuesIn(AllCapabilities()), + Values( +make_pair(string(kOpenCLMemoryModel) + + " OpEntryPoint Vertex %func \"shader\"" + + string(kVoidFVoid), ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + " OpEntryPoint TessellationControl %func \"shader\"" + + string(kVoidFVoid), TessellationDependencies()), +make_pair(string(kOpenCLMemoryModel) + + " OpEntryPoint TessellationEvaluation %func \"shader\"" + + string(kVoidFVoid), TessellationDependencies()), +make_pair(string(kOpenCLMemoryModel) + + " OpEntryPoint Geometry %func \"shader\"" + + string(kVoidFVoid), GeometryDependencies()), +make_pair(string(kOpenCLMemoryModel) + + " OpEntryPoint Fragment %func \"shader\"" + + string(kVoidFVoid), ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + " OpEntryPoint GLCompute %func \"shader\"" + + string(kVoidFVoid), ShaderDependencies()), +make_pair(string(kGLSL450MemoryModel) + + " OpEntryPoint Kernel %func \"shader\"" + + string(kVoidFVoid), KernelDependencies()) +)),); + +INSTANTIATE_TEST_CASE_P(AddressingAndMemoryModel, ValidateCapability, + Combine( + ValuesIn(AllCapabilities()), + Values( +make_pair(" OpCapability Shader" + " OpMemoryModel Logical Simple" + " OpEntryPoint Vertex %func \"shader\"" + + string(kVoidFVoid), AllCapabilities()), +make_pair(" OpCapability Shader" + " OpMemoryModel Logical GLSL450" + " OpEntryPoint Vertex %func \"shader\"" + + string(kVoidFVoid), AllCapabilities()), +make_pair(" OpCapability Kernel" + " OpMemoryModel Logical OpenCL" + " OpEntryPoint Kernel %func \"compute\"" + + string(kVoidFVoid), AllCapabilities()), +make_pair(" OpCapability Shader" + " OpMemoryModel Physical32 Simple" + " OpEntryPoint Vertex %func \"shader\"" + + string(kVoidFVoid), AddressesDependencies()), +make_pair(" OpCapability Shader" + " OpMemoryModel Physical32 GLSL450" + " OpEntryPoint Vertex %func \"shader\"" + + string(kVoidFVoid), AddressesDependencies()), +make_pair(" OpCapability Kernel" + " OpMemoryModel Physical32 OpenCL" + " OpEntryPoint Kernel %func \"compute\"" + + string(kVoidFVoid), AddressesDependencies()), +make_pair(" OpCapability Shader" + " OpMemoryModel Physical64 Simple" + " OpEntryPoint Vertex %func \"shader\"" + + string(kVoidFVoid), AddressesDependencies()), +make_pair(" OpCapability Shader" + " OpMemoryModel Physical64 GLSL450" + " OpEntryPoint Vertex %func \"shader\"" + + string(kVoidFVoid), AddressesDependencies()), +make_pair(" OpCapability Kernel" + " OpMemoryModel Physical64 OpenCL" + " OpEntryPoint Kernel %func \"compute\"" + + string(kVoidFVoid), AddressesDependencies()) +)),); + +INSTANTIATE_TEST_CASE_P(ExecutionMode, ValidateCapability, + Combine( + ValuesIn(AllCapabilities()), + Values( +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Geometry %func \"shader\" " + "OpExecutionMode %func Invocations 42" + + string(kVoidFVoid), GeometryDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint TessellationControl %func \"shader\" " + "OpExecutionMode %func SpacingEqual" + + string(kVoidFVoid), TessellationDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint TessellationControl %func \"shader\" " + "OpExecutionMode %func SpacingFractionalEven" + + string(kVoidFVoid), TessellationDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint TessellationControl %func \"shader\" " + "OpExecutionMode %func SpacingFractionalOdd" + + string(kVoidFVoid), TessellationDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint TessellationControl %func \"shader\" " + "OpExecutionMode %func VertexOrderCw" + + string(kVoidFVoid), TessellationDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint TessellationControl %func \"shader\" " + "OpExecutionMode %func VertexOrderCcw" + + string(kVoidFVoid), TessellationDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Vertex %func \"shader\" " + "OpExecutionMode %func PixelCenterInteger" + + string(kVoidFVoid), ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Vertex %func \"shader\" " + "OpExecutionMode %func OriginUpperLeft" + + string(kVoidFVoid), ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Vertex %func \"shader\" " + "OpExecutionMode %func OriginLowerLeft" + + string(kVoidFVoid), ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Vertex %func \"shader\" " + "OpExecutionMode %func EarlyFragmentTests" + + string(kVoidFVoid), ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint TessellationControl %func \"shader\" " + "OpExecutionMode %func PointMode" + + string(kVoidFVoid), TessellationDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Vertex %func \"shader\" " + "OpExecutionMode %func Xfb" + + string(kVoidFVoid), vector{"TransformFeedback"}), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Vertex %func \"shader\" " + "OpExecutionMode %func DepthReplacing" + + string(kVoidFVoid), ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Vertex %func \"shader\" " + "OpExecutionMode %func DepthGreater" + + string(kVoidFVoid), ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Vertex %func \"shader\" " + "OpExecutionMode %func DepthLess" + + string(kVoidFVoid), ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Vertex %func \"shader\" " + "OpExecutionMode %func DepthUnchanged" + + string(kVoidFVoid), ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"shader\" " + "OpExecutionMode %func LocalSize 42 42 42" + + string(kVoidFVoid), AllCapabilities()), +make_pair(string(kGLSL450MemoryModel) + + "OpEntryPoint Kernel %func \"shader\" " + "OpExecutionMode %func LocalSizeHint 42 42 42" + + string(kVoidFVoid), KernelDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Geometry %func \"shader\" " + "OpExecutionMode %func InputPoints" + + string(kVoidFVoid), GeometryDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Geometry %func \"shader\" " + "OpExecutionMode %func InputLines" + + string(kVoidFVoid), GeometryDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Geometry %func \"shader\" " + "OpExecutionMode %func InputLinesAdjacency" + + string(kVoidFVoid), GeometryDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Geometry %func \"shader\" " + "OpExecutionMode %func Triangles" + + string(kVoidFVoid), GeometryDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint TessellationControl %func \"shader\" " + "OpExecutionMode %func Triangles" + + string(kVoidFVoid), TessellationDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Geometry %func \"shader\" " + "OpExecutionMode %func InputTrianglesAdjacency" + + string(kVoidFVoid), GeometryDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint TessellationControl %func \"shader\" " + "OpExecutionMode %func Quads" + + string(kVoidFVoid), TessellationDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint TessellationControl %func \"shader\" " + "OpExecutionMode %func Isolines" + + string(kVoidFVoid), TessellationDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Geometry %func \"shader\" " + "OpExecutionMode %func OutputVertices 42" + + string(kVoidFVoid), GeometryDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint TessellationControl %func \"shader\" " + "OpExecutionMode %func OutputVertices 42" + + string(kVoidFVoid), TessellationDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Geometry %func \"shader\" " + "OpExecutionMode %func OutputPoints" + + string(kVoidFVoid), GeometryDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Geometry %func \"shader\" " + "OpExecutionMode %func OutputLineStrip" + + string(kVoidFVoid), GeometryDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Geometry %func \"shader\" " + "OpExecutionMode %func OutputTriangleStrip" + + string(kVoidFVoid), GeometryDependencies()), +make_pair(string(kGLSL450MemoryModel) + + "OpEntryPoint Kernel %func \"shader\" " + "OpExecutionMode %func VecTypeHint 2" + + string(kVoidFVoid), KernelDependencies()), +make_pair(string(kGLSL450MemoryModel) + + "OpEntryPoint Kernel %func \"shader\" " + "OpExecutionMode %func ContractionOff" + + string(kVoidFVoid), KernelDependencies()))),); + +// clang-format on + +INSTANTIATE_TEST_CASE_P( + ExecutionModeV11, ValidateCapabilityV11, + Combine(ValuesIn(AllCapabilities()), + Values(make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"shader\" " + "OpExecutionMode %func SubgroupSize 1" + + string(kVoidFVoid), + vector{"SubgroupDispatch"}), + make_pair( + string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"shader\" " + "OpExecutionMode %func SubgroupsPerWorkgroup 65535" + + string(kVoidFVoid), + vector{"SubgroupDispatch"}))), ); +// clang-format off + +INSTANTIATE_TEST_CASE_P(StorageClass, ValidateCapability, + Combine( + ValuesIn(AllCapabilities()), + Values( +make_pair(string(kGLSL450MemoryModel) + + " OpEntryPoint Vertex %func \"shader\"" + + " %intt = OpTypeInt 32 0\n" + " %ptrt = OpTypePointer UniformConstant %intt\n" + " %var = OpVariable %ptrt UniformConstant\n" + string(kVoidFVoid), + AllCapabilities()), +make_pair(string(kOpenCLMemoryModel) + + " OpEntryPoint Kernel %func \"compute\"" + + " %intt = OpTypeInt 32 0\n" + " %ptrt = OpTypePointer Input %intt" + " %var = OpVariable %ptrt Input\n" + string(kVoidFVoid), + AllCapabilities()), +make_pair(string(kOpenCLMemoryModel) + + " OpEntryPoint Vertex %func \"shader\"" + + " %intt = OpTypeInt 32 0\n" + " %ptrt = OpTypePointer Uniform %intt\n" + " %var = OpVariable %ptrt Uniform\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + " OpEntryPoint Vertex %func \"shader\"" + + " %intt = OpTypeInt 32 0\n" + " %ptrt = OpTypePointer Output %intt\n" + " %var = OpVariable %ptrt Output\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kGLSL450MemoryModel) + + " OpEntryPoint Vertex %func \"shader\"" + + " %intt = OpTypeInt 32 0\n" + " %ptrt = OpTypePointer Workgroup %intt\n" + " %var = OpVariable %ptrt Workgroup\n" + string(kVoidFVoid), + AllCapabilities()), +make_pair(string(kGLSL450MemoryModel) + + " OpEntryPoint Vertex %func \"shader\"" + + " %intt = OpTypeInt 32 0\n" + " %ptrt = OpTypePointer CrossWorkgroup %intt\n" + " %var = OpVariable %ptrt CrossWorkgroup\n" + string(kVoidFVoid), + AllCapabilities()), +make_pair(string(kOpenCLMemoryModel) + + " OpEntryPoint Kernel %func \"compute\"" + + " %intt = OpTypeInt 32 0\n" + " %ptrt = OpTypePointer Private %intt\n" + " %var = OpVariable %ptrt Private\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + " OpEntryPoint Kernel %func \"compute\"" + + " %intt = OpTypeInt 32 0\n" + " %ptrt = OpTypePointer PushConstant %intt\n" + " %var = OpVariable %ptrt PushConstant\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kGLSL450MemoryModel) + + " OpEntryPoint Vertex %func \"shader\"" + + " %intt = OpTypeInt 32 0\n" + " %ptrt = OpTypePointer AtomicCounter %intt\n" + " %var = OpVariable %ptrt AtomicCounter\n" + string(kVoidFVoid), + vector{"AtomicStorage"}), +make_pair(string(kGLSL450MemoryModel) + + " OpEntryPoint Vertex %func \"shader\"" + + " %intt = OpTypeInt 32 0\n" + " %ptrt = OpTypePointer Image %intt\n" + " %var = OpVariable %ptrt Image\n" + string(kVoidFVoid), + AllCapabilities()) +)),); + +INSTANTIATE_TEST_CASE_P(Dim, ValidateCapability, + Combine( + ValuesIn(AllCapabilities()), + Values( +make_pair(" OpCapability ImageBasic" + + string(kOpenCLMemoryModel) + + string(" OpEntryPoint Kernel %func \"compute\"") + + " %voidt = OpTypeVoid" + " %imgt = OpTypeImage %voidt 1D 0 0 0 0 Unknown" + string(kVoidFVoid2), + Sampled1DDependencies()), +make_pair(" OpCapability ImageBasic" + + string(kOpenCLMemoryModel) + + string(" OpEntryPoint Kernel %func \"compute\"") + + " %voidt = OpTypeVoid" + " %imgt = OpTypeImage %voidt 2D 0 0 0 0 Unknown" + string(kVoidFVoid2), + AllCapabilities()), +make_pair(" OpCapability ImageBasic" + + string(kOpenCLMemoryModel) + + string(" OpEntryPoint Kernel %func \"compute\"") + + " %voidt = OpTypeVoid" + " %imgt = OpTypeImage %voidt 3D 0 0 0 0 Unknown" + string(kVoidFVoid2), + AllCapabilities()), +make_pair(" OpCapability ImageBasic" + + string(kOpenCLMemoryModel) + + string(" OpEntryPoint Kernel %func \"compute\"") + + " %voidt = OpTypeVoid" + " %imgt = OpTypeImage %voidt Cube 0 0 0 0 Unknown" + string(kVoidFVoid2), + ShaderDependencies()), +make_pair(" OpCapability ImageBasic" + + string(kOpenCLMemoryModel) + + string(" OpEntryPoint Kernel %func \"compute\"") + + " %voidt = OpTypeVoid" + " %imgt = OpTypeImage %voidt Rect 0 0 0 0 Unknown" + string(kVoidFVoid2), + SampledRectDependencies()), +make_pair(" OpCapability ImageBasic" + + string(kOpenCLMemoryModel) + + string(" OpEntryPoint Kernel %func \"compute\"") + + " %voidt = OpTypeVoid" + " %imgt = OpTypeImage %voidt Buffer 0 0 0 0 Unknown" + string(kVoidFVoid2), + SampledBufferDependencies()), +make_pair(" OpCapability ImageBasic" + + string(kOpenCLMemoryModel) + + string(" OpEntryPoint Kernel %func \"compute\"") + + " %voidt = OpTypeVoid" + " %imgt = OpTypeImage %voidt SubpassData 0 0 0 2 Unknown" + string(kVoidFVoid2), + vector{"InputAttachment"}) +)),); + +// NOTE: All Sampler Address Modes require kernel capabilities but the +// OpConstantSampler requires LiteralSampler which depends on Kernel +INSTANTIATE_TEST_CASE_P(SamplerAddressingMode, ValidateCapability, + Combine( + ValuesIn(AllCapabilities()), + Values( +make_pair(string(kGLSL450MemoryModel) + + " OpEntryPoint Vertex %func \"shader\"" + " %samplert = OpTypeSampler" + " %sampler = OpConstantSampler %samplert None 1 Nearest" + + string(kVoidFVoid), + vector{"LiteralSampler"}), +make_pair(string(kGLSL450MemoryModel) + + " OpEntryPoint Vertex %func \"shader\"" + " %samplert = OpTypeSampler" + " %sampler = OpConstantSampler %samplert ClampToEdge 1 Nearest" + + string(kVoidFVoid), + vector{"LiteralSampler"}), +make_pair(string(kGLSL450MemoryModel) + + " OpEntryPoint Vertex %func \"shader\"" + " %samplert = OpTypeSampler" + " %sampler = OpConstantSampler %samplert Clamp 1 Nearest" + + string(kVoidFVoid), + vector{"LiteralSampler"}), +make_pair(string(kGLSL450MemoryModel) + + " OpEntryPoint Vertex %func \"shader\"" + " %samplert = OpTypeSampler" + " %sampler = OpConstantSampler %samplert Repeat 1 Nearest" + + string(kVoidFVoid), + vector{"LiteralSampler"}), +make_pair(string(kGLSL450MemoryModel) + + " OpEntryPoint Vertex %func \"shader\"" + " %samplert = OpTypeSampler" + " %sampler = OpConstantSampler %samplert RepeatMirrored 1 Nearest" + + string(kVoidFVoid), + vector{"LiteralSampler"}) +)),); + +//TODO(umar): Sampler Filter Mode +//TODO(umar): Image Format +//TODO(umar): Image Channel Order +//TODO(umar): Image Channel Data Type +//TODO(umar): Image Operands +//TODO(umar): FP Fast Math Mode +//TODO(umar): FP Rounding Mode +//TODO(umar): Linkage Type +//TODO(umar): Access Qualifier +//TODO(umar): Function Parameter Attribute + +INSTANTIATE_TEST_CASE_P(Decoration, ValidateCapability, + Combine( + ValuesIn(AllCapabilities()), + Values( +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt RelaxedPrecision\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt Block\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt BufferBlock\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt RowMajor\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + MatrixDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt ColMajor\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + MatrixDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt ArrayStride 1\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt MatrixStride 1\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + MatrixDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt GLSLShared\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt GLSLPacked\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kGLSL450MemoryModel) + + "OpEntryPoint Vertex %func \"shader\" \n" + "OpDecorate %intt CPacked\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + KernelDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt NoPerspective\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt Flat\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt Patch\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + TessellationDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt Centroid\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt Sample\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + vector{"SampleRateShading"}), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt Invariant\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt Restrict\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + AllCapabilities()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt Aliased\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + AllCapabilities()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt Volatile\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + AllCapabilities()), +make_pair(string(kGLSL450MemoryModel) + + "OpEntryPoint Vertex %func \"shader\" \n" + "OpDecorate %intt Constant\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + KernelDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt Coherent\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + AllCapabilities()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt NonWritable\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + AllCapabilities()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt NonReadable\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + AllCapabilities()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt Uniform\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kGLSL450MemoryModel) + + "OpEntryPoint Vertex %func \"shader\" \n" + "OpDecorate %intt SaturatedConversion\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + KernelDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt Stream 0\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + vector{"GeometryStreams"}), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt Location 0\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt Component 0\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt Index 0\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt Binding 0\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt DescriptorSet 0\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt Offset 0\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt XfbBuffer 0\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + vector{"TransformFeedback"}), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt XfbStride 0\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + vector{"TransformFeedback"}), +make_pair(string(kGLSL450MemoryModel) + + "OpEntryPoint Vertex %func \"shader\" \n" + "OpDecorate %intt FuncParamAttr Zext\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + KernelDependencies()), +make_pair(string(kGLSL450MemoryModel) + + "OpEntryPoint Vertex %func \"shader\" \n" + "OpDecorate %intt FPFastMathMode Fast\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + KernelDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt LinkageAttributes \"other\" Import\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + vector{"Linkage"}), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt NoContraction\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %intt InputAttachmentIndex 0\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + vector{"InputAttachment"}), +make_pair(string(kGLSL450MemoryModel) + + "OpEntryPoint Vertex %func \"shader\" \n" + "OpDecorate %intt Alignment 4\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + KernelDependencies()) +)),); + +// clang-format on +INSTANTIATE_TEST_CASE_P( + DecorationSpecId, ValidateCapability, + Combine(ValuesIn(AllSpirV10Capabilities()), + Values(make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Vertex %func \"shader\" \n" + + "OpDecorate %1 SpecId 1\n" + "%intt = OpTypeInt 32 0\n" + "%1 = OpSpecConstant %intt 0\n" + + string(kVoidFVoid), + ShaderDependencies()))), ); + +INSTANTIATE_TEST_CASE_P( + DecorationV11, ValidateCapabilityV11, + Combine(ValuesIn(AllCapabilities()), + Values(make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %p MaxByteOffset 0 " + "%i32 = OpTypeInt 32 0 " + "%pi32 = OpTypePointer Workgroup %i32 " + "%p = OpVariable %pi32 Workgroup " + + string(kVoidFVoid), + AddressesDependencies()), + // Trying to test OpDecorate here, but if this fails due to + // incorrect OpMemoryModel validation, that must also be + // fixed. + make_pair(string("OpMemoryModel Logical OpenCL " + "OpEntryPoint Kernel %func \"compute\" \n" + "OpDecorate %1 SpecId 1 " + "%intt = OpTypeInt 32 0 " + "%1 = OpSpecConstant %intt 0") + + string(kVoidFVoid), + KernelDependencies()), + make_pair(string("OpMemoryModel Logical Simple " + "OpEntryPoint Vertex %func \"shader\" \n" + "OpDecorate %1 SpecId 1 " + "%intt = OpTypeInt 32 0 " + "%1 = OpSpecConstant %intt 0") + + string(kVoidFVoid), + ShaderDependencies()))), ); +// clang-format off + +INSTANTIATE_TEST_CASE_P(BuiltIn, ValidateCapability, + Combine( + ValuesIn(AllCapabilities()), + Values( +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn Position\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +// Just mentioning PointSize, ClipDistance, or CullDistance as a BuiltIn does +// not trigger the requirement for the associated capability. +// See https://github.com/KhronosGroup/SPIRV-Tools/issues/365 +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn PointSize\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + AllCapabilities()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn ClipDistance\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + AllCapabilities()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn CullDistance\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + AllCapabilities()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn VertexId\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn InstanceId\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn PrimitiveId\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + GeometryTessellationDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn InvocationId\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + GeometryTessellationDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn Layer\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + GeometryDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn ViewportIndex\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + vector{"MultiViewport"}), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn TessLevelOuter\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + TessellationDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn TessLevelInner\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + TessellationDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn TessCoord\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + TessellationDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn PatchVertices\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + TessellationDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn FragCoord\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn PointCoord\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn FrontFacing\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn SampleId\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + vector{"SampleRateShading"}), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn SamplePosition\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + vector{"SampleRateShading"}), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn SampleMask\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn FragDepth\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn HelperInvocation\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn VertexIndex\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn InstanceIndex\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn NumWorkgroups\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + AllCapabilities()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn WorkgroupSize\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + AllCapabilities()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn WorkgroupId\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + AllCapabilities()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn LocalInvocationId\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + AllCapabilities()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn GlobalInvocationId\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + AllCapabilities()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn LocalInvocationIndex\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + AllCapabilities()), +make_pair(string(kGLSL450MemoryModel) + + "OpEntryPoint Vertex %func \"shader\" \n" + + "OpDecorate %intt BuiltIn WorkDim\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + KernelDependencies()), +make_pair(string(kGLSL450MemoryModel) + + "OpEntryPoint Vertex %func \"shader\" \n" + + "OpDecorate %intt BuiltIn GlobalSize\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + KernelDependencies()), +make_pair(string(kGLSL450MemoryModel) + + "OpEntryPoint Vertex %func \"shader\" \n" + + "OpDecorate %intt BuiltIn EnqueuedWorkgroupSize\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + KernelDependencies()), +make_pair(string(kGLSL450MemoryModel) + + "OpEntryPoint Vertex %func \"shader\" \n" + + "OpDecorate %intt BuiltIn GlobalOffset\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + KernelDependencies()), +make_pair(string(kGLSL450MemoryModel) + + "OpEntryPoint Vertex %func \"shader\" \n" + + "OpDecorate %intt BuiltIn GlobalLinearId\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + KernelDependencies()), +make_pair(string(kGLSL450MemoryModel) + + "OpEntryPoint Vertex %func \"shader\" \n" + + "OpDecorate %intt BuiltIn SubgroupSize\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + KernelDependencies()), +make_pair(string(kGLSL450MemoryModel) + + "OpEntryPoint Vertex %func \"shader\" \n" + + "OpDecorate %intt BuiltIn SubgroupMaxSize\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + KernelDependencies()), +make_pair(string(kGLSL450MemoryModel) + + "OpEntryPoint Vertex %func \"shader\" \n" + + "OpDecorate %intt BuiltIn NumSubgroups\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + KernelDependencies()), +make_pair(string(kGLSL450MemoryModel) + + "OpEntryPoint Vertex %func \"shader\" \n" + + "OpDecorate %intt BuiltIn NumEnqueuedSubgroups\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + KernelDependencies()), +make_pair(string(kGLSL450MemoryModel) + + "OpEntryPoint Vertex %func \"shader\" \n" + + "OpDecorate %intt BuiltIn SubgroupId\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + KernelDependencies()), +make_pair(string(kGLSL450MemoryModel) + + "OpEntryPoint Vertex %func \"shader\" \n" + + "OpDecorate %intt BuiltIn SubgroupLocalInvocationId\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + KernelDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn VertexIndex\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()), +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "OpDecorate %intt BuiltIn InstanceIndex\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + ShaderDependencies()) +)),); + +// Ensure that mere mention of PointSize, ClipDistance, or CullDistance as +// BuiltIns does not trigger the requirement for the associated +// capability. +// See https://github.com/KhronosGroup/SPIRV-Tools/issues/365 +INSTANTIATE_TEST_CASE_P(BuiltIn, ValidateCapabilityVulkan10, + Combine( + // All capabilities to try. + ValuesIn(AllSpirV10Capabilities()), + Values( +make_pair(string(kGLSL450MemoryModel) + + "OpEntryPoint Vertex %func \"shader\" \n" + "OpMemberDecorate %block 0 BuiltIn PointSize\n" + "%f32 = OpTypeFloat 32\n" + "%block = OpTypeStruct %f32\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + // Capabilities which should succeed. + AllVulkan10Capabilities()), +make_pair(string(kGLSL450MemoryModel) + + "OpEntryPoint Vertex %func \"shader\" \n" + "OpMemberDecorate %block 0 BuiltIn ClipDistance\n" + "%f32 = OpTypeFloat 32\n" + "%intt = OpTypeInt 32 0\n" + "%intt_4 = OpConstant %intt 4\n" + "%f32arr4 = OpTypeArray %f32 %intt_4\n" + "%block = OpTypeStruct %f32arr4\n" + string(kVoidFVoid), + AllVulkan10Capabilities()), +make_pair(string(kGLSL450MemoryModel) + + "OpEntryPoint Vertex %func \"shader\" \n" + "OpMemberDecorate %block 0 BuiltIn CullDistance\n" + "%f32 = OpTypeFloat 32\n" + "%intt = OpTypeInt 32 0\n" + "%intt_4 = OpConstant %intt 4\n" + "%f32arr4 = OpTypeArray %f32 %intt_4\n" + "%block = OpTypeStruct %f32arr4\n" + string(kVoidFVoid), + AllVulkan10Capabilities()) +)),); + +INSTANTIATE_TEST_CASE_P(BuiltIn, ValidateCapabilityOpenGL40, + Combine( + // OpenGL 4.0 is based on SPIR-V 1.0 + ValuesIn(AllSpirV10Capabilities()), + Values( +make_pair(string(kGLSL450MemoryModel) + + "OpEntryPoint Vertex %func \"shader\" \n" + + "OpDecorate %intt BuiltIn PointSize\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + AllSpirV10Capabilities()), +make_pair(string(kGLSL450MemoryModel) + + "OpEntryPoint Vertex %func \"shader\" \n" + + "OpDecorate %intt BuiltIn ClipDistance\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + AllSpirV10Capabilities()), +make_pair(string(kGLSL450MemoryModel) + + "OpEntryPoint Vertex %func \"shader\" \n" + + "OpDecorate %intt BuiltIn CullDistance\n" + "%intt = OpTypeInt 32 0\n" + string(kVoidFVoid), + AllSpirV10Capabilities()) +)),); + +// TODO(umar): Selection Control +// TODO(umar): Loop Control +// TODO(umar): Function Control +// TODO(umar): Memory Semantics +// TODO(umar): Memory Access +// TODO(umar): Scope +// TODO(umar): Group Operation +// TODO(umar): Kernel Enqueue Flags +// TODO(umar): Kernel Profiling Flags + +INSTANTIATE_TEST_CASE_P(MatrixOp, ValidateCapability, + Combine( + ValuesIn(AllCapabilities()), + Values( +make_pair(string(kOpenCLMemoryModel) + + "OpEntryPoint Kernel %func \"compute\" \n" + + "%f32 = OpTypeFloat 32\n" + "%vec3 = OpTypeVector %f32 3\n" + "%mat33 = OpTypeMatrix %vec3 3\n" + string(kVoidFVoid), + MatrixDependencies()))),); +// clang-format on + +#if 0 +// TODO(atgoo@github.com) The following test is not valid as it generates +// invalid combinations of images, instructions and image operands. +// +// Creates assembly containing an OpImageFetch instruction using operands for +// the image-operands part. The assembly defines constants %fzero and %izero +// that can be used for operands where IDs are required. The assembly is valid, +// apart from not declaring any capabilities required by the operands. +string ImageOperandsTemplate(const string& operands) { + ostringstream ss; + // clang-format off + ss << R"( +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical OpenCL + +%i32 = OpTypeInt 32 0 +%f32 = OpTypeFloat 32 +%v4i32 = OpTypeVector %i32 4 +%timg = OpTypeImage %i32 2D 0 0 0 0 Unknown +%pimg = OpTypePointer UniformConstant %timg +%tfun = OpTypeFunction %i32 + +%vimg = OpVariable %pimg UniformConstant +%izero = OpConstant %i32 0 +%fzero = OpConstant %f32 0. + +%main = OpFunction %i32 None %tfun +%lbl = OpLabel +%img = OpLoad %timg %vimg +%r1 = OpImageFetch %v4i32 %img %izero )" << operands << R"( +OpReturnValue %izero +OpFunctionEnd +)"; + // clang-format on + return ss.str(); +} + +INSTANTIATE_TEST_CASE_P( + TwoImageOperandsMask, ValidateCapability, + Combine( + ValuesIn(AllCapabilities()), + Values(make_pair(ImageOperandsTemplate("Bias|Lod %fzero %fzero"), + ShaderDependencies()), + make_pair(ImageOperandsTemplate("Lod|Offset %fzero %izero"), + vector{"ImageGatherExtended"}), + make_pair(ImageOperandsTemplate("Sample|MinLod %izero %fzero"), + vector{"MinLod"}), + make_pair(ImageOperandsTemplate("Lod|Sample %fzero %izero"), + AllCapabilities()))), ); +#endif + +// TODO(umar): Instruction capability checks + +// True if capability exists in env. +bool Exists(const std::string& capability, spv_target_env env) { + spv_operand_desc dummy; + return SPV_SUCCESS == libspirv::AssemblyGrammar(ScopedContext(env).context) + .lookupOperand(SPV_OPERAND_TYPE_CAPABILITY, + capability.c_str(), + capability.size(), &dummy); +} + +TEST_P(ValidateCapability, Capability) { + const string capability = Capability(GetParam()); + spv_target_env env = + (capability.empty() || Exists(capability, SPV_ENV_UNIVERSAL_1_0)) + ? SPV_ENV_UNIVERSAL_1_0 + : SPV_ENV_UNIVERSAL_1_1; + const string test_code = MakeAssembly(GetParam()); + CompileSuccessfully(test_code, env); + ASSERT_EQ(ExpectedResult(GetParam()), ValidateInstructions(env)) + << "target env: " << spvTargetEnvDescription(env) << "\ntest code:\n" + << test_code; +} + +TEST_P(ValidateCapabilityV11, Capability) { + const string test_code = MakeAssembly(GetParam()); + CompileSuccessfully(test_code, SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(ExpectedResult(GetParam()), + ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)) + << test_code; +} + +TEST_P(ValidateCapabilityVulkan10, Capability) { + const string test_code = MakeAssembly(GetParam()); + CompileSuccessfully(test_code, SPV_ENV_VULKAN_1_0); + ASSERT_EQ(ExpectedResult(GetParam()), + ValidateInstructions(SPV_ENV_VULKAN_1_0)) + << test_code; +} + +TEST_P(ValidateCapabilityOpenGL40, Capability) { + const string test_code = MakeAssembly(GetParam()); + CompileSuccessfully(test_code, SPV_ENV_OPENGL_4_0); + ASSERT_EQ(ExpectedResult(GetParam()), + ValidateInstructions(SPV_ENV_OPENGL_4_0)) + << test_code; +} + +TEST_F(ValidateCapability, SemanticsIdIsAnIdNotALiteral) { + // From https://github.com/KhronosGroup/SPIRV-Tools/issues/248 + // The validator was interpreting the memory semantics ID number + // as the value to be checked rather than an ID that references + // another value to be checked. + // In this case a raw ID of 64 was mistaken to mean a literal + // semantic value of UniformMemory, which would require the Shader + // capability. + const char str[] = R"( +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical OpenCL + +; %i32 has ID 1 +%i32 = OpTypeInt 32 0 +%tf = OpTypeFunction %i32 +%pi32 = OpTypePointer CrossWorkgroup %i32 +%var = OpVariable %pi32 CrossWorkgroup +%c = OpConstant %i32 100 +%scope = OpConstant %i32 1 ; Device scope + +; Fake an instruction with 64 as the result id. +; !64 = OpConstantNull %i32 +!0x3002e !1 !64 + +%f = OpFunction %i32 None %tf +%l = OpLabel +%result = OpAtomicIAdd %i32 %var %scope !64 %c +OpReturnValue %result +OpFunctionEnd +)"; + + CompileSuccessfully(str); + + // Since we are forcing usage of 64, the "id bound" in the binary header + // must be overwritten so that 64 is considered within bound. + // ID Bound is at index 3 of the binary. Set it to 65. + OverwriteAssembledBinary(3, 65); + + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateCapability, IntSignednessKernelGood) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical OpenCL +%i32 = OpTypeInt 32 0 +)"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateCapability, IntSignednessKernelBad) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical OpenCL +%i32 = OpTypeInt 32 1 +)"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("The Signedness in OpTypeInt must always be 0 when " + "Kernel capability is used.")); +} + +TEST_F(ValidateCapability, IntSignednessShaderGood) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +%u32 = OpTypeInt 32 0 +%i32 = OpTypeInt 32 1 +)"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateCapability, NonVulkan10Capability) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +%u32 = OpTypeInt 32 0 +%i32 = OpTypeInt 32 1 +)"; + CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_ERROR_INVALID_CAPABILITY, + ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Capability Linkage is not allowed by Vulkan 1.0")); +} + +TEST_F(ValidateCapability, Vulkan10EnabledByExtension) { + const std::string spirv = R"( +OpCapability Shader +OpCapability DrawParameters +OpExtension "SPV_KHR_shader_draw_parameters" +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %func "shader" +OpMemberDecorate %block 0 BuiltIn PointSize +%f32 = OpTypeFloat 32 +%block = OpTypeStruct %f32 +)" + string(kVoidFVoid); + + CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); +} + +TEST_F(ValidateCapability, Vulkan10NotEnabledByExtension) { + const std::string spirv = R"( +OpCapability Shader +OpCapability DrawParameters +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %func "shader" +OpDecorate %intt BuiltIn PointSize +%intt = OpTypeInt 32 0 +)" + string(kVoidFVoid); + + CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_ERROR_INVALID_CAPABILITY, + ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Capability DrawParameters is not allowed by Vulkan 1.0")); +} + +TEST_F(ValidateCapability, NonOpenCL12FullCapability) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Addresses +OpCapability Linkage +OpCapability Pipes +OpMemoryModel Physical64 OpenCL +%u32 = OpTypeInt 32 0 +)"; + CompileSuccessfully(spirv, SPV_ENV_OPENCL_1_2); + EXPECT_EQ(SPV_ERROR_INVALID_CAPABILITY, + ValidateInstructions(SPV_ENV_OPENCL_1_2)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Capability Pipes is not allowed by OpenCL 1.2 Full Profile")); +} + +TEST_F(ValidateCapability, OpenCL12FullEnabledByCapability) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Addresses +OpCapability Linkage +OpCapability ImageBasic +OpCapability Sampled1D +OpMemoryModel Physical64 OpenCL +%u32 = OpTypeInt 32 0 +)" + string(kVoidFVoid); + + CompileSuccessfully(spirv, SPV_ENV_OPENCL_1_2); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_OPENCL_1_2)); +} + +TEST_F(ValidateCapability, OpenCL12FullNotEnabledByCapability) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Addresses +OpCapability Linkage +OpCapability Sampled1D +OpMemoryModel Physical64 OpenCL +%u32 = OpTypeInt 32 0 +)" + string(kVoidFVoid); + + CompileSuccessfully(spirv, SPV_ENV_OPENCL_1_2); + EXPECT_EQ(SPV_ERROR_INVALID_CAPABILITY, + ValidateInstructions(SPV_ENV_OPENCL_1_2)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Capability Sampled1D is not allowed by OpenCL 1.2 Full Profile")); +} + +TEST_F(ValidateCapability, NonOpenCL12EmbeddedCapability) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Addresses +OpCapability Linkage +OpCapability Int64 +OpMemoryModel Physical64 OpenCL +%u32 = OpTypeInt 32 0 +)"; + CompileSuccessfully(spirv, SPV_ENV_OPENCL_EMBEDDED_1_2); + EXPECT_EQ(SPV_ERROR_INVALID_CAPABILITY, + ValidateInstructions(SPV_ENV_OPENCL_EMBEDDED_1_2)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Capability Int64 is not allowed by OpenCL 1.2 Embedded Profile")); +} + +TEST_F(ValidateCapability, OpenCL12EmbeddedEnabledByCapability) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Addresses +OpCapability Linkage +OpCapability ImageBasic +OpCapability Sampled1D +OpMemoryModel Physical64 OpenCL +%u32 = OpTypeInt 32 0 +)" + string(kVoidFVoid); + + CompileSuccessfully(spirv, SPV_ENV_OPENCL_EMBEDDED_1_2); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_OPENCL_EMBEDDED_1_2)); +} + +TEST_F(ValidateCapability, OpenCL12EmbeddedNotEnabledByCapability) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Addresses +OpCapability Linkage +OpCapability Sampled1D +OpMemoryModel Physical64 OpenCL +%u32 = OpTypeInt 32 0 +)" + string(kVoidFVoid); + + CompileSuccessfully(spirv, SPV_ENV_OPENCL_EMBEDDED_1_2); + EXPECT_EQ(SPV_ERROR_INVALID_CAPABILITY, + ValidateInstructions(SPV_ENV_OPENCL_EMBEDDED_1_2)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Capability Sampled1D is not allowed by OpenCL 1.2 " + "Embedded Profile")); +} + +TEST_F(ValidateCapability, OpenCL20FullCapability) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Addresses +OpCapability Linkage +OpCapability Pipes +OpMemoryModel Physical64 OpenCL +%u32 = OpTypeInt 32 0 +)"; + CompileSuccessfully(spirv, SPV_ENV_OPENCL_2_0); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_OPENCL_2_0)); +} + +TEST_F(ValidateCapability, NonOpenCL20FullCapability) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Addresses +OpCapability Linkage +OpCapability Matrix +OpMemoryModel Physical64 OpenCL +%u32 = OpTypeInt 32 0 +)"; + CompileSuccessfully(spirv, SPV_ENV_OPENCL_2_0); + EXPECT_EQ(SPV_ERROR_INVALID_CAPABILITY, + ValidateInstructions(SPV_ENV_OPENCL_2_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Capability Matrix is not allowed by OpenCL 2.0/2.1 Full Profile")); +} + +TEST_F(ValidateCapability, OpenCL20FullEnabledByCapability) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Addresses +OpCapability Linkage +OpCapability ImageBasic +OpCapability Sampled1D +OpMemoryModel Physical64 OpenCL +%u32 = OpTypeInt 32 0 +)" + string(kVoidFVoid); + + CompileSuccessfully(spirv, SPV_ENV_OPENCL_2_0); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_OPENCL_2_0)); +} + +TEST_F(ValidateCapability, OpenCL20FullNotEnabledByCapability) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Addresses +OpCapability Linkage +OpCapability Sampled1D +OpMemoryModel Physical64 OpenCL +%u32 = OpTypeInt 32 0 +)" + string(kVoidFVoid); + + CompileSuccessfully(spirv, SPV_ENV_OPENCL_2_0); + EXPECT_EQ(SPV_ERROR_INVALID_CAPABILITY, + ValidateInstructions(SPV_ENV_OPENCL_2_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Capability Sampled1D is not allowed by OpenCL 2.0/2.1 " + "Full Profile")); +} + +TEST_F(ValidateCapability, NonOpenCL20EmbeddedCapability) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Addresses +OpCapability Linkage +OpCapability Int64 +OpMemoryModel Physical64 OpenCL +%u32 = OpTypeInt 32 0 +)"; + CompileSuccessfully(spirv, SPV_ENV_OPENCL_EMBEDDED_2_0); + EXPECT_EQ(SPV_ERROR_INVALID_CAPABILITY, + ValidateInstructions(SPV_ENV_OPENCL_EMBEDDED_2_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Capability Int64 is not allowed by OpenCL 2.0/2.1 " + "Embedded Profile")); +} + +TEST_F(ValidateCapability, OpenCL20EmbeddedEnabledByCapability) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Addresses +OpCapability Linkage +OpCapability ImageBasic +OpCapability Sampled1D +OpMemoryModel Physical64 OpenCL +%u32 = OpTypeInt 32 0 +)" + string(kVoidFVoid); + + CompileSuccessfully(spirv, SPV_ENV_OPENCL_EMBEDDED_2_0); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_OPENCL_EMBEDDED_2_0)); +} + +TEST_F(ValidateCapability, OpenCL20EmbeddedNotEnabledByCapability) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Addresses +OpCapability Linkage +OpCapability Sampled1D +OpMemoryModel Physical64 OpenCL +%u32 = OpTypeInt 32 0 +)" + string(kVoidFVoid); + + CompileSuccessfully(spirv, SPV_ENV_OPENCL_EMBEDDED_2_0); + EXPECT_EQ(SPV_ERROR_INVALID_CAPABILITY, + ValidateInstructions(SPV_ENV_OPENCL_EMBEDDED_2_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Capability Sampled1D is not allowed by OpenCL 2.0/2.1 " + "Embedded Profile")); +} + +TEST_F(ValidateCapability, OpenCL22FullCapability) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Addresses +OpCapability Linkage +OpCapability PipeStorage +OpMemoryModel Physical64 OpenCL +%u32 = OpTypeInt 32 0 +)"; + CompileSuccessfully(spirv, SPV_ENV_OPENCL_2_2); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_OPENCL_2_2)); +} + +TEST_F(ValidateCapability, NonOpenCL22FullCapability) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Addresses +OpCapability Linkage +OpCapability Matrix +OpMemoryModel Physical64 OpenCL +%u32 = OpTypeInt 32 0 +)"; + CompileSuccessfully(spirv, SPV_ENV_OPENCL_2_2); + EXPECT_EQ(SPV_ERROR_INVALID_CAPABILITY, + ValidateInstructions(SPV_ENV_OPENCL_2_2)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Capability Matrix is not allowed by OpenCL 2.2 Full Profile")); +} + +TEST_F(ValidateCapability, OpenCL22FullEnabledByCapability) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Addresses +OpCapability Linkage +OpCapability ImageBasic +OpCapability Sampled1D +OpMemoryModel Physical64 OpenCL +%u32 = OpTypeInt 32 0 +)" + string(kVoidFVoid); + + CompileSuccessfully(spirv, SPV_ENV_OPENCL_2_2); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_OPENCL_2_2)); +} + +TEST_F(ValidateCapability, OpenCL22FullNotEnabledByCapability) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Addresses +OpCapability Linkage +OpCapability Sampled1D +OpMemoryModel Physical64 OpenCL +%u32 = OpTypeInt 32 0 +)" + string(kVoidFVoid); + + CompileSuccessfully(spirv, SPV_ENV_OPENCL_2_2); + EXPECT_EQ(SPV_ERROR_INVALID_CAPABILITY, + ValidateInstructions(SPV_ENV_OPENCL_2_2)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Capability Sampled1D is not allowed by OpenCL 2.2 Full Profile")); +} + +TEST_F(ValidateCapability, NonOpenCL22EmbeddedCapability) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Addresses +OpCapability Linkage +OpCapability Int64 +OpMemoryModel Physical64 OpenCL +%u32 = OpTypeInt 32 0 +)"; + CompileSuccessfully(spirv, SPV_ENV_OPENCL_EMBEDDED_2_2); + EXPECT_EQ(SPV_ERROR_INVALID_CAPABILITY, + ValidateInstructions(SPV_ENV_OPENCL_EMBEDDED_2_2)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Capability Int64 is not allowed by OpenCL 2.2 Embedded Profile")); +} + +TEST_F(ValidateCapability, OpenCL22EmbeddedEnabledByCapability) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Addresses +OpCapability Linkage +OpCapability ImageBasic +OpCapability Sampled1D +OpMemoryModel Physical64 OpenCL +%u32 = OpTypeInt 32 0 +)" + string(kVoidFVoid); + + CompileSuccessfully(spirv, SPV_ENV_OPENCL_EMBEDDED_2_2); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_OPENCL_EMBEDDED_2_2)); +} + +TEST_F(ValidateCapability, OpenCL22EmbeddedNotEnabledByCapability) { + const std::string spirv = R"( +OpCapability Kernel +OpCapability Addresses +OpCapability Linkage +OpCapability Sampled1D +OpMemoryModel Physical64 OpenCL +%u32 = OpTypeInt 32 0 +)" + string(kVoidFVoid); + + CompileSuccessfully(spirv, SPV_ENV_OPENCL_EMBEDDED_2_2); + EXPECT_EQ(SPV_ERROR_INVALID_CAPABILITY, + ValidateInstructions(SPV_ENV_OPENCL_EMBEDDED_2_2)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Capability Sampled1D is not allowed by OpenCL 2.2 " + "Embedded Profile")); +} + +} // namespace diff --git a/3rdparty/spirv-tools/test/val/val_cfg_test.cpp b/3rdparty/spirv-tools/test/val/val_cfg_test.cpp new file mode 100644 index 000000000..78cbe093d --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_cfg_test.cpp @@ -0,0 +1,1404 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Validation tests for Control Flow Graph + +#include +#include +#include +#include +#include +#include +#include + +#include "gmock/gmock.h" + +#include "source/diagnostic.h" +#include "source/validate.h" +#include "test_fixture.h" +#include "unit_spirv.h" +#include "val_fixtures.h" + +using std::array; +using std::make_pair; +using std::pair; +using std::string; +using std::stringstream; +using std::vector; + +using ::testing::HasSubstr; +using ::testing::MatchesRegex; + +using libspirv::BasicBlock; +using libspirv::ValidationState_t; + +using ValidateCFG = spvtest::ValidateBase; +using spvtest::ScopedContext; + +namespace { + +string nameOps() { return ""; } + +template +string nameOps(pair head, Args... names) { + return "OpName %" + head.first + " \"" + head.second + "\"\n" + + nameOps(names...); +} + +template +string nameOps(string head, Args... names) { + return "OpName %" + head + " \"" + head + "\"\n" + nameOps(names...); +} + +/// This class allows the easy creation of complex control flow without writing +/// SPIR-V. This class is used in the test cases below. +class Block { + string label_; + string body_; + SpvOp type_; + vector successors_; + + public: + /// Creates a Block with a given label + /// + /// @param[in]: label the label id of the block + /// @param[in]: type the branch instruciton that ends the block + explicit Block(string label, SpvOp type = SpvOpBranch) + : label_(label), body_(), type_(type), successors_() {} + + /// Sets the instructions which will appear in the body of the block + Block& SetBody(std::string body) { + body_ = body; + return *this; + } + + Block& AppendBody(std::string body) { + body_ += body; + return *this; + } + + /// Converts the block into a SPIR-V string + operator string() { + stringstream out; + out << std::setw(8) << "%" + label_ + " = OpLabel \n"; + if (!body_.empty()) { + out << body_; + } + + switch (type_) { + case SpvOpBranchConditional: + out << "OpBranchConditional %cond "; + for (Block& b : successors_) { + out << "%" + b.label_ + " "; + } + break; + case SpvOpSwitch: { + out << "OpSwitch %one %" + successors_.front().label_; + stringstream ss; + for (size_t i = 1; i < successors_.size(); i++) { + ss << " " << i << " %" << successors_[i].label_; + } + out << ss.str(); + } break; + case SpvOpReturn: + assert(successors_.size() == 0); + out << "OpReturn\n"; + break; + case SpvOpUnreachable: + assert(successors_.size() == 0); + out << "OpUnreachable\n"; + break; + case SpvOpBranch: + assert(successors_.size() == 1); + out << "OpBranch %" + successors_.front().label_; + break; + default: + assert(1 == 0 && "Unhandled"); + } + out << "\n"; + + return out.str(); + } + friend Block& operator>>(Block& curr, vector successors); + friend Block& operator>>(Block& lhs, Block& successor); +}; + +/// Assigns the successors for the Block on the lhs +Block& operator>>(Block& lhs, vector successors) { + if (lhs.type_ == SpvOpBranchConditional) { + assert(successors.size() == 2); + } else if (lhs.type_ == SpvOpSwitch) { + assert(successors.size() > 1); + } + lhs.successors_ = successors; + return lhs; +} + +/// Assigns the successor for the Block on the lhs +Block& operator>>(Block& lhs, Block& successor) { + assert(lhs.type_ == SpvOpBranch); + lhs.successors_.push_back(successor); + return lhs; +} + +const char* header(SpvCapability cap) { + static const char* shader_header = + "OpCapability Shader\n" + "OpCapability Linkage\n" + "OpMemoryModel Logical GLSL450\n"; + + static const char* kernel_header = + "OpCapability Kernel\n" + "OpCapability Linkage\n" + "OpMemoryModel Logical OpenCL\n"; + + return (cap == SpvCapabilityShader) ? shader_header : kernel_header; +} + +const char* types_consts() { + static const char* types = + "%voidt = OpTypeVoid\n" + "%boolt = OpTypeBool\n" + "%intt = OpTypeInt 32 0\n" + "%one = OpConstant %intt 1\n" + "%two = OpConstant %intt 2\n" + "%ptrt = OpTypePointer Function %intt\n" + "%funct = OpTypeFunction %voidt\n"; + + return types; +} + +INSTANTIATE_TEST_CASE_P(StructuredControlFlow, ValidateCFG, + ::testing::Values(SpvCapabilityShader, + SpvCapabilityKernel)); + +TEST_P(ValidateCFG, LoopReachableFromEntryButNeverLeadingToReturn) { + // In this case, the loop is reachable from a node without a predecessor, + // but never reaches a node with a return. + // + // This motivates the need for the pseudo-exit node to have a node + // from a cycle in its predecessors list. Otherwise the validator's + // post-dominance calculation will go into an infinite loop. + // + // For more motivation, see + // https://github.com/KhronosGroup/SPIRV-Tools/issues/279 + string str = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + + OpName %entry "entry" + OpName %loop "loop" + OpName %exit "exit" + +%voidt = OpTypeVoid +%funct = OpTypeFunction %voidt + +%main = OpFunction %voidt None %funct +%entry = OpLabel + OpBranch %loop +%loop = OpLabel + OpLoopMerge %exit %loop None + OpBranch %loop +%exit = OpLabel + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()) << str; +} + +TEST_P(ValidateCFG, LoopUnreachableFromEntryButLeadingToReturn) { + // In this case, the loop is not reachable from a node without a + // predecessor, but eventually reaches a node with a return. + // + // This motivates the need for the pseudo-entry node to have a node + // from a cycle in its successors list. Otherwise the validator's + // dominance calculation will go into an infinite loop. + // + // For more motivation, see + // https://github.com/KhronosGroup/SPIRV-Tools/issues/279 + // Before that fix, we'd have an infinite loop when calculating + // post-dominators. + string str = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + + OpName %entry "entry" + OpName %loop "loop" + OpName %cont "cont" + OpName %exit "exit" + +%voidt = OpTypeVoid +%funct = OpTypeFunction %voidt +%boolt = OpTypeBool +%false = OpConstantFalse %boolt + +%main = OpFunction %voidt None %funct +%entry = OpLabel + OpReturn + +%loop = OpLabel + OpLoopMerge %exit %cont None + OpBranch %cont + +%cont = OpLabel + OpBranchConditional %false %loop %exit + +%exit = OpLabel + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()) + << str << getDiagnosticString(); +} + +TEST_P(ValidateCFG, Simple) { + bool is_shader = GetParam() == SpvCapabilityShader; + Block entry("entry"); + Block loop("loop", SpvOpBranchConditional); + Block cont("cont"); + Block merge("merge", SpvOpReturn); + + entry.SetBody("%cond = OpSLessThan %boolt %one %two\n"); + if (is_shader) { + loop.SetBody("OpLoopMerge %merge %cont None\n"); + } + + string str = + header(GetParam()) + + nameOps("loop", "entry", "cont", "merge", make_pair("func", "Main")) + + types_consts() + "%func = OpFunction %voidt None %funct\n"; + + str += entry >> loop; + str += loop >> vector({cont, merge}); + str += cont >> loop; + str += merge; + str += "OpFunctionEnd\n"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateCFG, Variable) { + Block entry("entry"); + Block cont("cont"); + Block exit("exit", SpvOpReturn); + + entry.SetBody("%var = OpVariable %ptrt Function\n"); + + string str = header(GetParam()) + nameOps(make_pair("func", "Main")) + + types_consts() + " %func = OpFunction %voidt None %funct\n"; + str += entry >> cont; + str += cont >> exit; + str += exit; + str += "OpFunctionEnd\n"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateCFG, VariableNotInFirstBlockBad) { + Block entry("entry"); + Block cont("cont"); + Block exit("exit", SpvOpReturn); + + // This operation should only be performed in the entry block + cont.SetBody("%var = OpVariable %ptrt Function\n"); + + string str = header(GetParam()) + nameOps(make_pair("func", "Main")) + + types_consts() + " %func = OpFunction %voidt None %funct\n"; + + str += entry >> cont; + str += cont >> exit; + str += exit; + str += "OpFunctionEnd\n"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Variables can only be defined in the first block of a function")); +} + +TEST_P(ValidateCFG, BlockSelfLoopIsOk) { + bool is_shader = GetParam() == SpvCapabilityShader; + Block entry("entry"); + Block loop("loop", SpvOpBranchConditional); + Block merge("merge", SpvOpReturn); + + entry.SetBody("%cond = OpSLessThan %boolt %one %two\n"); + if (is_shader) loop.SetBody("OpLoopMerge %merge %loop None\n"); + + string str = header(GetParam()) + + nameOps("loop", "merge", make_pair("func", "Main")) + + types_consts() + "%func = OpFunction %voidt None %funct\n"; + + str += entry >> loop; + // loop branches to itself, but does not trigger an error. + str += loop >> vector({merge, loop}); + str += merge; + str += "OpFunctionEnd\n"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString(); +} + +TEST_P(ValidateCFG, BlockAppearsBeforeDominatorBad) { + bool is_shader = GetParam() == SpvCapabilityShader; + Block entry("entry"); + Block cont("cont"); + Block branch("branch", SpvOpBranchConditional); + Block merge("merge", SpvOpReturn); + + entry.SetBody("%cond = OpSLessThan %boolt %one %two\n"); + if (is_shader) branch.SetBody("OpSelectionMerge %merge None\n"); + + string str = header(GetParam()) + + nameOps("cont", "branch", make_pair("func", "Main")) + + types_consts() + "%func = OpFunction %voidt None %funct\n"; + + str += entry >> branch; + str += cont >> merge; // cont appears before its dominator + str += branch >> vector({cont, merge}); + str += merge; + str += "OpFunctionEnd\n"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + MatchesRegex("Block .\\[cont\\] appears in the binary " + "before its dominator .\\[branch\\]")); +} + +TEST_P(ValidateCFG, MergeBlockTargetedByMultipleHeaderBlocksBad) { + bool is_shader = GetParam() == SpvCapabilityShader; + Block entry("entry"); + Block loop("loop"); + Block selection("selection", SpvOpBranchConditional); + Block merge("merge", SpvOpReturn); + + entry.SetBody("%cond = OpSLessThan %boolt %one %two\n"); + if (is_shader) loop.SetBody(" OpLoopMerge %merge %loop None\n"); + + // cannot share the same merge + if (is_shader) selection.SetBody("OpSelectionMerge %merge None\n"); + + string str = header(GetParam()) + + nameOps("merge", make_pair("func", "Main")) + types_consts() + + "%func = OpFunction %voidt None %funct\n"; + + str += entry >> loop; + str += loop >> selection; + str += selection >> vector({loop, merge}); + str += merge; + str += "OpFunctionEnd\n"; + + CompileSuccessfully(str); + if (is_shader) { + ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + MatchesRegex("Block .\\[merge\\] is already a merge block " + "for another header")); + } else { + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); + } +} + +TEST_P(ValidateCFG, MergeBlockTargetedByMultipleHeaderBlocksSelectionBad) { + bool is_shader = GetParam() == SpvCapabilityShader; + Block entry("entry"); + Block loop("loop", SpvOpBranchConditional); + Block selection("selection", SpvOpBranchConditional); + Block merge("merge", SpvOpReturn); + + entry.SetBody("%cond = OpSLessThan %boolt %one %two\n"); + if (is_shader) selection.SetBody(" OpSelectionMerge %merge None\n"); + + // cannot share the same merge + if (is_shader) loop.SetBody(" OpLoopMerge %merge %loop None\n"); + + string str = header(GetParam()) + + nameOps("merge", make_pair("func", "Main")) + types_consts() + + "%func = OpFunction %voidt None %funct\n"; + + str += entry >> selection; + str += selection >> vector({merge, loop}); + str += loop >> vector({loop, merge}); + str += merge; + str += "OpFunctionEnd\n"; + + CompileSuccessfully(str); + if (is_shader) { + ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + MatchesRegex("Block .\\[merge\\] is already a merge block " + "for another header")); + } else { + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); + } +} + +TEST_P(ValidateCFG, BranchTargetFirstBlockBad) { + Block entry("entry"); + Block bad("bad"); + Block end("end", SpvOpReturn); + string str = header(GetParam()) + + nameOps("entry", "bad", make_pair("func", "Main")) + + types_consts() + "%func = OpFunction %voidt None %funct\n"; + + str += entry >> bad; + str += bad >> entry; // Cannot target entry block + str += end; + str += "OpFunctionEnd\n"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + MatchesRegex("First block .\\[entry\\] of function .\\[Main\\] " + "is targeted by block .\\[bad\\]")); +} + +TEST_P(ValidateCFG, BranchConditionalTrueTargetFirstBlockBad) { + Block entry("entry"); + Block bad("bad", SpvOpBranchConditional); + Block exit("exit", SpvOpReturn); + + entry.SetBody("%cond = OpSLessThan %boolt %one %two\n"); + bad.SetBody(" OpLoopMerge %entry %exit None\n"); + + string str = header(GetParam()) + + nameOps("entry", "bad", make_pair("func", "Main")) + + types_consts() + "%func = OpFunction %voidt None %funct\n"; + + str += entry >> bad; + str += bad >> vector({entry, exit}); // cannot target entry block + str += exit; + str += "OpFunctionEnd\n"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + MatchesRegex("First block .\\[entry\\] of function .\\[Main\\] " + "is targeted by block .\\[bad\\]")); +} + +TEST_P(ValidateCFG, BranchConditionalFalseTargetFirstBlockBad) { + Block entry("entry"); + Block bad("bad", SpvOpBranchConditional); + Block t("t"); + Block merge("merge"); + Block end("end", SpvOpReturn); + + entry.SetBody("%cond = OpSLessThan %boolt %one %two\n"); + bad.SetBody("OpLoopMerge %merge %cont None\n"); + + string str = header(GetParam()) + + nameOps("entry", "bad", make_pair("func", "Main")) + + types_consts() + "%func = OpFunction %voidt None %funct\n"; + + str += entry >> bad; + str += bad >> vector({t, entry}); + str += merge >> end; + str += end; + str += "OpFunctionEnd\n"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + MatchesRegex("First block .\\[entry\\] of function .\\[Main\\] " + "is targeted by block .\\[bad\\]")); +} + +TEST_P(ValidateCFG, SwitchTargetFirstBlockBad) { + Block entry("entry"); + Block bad("bad", SpvOpSwitch); + Block block1("block1"); + Block block2("block2"); + Block block3("block3"); + Block def("def"); // default block + Block merge("merge"); + Block end("end", SpvOpReturn); + + entry.SetBody("%cond = OpSLessThan %boolt %one %two\n"); + bad.SetBody("OpSelectionMerge %merge None\n"); + + string str = header(GetParam()) + + nameOps("entry", "bad", make_pair("func", "Main")) + + types_consts() + "%func = OpFunction %voidt None %funct\n"; + + str += entry >> bad; + str += bad >> vector({def, block1, block2, block3, entry}); + str += def >> merge; + str += block1 >> merge; + str += block2 >> merge; + str += block3 >> merge; + str += merge >> end; + str += end; + str += "OpFunctionEnd\n"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + MatchesRegex("First block .\\[entry\\] of function .\\[Main\\] " + "is targeted by block .\\[bad\\]")); +} + +TEST_P(ValidateCFG, BranchToBlockInOtherFunctionBad) { + Block entry("entry"); + Block middle("middle", SpvOpBranchConditional); + Block end("end", SpvOpReturn); + + entry.SetBody("%cond = OpSLessThan %boolt %one %two\n"); + middle.SetBody("OpSelectionMerge %end None\n"); + + Block entry2("entry2"); + Block middle2("middle2"); + Block end2("end2", SpvOpReturn); + + string str = header(GetParam()) + + nameOps("middle2", make_pair("func", "Main")) + types_consts() + + "%func = OpFunction %voidt None %funct\n"; + + str += entry >> middle; + str += middle >> vector({end, middle2}); + str += end; + str += "OpFunctionEnd\n"; + + str += "%func2 = OpFunction %voidt None %funct\n"; + str += entry2 >> middle2; + str += middle2 >> end2; + str += end2; + str += "OpFunctionEnd\n"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + MatchesRegex("Block\\(s\\) \\{.\\[middle2\\] .\\} are referenced but not " + "defined in function .\\[Main\\]")); +} + +TEST_P(ValidateCFG, HeaderDoesntDominatesMergeBad) { + bool is_shader = GetParam() == SpvCapabilityShader; + Block entry("entry"); + Block head("head", SpvOpBranchConditional); + Block f("f"); + Block merge("merge", SpvOpReturn); + + head.SetBody("%cond = OpSLessThan %boolt %one %two\n"); + + if (is_shader) head.AppendBody("OpSelectionMerge %merge None\n"); + + string str = header(GetParam()) + + nameOps("head", "merge", make_pair("func", "Main")) + + types_consts() + "%func = OpFunction %voidt None %funct\n"; + + str += entry >> merge; + str += head >> vector({merge, f}); + str += f >> merge; + str += merge; + str += "OpFunctionEnd\n"; + + CompileSuccessfully(str); + if (is_shader) { + ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + MatchesRegex("The selection construct with the selection header " + ".\\[head\\] does not dominate the merge block " + ".\\[merge\\]")); + } else { + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); + } +} + +TEST_P(ValidateCFG, HeaderDoesntStrictlyDominateMergeBad) { + // If a merge block is reachable, then it must be strictly dominated by + // its header block. + bool is_shader = GetParam() == SpvCapabilityShader; + Block head("head", SpvOpBranchConditional); + Block exit("exit", SpvOpReturn); + + head.SetBody("%cond = OpSLessThan %boolt %one %two\n"); + + if (is_shader) head.AppendBody("OpSelectionMerge %head None\n"); + + string str = header(GetParam()) + + nameOps("head", "exit", make_pair("func", "Main")) + + types_consts() + "%func = OpFunction %voidt None %funct\n"; + + str += head >> vector({exit, exit}); + str += exit; + str += "OpFunctionEnd\n"; + + CompileSuccessfully(str); + if (is_shader) { + ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + MatchesRegex("The selection construct with the selection header " + ".\\[head\\] does not strictly dominate the merge block " + ".\\[head\\]")); + } else { + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()) << str; + } +} + +TEST_P(ValidateCFG, UnreachableMerge) { + bool is_shader = GetParam() == SpvCapabilityShader; + Block entry("entry"); + Block branch("branch", SpvOpBranchConditional); + Block t("t", SpvOpReturn); + Block f("f", SpvOpReturn); + Block merge("merge", SpvOpReturn); + + entry.SetBody("%cond = OpSLessThan %boolt %one %two\n"); + if (is_shader) branch.AppendBody("OpSelectionMerge %merge None\n"); + + string str = header(GetParam()) + + nameOps("branch", "merge", make_pair("func", "Main")) + + types_consts() + "%func = OpFunction %voidt None %funct\n"; + + str += entry >> branch; + str += branch >> vector({t, f}); + str += t; + str += f; + str += merge; + str += "OpFunctionEnd\n"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateCFG, UnreachableMergeDefinedByOpUnreachable) { + bool is_shader = GetParam() == SpvCapabilityShader; + Block entry("entry"); + Block branch("branch", SpvOpBranchConditional); + Block t("t", SpvOpReturn); + Block f("f", SpvOpReturn); + Block merge("merge", SpvOpUnreachable); + + entry.SetBody("%cond = OpSLessThan %boolt %one %two\n"); + if (is_shader) branch.AppendBody("OpSelectionMerge %merge None\n"); + + string str = header(GetParam()) + + nameOps("branch", "merge", make_pair("func", "Main")) + + types_consts() + "%func = OpFunction %voidt None %funct\n"; + + str += entry >> branch; + str += branch >> vector({t, f}); + str += t; + str += f; + str += merge; + str += "OpFunctionEnd\n"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateCFG, UnreachableBlock) { + Block entry("entry"); + Block unreachable("unreachable"); + Block exit("exit", SpvOpReturn); + + string str = header(GetParam()) + + nameOps("unreachable", "exit", make_pair("func", "Main")) + + types_consts() + "%func = OpFunction %voidt None %funct\n"; + + str += entry >> exit; + str += unreachable >> exit; + str += exit; + str += "OpFunctionEnd\n"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateCFG, UnreachableBranch) { + bool is_shader = GetParam() == SpvCapabilityShader; + Block entry("entry"); + Block unreachable("unreachable", SpvOpBranchConditional); + Block unreachablechildt("unreachablechildt"); + Block unreachablechildf("unreachablechildf"); + Block merge("merge"); + Block exit("exit", SpvOpReturn); + + unreachable.SetBody("%cond = OpSLessThan %boolt %one %two\n"); + if (is_shader) unreachable.AppendBody("OpSelectionMerge %merge None\n"); + string str = header(GetParam()) + + nameOps("unreachable", "exit", make_pair("func", "Main")) + + types_consts() + "%func = OpFunction %voidt None %funct\n"; + + str += entry >> exit; + str += unreachable >> vector({unreachablechildt, unreachablechildf}); + str += unreachablechildt >> merge; + str += unreachablechildf >> merge; + str += merge >> exit; + str += exit; + str += "OpFunctionEnd\n"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateCFG, EmptyFunction) { + string str = header(GetParam()) + string(types_consts()) + + R"(%func = OpFunction %voidt None %funct + %l = OpLabel + OpReturn + OpFunctionEnd)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateCFG, SingleBlockLoop) { + bool is_shader = GetParam() == SpvCapabilityShader; + Block entry("entry"); + Block loop("loop", SpvOpBranchConditional); + Block exit("exit", SpvOpReturn); + + entry.SetBody("%cond = OpSLessThan %boolt %one %two\n"); + if (is_shader) loop.AppendBody("OpLoopMerge %exit %loop None\n"); + + string str = header(GetParam()) + string(types_consts()) + + "%func = OpFunction %voidt None %funct\n"; + + str += entry >> loop; + str += loop >> vector({loop, exit}); + str += exit; + str += "OpFunctionEnd"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateCFG, NestedLoops) { + bool is_shader = GetParam() == SpvCapabilityShader; + Block entry("entry"); + Block loop1("loop1"); + Block loop1_cont_break_block("loop1_cont_break_block", + SpvOpBranchConditional); + Block loop2("loop2", SpvOpBranchConditional); + Block loop2_merge("loop2_merge"); + Block loop1_merge("loop1_merge"); + Block exit("exit", SpvOpReturn); + + entry.SetBody("%cond = OpSLessThan %boolt %one %two\n"); + if (is_shader) { + loop1.SetBody("OpLoopMerge %loop1_merge %loop2 None\n"); + loop2.SetBody("OpLoopMerge %loop2_merge %loop2 None\n"); + } + + string str = header(GetParam()) + nameOps("loop2", "loop2_merge") + + types_consts() + "%func = OpFunction %voidt None %funct\n"; + + str += entry >> loop1; + str += loop1 >> loop1_cont_break_block; + str += loop1_cont_break_block >> vector({loop1_merge, loop2}); + str += loop2 >> vector({loop2, loop2_merge}); + str += loop2_merge >> loop1; + str += loop1_merge >> exit; + str += exit; + str += "OpFunctionEnd"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateCFG, NestedSelection) { + bool is_shader = GetParam() == SpvCapabilityShader; + Block entry("entry"); + const int N = 256; + vector if_blocks; + vector merge_blocks; + Block inner("inner"); + + entry.SetBody("%cond = OpSLessThan %boolt %one %two\n"); + + if_blocks.emplace_back("if0", SpvOpBranchConditional); + + if (is_shader) if_blocks[0].SetBody("OpSelectionMerge %if_merge0 None\n"); + merge_blocks.emplace_back("if_merge0", SpvOpReturn); + + for (int i = 1; i < N; i++) { + stringstream ss; + ss << i; + if_blocks.emplace_back("if" + ss.str(), SpvOpBranchConditional); + if (is_shader) + if_blocks[i].SetBody("OpSelectionMerge %if_merge" + ss.str() + " None\n"); + merge_blocks.emplace_back("if_merge" + ss.str(), SpvOpBranch); + } + string str = header(GetParam()) + string(types_consts()) + + "%func = OpFunction %voidt None %funct\n"; + + str += entry >> if_blocks[0]; + for (int i = 0; i < N - 1; i++) { + str += if_blocks[i] >> vector({if_blocks[i + 1], merge_blocks[i]}); + } + str += if_blocks.back() >> vector({inner, merge_blocks.back()}); + str += inner >> merge_blocks.back(); + for (int i = N - 1; i > 0; i--) { + str += merge_blocks[i] >> merge_blocks[i - 1]; + } + str += merge_blocks[0]; + str += "OpFunctionEnd"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateCFG, BackEdgeBlockDoesntPostDominateContinueTargetBad) { + bool is_shader = GetParam() == SpvCapabilityShader; + Block entry("entry"); + Block loop1("loop1", SpvOpBranchConditional); + Block loop2("loop2", SpvOpBranchConditional); + Block loop2_merge("loop2_merge", SpvOpBranchConditional); + Block be_block("be_block"); + Block exit("exit", SpvOpReturn); + + entry.SetBody("%cond = OpSLessThan %boolt %one %two\n"); + if (is_shader) { + loop1.SetBody("OpLoopMerge %exit %loop2_merge None\n"); + loop2.SetBody("OpLoopMerge %loop2_merge %loop2 None\n"); + } + + string str = header(GetParam()) + + nameOps("loop1", "loop2", "be_block", "loop2_merge") + + types_consts() + "%func = OpFunction %voidt None %funct\n"; + + str += entry >> loop1; + str += loop1 >> vector({loop2, exit}); + str += loop2 >> vector({loop2, loop2_merge}); + str += loop2_merge >> vector({be_block, exit}); + str += be_block >> loop1; + str += exit; + str += "OpFunctionEnd"; + + CompileSuccessfully(str); + if (GetParam() == SpvCapabilityShader) { + ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + MatchesRegex("The continue construct with the continue target " + ".\\[loop2_merge\\] is not post dominated by the " + "back-edge block .\\[be_block\\]")); + } else { + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); + } +} + +TEST_P(ValidateCFG, BranchingToNonLoopHeaderBlockBad) { + bool is_shader = GetParam() == SpvCapabilityShader; + Block entry("entry"); + Block split("split", SpvOpBranchConditional); + Block t("t"); + Block f("f"); + Block exit("exit", SpvOpReturn); + + entry.SetBody("%cond = OpSLessThan %boolt %one %two\n"); + if (is_shader) split.SetBody("OpSelectionMerge %exit None\n"); + + string str = header(GetParam()) + nameOps("split", "f") + types_consts() + + "%func = OpFunction %voidt None %funct\n"; + + str += entry >> split; + str += split >> vector({t, f}); + str += t >> exit; + str += f >> split; + str += exit; + str += "OpFunctionEnd"; + + CompileSuccessfully(str); + if (is_shader) { + ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + MatchesRegex("Back-edges \\(.\\[f\\] -> .\\[split\\]\\) can only " + "be formed between a block and a loop header.")); + } else { + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); + } +} + +TEST_P(ValidateCFG, BranchingToSameNonLoopHeaderBlockBad) { + bool is_shader = GetParam() == SpvCapabilityShader; + Block entry("entry"); + Block split("split", SpvOpBranchConditional); + Block exit("exit", SpvOpReturn); + + entry.SetBody("%cond = OpSLessThan %boolt %one %two\n"); + if (is_shader) split.SetBody("OpSelectionMerge %exit None\n"); + + string str = header(GetParam()) + nameOps("split") + types_consts() + + "%func = OpFunction %voidt None %funct\n"; + + str += entry >> split; + str += split >> vector({split, exit}); + str += exit; + str += "OpFunctionEnd"; + + CompileSuccessfully(str); + if (is_shader) { + ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + MatchesRegex( + "Back-edges \\(.\\[split\\] -> .\\[split\\]\\) can only be " + "formed between a block and a loop header.")); + } else { + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); + } +} + +TEST_P(ValidateCFG, MultipleBackEdgeBlocksToLoopHeaderBad) { + bool is_shader = GetParam() == SpvCapabilityShader; + Block entry("entry"); + Block loop("loop", SpvOpBranchConditional); + Block back0("back0"); + Block back1("back1"); + Block merge("merge", SpvOpReturn); + + entry.SetBody("%cond = OpSLessThan %boolt %one %two\n"); + if (is_shader) loop.SetBody("OpLoopMerge %merge %back0 None\n"); + + string str = header(GetParam()) + nameOps("loop", "back0", "back1") + + types_consts() + "%func = OpFunction %voidt None %funct\n"; + + str += entry >> loop; + str += loop >> vector({back0, back1}); + str += back0 >> loop; + str += back1 >> loop; + str += merge; + str += "OpFunctionEnd"; + + CompileSuccessfully(str); + if (is_shader) { + ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + MatchesRegex( + "Loop header .\\[loop\\] is targeted by 2 back-edge blocks " + "but the standard requires exactly one")) + << str; + } else { + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); + } +} + +TEST_P(ValidateCFG, ContinueTargetMustBePostDominatedByBackEdge) { + bool is_shader = GetParam() == SpvCapabilityShader; + Block entry("entry"); + Block loop("loop", SpvOpBranchConditional); + Block cheader("cheader", SpvOpBranchConditional); + Block be_block("be_block"); + Block merge("merge", SpvOpReturn); + Block exit("exit", SpvOpReturn); + + entry.SetBody("%cond = OpSLessThan %boolt %one %two\n"); + if (is_shader) loop.SetBody("OpLoopMerge %merge %cheader None\n"); + + string str = header(GetParam()) + nameOps("cheader", "be_block") + + types_consts() + "%func = OpFunction %voidt None %funct\n"; + + str += entry >> loop; + str += loop >> vector({cheader, merge}); + str += cheader >> vector({exit, be_block}); + str += exit; // Branches out of a continue construct + str += be_block >> loop; + str += merge; + str += "OpFunctionEnd"; + + CompileSuccessfully(str); + if (is_shader) { + ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + MatchesRegex("The continue construct with the continue target " + ".\\[cheader\\] is not post dominated by the " + "back-edge block .\\[be_block\\]")); + } else { + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); + } +} + +TEST_P(ValidateCFG, BranchOutOfConstructToMergeBad) { + bool is_shader = GetParam() == SpvCapabilityShader; + Block entry("entry"); + Block loop("loop", SpvOpBranchConditional); + Block cont("cont", SpvOpBranchConditional); + Block merge("merge", SpvOpReturn); + + entry.SetBody("%cond = OpSLessThan %boolt %one %two\n"); + if (is_shader) loop.SetBody("OpLoopMerge %merge %loop None\n"); + + string str = header(GetParam()) + nameOps("cont", "loop") + types_consts() + + "%func = OpFunction %voidt None %funct\n"; + + str += entry >> loop; + str += loop >> vector({cont, merge}); + str += cont >> vector({loop, merge}); + str += merge; + str += "OpFunctionEnd"; + + CompileSuccessfully(str); + if (is_shader) { + ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + MatchesRegex("The continue construct with the continue target " + ".\\[loop\\] is not post dominated by the " + "back-edge block .\\[cont\\]")) + << str; + } else { + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); + } +} + +TEST_P(ValidateCFG, BranchOutOfConstructBad) { + bool is_shader = GetParam() == SpvCapabilityShader; + Block entry("entry"); + Block loop("loop", SpvOpBranchConditional); + Block cont("cont", SpvOpBranchConditional); + Block merge("merge"); + Block exit("exit", SpvOpReturn); + + entry.SetBody("%cond = OpSLessThan %boolt %one %two\n"); + if (is_shader) loop.SetBody("OpLoopMerge %merge %loop None\n"); + + string str = header(GetParam()) + nameOps("cont", "loop") + types_consts() + + "%func = OpFunction %voidt None %funct\n"; + + str += entry >> loop; + str += loop >> vector({cont, merge}); + str += cont >> vector({loop, exit}); + str += merge >> exit; + str += exit; + str += "OpFunctionEnd"; + + CompileSuccessfully(str); + if (is_shader) { + ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + MatchesRegex("The continue construct with the continue target " + ".\\[loop\\] is not post dominated by the " + "back-edge block .\\[cont\\]")); + } else { + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); + } +} + +TEST_F(ValidateCFG, OpSwitchToUnreachableBlock) { + Block entry("entry", SpvOpSwitch); + Block case0("case0"); + Block case1("case1"); + Block case2("case2"); + Block def("default", SpvOpUnreachable); + Block phi("phi", SpvOpReturn); + + string str = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" %id +OpExecutionMode %main LocalSize 1 1 1 +OpSource GLSL 430 +OpName %main "main" +OpDecorate %id BuiltIn GlobalInvocationId +%void = OpTypeVoid +%voidf = OpTypeFunction %void +%u32 = OpTypeInt 32 0 +%f32 = OpTypeFloat 32 +%uvec3 = OpTypeVector %u32 3 +%fvec3 = OpTypeVector %f32 3 +%uvec3ptr = OpTypePointer Input %uvec3 +%id = OpVariable %uvec3ptr Input +%one = OpConstant %u32 1 +%three = OpConstant %u32 3 +%main = OpFunction %void None %voidf +)"; + + entry.SetBody( + "%idval = OpLoad %uvec3 %id\n" + "%x = OpCompositeExtract %u32 %idval 0\n" + "%selector = OpUMod %u32 %x %three\n" + "OpSelectionMerge %phi None\n"); + str += entry >> vector({def, case0, case1, case2}); + str += case1 >> phi; + str += def; + str += phi; + str += case0 >> phi; + str += case2 >> phi; + str += "OpFunctionEnd"; + + CompileSuccessfully(str); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateCFG, LoopWithZeroBackEdgesBad) { + string str = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" + OpName %loop "loop" +%voidt = OpTypeVoid +%funct = OpTypeFunction %voidt +%main = OpFunction %voidt None %funct +%loop = OpLabel + OpLoopMerge %exit %exit None + OpBranch %exit +%exit = OpLabel + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + MatchesRegex("Loop header .\\[loop\\] is targeted by " + "0 back-edge blocks but the standard requires exactly " + "one")); +} + +TEST_F(ValidateCFG, LoopWithBackEdgeFromUnreachableContinueConstructGood) { + string str = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" + OpName %loop "loop" +%voidt = OpTypeVoid +%funct = OpTypeFunction %voidt +%floatt = OpTypeFloat 32 +%boolt = OpTypeBool +%one = OpConstant %floatt 1 +%two = OpConstant %floatt 2 +%main = OpFunction %voidt None %funct +%entry = OpLabel + OpBranch %loop +%loop = OpLabel + OpLoopMerge %exit %cont None + OpBranch %16 +%16 = OpLabel +%cond = OpFOrdLessThan %boolt %one %two + OpBranchConditional %cond %body %exit +%body = OpLabel + OpReturn +%cont = OpLabel ; Reachable only from OpLoopMerge ContinueTarget parameter + OpBranch %loop ; Should be considered a back-edge +%exit = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString(); +} + +TEST_P(ValidateCFG, + NestedConstructWithUnreachableMergeBlockBranchingToOuterMergeBlock) { + // Test for https://github.com/KhronosGroup/SPIRV-Tools/issues/297 + // The nested construct has an unreachable merge block. In the + // augmented CFG that merge block + // we still determine that the + bool is_shader = GetParam() == SpvCapabilityShader; + Block entry("entry", SpvOpBranchConditional); + Block inner_head("inner_head", SpvOpBranchConditional); + Block inner_true("inner_true", SpvOpReturn); + Block inner_false("inner_false", SpvOpReturn); + Block inner_merge("inner_merge"); + Block exit("exit", SpvOpReturn); + + entry.SetBody("%cond = OpSLessThan %boolt %one %two\n"); + if (is_shader) { + entry.AppendBody("OpSelectionMerge %exit None\n"); + inner_head.SetBody("OpSelectionMerge %inner_merge None\n"); + } + + string str = header(GetParam()) + nameOps("entry", "inner_merge", "exit") + + types_consts() + "%func = OpFunction %voidt None %funct\n"; + + str += entry >> vector({inner_head, exit}); + str += inner_head >> vector({inner_true, inner_false}); + str += inner_true; + str += inner_false; + str += inner_merge >> exit; + str += exit; + str += "OpFunctionEnd"; + + CompileSuccessfully(str); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString(); +} + +TEST_P(ValidateCFG, ContinueTargetCanBeMergeBlockForNestedStructureGood) { + // This example is valid. It shows that the validator can't just add + // an edge from the loop head to the continue target. If that edge + // is added, then the "if_merge" block is both the continue target + // for the loop and also the merge block for the nested selection, but + // then it wouldn't be dominated by "if_head", the header block for the + // nested selection. + bool is_shader = GetParam() == SpvCapabilityShader; + Block entry("entry"); + Block loop("loop"); + Block if_head("if_head", SpvOpBranchConditional); + Block if_true("if_true"); + Block if_merge("if_merge", SpvOpBranchConditional); + Block merge("merge", SpvOpReturn); + + entry.SetBody("%cond = OpSLessThan %boolt %one %two\n"); + if (is_shader) { + loop.SetBody("OpLoopMerge %merge %if_merge None\n"); + if_head.SetBody("OpSelectionMerge %if_merge None\n"); + } + + string str = + header(GetParam()) + + nameOps("entry", "loop", "if_head", "if_true", "if_merge", "merge") + + types_consts() + "%func = OpFunction %voidt None %funct\n"; + + str += entry >> loop; + str += loop >> if_head; + str += if_head >> vector({if_true, if_merge}); + str += if_true >> if_merge; + str += if_merge >> vector({loop, merge}); + str += merge; + str += "OpFunctionEnd"; + + CompileSuccessfully(str); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString(); +} + +TEST_P(ValidateCFG, SingleLatchBlockMultipleBranchesToLoopHeader) { + // This test case ensures we allow both branches of a loop latch block + // to go back to the loop header. It still counts as a single back edge. + bool is_shader = GetParam() == SpvCapabilityShader; + Block entry("entry"); + Block loop("loop", SpvOpBranchConditional); + Block latch("latch", SpvOpBranchConditional); + Block merge("merge", SpvOpReturn); + + entry.SetBody("%cond = OpSLessThan %boolt %one %two\n"); + if (is_shader) { + loop.SetBody("OpLoopMerge %merge %latch None\n"); + } + + string str = header(GetParam()) + nameOps("entry", "loop", "latch", "merge") + + types_consts() + "%func = OpFunction %voidt None %funct\n"; + + str += entry >> loop; + str += loop >> vector({latch, merge}); + str += latch >> vector({loop, loop}); // This is the key + str += merge; + str += "OpFunctionEnd"; + + CompileSuccessfully(str); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) + << str << getDiagnosticString(); +} + +TEST_P(ValidateCFG, SingleLatchBlockHeaderContinueTargetIsItselfGood) { + // This test case ensures we don't count a Continue Target from a loop + // header to itself as a self-loop when computing back edges. + // Also, it detects that there is an edge from %latch to the pseudo-exit + // node, rather than from %loop. In particular, it detects that we + // have used the *reverse* textual order of blocks when computing + // predecessor traversal roots. + bool is_shader = GetParam() == SpvCapabilityShader; + Block entry("entry"); + Block loop("loop"); + Block latch("latch"); + Block merge("merge", SpvOpReturn); + + entry.SetBody("%cond = OpSLessThan %boolt %one %two\n"); + if (is_shader) { + loop.SetBody("OpLoopMerge %merge %loop None\n"); + } + + string str = header(GetParam()) + nameOps("entry", "loop", "latch", "merge") + + types_consts() + "%func = OpFunction %voidt None %funct\n"; + + str += entry >> loop; + str += loop >> latch; + str += latch >> loop; + str += merge; + str += "OpFunctionEnd"; + + CompileSuccessfully(str); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) + << str << getDiagnosticString(); +} + +// Unit test to check the case where a basic block is the entry block of 2 +// different constructs. In this case, the basic block is the entry block of a +// continue construct as well as a selection construct. See issue# 517 for more +// details. +TEST_F(ValidateCFG, BasicBlockIsEntryBlockOfTwoConstructsGood) { + std::string spirv = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + %void = OpTypeVoid + %bool = OpTypeBool + %int = OpTypeInt 32 1 + %void_func = OpTypeFunction %void + %int_0 = OpConstant %int 0 + %testfun = OpFunction %void None %void_func + %label_1 = OpLabel + OpBranch %start + %start = OpLabel + %cond = OpSLessThan %bool %int_0 %int_0 + ; + ; Note: In this case, the "target" block is both the entry block of + ; the continue construct of the loop as well as the entry block of + ; the selection construct. + ; + OpLoopMerge %loop_merge %target None + OpBranchConditional %cond %target %loop_merge + %loop_merge = OpLabel + OpReturn + %target = OpLabel + OpSelectionMerge %selection_merge None + OpBranchConditional %cond %do_stuff %do_other_stuff + %do_other_stuff = OpLabel + OpBranch %selection_merge + %selection_merge = OpLabel + OpBranch %start + %do_stuff = OpLabel + OpBranch %selection_merge + OpFunctionEnd + )"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateCFG, OpReturnInNonVoidFunc) { + std::string spirv = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + %int = OpTypeInt 32 1 + %int_func = OpTypeFunction %int + %testfun = OpFunction %int None %int_func + %label_1 = OpLabel + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(spirv); + ASSERT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpReturn can only be called from a function with void return type")); +} + +/// TODO(umar): Switch instructions +/// TODO(umar): Nested CFG constructs +} // namespace diff --git a/3rdparty/spirv-tools/test/val/val_composites_test.cpp b/3rdparty/spirv-tools/test/val/val_composites_test.cpp new file mode 100644 index 000000000..7ed8b172d --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_composites_test.cpp @@ -0,0 +1,1497 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "gmock/gmock.h" +#include "unit_spirv.h" +#include "val_fixtures.h" + +namespace { + +using ::testing::HasSubstr; +using ::testing::Not; + +using ValidateComposites = spvtest::ValidateBase; + +std::string GenerateShaderCode( + const std::string& body, + const std::string& capabilities_and_extensions = "", + const std::string& execution_model = "Fragment") { + std::ostringstream ss; + ss << R"( +OpCapability Shader +OpCapability Float64 +)"; + + ss << capabilities_and_extensions; + ss << "OpMemoryModel Logical GLSL450\n"; + ss << "OpEntryPoint " << execution_model << " %main \"main\"\n"; + + ss << R"( +%void = OpTypeVoid +%func = OpTypeFunction %void +%bool = OpTypeBool +%f32 = OpTypeFloat 32 +%f64 = OpTypeFloat 64 +%u32 = OpTypeInt 32 0 +%s32 = OpTypeInt 32 1 +%f32vec2 = OpTypeVector %f32 2 +%f32vec3 = OpTypeVector %f32 3 +%f32vec4 = OpTypeVector %f32 4 +%f64vec2 = OpTypeVector %f64 2 +%u32vec2 = OpTypeVector %u32 2 +%u32vec4 = OpTypeVector %u32 4 +%f64mat22 = OpTypeMatrix %f64vec2 2 +%f32mat22 = OpTypeMatrix %f32vec2 2 +%f32mat23 = OpTypeMatrix %f32vec2 3 +%f32mat32 = OpTypeMatrix %f32vec3 2 + +%f32_0 = OpConstant %f32 0 +%f32_1 = OpConstant %f32 1 +%f32_2 = OpConstant %f32 2 +%f32_3 = OpConstant %f32 3 +%f32vec2_01 = OpConstantComposite %f32vec2 %f32_0 %f32_1 +%f32vec2_12 = OpConstantComposite %f32vec2 %f32_1 %f32_2 +%f32vec4_0123 = OpConstantComposite %f32vec4 %f32_0 %f32_1 %f32_2 %f32_3 + +%u32_0 = OpConstant %u32 0 +%u32_1 = OpConstant %u32 1 +%u32_2 = OpConstant %u32 2 +%u32_3 = OpConstant %u32 3 + +%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1 +%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3 + +%f32mat22_1212 = OpConstantComposite %f32mat22 %f32vec2_12 %f32vec2_12 +%f32mat23_121212 = OpConstantComposite %f32mat23 %f32vec2_12 %f32vec2_12 %f32vec2_12 + +%f32vec2arr3 = OpTypeArray %f32vec2 %u32_3 +%f32vec2rarr = OpTypeRuntimeArray %f32vec2 + +%f32u32struct = OpTypeStruct %f32 %u32 +%big_struct = OpTypeStruct %f32 %f32vec4 %f32mat23 %f32vec2arr3 %f32vec2rarr %f32u32struct + +%ptr_big_struct = OpTypePointer Uniform %big_struct +%var_big_struct = OpVariable %ptr_big_struct Uniform + +%main = OpFunction %void None %func +%main_entry = OpLabel +)"; + + ss << body; + + ss << R"( +OpReturn +OpFunctionEnd)"; + + return ss.str(); +} + +// Returns header for legacy tests taken from val_id_test.cpp. +std::string GetHeaderForTestsFromValId() { + return R"( +OpCapability Shader +OpCapability Linkage +OpCapability Addresses +OpCapability Pipes +OpCapability LiteralSampler +OpCapability DeviceEnqueue +OpCapability Vector16 +OpCapability Int8 +OpCapability Int16 +OpCapability Int64 +OpCapability Float64 +OpMemoryModel Logical GLSL450 +%void = OpTypeVoid +%void_f = OpTypeFunction %void +%int = OpTypeInt 32 0 +%float = OpTypeFloat 32 +%v3float = OpTypeVector %float 3 +%mat4x3 = OpTypeMatrix %v3float 4 +%_ptr_Private_mat4x3 = OpTypePointer Private %mat4x3 +%_ptr_Private_float = OpTypePointer Private %float +%my_matrix = OpVariable %_ptr_Private_mat4x3 Private +%my_float_var = OpVariable %_ptr_Private_float Private +%_ptr_Function_float = OpTypePointer Function %float +%int_0 = OpConstant %int 0 +%int_1 = OpConstant %int 1 +%int_2 = OpConstant %int 2 +%int_3 = OpConstant %int 3 +%int_5 = OpConstant %int 5 + +; Making the following nested structures. +; +; struct S { +; bool b; +; vec4 v[5]; +; int i; +; mat4x3 m[5]; +; } +; uniform blockName { +; S s; +; bool cond; +; RunTimeArray arr; +; } + +%f32arr = OpTypeRuntimeArray %float +%bool = OpTypeBool +%v4float = OpTypeVector %float 4 +%array5_mat4x3 = OpTypeArray %mat4x3 %int_5 +%array5_vec4 = OpTypeArray %v4float %int_5 +%_ptr_Uniform_float = OpTypePointer Uniform %float +%_ptr_Function_vec4 = OpTypePointer Function %v4float +%_ptr_Uniform_vec4 = OpTypePointer Uniform %v4float +%struct_s = OpTypeStruct %bool %array5_vec4 %int %array5_mat4x3 +%struct_blockName = OpTypeStruct %struct_s %bool %f32arr +%_ptr_Uniform_blockName = OpTypePointer Uniform %struct_blockName +%_ptr_Uniform_struct_s = OpTypePointer Uniform %struct_s +%_ptr_Uniform_array5_mat4x3 = OpTypePointer Uniform %array5_mat4x3 +%_ptr_Uniform_mat4x3 = OpTypePointer Uniform %mat4x3 +%_ptr_Uniform_v3float = OpTypePointer Uniform %v3float +%blockName_var = OpVariable %_ptr_Uniform_blockName Uniform +%spec_int = OpSpecConstant %int 2 +%func = OpFunction %void None %void_f +%my_label = OpLabel +)"; +} + +TEST_F(ValidateComposites, VectorExtractDynamicSuccess) { + const std::string body = R"( +%val1 = OpVectorExtractDynamic %f32 %f32vec4_0123 %u32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateComposites, VectorExtractDynamicWrongResultType) { + const std::string body = R"( +%val1 = OpVectorExtractDynamic %f32vec4 %f32vec4_0123 %u32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("VectorExtractDynamic: " + "expected Result Type to be a scalar type")); +} + +TEST_F(ValidateComposites, VectorExtractDynamicNotVector) { + const std::string body = R"( +%val1 = OpVectorExtractDynamic %f32 %f32mat22_1212 %u32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("VectorExtractDynamic: " + "expected Vector type to be OpTypeVector")); +} + +TEST_F(ValidateComposites, VectorExtractDynamicWrongVectorComponent) { + const std::string body = R"( +%val1 = OpVectorExtractDynamic %f32 %u32vec4_0123 %u32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("VectorExtractDynamic: " + "expected Vector component type to be equal to Result Type")); +} + +TEST_F(ValidateComposites, VectorExtractDynamicWrongIndexType) { + const std::string body = R"( +%val1 = OpVectorExtractDynamic %f32 %f32vec4_0123 %f32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("VectorExtractDynamic: " + "expected Index to be int scalar")); +} + +TEST_F(ValidateComposites, VectorInsertDynamicSuccess) { + const std::string body = R"( +%val1 = OpVectorInsertDynamic %f32vec4 %f32vec4_0123 %f32_1 %u32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateComposites, VectorInsertDynamicWrongResultType) { + const std::string body = R"( +%val1 = OpVectorInsertDynamic %f32 %f32vec4_0123 %f32_1 %u32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("VectorInsertDynamic: " + "expected Result Type to be OpTypeVector")); +} + +TEST_F(ValidateComposites, VectorInsertDynamicNotVector) { + const std::string body = R"( +%val1 = OpVectorInsertDynamic %f32vec4 %f32mat22_1212 %f32_1 %u32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("VectorInsertDynamic: " + "expected Vector type to be equal to Result Type")); +} + +TEST_F(ValidateComposites, VectorInsertDynamicWrongComponentType) { + const std::string body = R"( +%val1 = OpVectorInsertDynamic %f32vec4 %f32vec4_0123 %u32_1 %u32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("VectorInsertDynamic: " + "expected Component type to be equal to Result Type " + "component type")); +} + +TEST_F(ValidateComposites, VectorInsertDynamicWrongIndexType) { + const std::string body = R"( +%val1 = OpVectorInsertDynamic %f32vec4 %f32vec4_0123 %f32_1 %f32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("VectorInsertDynamic: " + "expected Index to be int scalar")); +} + +TEST_F(ValidateComposites, CompositeConstructNotComposite) { + const std::string body = R"( +%val1 = OpCompositeConstruct %f32 %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("CompositeConstruct: " + "expected Result Type to be a composite type")); +} + +TEST_F(ValidateComposites, CompositeConstructVectorSuccess) { + const std::string body = R"( +%val1 = OpCompositeConstruct %f32vec4 %f32vec2_12 %f32vec2_12 +%val2 = OpCompositeConstruct %f32vec4 %f32vec2_12 %f32_0 %f32_0 +%val3 = OpCompositeConstruct %f32vec4 %f32_0 %f32_0 %f32vec2_12 +%val4 = OpCompositeConstruct %f32vec4 %f32_0 %f32_1 %f32_2 %f32_3 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateComposites, CompositeConstructVectorOnlyOneConstituent) { + const std::string body = R"( +%val1 = OpCompositeConstruct %f32vec4 %f32vec4_0123 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("CompositeConstruct: " + "expected number of constituents to be at least 2")); +} + +TEST_F(ValidateComposites, CompositeConstructVectorWrongConsituent1) { + const std::string body = R"( +%val1 = OpCompositeConstruct %f32vec4 %f32 %f32vec2_12 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("CompositeConstruct: " + "expected Constituents to be scalars or vectors of the same " + "type as Result Type components")); +} + +TEST_F(ValidateComposites, CompositeConstructVectorWrongConsituent2) { + const std::string body = R"( +%val1 = OpCompositeConstruct %f32vec4 %f32vec2_12 %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("CompositeConstruct: " + "expected Constituents to be scalars or vectors of the same " + "type as Result Type components")); +} + +TEST_F(ValidateComposites, CompositeConstructVectorWrongConsituent3) { + const std::string body = R"( +%val1 = OpCompositeConstruct %f32vec4 %f32vec2_12 %u32_0 %f32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("CompositeConstruct: " + "expected Constituents to be scalars or vectors of the same " + "type as Result Type components")); +} + +TEST_F(ValidateComposites, CompositeConstructVectorWrongComponentNumber1) { + const std::string body = R"( +%val1 = OpCompositeConstruct %f32vec4 %f32vec2_12 %f32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("CompositeConstruct: " + "expected total number of given components to be equal to the " + "size of Result Type vector")); +} + +TEST_F(ValidateComposites, CompositeConstructVectorWrongComponentNumber2) { + const std::string body = R"( +%val1 = OpCompositeConstruct %f32vec4 %f32vec2_12 %f32vec2_12 %f32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("CompositeConstruct: " + "expected total number of given components to be equal to the " + "size of Result Type vector")); +} + +TEST_F(ValidateComposites, CompositeConstructMatrixSuccess) { + const std::string body = R"( +%val1 = OpCompositeConstruct %f32mat22 %f32vec2_12 %f32vec2_12 +%val2 = OpCompositeConstruct %f32mat23 %f32vec2_12 %f32vec2_12 %f32vec2_12 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateComposites, CompositeConstructVectorWrongConsituentNumber1) { + const std::string body = R"( +%val1 = OpCompositeConstruct %f32mat22 %f32vec2_12 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("CompositeConstruct: " + "expected total number of Constituents to be equal to the " + "number of columns of Result Type matrix")); +} + +TEST_F(ValidateComposites, CompositeConstructVectorWrongConsituentNumber2) { + const std::string body = R"( +%val1 = OpCompositeConstruct %f32mat22 %f32vec2_12 %f32vec2_12 %f32vec2_12 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("CompositeConstruct: " + "expected total number of Constituents to be equal to the " + "number of columns of Result Type matrix")); +} + +TEST_F(ValidateComposites, CompositeConstructVectorWrongConsituent) { + const std::string body = R"( +%val1 = OpCompositeConstruct %f32mat22 %f32vec2_12 %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("CompositeConstruct: " + "expected Constituent type to be equal to the column type " + "Result Type matrix")); +} + +TEST_F(ValidateComposites, CompositeConstructArraySuccess) { + const std::string body = R"( +%val1 = OpCompositeConstruct %f32vec2arr3 %f32vec2_12 %f32vec2_12 %f32vec2_12 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateComposites, CompositeConstructArrayWrongConsituentNumber1) { + const std::string body = R"( +%val1 = OpCompositeConstruct %f32vec2arr3 %f32vec2_12 %f32vec2_12 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("CompositeConstruct: " + "expected total number of Constituents to be equal to the " + "number of elements of Result Type array")); +} + +TEST_F(ValidateComposites, CompositeConstructArrayWrongConsituentNumber2) { + const std::string body = R"( +%val1 = OpCompositeConstruct %f32vec2arr3 %f32vec2_12 %f32vec2_12 %f32vec2_12 %f32vec2_12 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("CompositeConstruct: " + "expected total number of Constituents to be equal to the " + "number of elements of Result Type array")); +} + +TEST_F(ValidateComposites, CompositeConstructArrayWrongConsituent) { + const std::string body = R"( +%val1 = OpCompositeConstruct %f32vec2arr3 %f32vec2_12 %u32vec2_01 %f32vec2_12 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("CompositeConstruct: " + "expected Constituent type to be equal to the column type " + "Result Type array")); +} + +TEST_F(ValidateComposites, CompositeConstructStructSuccess) { + const std::string body = R"( +%val1 = OpCompositeConstruct %f32u32struct %f32_0 %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateComposites, CompositeConstructStructWrongConstituentNumber1) { + const std::string body = R"( +%val1 = OpCompositeConstruct %f32u32struct %f32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("CompositeConstruct: " + "expected total number of Constituents to be equal to the " + "number of members of Result Type struct")); +} + +TEST_F(ValidateComposites, CompositeConstructStructWrongConstituentNumber2) { + const std::string body = R"( +%val1 = OpCompositeConstruct %f32u32struct %f32_0 %u32_1 %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("CompositeConstruct: " + "expected total number of Constituents to be equal to the " + "number of members of Result Type struct")); +} + +TEST_F(ValidateComposites, CompositeConstructStructWrongConstituent) { + const std::string body = R"( +%val1 = OpCompositeConstruct %f32u32struct %f32_0 %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("CompositeConstruct: " + "expected Constituent type to be equal to the " + "corresponding member type of Result Type struct")); +} + +TEST_F(ValidateComposites, CopyObjectSuccess) { + const std::string body = R"( +%val1 = OpCopyObject %f32 %f32_0 +%val2 = OpCopyObject %f32vec4 %f32vec4_0123 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateComposites, CopyObjectResultTypeNotType) { + const std::string body = R"( +%val1 = OpCopyObject %f32_0 %f32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("CopyObject: expected Result Type to be a type")); +} + +TEST_F(ValidateComposites, CopyObjectWrongOperandType) { + const std::string body = R"( +%val1 = OpCopyObject %f32 %u32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("CopyObject: " + "expected Result Type and Operand type to be the same")); +} + +TEST_F(ValidateComposites, TransposeSuccess) { + const std::string body = R"( +%val1 = OpTranspose %f32mat32 %f32mat23_121212 +%val2 = OpTranspose %f32mat22 %f32mat22_1212 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateComposites, TransposeResultTypeNotMatrix) { + const std::string body = R"( +%val1 = OpTranspose %f32vec4 %f32mat22_1212 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Transpose: expected Result Type to be a matrix type")); +} + +TEST_F(ValidateComposites, TransposeDifferentComponentTypes) { + const std::string body = R"( +%val1 = OpTranspose %f64mat22 %f32mat22_1212 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Transpose: " + "expected component types of Matrix and Result Type to be " + "identical")); +} + +TEST_F(ValidateComposites, TransposeIncompatibleDimensions1) { + const std::string body = R"( +%val1 = OpTranspose %f32mat23 %f32mat22_1212 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Transpose: expected number of columns and the column size " + "of Matrix to be the reverse of those of Result Type")); +} + +TEST_F(ValidateComposites, TransposeIncompatibleDimensions2) { + const std::string body = R"( +%val1 = OpTranspose %f32mat32 %f32mat22_1212 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Transpose: expected number of columns and the column size " + "of Matrix to be the reverse of those of Result Type")); +} + +TEST_F(ValidateComposites, TransposeIncompatibleDimensions3) { + const std::string body = R"( +%val1 = OpTranspose %f32mat23 %f32mat23_121212 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Transpose: expected number of columns and the column size " + "of Matrix to be the reverse of those of Result Type")); +} + +TEST_F(ValidateComposites, CompositeExtractSuccess) { + const std::string body = R"( +%val1 = OpCompositeExtract %f32 %f32vec4_0123 1 +%val2 = OpCompositeExtract %u32 %u32vec4_0123 0 +%val3 = OpCompositeExtract %f32 %f32mat22_1212 0 1 +%val4 = OpCompositeExtract %f32vec2 %f32mat22_1212 0 +%array = OpCompositeConstruct %f32vec2arr3 %f32vec2_12 %f32vec2_12 %f32vec2_12 +%val5 = OpCompositeExtract %f32vec2 %array 2 +%val6 = OpCompositeExtract %f32 %array 2 1 +%struct = OpLoad %big_struct %var_big_struct +%val7 = OpCompositeExtract %f32 %struct 0 +%val8 = OpCompositeExtract %f32vec4 %struct 1 +%val9 = OpCompositeExtract %f32 %struct 1 2 +%val10 = OpCompositeExtract %f32mat23 %struct 2 +%val11 = OpCompositeExtract %f32vec2 %struct 2 2 +%val12 = OpCompositeExtract %f32 %struct 2 2 1 +%val13 = OpCompositeExtract %f32vec2 %struct 3 2 +%val14 = OpCompositeExtract %f32 %struct 3 2 1 +%val15 = OpCompositeExtract %f32vec2 %struct 4 100 +%val16 = OpCompositeExtract %f32 %struct 4 1000 1 +%val17 = OpCompositeExtract %f32 %struct 5 0 +%val18 = OpCompositeExtract %u32 %struct 5 1 +%val19 = OpCompositeExtract %big_struct %struct +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateComposites, CompositeExtractNotObject) { + const std::string body = R"( +%val1 = OpCompositeExtract %f32 %f32vec4 1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("CompositeExtract: expected Composite to be an object " + "of composite type")); +} + +TEST_F(ValidateComposites, CompositeExtractNotComposite) { + const std::string body = R"( +%val1 = OpCompositeExtract %f32 %f32_1 0 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpCompositeExtract reached non-composite type while " + "indexes still remain to be traversed.")); +} + +TEST_F(ValidateComposites, CompositeExtractVectorOutOfBounds) { + const std::string body = R"( +%val1 = OpCompositeExtract %f32 %f32vec4_0123 4 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("CompositeExtract: vector access is out of bounds, " + "vector size is 4, but access index is 4")); +} + +TEST_F(ValidateComposites, CompositeExtractMatrixOutOfCols) { + const std::string body = R"( +%val1 = OpCompositeExtract %f32 %f32mat23_121212 3 1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("CompositeExtract: matrix access is out of bounds, " + "matrix has 3 columns, but access index is 3")); +} + +TEST_F(ValidateComposites, CompositeExtractMatrixOutOfRows) { + const std::string body = R"( +%val1 = OpCompositeExtract %f32 %f32mat23_121212 2 5 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("CompositeExtract: vector access is out of bounds, " + "vector size is 2, but access index is 5")); +} + +TEST_F(ValidateComposites, CompositeExtractArrayOutOfBounds) { + const std::string body = R"( +%array = OpCompositeConstruct %f32vec2arr3 %f32vec2_12 %f32vec2_12 %f32vec2_12 +%val1 = OpCompositeExtract %f32vec2 %array 3 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("CompositeExtract: array access is out of bounds, " + "array size is 3, but access index is 3")); +} + +TEST_F(ValidateComposites, CompositeExtractStructOutOfBounds) { + const std::string body = R"( +%struct = OpLoad %big_struct %var_big_struct +%val1 = OpCompositeExtract %f32 %struct 6 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Index is out of bounds: OpCompositeExtract can not " + "find index 6 into the structure '37'. This " + "structure has 6 members. Largest valid index is 5.")); +} + +TEST_F(ValidateComposites, CompositeExtractNestedVectorOutOfBounds) { + const std::string body = R"( +%struct = OpLoad %big_struct %var_big_struct +%val1 = OpCompositeExtract %f32 %struct 3 1 5 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("CompositeExtract: vector access is out of bounds, " + "vector size is 2, but access index is 5")); +} + +TEST_F(ValidateComposites, CompositeExtractTooManyIndices) { + const std::string body = R"( +%struct = OpLoad %big_struct %var_big_struct +%val1 = OpCompositeExtract %f32 %struct 3 1 1 2 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpCompositeExtract reached non-composite type while " + "indexes still remain to be traversed.")); +} + +TEST_F(ValidateComposites, CompositeExtractWrongType1) { + const std::string body = R"( +%struct = OpLoad %big_struct %var_big_struct +%val1 = OpCompositeExtract %f32vec2 %struct 3 1 1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpCompositeExtract result type (OpTypeVector) does not match the " + "type that results from indexing into the composite (OpTypeFloat).")); +} + +TEST_F(ValidateComposites, CompositeExtractWrongType2) { + const std::string body = R"( +%struct = OpLoad %big_struct %var_big_struct +%val1 = OpCompositeExtract %f32 %struct 3 1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpCompositeExtract result type (OpTypeFloat) does not " + "match the type that results from indexing into the " + "composite (OpTypeVector).")); +} + +TEST_F(ValidateComposites, CompositeExtractWrongType3) { + const std::string body = R"( +%struct = OpLoad %big_struct %var_big_struct +%val1 = OpCompositeExtract %f32 %struct 2 1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpCompositeExtract result type (OpTypeFloat) does not " + "match the type that results from indexing into the " + "composite (OpTypeVector).")); +} + +TEST_F(ValidateComposites, CompositeExtractWrongType4) { + const std::string body = R"( +%struct = OpLoad %big_struct %var_big_struct +%val1 = OpCompositeExtract %f32 %struct 4 1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpCompositeExtract result type (OpTypeFloat) does not " + "match the type that results from indexing into the " + "composite (OpTypeVector).")); +} + +TEST_F(ValidateComposites, CompositeExtractWrongType5) { + const std::string body = R"( +%struct = OpLoad %big_struct %var_big_struct +%val1 = OpCompositeExtract %f32 %struct 5 1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpCompositeExtract result type (OpTypeFloat) does not match the " + "type that results from indexing into the composite (OpTypeInt).")); +} + +TEST_F(ValidateComposites, CompositeInsertSuccess) { + const std::string body = R"( +%val1 = OpCompositeInsert %f32vec4 %f32_1 %f32vec4_0123 0 +%val2 = OpCompositeInsert %u32vec4 %u32_1 %u32vec4_0123 0 +%val3 = OpCompositeInsert %f32mat22 %f32_2 %f32mat22_1212 0 1 +%val4 = OpCompositeInsert %f32mat22 %f32vec2_01 %f32mat22_1212 0 +%array = OpCompositeConstruct %f32vec2arr3 %f32vec2_12 %f32vec2_12 %f32vec2_12 +%val5 = OpCompositeInsert %f32vec2arr3 %f32vec2_01 %array 2 +%val6 = OpCompositeInsert %f32vec2arr3 %f32_3 %array 2 1 +%struct = OpLoad %big_struct %var_big_struct +%val7 = OpCompositeInsert %big_struct %f32_3 %struct 0 +%val8 = OpCompositeInsert %big_struct %f32vec4_0123 %struct 1 +%val9 = OpCompositeInsert %big_struct %f32_3 %struct 1 2 +%val10 = OpCompositeInsert %big_struct %f32mat23_121212 %struct 2 +%val11 = OpCompositeInsert %big_struct %f32vec2_01 %struct 2 2 +%val12 = OpCompositeInsert %big_struct %f32_3 %struct 2 2 1 +%val13 = OpCompositeInsert %big_struct %f32vec2_01 %struct 3 2 +%val14 = OpCompositeInsert %big_struct %f32_3 %struct 3 2 1 +%val15 = OpCompositeInsert %big_struct %f32vec2_01 %struct 4 100 +%val16 = OpCompositeInsert %big_struct %f32_3 %struct 4 1000 1 +%val17 = OpCompositeInsert %big_struct %f32_3 %struct 5 0 +%val18 = OpCompositeInsert %big_struct %u32_3 %struct 5 1 +%val19 = OpCompositeInsert %big_struct %struct %struct +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateComposites, CompositeInsertResultTypeDifferentFromComposite) { + const std::string body = R"( +%val1 = OpCompositeInsert %f32 %f32_1 %f32vec4_0123 0 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("The Result Type must be the same as Composite type in " + "OpCompositeInsert yielding Result Id 5.")); +} + +TEST_F(ValidateComposites, CompositeInsertNotComposite) { + const std::string body = R"( +%val1 = OpCompositeInsert %f32 %f32_1 %f32_0 0 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpCompositeInsert reached non-composite type while " + "indexes still remain to be traversed.")); +} + +TEST_F(ValidateComposites, CompositeInsertVectorOutOfBounds) { + const std::string body = R"( +%val1 = OpCompositeInsert %f32vec4 %f32_1 %f32vec4_0123 4 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("CompositeInsert: vector access is out of bounds, " + "vector size is 4, but access index is 4")); +} + +TEST_F(ValidateComposites, CompositeInsertMatrixOutOfCols) { + const std::string body = R"( +%val1 = OpCompositeInsert %f32mat23 %f32_1 %f32mat23_121212 3 1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("CompositeInsert: matrix access is out of bounds, " + "matrix has 3 columns, but access index is 3")); +} + +TEST_F(ValidateComposites, CompositeInsertMatrixOutOfRows) { + const std::string body = R"( +%val1 = OpCompositeInsert %f32mat23 %f32_1 %f32mat23_121212 2 5 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("CompositeInsert: vector access is out of bounds, " + "vector size is 2, but access index is 5")); +} + +TEST_F(ValidateComposites, CompositeInsertArrayOutOfBounds) { + const std::string body = R"( +%array = OpCompositeConstruct %f32vec2arr3 %f32vec2_12 %f32vec2_12 %f32vec2_12 +%val1 = OpCompositeInsert %f32vec2arr3 %f32vec2_01 %array 3 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("CompositeInsert: array access is out of bounds, array " + "size is 3, but access index is 3")); +} + +TEST_F(ValidateComposites, CompositeInsertStructOutOfBounds) { + const std::string body = R"( +%struct = OpLoad %big_struct %var_big_struct +%val1 = OpCompositeInsert %big_struct %f32_1 %struct 6 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Index is out of bounds: OpCompositeInsert can not " + "find index 6 into the structure '37'. This " + "structure has 6 members. Largest valid index is 5.")); +} + +TEST_F(ValidateComposites, CompositeInsertNestedVectorOutOfBounds) { + const std::string body = R"( +%struct = OpLoad %big_struct %var_big_struct +%val1 = OpCompositeInsert %big_struct %f32_1 %struct 3 1 5 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("CompositeInsert: vector access is out of bounds, " + "vector size is 2, but access index is 5")); +} + +TEST_F(ValidateComposites, CompositeInsertTooManyIndices) { + const std::string body = R"( +%struct = OpLoad %big_struct %var_big_struct +%val1 = OpCompositeInsert %big_struct %f32_1 %struct 3 1 1 2 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpCompositeInsert reached non-composite type while " + "indexes still remain to be traversed.")); +} + +TEST_F(ValidateComposites, CompositeInsertWrongType1) { + const std::string body = R"( +%struct = OpLoad %big_struct %var_big_struct +%val1 = OpCompositeInsert %big_struct %f32vec2_01 %struct 3 1 1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("The Object type (OpTypeVector) in OpCompositeInsert " + "does not match the type that results from indexing " + "into the Composite (OpTypeFloat).")); +} + +TEST_F(ValidateComposites, CompositeInsertWrongType2) { + const std::string body = R"( +%struct = OpLoad %big_struct %var_big_struct +%val1 = OpCompositeInsert %big_struct %f32_1 %struct 3 1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("The Object type (OpTypeFloat) in OpCompositeInsert " + "does not match the type that results from indexing " + "into the Composite (OpTypeVector).")); +} + +TEST_F(ValidateComposites, CompositeInsertWrongType3) { + const std::string body = R"( +%struct = OpLoad %big_struct %var_big_struct +%val1 = OpCompositeInsert %big_struct %f32_1 %struct 2 1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("The Object type (OpTypeFloat) in OpCompositeInsert " + "does not match the type that results from indexing " + "into the Composite (OpTypeVector).")); +} + +TEST_F(ValidateComposites, CompositeInsertWrongType4) { + const std::string body = R"( +%struct = OpLoad %big_struct %var_big_struct +%val1 = OpCompositeInsert %big_struct %f32_1 %struct 4 1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("The Object type (OpTypeFloat) in OpCompositeInsert " + "does not match the type that results from indexing " + "into the Composite (OpTypeVector).")); +} + +TEST_F(ValidateComposites, CompositeInsertWrongType5) { + const std::string body = R"( +%struct = OpLoad %big_struct %var_big_struct +%val1 = OpCompositeInsert %big_struct %f32_1 %struct 5 1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("The Object type (OpTypeFloat) in OpCompositeInsert " + "does not match the type that results from indexing " + "into the Composite (OpTypeInt).")); +} + +// Tests ported from val_id_test.cpp. + +// Valid. Tests both CompositeExtract and CompositeInsert with 255 indexes. +TEST_F(ValidateComposites, CompositeExtractInsertLimitsGood) { + int depth = 255; + std::string header = GetHeaderForTestsFromValId(); + header.erase(header.find("%func")); + std::ostringstream spirv; + spirv << header << std::endl; + + // Build nested structures. Struct 'i' contains struct 'i-1' + spirv << "%s_depth_1 = OpTypeStruct %float\n"; + for (int i = 2; i <= depth; ++i) { + spirv << "%s_depth_" << i << " = OpTypeStruct %s_depth_" << i - 1 << "\n"; + } + + // Define Pointer and Variable to use for CompositeExtract/Insert. + spirv << "%_ptr_Uniform_deep_struct = OpTypePointer Uniform %s_depth_" + << depth << "\n"; + spirv << "%deep_var = OpVariable %_ptr_Uniform_deep_struct Uniform\n"; + + // Function Start + spirv << R"( + %func = OpFunction %void None %void_f + %my_label = OpLabel + )"; + + // OpCompositeExtract/Insert with 'n' indexes (n = depth) + spirv << "%deep = OpLoad %s_depth_" << depth << " %deep_var" << std::endl; + spirv << "%entry = OpCompositeExtract %float %deep"; + for (int i = 0; i < depth; ++i) { + spirv << " 0"; + } + spirv << std::endl; + spirv << "%new_composite = OpCompositeInsert %s_depth_" << depth + << " %entry %deep"; + for (int i = 0; i < depth; ++i) { + spirv << " 0"; + } + spirv << std::endl; + + // Function end + spirv << R"( + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid: 256 indexes passed to OpCompositeExtract. Limit is 255. +TEST_F(ValidateComposites, CompositeExtractArgCountExceededLimitBad) { + std::ostringstream spirv; + spirv << GetHeaderForTestsFromValId() << std::endl; + spirv << "%matrix = OpLoad %mat4x3 %my_matrix" << std::endl; + spirv << "%entry = OpCompositeExtract %float %matrix"; + for (int i = 0; i < 256; ++i) { + spirv << " 0"; + } + spirv << R"( + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("The number of indexes in OpCompositeExtract may not " + "exceed 255. Found 256 indexes.")); +} + +// Invalid: 256 indexes passed to OpCompositeInsert. Limit is 255. +TEST_F(ValidateComposites, CompositeInsertArgCountExceededLimitBad) { + std::ostringstream spirv; + spirv << GetHeaderForTestsFromValId() << std::endl; + spirv << "%matrix = OpLoad %mat4x3 %my_matrix" << std::endl; + spirv << "%new_composite = OpCompositeInsert %mat4x3 %int_0 %matrix"; + for (int i = 0; i < 256; ++i) { + spirv << " 0"; + } + spirv << R"( + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("The number of indexes in OpCompositeInsert may not " + "exceed 255. Found 256 indexes.")); +} + +// Invalid: In OpCompositeInsert, result type must be the same as composite type +TEST_F(ValidateComposites, CompositeInsertWrongResultTypeBad) { + std::ostringstream spirv; + spirv << GetHeaderForTestsFromValId() << std::endl; + spirv << "%matrix = OpLoad %mat4x3 %my_matrix" << std::endl; + spirv << "%float_entry = OpCompositeExtract %float %matrix 0 1" << std::endl; + spirv << "%new_composite = OpCompositeInsert %float %float_entry %matrix 0 1" + << std::endl; + spirv << R"(OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("The Result Type must be the same as Composite type")); +} + +// Valid: No Indexes were passed to OpCompositeExtract, and the Result Type is +// the same as the Base Composite type. +TEST_F(ValidateComposites, CompositeExtractNoIndexesGood) { + std::ostringstream spirv; + spirv << GetHeaderForTestsFromValId() << std::endl; + spirv << "%matrix = OpLoad %mat4x3 %my_matrix" << std::endl; + spirv << "%float_entry = OpCompositeExtract %mat4x3 %matrix" << std::endl; + spirv << R"(OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid: No Indexes were passed to OpCompositeExtract, but the Result Type is +// different from the Base Composite type. +TEST_F(ValidateComposites, CompositeExtractNoIndexesBad) { + std::ostringstream spirv; + spirv << GetHeaderForTestsFromValId() << std::endl; + spirv << "%matrix = OpLoad %mat4x3 %my_matrix" << std::endl; + spirv << "%float_entry = OpCompositeExtract %float %matrix" << std::endl; + spirv << R"(OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpCompositeExtract result type (OpTypeFloat) does not " + "match the type that results from indexing into the " + "composite (OpTypeMatrix).")); +} + +// Valid: No Indexes were passed to OpCompositeInsert, and the type of the +// Object argument matches the Composite type. +TEST_F(ValidateComposites, CompositeInsertMissingIndexesGood) { + std::ostringstream spirv; + spirv << GetHeaderForTestsFromValId() << std::endl; + spirv << "%matrix = OpLoad %mat4x3 %my_matrix" << std::endl; + spirv << "%matrix_2 = OpLoad %mat4x3 %my_matrix" << std::endl; + spirv << "%new_composite = OpCompositeInsert %mat4x3 %matrix_2 %matrix"; + spirv << R"( + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid: No Indexes were passed to OpCompositeInsert, but the type of the +// Object argument does not match the Composite type. +TEST_F(ValidateComposites, CompositeInsertMissingIndexesBad) { + std::ostringstream spirv; + spirv << GetHeaderForTestsFromValId() << std::endl; + spirv << "%matrix = OpLoad %mat4x3 %my_matrix" << std::endl; + spirv << "%new_composite = OpCompositeInsert %mat4x3 %int_0 %matrix"; + spirv << R"( + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("The Object type (OpTypeInt) in OpCompositeInsert does " + "not match the type that results from indexing into " + "the Composite (OpTypeMatrix).")); +} + +// Valid: Tests that we can index into Struct, Array, Matrix, and Vector! +TEST_F(ValidateComposites, CompositeExtractInsertIndexIntoAllTypesGood) { + // indexes that we are passing are: 0, 3, 1, 2, 0 + // 0 will select the struct_s within the base struct (blockName) + // 3 will select the Array that contains 5 matrices + // 1 will select the Matrix that is at index 1 of the array + // 2 will select the column (which is a vector) within the matrix at index 2 + // 0 will select the element at the index 0 of the vector. (which is a float). + std::ostringstream spirv; + spirv << GetHeaderForTestsFromValId() << R"( + %myblock = OpLoad %struct_blockName %blockName_var + %ss = OpCompositeExtract %struct_s %myblock 0 + %sa = OpCompositeExtract %array5_mat4x3 %myblock 0 3 + %sm = OpCompositeExtract %mat4x3 %myblock 0 3 1 + %sc = OpCompositeExtract %v3float %myblock 0 3 1 2 + %fl = OpCompositeExtract %float %myblock 0 3 1 2 0 + ; + ; Now let's insert back at different levels... + ; + %b1 = OpCompositeInsert %struct_blockName %ss %myblock 0 + %b2 = OpCompositeInsert %struct_blockName %sa %myblock 0 3 + %b3 = OpCompositeInsert %struct_blockName %sm %myblock 0 3 1 + %b4 = OpCompositeInsert %struct_blockName %sc %myblock 0 3 1 2 + %b5 = OpCompositeInsert %struct_blockName %fl %myblock 0 3 1 2 0 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid. More indexes are provided than needed for OpCompositeExtract. +TEST_F(ValidateComposites, CompositeExtractReachedScalarBad) { + // indexes that we are passing are: 0, 3, 1, 2, 0 + // 0 will select the struct_s within the base struct (blockName) + // 3 will select the Array that contains 5 matrices + // 1 will select the Matrix that is at index 1 of the array + // 2 will select the column (which is a vector) within the matrix at index 2 + // 0 will select the element at the index 0 of the vector. (which is a float). + std::ostringstream spirv; + spirv << GetHeaderForTestsFromValId() << R"( + %myblock = OpLoad %struct_blockName %blockName_var + %fl = OpCompositeExtract %float %myblock 0 3 1 2 0 1 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpCompositeExtract reached non-composite type while " + "indexes still remain to be traversed.")); +} + +// Invalid. More indexes are provided than needed for OpCompositeInsert. +TEST_F(ValidateComposites, CompositeInsertReachedScalarBad) { + // indexes that we are passing are: 0, 3, 1, 2, 0 + // 0 will select the struct_s within the base struct (blockName) + // 3 will select the Array that contains 5 matrices + // 1 will select the Matrix that is at index 1 of the array + // 2 will select the column (which is a vector) within the matrix at index 2 + // 0 will select the element at the index 0 of the vector. (which is a float). + std::ostringstream spirv; + spirv << GetHeaderForTestsFromValId() << R"( + %myblock = OpLoad %struct_blockName %blockName_var + %fl = OpCompositeExtract %float %myblock 0 3 1 2 0 + %b5 = OpCompositeInsert %struct_blockName %fl %myblock 0 3 1 2 0 1 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpCompositeInsert reached non-composite type while " + "indexes still remain to be traversed.")); +} + +// Invalid. Result type doesn't match the type we get from indexing into +// the composite. +TEST_F(ValidateComposites, + CompositeExtractResultTypeDoesntMatchIndexedTypeBad) { + // indexes that we are passing are: 0, 3, 1, 2, 0 + // 0 will select the struct_s within the base struct (blockName) + // 3 will select the Array that contains 5 matrices + // 1 will select the Matrix that is at index 1 of the array + // 2 will select the column (which is a vector) within the matrix at index 2 + // 0 will select the element at the index 0 of the vector. (which is a float). + std::ostringstream spirv; + spirv << GetHeaderForTestsFromValId() << R"( + %myblock = OpLoad %struct_blockName %blockName_var + %fl = OpCompositeExtract %int %myblock 0 3 1 2 0 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpCompositeExtract result type (OpTypeInt) does not " + "match the type that results from indexing into the " + "composite (OpTypeFloat).")); +} + +// Invalid. Given object type doesn't match the type we get from indexing into +// the composite. +TEST_F(ValidateComposites, CompositeInsertObjectTypeDoesntMatchIndexedTypeBad) { + // indexes that we are passing are: 0, 3, 1, 2, 0 + // 0 will select the struct_s within the base struct (blockName) + // 3 will select the Array that contains 5 matrices + // 1 will select the Matrix that is at index 1 of the array + // 2 will select the column (which is a vector) within the matrix at index 2 + // 0 will select the element at the index 0 of the vector. (which is a float). + // We are trying to insert an integer where we should be inserting a float. + std::ostringstream spirv; + spirv << GetHeaderForTestsFromValId() << R"( + %myblock = OpLoad %struct_blockName %blockName_var + %b5 = OpCompositeInsert %struct_blockName %int_0 %myblock 0 3 1 2 0 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("he Object type (OpTypeInt) in OpCompositeInsert does " + "not match the type that results from indexing into " + "the Composite (OpTypeFloat).")); +} + +// Invalid. Index into a struct is larger than the number of struct members. +TEST_F(ValidateComposites, CompositeExtractStructIndexOutOfBoundBad) { + // struct_blockName has 3 members (index 0,1,2). We'll try to access index 3. + std::ostringstream spirv; + spirv << GetHeaderForTestsFromValId() << R"( + %myblock = OpLoad %struct_blockName %blockName_var + %ss = OpCompositeExtract %struct_s %myblock 3 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Index is out of bounds: OpCompositeExtract can not " + "find index 3 into the structure '26'. This " + "structure has 3 members. Largest valid index is 2.")); +} + +// Invalid. Index into a struct is larger than the number of struct members. +TEST_F(ValidateComposites, CompositeInsertStructIndexOutOfBoundBad) { + // struct_blockName has 3 members (index 0,1,2). We'll try to access index 3. + std::ostringstream spirv; + spirv << GetHeaderForTestsFromValId() << R"( + %myblock = OpLoad %struct_blockName %blockName_var + %ss = OpCompositeExtract %struct_s %myblock 0 + %new_composite = OpCompositeInsert %struct_blockName %ss %myblock 3 + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Index is out of bounds: OpCompositeInsert can not find " + "index 3 into the structure '26'. This structure " + "has 3 members. Largest valid index is 2.")); +} + +// #1403: Ensure that the default spec constant value is not used to check the +// extract index. +TEST_F(ValidateComposites, ExtractFromSpecConstantSizedArray) { + std::string spirv = R"( +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical OpenCL +OpDecorate %spec_const SpecId 1 +%void = OpTypeVoid +%uint = OpTypeInt 32 0 +%spec_const = OpSpecConstant %uint 3 +%uint_array = OpTypeArray %uint %spec_const +%undef = OpUndef %uint_array +%voidf = OpTypeFunction %void +%func = OpFunction %void None %voidf +%1 = OpLabel +%2 = OpCompositeExtract %uint %undef 4 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// #1403: Ensure that spec constant ops do not produce false positives. +TEST_F(ValidateComposites, ExtractFromSpecConstantOpSizedArray) { + std::string spirv = R"( +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical OpenCL +OpDecorate %spec_const SpecId 1 +%void = OpTypeVoid +%uint = OpTypeInt 32 0 +%const = OpConstant %uint 1 +%spec_const = OpSpecConstant %uint 3 +%spec_const_op = OpSpecConstantOp %uint IAdd %spec_const %const +%uint_array = OpTypeArray %uint %spec_const_op +%undef = OpUndef %uint_array +%voidf = OpTypeFunction %void +%func = OpFunction %void None %voidf +%1 = OpLabel +%2 = OpCompositeExtract %uint %undef 4 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// #1403: Ensure that the default spec constant value is not used to check the +// size of the array for a composite construct. This code has limited actual +// value as it is incorrect unless the specialization constant is assigned the +// value of 2, but it is still a valid module. +TEST_F(ValidateComposites, CompositeConstructSpecConstantSizedArray) { + std::string spirv = R"( +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical OpenCL +OpDecorate %spec_const SpecId 1 +%void = OpTypeVoid +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%spec_const = OpSpecConstant %uint 3 +%uint_array = OpTypeArray %uint %spec_const +%voidf = OpTypeFunction %void +%func = OpFunction %void None %voidf +%1 = OpLabel +%2 = OpCompositeConstruct %uint_array %uint_0 %uint_0 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/val/val_conversion_test.cpp b/3rdparty/spirv-tools/test/val/val_conversion_test.cpp new file mode 100644 index 000000000..66a3e68f7 --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_conversion_test.cpp @@ -0,0 +1,1106 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Tests for unique type declaration rules validator. + +#include + +#include "gmock/gmock.h" +#include "unit_spirv.h" +#include "val_fixtures.h" + +namespace { + +using ::testing::HasSubstr; +using ::testing::Not; + +using ValidateConversion = spvtest::ValidateBase; + +std::string GenerateShaderCode( + const std::string& body, + const std::string& capabilities_and_extensions = "") { + const std::string capabilities = + R"( +OpCapability Shader +OpCapability Int64 +OpCapability Float64)"; + + const std::string after_extension_before_body = + R"( +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +%void = OpTypeVoid +%func = OpTypeFunction %void +%bool = OpTypeBool +%f32 = OpTypeFloat 32 +%u32 = OpTypeInt 32 0 +%s32 = OpTypeInt 32 1 +%f64 = OpTypeFloat 64 +%u64 = OpTypeInt 64 0 +%s64 = OpTypeInt 64 1 +%boolvec2 = OpTypeVector %bool 2 +%s32vec2 = OpTypeVector %s32 2 +%u32vec2 = OpTypeVector %u32 2 +%u64vec2 = OpTypeVector %u64 2 +%f32vec2 = OpTypeVector %f32 2 +%f64vec2 = OpTypeVector %f64 2 +%boolvec3 = OpTypeVector %bool 3 +%u32vec3 = OpTypeVector %u32 3 +%u64vec3 = OpTypeVector %u64 3 +%s32vec3 = OpTypeVector %s32 3 +%f32vec3 = OpTypeVector %f32 3 +%f64vec3 = OpTypeVector %f64 3 +%boolvec4 = OpTypeVector %bool 4 +%u32vec4 = OpTypeVector %u32 4 +%u64vec4 = OpTypeVector %u64 4 +%s32vec4 = OpTypeVector %s32 4 +%f32vec4 = OpTypeVector %f32 4 +%f64vec4 = OpTypeVector %f64 4 + +%f32_0 = OpConstant %f32 0 +%f32_1 = OpConstant %f32 1 +%f32_2 = OpConstant %f32 2 +%f32_3 = OpConstant %f32 3 +%f32_4 = OpConstant %f32 4 + +%s32_0 = OpConstant %s32 0 +%s32_1 = OpConstant %s32 1 +%s32_2 = OpConstant %s32 2 +%s32_3 = OpConstant %s32 3 +%s32_4 = OpConstant %s32 4 +%s32_m1 = OpConstant %s32 -1 + +%u32_0 = OpConstant %u32 0 +%u32_1 = OpConstant %u32 1 +%u32_2 = OpConstant %u32 2 +%u32_3 = OpConstant %u32 3 +%u32_4 = OpConstant %u32 4 + +%f64_0 = OpConstant %f64 0 +%f64_1 = OpConstant %f64 1 +%f64_2 = OpConstant %f64 2 +%f64_3 = OpConstant %f64 3 +%f64_4 = OpConstant %f64 4 + +%s64_0 = OpConstant %s64 0 +%s64_1 = OpConstant %s64 1 +%s64_2 = OpConstant %s64 2 +%s64_3 = OpConstant %s64 3 +%s64_4 = OpConstant %s64 4 +%s64_m1 = OpConstant %s64 -1 + +%u64_0 = OpConstant %u64 0 +%u64_1 = OpConstant %u64 1 +%u64_2 = OpConstant %u64 2 +%u64_3 = OpConstant %u64 3 +%u64_4 = OpConstant %u64 4 + +%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1 +%u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2 +%u32vec3_012 = OpConstantComposite %u32vec3 %u32_0 %u32_1 %u32_2 +%u32vec3_123 = OpConstantComposite %u32vec3 %u32_1 %u32_2 %u32_3 +%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3 +%u32vec4_1234 = OpConstantComposite %u32vec4 %u32_1 %u32_2 %u32_3 %u32_4 + +%s32vec2_01 = OpConstantComposite %s32vec2 %s32_0 %s32_1 +%s32vec2_12 = OpConstantComposite %s32vec2 %s32_1 %s32_2 +%s32vec3_012 = OpConstantComposite %s32vec3 %s32_0 %s32_1 %s32_2 +%s32vec3_123 = OpConstantComposite %s32vec3 %s32_1 %s32_2 %s32_3 +%s32vec4_0123 = OpConstantComposite %s32vec4 %s32_0 %s32_1 %s32_2 %s32_3 +%s32vec4_1234 = OpConstantComposite %s32vec4 %s32_1 %s32_2 %s32_3 %s32_4 + +%f32vec2_01 = OpConstantComposite %f32vec2 %f32_0 %f32_1 +%f32vec2_12 = OpConstantComposite %f32vec2 %f32_1 %f32_2 +%f32vec3_012 = OpConstantComposite %f32vec3 %f32_0 %f32_1 %f32_2 +%f32vec3_123 = OpConstantComposite %f32vec3 %f32_1 %f32_2 %f32_3 +%f32vec4_0123 = OpConstantComposite %f32vec4 %f32_0 %f32_1 %f32_2 %f32_3 +%f32vec4_1234 = OpConstantComposite %f32vec4 %f32_1 %f32_2 %f32_3 %f32_4 + +%f64vec2_01 = OpConstantComposite %f64vec2 %f64_0 %f64_1 +%f64vec2_12 = OpConstantComposite %f64vec2 %f64_1 %f64_2 +%f64vec3_012 = OpConstantComposite %f64vec3 %f64_0 %f64_1 %f64_2 +%f64vec3_123 = OpConstantComposite %f64vec3 %f64_1 %f64_2 %f64_3 +%f64vec4_0123 = OpConstantComposite %f64vec4 %f64_0 %f64_1 %f64_2 %f64_3 +%f64vec4_1234 = OpConstantComposite %f64vec4 %f64_1 %f64_2 %f64_3 %f64_4 + +%true = OpConstantTrue %bool +%false = OpConstantFalse %bool + +%f32ptr_func = OpTypePointer Function %f32 + +%main = OpFunction %void None %func +%main_entry = OpLabel)"; + + const std::string after_body = + R"( +OpReturn +OpFunctionEnd)"; + + return capabilities + capabilities_and_extensions + + after_extension_before_body + body + after_body; +} + +std::string GenerateKernelCode( + const std::string& body, + const std::string& capabilities_and_extensions = "") { + const std::string capabilities = + R"( +OpCapability Addresses +OpCapability Kernel +OpCapability Linkage +OpCapability GenericPointer +OpCapability Int64 +OpCapability Float64)"; + + const std::string after_extension_before_body = + R"( +OpMemoryModel Physical32 OpenCL +%void = OpTypeVoid +%func = OpTypeFunction %void +%bool = OpTypeBool +%f32 = OpTypeFloat 32 +%u32 = OpTypeInt 32 0 +%f64 = OpTypeFloat 64 +%u64 = OpTypeInt 64 0 +%boolvec2 = OpTypeVector %bool 2 +%u32vec2 = OpTypeVector %u32 2 +%u64vec2 = OpTypeVector %u64 2 +%f32vec2 = OpTypeVector %f32 2 +%f64vec2 = OpTypeVector %f64 2 +%boolvec3 = OpTypeVector %bool 3 +%u32vec3 = OpTypeVector %u32 3 +%u64vec3 = OpTypeVector %u64 3 +%f32vec3 = OpTypeVector %f32 3 +%f64vec3 = OpTypeVector %f64 3 +%boolvec4 = OpTypeVector %bool 4 +%u32vec4 = OpTypeVector %u32 4 +%u64vec4 = OpTypeVector %u64 4 +%f32vec4 = OpTypeVector %f32 4 +%f64vec4 = OpTypeVector %f64 4 + +%f32_0 = OpConstant %f32 0 +%f32_1 = OpConstant %f32 1 +%f32_2 = OpConstant %f32 2 +%f32_3 = OpConstant %f32 3 +%f32_4 = OpConstant %f32 4 + +%u32_0 = OpConstant %u32 0 +%u32_1 = OpConstant %u32 1 +%u32_2 = OpConstant %u32 2 +%u32_3 = OpConstant %u32 3 +%u32_4 = OpConstant %u32 4 + +%f64_0 = OpConstant %f64 0 +%f64_1 = OpConstant %f64 1 +%f64_2 = OpConstant %f64 2 +%f64_3 = OpConstant %f64 3 +%f64_4 = OpConstant %f64 4 + +%u64_0 = OpConstant %u64 0 +%u64_1 = OpConstant %u64 1 +%u64_2 = OpConstant %u64 2 +%u64_3 = OpConstant %u64 3 +%u64_4 = OpConstant %u64 4 + +%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1 +%u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2 +%u32vec3_012 = OpConstantComposite %u32vec3 %u32_0 %u32_1 %u32_2 +%u32vec3_123 = OpConstantComposite %u32vec3 %u32_1 %u32_2 %u32_3 +%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3 +%u32vec4_1234 = OpConstantComposite %u32vec4 %u32_1 %u32_2 %u32_3 %u32_4 + +%f32vec2_01 = OpConstantComposite %f32vec2 %f32_0 %f32_1 +%f32vec2_12 = OpConstantComposite %f32vec2 %f32_1 %f32_2 +%f32vec3_012 = OpConstantComposite %f32vec3 %f32_0 %f32_1 %f32_2 +%f32vec3_123 = OpConstantComposite %f32vec3 %f32_1 %f32_2 %f32_3 +%f32vec4_0123 = OpConstantComposite %f32vec4 %f32_0 %f32_1 %f32_2 %f32_3 +%f32vec4_1234 = OpConstantComposite %f32vec4 %f32_1 %f32_2 %f32_3 %f32_4 + +%f64vec2_01 = OpConstantComposite %f64vec2 %f64_0 %f64_1 +%f64vec2_12 = OpConstantComposite %f64vec2 %f64_1 %f64_2 +%f64vec3_012 = OpConstantComposite %f64vec3 %f64_0 %f64_1 %f64_2 +%f64vec3_123 = OpConstantComposite %f64vec3 %f64_1 %f64_2 %f64_3 +%f64vec4_0123 = OpConstantComposite %f64vec4 %f64_0 %f64_1 %f64_2 %f64_3 +%f64vec4_1234 = OpConstantComposite %f64vec4 %f64_1 %f64_2 %f64_3 %f64_4 + +%true = OpConstantTrue %bool +%false = OpConstantFalse %bool + +%f32ptr_func = OpTypePointer Function %f32 +%u32ptr_func = OpTypePointer Function %u32 +%f32ptr_gen = OpTypePointer Generic %f32 +%f32ptr_inp = OpTypePointer Input %f32 +%f32ptr_wg = OpTypePointer Workgroup %f32 +%f32ptr_cwg = OpTypePointer CrossWorkgroup %f32 + +%f32inp = OpVariable %f32ptr_inp Input + +%main = OpFunction %void None %func +%main_entry = OpLabel)"; + + const std::string after_body = + R"( +OpReturn +OpFunctionEnd)"; + + return capabilities + capabilities_and_extensions + + after_extension_before_body + body + after_body; +} + +TEST_F(ValidateConversion, ConvertFToUSuccess) { + const std::string body = R"( +%val1 = OpConvertFToU %u32 %f32_1 +%val2 = OpConvertFToU %u32 %f64_0 +%val3 = OpConvertFToU %u32vec2 %f32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateConversion, ConvertFToUWrongResultType) { + const std::string body = R"( +%val = OpConvertFToU %s32 %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected unsigned int scalar or vector type as Result " + "Type: ConvertFToU")); +} + +TEST_F(ValidateConversion, ConvertFToUWrongInputType) { + const std::string body = R"( +%val = OpConvertFToU %u32 %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected input to be float scalar or vector: ConvertFToU")); +} + +TEST_F(ValidateConversion, ConvertFToUDifferentDimension) { + const std::string body = R"( +%val = OpConvertFToU %u32 %f32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected input to have the same dimension as Result " + "Type: ConvertFToU")); +} + +TEST_F(ValidateConversion, ConvertFToSSuccess) { + const std::string body = R"( +%val1 = OpConvertFToS %s32 %f32_1 +%val2 = OpConvertFToS %u32 %f64_0 +%val3 = OpConvertFToS %s32vec2 %f32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateConversion, ConvertFToSWrongResultType) { + const std::string body = R"( +%val = OpConvertFToS %bool %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected int scalar or vector type as Result Type: ConvertFToS")); +} + +TEST_F(ValidateConversion, ConvertFToSWrongInputType) { + const std::string body = R"( +%val = OpConvertFToS %s32 %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected input to be float scalar or vector: ConvertFToS")); +} + +TEST_F(ValidateConversion, ConvertFToSDifferentDimension) { + const std::string body = R"( +%val = OpConvertFToS %u32 %f32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected input to have the same dimension as Result " + "Type: ConvertFToS")); +} + +TEST_F(ValidateConversion, ConvertSToFSuccess) { + const std::string body = R"( +%val1 = OpConvertSToF %f32 %u32_1 +%val2 = OpConvertSToF %f32 %s64_0 +%val3 = OpConvertSToF %f32vec2 %s32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateConversion, ConvertSToFWrongResultType) { + const std::string body = R"( +%val = OpConvertSToF %u32 %s32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected float scalar or vector type as Result Type: ConvertSToF")); +} + +TEST_F(ValidateConversion, ConvertSToFWrongInputType) { + const std::string body = R"( +%val = OpConvertSToF %f32 %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected input to be int scalar or vector: ConvertSToF")); +} + +TEST_F(ValidateConversion, ConvertSToFDifferentDimension) { + const std::string body = R"( +%val = OpConvertSToF %f32 %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected input to have the same dimension as Result " + "Type: ConvertSToF")); +} + +TEST_F(ValidateConversion, UConvertSuccess) { + const std::string body = R"( +%val1 = OpUConvert %u32 %u64_1 +%val2 = OpUConvert %u64 %s32_0 +%val3 = OpUConvert %u64vec2 %s32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateConversion, UConvertWrongResultType) { + const std::string body = R"( +%val = OpUConvert %s32 %s32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected unsigned int scalar or vector type as Result " + "Type: UConvert")); +} + +TEST_F(ValidateConversion, UConvertWrongInputType) { + const std::string body = R"( +%val = OpUConvert %u32 %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected input to be int scalar or vector: UConvert")); +} + +TEST_F(ValidateConversion, UConvertDifferentDimension) { + const std::string body = R"( +%val = OpUConvert %u32 %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected input to have the same dimension as Result " + "Type: UConvert")); +} + +TEST_F(ValidateConversion, UConvertSameBitWidth) { + const std::string body = R"( +%val = OpUConvert %u32 %s32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected input to have different bit width from " + "Result Type: UConvert")); +} + +TEST_F(ValidateConversion, SConvertSuccess) { + const std::string body = R"( +%val1 = OpSConvert %s32 %u64_1 +%val2 = OpSConvert %s64 %s32_0 +%val3 = OpSConvert %u64vec2 %s32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateConversion, SConvertWrongResultType) { + const std::string body = R"( +%val = OpSConvert %f32 %s32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected int scalar or vector type as Result Type: SConvert")); +} + +TEST_F(ValidateConversion, SConvertWrongInputType) { + const std::string body = R"( +%val = OpSConvert %u32 %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected input to be int scalar or vector: SConvert")); +} + +TEST_F(ValidateConversion, SConvertDifferentDimension) { + const std::string body = R"( +%val = OpSConvert %s32 %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected input to have the same dimension as Result " + "Type: SConvert")); +} + +TEST_F(ValidateConversion, SConvertSameBitWidth) { + const std::string body = R"( +%val = OpSConvert %u32 %s32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected input to have different bit width from " + "Result Type: SConvert")); +} + +TEST_F(ValidateConversion, FConvertSuccess) { + const std::string body = R"( +%val1 = OpFConvert %f32 %f64_1 +%val2 = OpFConvert %f64 %f32_0 +%val3 = OpFConvert %f64vec2 %f32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateConversion, FConvertWrongResultType) { + const std::string body = R"( +%val = OpFConvert %u32 %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected float scalar or vector type as Result Type: FConvert")); +} + +TEST_F(ValidateConversion, FConvertWrongInputType) { + const std::string body = R"( +%val = OpFConvert %f32 %u64_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected input to be float scalar or vector: FConvert")); +} + +TEST_F(ValidateConversion, FConvertDifferentDimension) { + const std::string body = R"( +%val = OpFConvert %f64 %f32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected input to have the same dimension as Result " + "Type: FConvert")); +} + +TEST_F(ValidateConversion, FConvertSameBitWidth) { + const std::string body = R"( +%val = OpFConvert %f32 %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected input to have different bit width from " + "Result Type: FConvert")); +} + +TEST_F(ValidateConversion, QuantizeToF16Success) { + const std::string body = R"( +%val1 = OpQuantizeToF16 %f32 %f32_1 +%val2 = OpQuantizeToF16 %f32 %f32_0 +%val3 = OpQuantizeToF16 %f32vec2 %f32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateConversion, QuantizeToF16WrongResultType) { + const std::string body = R"( +%val = OpQuantizeToF16 %u32 %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected 32-bit float scalar or vector type as Result Type: " + "QuantizeToF16")); +} + +TEST_F(ValidateConversion, QuantizeToF16WrongResultTypeBitWidth) { + const std::string body = R"( +%val = OpQuantizeToF16 %u64 %f64_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected 32-bit float scalar or vector type as Result Type: " + "QuantizeToF16")); +} + +TEST_F(ValidateConversion, QuantizeToF16WrongInputType) { + const std::string body = R"( +%val = OpQuantizeToF16 %f32 %f64_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected input type to be equal to Result Type: QuantizeToF16")); +} + +TEST_F(ValidateConversion, ConvertPtrToUSuccess) { + const std::string body = R"( +%ptr = OpVariable %f32ptr_func Function +%val1 = OpConvertPtrToU %u32 %ptr +%val2 = OpConvertPtrToU %u64 %ptr +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateConversion, ConvertPtrToUWrongResultType) { + const std::string body = R"( +%ptr = OpVariable %f32ptr_func Function +%val = OpConvertPtrToU %f32 %ptr +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected unsigned int scalar type as Result Type: " + "ConvertPtrToU")); +} + +TEST_F(ValidateConversion, ConvertPtrToUNotPointer) { + const std::string body = R"( +%val = OpConvertPtrToU %u32 %f32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected input to be a pointer: ConvertPtrToU")); +} + +TEST_F(ValidateConversion, SatConvertSToUSuccess) { + const std::string body = R"( +%val1 = OpSatConvertSToU %u32 %u64_2 +%val2 = OpSatConvertSToU %u64 %u32_1 +%val3 = OpSatConvertSToU %u64vec2 %u32vec2_12 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateConversion, SatConvertSToUWrongResultType) { + const std::string body = R"( +%val = OpSatConvertSToU %f32 %u32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected int scalar or vector type as Result Type: " + "SatConvertSToU")); +} + +TEST_F(ValidateConversion, SatConvertSToUWrongInputType) { + const std::string body = R"( +%val = OpSatConvertSToU %u32 %f32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected int scalar or vector as input: SatConvertSToU")); +} + +TEST_F(ValidateConversion, SatConvertSToUDifferentDimension) { + const std::string body = R"( +%val = OpSatConvertSToU %u32 %u32vec2_12 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected input to have the same dimension as Result Type: " + "SatConvertSToU")); +} + +TEST_F(ValidateConversion, ConvertUToPtrSuccess) { + const std::string body = R"( +%val1 = OpConvertUToPtr %f32ptr_func %u32_1 +%val2 = OpConvertUToPtr %f32ptr_func %u64_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateConversion, ConvertUToPtrWrongResultType) { + const std::string body = R"( +%val = OpConvertUToPtr %f32 %u32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type to be a pointer: ConvertUToPtr")); +} + +TEST_F(ValidateConversion, ConvertUToPtrNotInt) { + const std::string body = R"( +%val = OpConvertUToPtr %f32ptr_func %f32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected int scalar as input: ConvertUToPtr")); +} + +TEST_F(ValidateConversion, ConvertUToPtrNotIntScalar) { + const std::string body = R"( +%val = OpConvertUToPtr %f32ptr_func %u32vec2_12 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected int scalar as input: ConvertUToPtr")); +} + +TEST_F(ValidateConversion, PtrCastToGenericSuccess) { + const std::string body = R"( +%ptr_func = OpVariable %f32ptr_func Function +%val = OpPtrCastToGeneric %f32ptr_gen %ptr_func +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateConversion, PtrCastToGenericWrongResultType) { + const std::string body = R"( +%ptr_func = OpVariable %f32ptr_func Function +%val = OpPtrCastToGeneric %f32 %ptr_func +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Result Type to be a pointer: PtrCastToGeneric")); +} + +TEST_F(ValidateConversion, PtrCastToGenericWrongResultStorageClass) { + const std::string body = R"( +%ptr_func = OpVariable %f32ptr_func Function +%val = OpPtrCastToGeneric %f32ptr_func %ptr_func +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type to have storage class Generic: " + "PtrCastToGeneric")); +} + +TEST_F(ValidateConversion, PtrCastToGenericWrongInputType) { + const std::string body = R"( +%ptr_func = OpVariable %f32ptr_func Function +%val = OpPtrCastToGeneric %f32ptr_gen %f32 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected input to be a pointer: PtrCastToGeneric")); +} + +TEST_F(ValidateConversion, PtrCastToGenericWrongInputStorageClass) { + const std::string body = R"( +%val = OpPtrCastToGeneric %f32ptr_gen %f32inp +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected input to have storage class Workgroup, " + "CrossWorkgroup or Function: PtrCastToGeneric")); +} + +TEST_F(ValidateConversion, PtrCastToGenericPointToDifferentType) { + const std::string body = R"( +%ptr_func = OpVariable %u32ptr_func Function +%val = OpPtrCastToGeneric %f32ptr_gen %ptr_func +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected input and Result Type to point to the same type: " + "PtrCastToGeneric")); +} + +TEST_F(ValidateConversion, GenericCastToPtrSuccess) { + const std::string body = R"( +%ptr_func = OpVariable %f32ptr_func Function +%ptr_gen = OpPtrCastToGeneric %f32ptr_gen %ptr_func +%ptr_func2 = OpGenericCastToPtr %f32ptr_func %ptr_gen +%ptr_wg = OpGenericCastToPtr %f32ptr_wg %ptr_gen +%ptr_cwg = OpGenericCastToPtr %f32ptr_cwg %ptr_gen +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateConversion, GenericCastToPtrWrongResultType) { + const std::string body = R"( +%ptr_func = OpVariable %f32ptr_func Function +%ptr_gen = OpPtrCastToGeneric %f32ptr_gen %ptr_func +%ptr_func2 = OpGenericCastToPtr %f32 %ptr_gen +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Result Type to be a pointer: GenericCastToPtr")); +} + +TEST_F(ValidateConversion, GenericCastToPtrWrongResultStorageClass) { + const std::string body = R"( +%ptr_func = OpVariable %f32ptr_func Function +%ptr_gen = OpPtrCastToGeneric %f32ptr_gen %ptr_func +%ptr_func2 = OpGenericCastToPtr %f32ptr_gen %ptr_gen +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type to have storage class Workgroup, " + "CrossWorkgroup or Function: GenericCastToPtr")); +} + +TEST_F(ValidateConversion, GenericCastToPtrWrongInputType) { + const std::string body = R"( +%ptr_func = OpVariable %f32ptr_func Function +%ptr_gen = OpPtrCastToGeneric %f32ptr_gen %ptr_func +%ptr_func2 = OpGenericCastToPtr %f32ptr_func %f32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected input to be a pointer: GenericCastToPtr")); +} + +TEST_F(ValidateConversion, GenericCastToPtrWrongInputStorageClass) { + const std::string body = R"( +%ptr_func = OpVariable %f32ptr_func Function +%ptr_func2 = OpGenericCastToPtr %f32ptr_func %ptr_func +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected input to have storage class Generic: " + "GenericCastToPtr")); +} + +TEST_F(ValidateConversion, GenericCastToPtrPointToDifferentType) { + const std::string body = R"( +%ptr_func = OpVariable %f32ptr_func Function +%ptr_gen = OpPtrCastToGeneric %f32ptr_gen %ptr_func +%ptr_func2 = OpGenericCastToPtr %u32ptr_func %ptr_gen +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected input and Result Type to point to the same type: " + "GenericCastToPtr")); +} + +TEST_F(ValidateConversion, GenericCastToPtrExplicitSuccess) { + const std::string body = R"( +%ptr_func = OpVariable %f32ptr_func Function +%ptr_gen = OpPtrCastToGeneric %f32ptr_gen %ptr_func +%ptr_func2 = OpGenericCastToPtrExplicit %f32ptr_func %ptr_gen Function +%ptr_wg = OpGenericCastToPtrExplicit %f32ptr_wg %ptr_gen Workgroup +%ptr_cwg = OpGenericCastToPtrExplicit %f32ptr_cwg %ptr_gen CrossWorkgroup +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateConversion, GenericCastToPtrExplicitWrongResultType) { + const std::string body = R"( +%ptr_func = OpVariable %f32ptr_func Function +%ptr_gen = OpPtrCastToGeneric %f32ptr_gen %ptr_func +%ptr_func2 = OpGenericCastToPtrExplicit %f32 %ptr_gen Function +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected Result Type to be a pointer: GenericCastToPtrExplicit")); +} + +TEST_F(ValidateConversion, GenericCastToPtrExplicitResultStorageClassDiffers) { + const std::string body = R"( +%ptr_func = OpVariable %f32ptr_func Function +%ptr_gen = OpPtrCastToGeneric %f32ptr_gen %ptr_func +%ptr_func2 = OpGenericCastToPtrExplicit %f32ptr_func %ptr_gen Workgroup +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type to be of target storage class: " + "GenericCastToPtrExplicit")); +} + +TEST_F(ValidateConversion, GenericCastToPtrExplicitWrongResultStorageClass) { + const std::string body = R"( +%ptr_func = OpVariable %f32ptr_func Function +%ptr_gen = OpPtrCastToGeneric %f32ptr_gen %ptr_func +%ptr_func2 = OpGenericCastToPtrExplicit %f32ptr_gen %ptr_gen Generic +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected target storage class to be Workgroup, " + "CrossWorkgroup or Function: GenericCastToPtrExplicit")); +} + +TEST_F(ValidateConversion, GenericCastToPtrExplicitWrongInputType) { + const std::string body = R"( +%ptr_func = OpVariable %f32ptr_func Function +%ptr_gen = OpPtrCastToGeneric %f32ptr_gen %ptr_func +%ptr_func2 = OpGenericCastToPtrExplicit %f32ptr_func %f32_1 Function +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected input to be a pointer: GenericCastToPtrExplicit")); +} + +TEST_F(ValidateConversion, GenericCastToPtrExplicitWrongInputStorageClass) { + const std::string body = R"( +%ptr_func = OpVariable %f32ptr_func Function +%ptr_func2 = OpGenericCastToPtrExplicit %f32ptr_func %ptr_func Function +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected input to have storage class Generic: " + "GenericCastToPtrExplicit")); +} + +TEST_F(ValidateConversion, GenericCastToPtrExplicitPointToDifferentType) { + const std::string body = R"( +%ptr_func = OpVariable %f32ptr_func Function +%ptr_gen = OpPtrCastToGeneric %f32ptr_gen %ptr_func +%ptr_func2 = OpGenericCastToPtrExplicit %u32ptr_func %ptr_gen Function +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected input and Result Type to point to the same type: " + "GenericCastToPtrExplicit")); +} + +TEST_F(ValidateConversion, BitcastSuccess) { + const std::string body = R"( +%ptr = OpVariable %f32ptr_func Function +%val1 = OpBitcast %u32 %ptr +%val2 = OpBitcast %u64 %ptr +%val3 = OpBitcast %f32ptr_func %u32_1 +%val4 = OpBitcast %f32ptr_wg %u64_1 +%val5 = OpBitcast %f32 %u32_1 +%val6 = OpBitcast %f32vec2 %u32vec2_12 +%val7 = OpBitcast %f32vec2 %u64_1 +%val8 = OpBitcast %f64 %u32vec2_12 +%val9 = OpBitcast %f32vec4 %f64vec2_12 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateConversion, BitcastInputHasNoType) { + const std::string body = R"( +%val = OpBitcast %u32 %f32 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected input to have a type: Bitcast")); +} + +TEST_F(ValidateConversion, BitcastWrongResultType) { + const std::string body = R"( +%val = OpBitcast %bool %f32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Result Type to be a pointer or int or float vector " + "or scalar type: Bitcast")); +} + +TEST_F(ValidateConversion, BitcastWrongInputType) { + const std::string body = R"( +%val = OpBitcast %u32 %true +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected input to be a pointer or int or float vector " + "or scalar: Bitcast")); +} + +TEST_F(ValidateConversion, BitcastPtrWrongInputType) { + const std::string body = R"( +%val = OpBitcast %u32ptr_func %f32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected input to be a pointer or int scalar if Result Type " + "is pointer: Bitcast")); +} + +TEST_F(ValidateConversion, BitcastPtrWrongResultType) { + const std::string body = R"( +%val = OpBitcast %f32 %f32inp +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Pointer can only be converted to another pointer or int scalar: " + "Bitcast")); +} + +TEST_F(ValidateConversion, BitcastDifferentTotalBitWidth) { + const std::string body = R"( +%val = OpBitcast %f32 %u64_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected input to have the same total bit width as Result Type: " + "Bitcast")); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/val/val_data_test.cpp b/3rdparty/spirv-tools/test/val/val_data_test.cpp new file mode 100644 index 000000000..afa6afb6d --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_data_test.cpp @@ -0,0 +1,595 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Validation tests for Data Rules. + +#include +#include +#include + +#include "gmock/gmock.h" +#include "unit_spirv.h" +#include "val_fixtures.h" + +namespace { + +using ::testing::HasSubstr; +using ::testing::MatchesRegex; + +using std::pair; +using std::string; +using std::stringstream; + +using ValidateData = spvtest::ValidateBase>; + +string HeaderWith(std::string cap) { + return std::string("OpCapability Shader OpCapability Linkage OpCapability ") + + cap + " OpMemoryModel Logical GLSL450 "; +} + +string header = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 +)"; +string header_with_addresses = R"( + OpCapability Addresses + OpCapability Kernel + OpCapability GenericPointer + OpCapability Linkage + OpMemoryModel Physical32 OpenCL +)"; +string header_with_vec16_cap = R"( + OpCapability Shader + OpCapability Vector16 + OpCapability Linkage + OpMemoryModel Logical GLSL450 +)"; +string header_with_int8 = R"( + OpCapability Shader + OpCapability Linkage + OpCapability Int8 + OpMemoryModel Logical GLSL450 +)"; +string header_with_int16 = R"( + OpCapability Shader + OpCapability Linkage + OpCapability Int16 + OpMemoryModel Logical GLSL450 +)"; +string header_with_int64 = R"( + OpCapability Shader + OpCapability Linkage + OpCapability Int64 + OpMemoryModel Logical GLSL450 +)"; +string header_with_float16 = R"( + OpCapability Shader + OpCapability Linkage + OpCapability Float16 + OpMemoryModel Logical GLSL450 +)"; +string header_with_float16_buffer = R"( + OpCapability Shader + OpCapability Linkage + OpCapability Float16Buffer + OpMemoryModel Logical GLSL450 +)"; +string header_with_float64 = R"( + OpCapability Shader + OpCapability Linkage + OpCapability Float64 + OpMemoryModel Logical GLSL450 +)"; + +string invalid_comp_error = "Illegal number of components"; +string missing_cap_error = "requires the Vector16 capability"; +string missing_int8_cap_error = "requires the Int8 capability"; +string missing_int16_cap_error = + "requires the Int16 capability," + " or an extension that explicitly enables 16-bit integers."; +string missing_int64_cap_error = "requires the Int64 capability"; +string missing_float16_cap_error = + "requires the Float16 or Float16Buffer capability," + " or an extension that explicitly enables 16-bit floating point."; +string missing_float64_cap_error = "requires the Float64 capability"; +string invalid_num_bits_error = "Invalid number of bits"; + +TEST_F(ValidateData, vec0) { + string str = header + R"( +%1 = OpTypeFloat 32 +%2 = OpTypeVector %1 0 +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(invalid_comp_error)); +} + +TEST_F(ValidateData, vec1) { + string str = header + R"( +%1 = OpTypeFloat 32 +%2 = OpTypeVector %1 1 +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(invalid_comp_error)); +} + +TEST_F(ValidateData, vec2) { + string str = header + R"( +%1 = OpTypeFloat 32 +%2 = OpTypeVector %1 2 +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateData, vec3) { + string str = header + R"( +%1 = OpTypeFloat 32 +%2 = OpTypeVector %1 3 +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateData, vec4) { + string str = header + R"( +%1 = OpTypeFloat 32 +%2 = OpTypeVector %1 4 +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateData, vec5) { + string str = header + R"( +%1 = OpTypeFloat 32 +%2 = OpTypeVector %1 5 +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(invalid_comp_error)); +} + +TEST_F(ValidateData, vec8) { + string str = header + R"( +%1 = OpTypeFloat 32 +%2 = OpTypeVector %1 8 +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(missing_cap_error)); +} + +TEST_F(ValidateData, vec8_with_capability) { + string str = header_with_vec16_cap + R"( +%1 = OpTypeFloat 32 +%2 = OpTypeVector %1 8 +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateData, vec16) { + string str = header + R"( +%1 = OpTypeFloat 32 +%2 = OpTypeVector %1 8 +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(missing_cap_error)); +} + +TEST_F(ValidateData, vec16_with_capability) { + string str = header_with_vec16_cap + R"( +%1 = OpTypeFloat 32 +%2 = OpTypeVector %1 16 +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateData, vec15) { + string str = header + R"( +%1 = OpTypeFloat 32 +%2 = OpTypeVector %1 15 +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(invalid_comp_error)); +} + +TEST_F(ValidateData, int8_good) { + string str = header_with_int8 + "%2 = OpTypeInt 8 0"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateData, int8_bad) { + string str = header + "%2 = OpTypeInt 8 1"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(missing_int8_cap_error)); +} + +TEST_F(ValidateData, int16_good) { + string str = header_with_int16 + "%2 = OpTypeInt 16 1"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateData, storage_uniform_buffer_block_16_good) { + string str = HeaderWith( + "StorageUniformBufferBlock16 " + "OpExtension \"SPV_KHR_16bit_storage\"") + + "%2 = OpTypeInt 16 1 %3 = OpTypeFloat 16"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateData, storage_uniform_16_good) { + string str = + HeaderWith("StorageUniform16 OpExtension \"SPV_KHR_16bit_storage\"") + + "%2 = OpTypeInt 16 1 %3 = OpTypeFloat 16"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateData, storage_push_constant_16_good) { + string str = HeaderWith( + "StoragePushConstant16 " + "OpExtension \"SPV_KHR_16bit_storage\"") + + "%2 = OpTypeInt 16 1 %3 = OpTypeFloat 16"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateData, storage_input_output_16_good) { + string str = HeaderWith( + "StorageInputOutput16 " + "OpExtension \"SPV_KHR_16bit_storage\"") + + "%2 = OpTypeInt 16 1 %3 = OpTypeFloat 16"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateData, int16_bad) { + string str = header + "%2 = OpTypeInt 16 1"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(missing_int16_cap_error)); +} + +TEST_F(ValidateData, int64_good) { + string str = header_with_int64 + "%2 = OpTypeInt 64 1"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateData, int64_bad) { + string str = header + "%2 = OpTypeInt 64 1"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(missing_int64_cap_error)); +} + +// Number of bits in an integer may be only one of: {8,16,32,64} +TEST_F(ValidateData, int_invalid_num_bits) { + string str = header + "%2 = OpTypeInt 48 1"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(invalid_num_bits_error)); +} + +TEST_F(ValidateData, float16_good) { + string str = header_with_float16 + "%2 = OpTypeFloat 16"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateData, float16_buffer_good) { + string str = header_with_float16_buffer + "%2 = OpTypeFloat 16"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateData, float16_bad) { + string str = header + "%2 = OpTypeFloat 16"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(missing_float16_cap_error)); +} + +TEST_F(ValidateData, float64_good) { + string str = header_with_float64 + "%2 = OpTypeFloat 64"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateData, float64_bad) { + string str = header + "%2 = OpTypeFloat 64"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(missing_float64_cap_error)); +} + +// Number of bits in a float may be only one of: {16,32,64} +TEST_F(ValidateData, float_invalid_num_bits) { + string str = header + "%2 = OpTypeFloat 48"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(invalid_num_bits_error)); +} + +TEST_F(ValidateData, matrix_data_type_float) { + string str = header + R"( +%f32 = OpTypeFloat 32 +%vec3 = OpTypeVector %f32 3 +%mat33 = OpTypeMatrix %vec3 3 +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateData, matrix_bad_column_type) { + string str = header + R"( +%f32 = OpTypeFloat 32 +%mat33 = OpTypeMatrix %f32 3 +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Columns in a matrix must be of type vector")); +} + +TEST_F(ValidateData, matrix_data_type_int) { + string str = header + R"( +%int32 = OpTypeInt 32 1 +%vec3 = OpTypeVector %int32 3 +%mat33 = OpTypeMatrix %vec3 3 +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("can only be parameterized with floating-point types")); +} + +TEST_F(ValidateData, matrix_data_type_bool) { + string str = header + R"( +%boolt = OpTypeBool +%vec3 = OpTypeVector %boolt 3 +%mat33 = OpTypeMatrix %vec3 3 +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("can only be parameterized with floating-point types")); +} + +TEST_F(ValidateData, matrix_with_0_columns) { + string str = header + R"( +%f32 = OpTypeFloat 32 +%vec3 = OpTypeVector %f32 3 +%mat33 = OpTypeMatrix %vec3 0 +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("can only be parameterized as having only 2, 3, or 4 columns")); +} + +TEST_F(ValidateData, matrix_with_1_column) { + string str = header + R"( +%f32 = OpTypeFloat 32 +%vec3 = OpTypeVector %f32 3 +%mat33 = OpTypeMatrix %vec3 1 +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("can only be parameterized as having only 2, 3, or 4 columns")); +} + +TEST_F(ValidateData, matrix_with_2_columns) { + string str = header + R"( +%f32 = OpTypeFloat 32 +%vec3 = OpTypeVector %f32 3 +%mat33 = OpTypeMatrix %vec3 2 +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateData, matrix_with_3_columns) { + string str = header + R"( +%f32 = OpTypeFloat 32 +%vec3 = OpTypeVector %f32 3 +%mat33 = OpTypeMatrix %vec3 3 +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateData, matrix_with_4_columns) { + string str = header + R"( +%f32 = OpTypeFloat 32 +%vec3 = OpTypeVector %f32 3 +%mat33 = OpTypeMatrix %vec3 4 +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateData, matrix_with_5_column) { + string str = header + R"( +%f32 = OpTypeFloat 32 +%vec3 = OpTypeVector %f32 3 +%mat33 = OpTypeMatrix %vec3 5 +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("can only be parameterized as having only 2, 3, or 4 columns")); +} + +TEST_F(ValidateData, specialize_int) { + string str = header + R"( +%i32 = OpTypeInt 32 1 +%len = OpSpecConstant %i32 2)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateData, specialize_float) { + string str = header + R"( +%f32 = OpTypeFloat 32 +%len = OpSpecConstant %f32 2)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateData, specialize_boolean) { + string str = header + R"( +%2 = OpTypeBool +%3 = OpSpecConstantTrue %2 +%4 = OpSpecConstantFalse %2)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateData, specialize_boolean_to_int) { + string str = header + R"( +%2 = OpTypeInt 32 1 +%3 = OpSpecConstantTrue %2 +%4 = OpSpecConstantFalse %2)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Specialization constant must be a boolean")); +} + +TEST_F(ValidateData, missing_forward_pointer_decl) { + string str = header_with_addresses + R"( +%uintt = OpTypeInt 32 0 +%3 = OpTypeStruct %fwd_ptrt %uintt +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("must first be declared using OpTypeForwardPointer")); +} + +TEST_F(ValidateData, forward_pointer_missing_definition) { + string str = header_with_addresses + R"( +OpTypeForwardPointer %_ptr_Generic_struct_A Generic +%uintt = OpTypeInt 32 0 +%struct_B = OpTypeStruct %uintt %_ptr_Generic_struct_A +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("forward referenced IDs have not been defined")); +} + +TEST_F(ValidateData, forward_ref_bad_type) { + string str = header_with_addresses + R"( +OpTypeForwardPointer %_ptr_Generic_struct_A Generic +%uintt = OpTypeInt 32 0 +%struct_B = OpTypeStruct %uintt %_ptr_Generic_struct_A +%_ptr_Generic_struct_A = OpTypeFloat 32 +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Found a forward reference to a non-pointer type in " + "OpTypeStruct instruction.")); +} + +TEST_F(ValidateData, forward_ref_points_to_non_struct) { + string str = header_with_addresses + R"( +OpTypeForwardPointer %_ptr_Generic_struct_A Generic +%uintt = OpTypeInt 32 0 +%struct_B = OpTypeStruct %uintt %_ptr_Generic_struct_A +%_ptr_Generic_struct_A = OpTypePointer Generic %uintt +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("A forward reference operand in an OpTypeStruct must " + "be an OpTypePointer that points to an OpTypeStruct. " + "Found OpTypePointer that points to OpTypeInt.")); +} + +TEST_F(ValidateData, struct_forward_pointer_good) { + string str = header_with_addresses + R"( +OpTypeForwardPointer %_ptr_Generic_struct_A Generic +%uintt = OpTypeInt 32 0 +%struct_B = OpTypeStruct %uintt %_ptr_Generic_struct_A +%struct_C = OpTypeStruct %uintt %struct_B +%struct_A = OpTypeStruct %uintt %struct_C +%_ptr_Generic_struct_A = OpTypePointer Generic %struct_C +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateData, ext_16bit_storage_caps_allow_free_fp_rounding_mode) { + for (const char* cap : {"StorageUniform16", "StorageUniformBufferBlock16", + "StoragePushConstant16", "StorageInputOutput16"}) { + for (const char* mode : {"RTE", "RTZ", "RTP", "RTN"}) { + string str = string(R"( + OpCapability Shader + OpCapability Linkage + OpCapability )") + + cap + R"( + OpExtension "SPV_KHR_16bit_storage" + OpMemoryModel Logical GLSL450 + OpDecorate %2 FPRoundingMode )" + + mode + R"( + %1 = OpTypeFloat 32 + %2 = OpConstant %1 1.25 + )"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); + } + } +} + +TEST_F(ValidateData, vulkan_disallow_free_fp_rounding_mode) { + for (const char* mode : {"RTE", "RTZ"}) { + for (const auto env : {SPV_ENV_VULKAN_1_0, SPV_ENV_VULKAN_1_1}) { + string str = string(R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpDecorate %2 FPRoundingMode )") + + mode + R"( + %1 = OpTypeFloat 32 + %2 = OpConstant %1 1.25 + )"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_CAPABILITY, ValidateInstructions(env)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Operand 2 of Decorate requires one of these capabilities: " + "StorageBuffer16BitAccess StorageUniform16 " + "StoragePushConstant16 StorageInputOutput16")); + } + } +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/val/val_decoration_test.cpp b/3rdparty/spirv-tools/test/val/val_decoration_test.cpp new file mode 100644 index 000000000..2c0960c0b --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_decoration_test.cpp @@ -0,0 +1,551 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Common validation fixtures for unit tests + +#include "gmock/gmock.h" +#include "source/val/decoration.h" +#include "unit_spirv.h" +#include "val_fixtures.h" + +namespace { + +using ::testing::Eq; +using ::testing::HasSubstr; +using libspirv::Decoration; +using std::string; +using std::vector; + +using ValidateDecorations = spvtest::ValidateBase; + +TEST_F(ValidateDecorations, ValidateOpDecorateRegistration) { + string spirv = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpDecorate %1 ArrayStride 4 + OpDecorate %1 Uniform + %2 = OpTypeFloat 32 + %1 = OpTypeRuntimeArray %2 + ; Since %1 is used first in Decoration, it gets id 1. +)"; + const uint32_t id = 1; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState()); + // Must have 2 decorations. + EXPECT_THAT(vstate_->id_decorations(id), + Eq(vector{Decoration(SpvDecorationArrayStride, {4}), + Decoration(SpvDecorationUniform)})); +} + +TEST_F(ValidateDecorations, ValidateOpMemberDecorateRegistration) { + string spirv = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpDecorate %_arr_double_uint_6 ArrayStride 4 + OpMemberDecorate %_struct_115 2 NonReadable + OpMemberDecorate %_struct_115 2 Offset 2 + OpDecorate %_struct_115 BufferBlock + %float = OpTypeFloat 32 + %uint = OpTypeInt 32 0 + %uint_6 = OpConstant %uint 6 + %_arr_double_uint_6 = OpTypeArray %float %uint_6 + %_struct_115 = OpTypeStruct %float %float %_arr_double_uint_6 +)"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState()); + + // The array must have 1 decoration. + const uint32_t arr_id = 1; + EXPECT_THAT( + vstate_->id_decorations(arr_id), + Eq(vector{Decoration(SpvDecorationArrayStride, {4})})); + + // The struct must have 3 decorations. + const uint32_t struct_id = 2; + EXPECT_THAT(vstate_->id_decorations(struct_id), + Eq(vector{Decoration(SpvDecorationNonReadable, {}, 2), + Decoration(SpvDecorationOffset, {2}, 2), + Decoration(SpvDecorationBufferBlock)})); +} + +TEST_F(ValidateDecorations, ValidateGroupDecorateRegistration) { + string spirv = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpDecorate %1 DescriptorSet 0 + OpDecorate %1 NonWritable + OpDecorate %1 Restrict + %1 = OpDecorationGroup + OpGroupDecorate %1 %2 %3 + OpGroupDecorate %1 %4 + %float = OpTypeFloat 32 +%_runtimearr_float = OpTypeRuntimeArray %float + %_struct_9 = OpTypeStruct %_runtimearr_float +%_ptr_Uniform__struct_9 = OpTypePointer Uniform %_struct_9 + %2 = OpVariable %_ptr_Uniform__struct_9 Uniform + %_struct_10 = OpTypeStruct %_runtimearr_float +%_ptr_Uniform__struct_10 = OpTypePointer Uniform %_struct_10 + %3 = OpVariable %_ptr_Uniform__struct_10 Uniform + %_struct_11 = OpTypeStruct %_runtimearr_float +%_ptr_Uniform__struct_11 = OpTypePointer Uniform %_struct_11 + %4 = OpVariable %_ptr_Uniform__struct_11 Uniform + )"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState()); + + // Decoration group has 3 decorations. + auto expected_decorations = vector{ + Decoration(SpvDecorationDescriptorSet, {0}), + Decoration(SpvDecorationNonWritable), Decoration(SpvDecorationRestrict)}; + + // Decoration group is applied to id 1, 2, 3, and 4. Note that id 1 (which is + // the decoration group id) also has all the decorations. + EXPECT_THAT(vstate_->id_decorations(1), Eq(expected_decorations)); + EXPECT_THAT(vstate_->id_decorations(2), Eq(expected_decorations)); + EXPECT_THAT(vstate_->id_decorations(3), Eq(expected_decorations)); + EXPECT_THAT(vstate_->id_decorations(4), Eq(expected_decorations)); +} + +TEST_F(ValidateDecorations, ValidateGroupMemberDecorateRegistration) { + string spirv = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpDecorate %1 Offset 3 + %1 = OpDecorationGroup + OpGroupMemberDecorate %1 %_struct_1 3 %_struct_2 3 %_struct_3 3 + %float = OpTypeFloat 32 +%_runtimearr = OpTypeRuntimeArray %float + %_struct_1 = OpTypeStruct %float %float %float %_runtimearr + %_struct_2 = OpTypeStruct %float %float %float %_runtimearr + %_struct_3 = OpTypeStruct %float %float %float %_runtimearr + )"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState()); + // Decoration group has 1 decoration. + auto expected_decorations = + vector{Decoration(SpvDecorationOffset, {3}, 3)}; + + // Decoration group is applied to id 2, 3, and 4. + EXPECT_THAT(vstate_->id_decorations(2), Eq(expected_decorations)); + EXPECT_THAT(vstate_->id_decorations(3), Eq(expected_decorations)); + EXPECT_THAT(vstate_->id_decorations(4), Eq(expected_decorations)); +} + +TEST_F(ValidateDecorations, LinkageImportUsedForInitializedVariableBad) { + string spirv = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpDecorate %target LinkageAttributes "link_ptr" Import + %float = OpTypeFloat 32 + %_ptr_float = OpTypePointer Uniform %float + %zero = OpConstantNull %float + %target = OpVariable %_ptr_float Uniform %zero + )"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("A module-scope OpVariable with initialization value " + "cannot be marked with the Import Linkage Type.")); +} +TEST_F(ValidateDecorations, LinkageExportUsedForInitializedVariableGood) { + string spirv = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpDecorate %target LinkageAttributes "link_ptr" Export + %float = OpTypeFloat 32 + %_ptr_float = OpTypePointer Uniform %float + %zero = OpConstantNull %float + %target = OpVariable %_ptr_float Uniform %zero + )"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState()); +} + +TEST_F(ValidateDecorations, StructAllMembersHaveBuiltInDecorationsGood) { + string spirv = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpMemberDecorate %_struct_1 0 BuiltIn Position + OpMemberDecorate %_struct_1 1 BuiltIn Position + OpMemberDecorate %_struct_1 2 BuiltIn Position + OpMemberDecorate %_struct_1 3 BuiltIn Position + %float = OpTypeFloat 32 +%_runtimearr = OpTypeRuntimeArray %float + %_struct_1 = OpTypeStruct %float %float %float %_runtimearr + )"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState()); +} + +TEST_F(ValidateDecorations, MixedBuiltInDecorationsBad) { + string spirv = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpMemberDecorate %_struct_1 0 BuiltIn Position + OpMemberDecorate %_struct_1 1 BuiltIn Position + %float = OpTypeFloat 32 +%_runtimearr = OpTypeRuntimeArray %float + %_struct_1 = OpTypeStruct %float %float %float %_runtimearr + )"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("When BuiltIn decoration is applied to a structure-type " + "member, all members of that structure type must also be " + "decorated with BuiltIn (No allowed mixing of built-in " + "variables and non-built-in variables within a single " + "structure). Structure id 1 does not meet this requirement.")); +} + +TEST_F(ValidateDecorations, StructContainsBuiltInStructBad) { + string spirv = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpMemberDecorate %_struct_1 0 BuiltIn Position + OpMemberDecorate %_struct_1 1 BuiltIn Position + OpMemberDecorate %_struct_1 2 BuiltIn Position + OpMemberDecorate %_struct_1 3 BuiltIn Position + %float = OpTypeFloat 32 +%_runtimearr = OpTypeRuntimeArray %float + %_struct_1 = OpTypeStruct %float %float %float %_runtimearr + %_struct_2 = OpTypeStruct %_struct_1 + )"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Structure 1 contains members with BuiltIn " + "decoration. Therefore this structure may not be " + "contained as a member of another structure type. " + "Structure 4 contains structure 1.")); +} + +TEST_F(ValidateDecorations, StructContainsNonBuiltInStructGood) { + string spirv = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + %float = OpTypeFloat 32 + %_struct_1 = OpTypeStruct %float + %_struct_2 = OpTypeStruct %_struct_1 + )"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState()); +} + +TEST_F(ValidateDecorations, MultipleBuiltInObjectsConsumedByOpEntryPointBad) { + string spirv = R"( + OpCapability Shader + OpCapability Geometry + OpMemoryModel Logical GLSL450 + OpEntryPoint Geometry %main "main" %in_1 %in_2 + OpMemberDecorate %struct_1 0 BuiltIn InvocationId + OpMemberDecorate %struct_2 0 BuiltIn Position + %int = OpTypeInt 32 1 + %void = OpTypeVoid + %func = OpTypeFunction %void + %float = OpTypeFloat 32 + %struct_1 = OpTypeStruct %int + %struct_2 = OpTypeStruct %float +%ptr_builtin_1 = OpTypePointer Input %struct_1 +%ptr_builtin_2 = OpTypePointer Input %struct_2 +%in_1 = OpVariable %ptr_builtin_1 Input +%in_2 = OpVariable %ptr_builtin_2 Input + %main = OpFunction %void None %func + %5 = OpLabel + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateAndRetrieveValidationState()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("There must be at most one object per Storage Class " + "that can contain a structure type containing members " + "decorated with BuiltIn, consumed per entry-point.")); +} + +TEST_F(ValidateDecorations, + OneBuiltInObjectPerStorageClassConsumedByOpEntryPointGood) { + string spirv = R"( + OpCapability Shader + OpCapability Geometry + OpMemoryModel Logical GLSL450 + OpEntryPoint Geometry %main "main" %in_1 %out_1 + OpMemberDecorate %struct_1 0 BuiltIn InvocationId + OpMemberDecorate %struct_2 0 BuiltIn Position + %int = OpTypeInt 32 1 + %void = OpTypeVoid + %func = OpTypeFunction %void + %float = OpTypeFloat 32 + %struct_1 = OpTypeStruct %int + %struct_2 = OpTypeStruct %float +%ptr_builtin_1 = OpTypePointer Input %struct_1 +%ptr_builtin_2 = OpTypePointer Output %struct_2 +%in_1 = OpVariable %ptr_builtin_1 Input +%out_1 = OpVariable %ptr_builtin_2 Output + %main = OpFunction %void None %func + %5 = OpLabel + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState()); +} + +TEST_F(ValidateDecorations, NoBuiltInObjectsConsumedByOpEntryPointGood) { + string spirv = R"( + OpCapability Shader + OpCapability Geometry + OpMemoryModel Logical GLSL450 + OpEntryPoint Geometry %main "main" %in_1 %out_1 + %int = OpTypeInt 32 1 + %void = OpTypeVoid + %func = OpTypeFunction %void + %float = OpTypeFloat 32 + %struct_1 = OpTypeStruct %int + %struct_2 = OpTypeStruct %float +%ptr_builtin_1 = OpTypePointer Input %struct_1 +%ptr_builtin_2 = OpTypePointer Output %struct_2 +%in_1 = OpVariable %ptr_builtin_1 Input +%out_1 = OpVariable %ptr_builtin_2 Output + %main = OpFunction %void None %func + %5 = OpLabel + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState()); +} + +TEST_F(ValidateDecorations, EntryPointFunctionHasLinkageAttributeBad) { + string spirv = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %main "main" + OpDecorate %main LinkageAttributes "import_main" Import +%1 = OpTypeVoid +%2 = OpTypeFunction %1 +%main = OpFunction %1 None %2 +%4 = OpLabel + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("The LinkageAttributes Decoration (Linkage name: import_main) " + "cannot be applied to function id 1 because it is targeted by " + "an OpEntryPoint instruction.")); +} + +TEST_F(ValidateDecorations, FunctionDeclarationWithoutImportLinkageBad) { + string spirv = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + %void = OpTypeVoid + %func = OpTypeFunction %void + %main = OpFunction %void None %func + OpFunctionEnd + )"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateAndRetrieveValidationState()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Function declaration (id 3) must have a LinkageAttributes " + "decoration with the Import Linkage type.")); +} + +TEST_F(ValidateDecorations, FunctionDeclarationWithImportLinkageGood) { + string spirv = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpDecorate %main LinkageAttributes "link_fn" Import + %void = OpTypeVoid + %func = OpTypeFunction %void + %main = OpFunction %void None %func + OpFunctionEnd + )"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState()); +} + +TEST_F(ValidateDecorations, FunctionDeclarationWithExportLinkageBad) { + string spirv = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpDecorate %main LinkageAttributes "link_fn" Export + %void = OpTypeVoid + %func = OpTypeFunction %void + %main = OpFunction %void None %func + OpFunctionEnd + )"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateAndRetrieveValidationState()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Function declaration (id 1) must have a LinkageAttributes " + "decoration with the Import Linkage type.")); +} + +TEST_F(ValidateDecorations, FunctionDefinitionWithImportLinkageBad) { + string spirv = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpDecorate %main LinkageAttributes "link_fn" Import + %void = OpTypeVoid + %func = OpTypeFunction %void + %main = OpFunction %void None %func + %label = OpLabel + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateAndRetrieveValidationState()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Function definition (id 1) may not be decorated with " + "Import Linkage type.")); +} + +TEST_F(ValidateDecorations, FunctionDefinitionWithoutImportLinkageGood) { + string spirv = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + %void = OpTypeVoid + %func = OpTypeFunction %void + %main = OpFunction %void None %func + %label = OpLabel + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState()); +} + +TEST_F(ValidateDecorations, BuiltinVariablesGoodVulkan) { + const spv_target_env env = SPV_ENV_VULKAN_1_0; + std::string spirv = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %gl_FragCoord %_entryPointOutput +OpExecutionMode %main OriginUpperLeft +OpSource HLSL 500 +OpDecorate %gl_FragCoord BuiltIn FragCoord +OpDecorate %_entryPointOutput Location 0 +%void = OpTypeVoid +%3 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%float_0 = OpConstant %float 0 +%14 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%gl_FragCoord = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_entryPointOutput = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %3 +%5 = OpLabel +OpStore %_entryPointOutput %14 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, env); + EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState(env)); +} + +TEST_F(ValidateDecorations, BuiltinVariablesWithLocationDecorationVulkan) { + const spv_target_env env = SPV_ENV_VULKAN_1_0; + std::string spirv = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %gl_FragCoord %_entryPointOutput +OpExecutionMode %main OriginUpperLeft +OpSource HLSL 500 +OpDecorate %gl_FragCoord BuiltIn FragCoord +OpDecorate %gl_FragCoord Location 0 +OpDecorate %_entryPointOutput Location 0 +%void = OpTypeVoid +%3 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%float_0 = OpConstant %float 0 +%14 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%gl_FragCoord = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_entryPointOutput = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %3 +%5 = OpLabel +OpStore %_entryPointOutput %14 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, env); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState(env)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("A BuiltIn variable (id 2) cannot have any Location or " + "Component decorations")); +} +TEST_F(ValidateDecorations, BuiltinVariablesWithComponentDecorationVulkan) { + const spv_target_env env = SPV_ENV_VULKAN_1_0; + std::string spirv = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %gl_FragCoord %_entryPointOutput +OpExecutionMode %main OriginUpperLeft +OpSource HLSL 500 +OpDecorate %gl_FragCoord BuiltIn FragCoord +OpDecorate %gl_FragCoord Component 0 +OpDecorate %_entryPointOutput Location 0 +%void = OpTypeVoid +%3 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%float_0 = OpConstant %float 0 +%14 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%gl_FragCoord = OpVariable %_ptr_Input_v4float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_entryPointOutput = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %3 +%5 = OpLabel +OpStore %_entryPointOutput %14 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, env); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState(env)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("A BuiltIn variable (id 2) cannot have any Location or " + "Component decorations")); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/val/val_derivatives_test.cpp b/3rdparty/spirv-tools/test/val/val_derivatives_test.cpp new file mode 100644 index 000000000..864dad096 --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_derivatives_test.cpp @@ -0,0 +1,148 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "gmock/gmock.h" +#include "unit_spirv.h" +#include "val_fixtures.h" + +namespace { + +using ::testing::HasSubstr; +using ::testing::Not; + +using ValidateDerivatives = spvtest::ValidateBase; + +std::string GenerateShaderCode( + const std::string& body, + const std::string& capabilities_and_extensions = "", + const std::string& execution_model = "Fragment") { + std::stringstream ss; + ss << R"( +OpCapability Shader +OpCapability DerivativeControl +)"; + + ss << capabilities_and_extensions; + ss << "OpMemoryModel Logical GLSL450\n"; + ss << "OpEntryPoint " << execution_model << " %main \"main\"\n"; + + ss << R"( +%void = OpTypeVoid +%func = OpTypeFunction %void +%bool = OpTypeBool +%f32 = OpTypeFloat 32 +%u32 = OpTypeInt 32 0 +%s32 = OpTypeInt 32 1 +%f32vec4 = OpTypeVector %f32 4 + +%f32_ptr_input = OpTypePointer Input %f32 +%f32_var_input = OpVariable %f32_ptr_input Input + +%f32vec4_ptr_input = OpTypePointer Input %f32vec4 +%f32vec4_var_input = OpVariable %f32vec4_ptr_input Input + +%main = OpFunction %void None %func +%main_entry = OpLabel +)"; + + ss << body; + + ss << R"( +OpReturn +OpFunctionEnd)"; + + return ss.str(); +} + +TEST_F(ValidateDerivatives, ScalarSuccess) { + const std::string body = R"( +%f32_var = OpLoad %f32 %f32_var_input +%val1 = OpDPdx %f32 %f32_var +%val2 = OpDPdy %f32 %f32_var +%val3 = OpFwidth %f32 %f32_var +%val4 = OpDPdxFine %f32 %f32_var +%val5 = OpDPdyFine %f32 %f32_var +%val6 = OpFwidthFine %f32 %f32_var +%val7 = OpDPdxCoarse %f32 %f32_var +%val8 = OpDPdyCoarse %f32 %f32_var +%val9 = OpFwidthCoarse %f32 %f32_var +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateDerivatives, VectorSuccess) { + const std::string body = R"( +%f32vec4_var = OpLoad %f32vec4 %f32vec4_var_input +%val1 = OpDPdx %f32vec4 %f32vec4_var +%val2 = OpDPdy %f32vec4 %f32vec4_var +%val3 = OpFwidth %f32vec4 %f32vec4_var +%val4 = OpDPdxFine %f32vec4 %f32vec4_var +%val5 = OpDPdyFine %f32vec4 %f32vec4_var +%val6 = OpFwidthFine %f32vec4 %f32vec4_var +%val7 = OpDPdxCoarse %f32vec4 %f32vec4_var +%val8 = OpDPdyCoarse %f32vec4 %f32vec4_var +%val9 = OpFwidthCoarse %f32vec4 %f32vec4_var +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateDerivatives, OpDPdxWrongResultType) { + const std::string body = R"( +%f32_var = OpLoad %f32 %f32_var_input +%val1 = OpDPdx %u32 %f32vec4 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Result Type to be float scalar or vector type: " + "DPdx")); +} + +TEST_F(ValidateDerivatives, OpDPdxWrongPType) { + const std::string body = R"( +%f32vec4_var = OpLoad %f32vec4 %f32vec4_var_input +%val1 = OpDPdx %f32 %f32vec4_var +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected P type and Result Type to be the same: " + "DPdx")); +} + +TEST_F(ValidateDerivatives, OpDPdxWrongExecutionModel) { + const std::string body = R"( +%f32vec4_var = OpLoad %f32vec4 %f32vec4_var_input +%val1 = OpDPdx %f32vec4 %f32vec4_var +)"; + + CompileSuccessfully(GenerateShaderCode(body, "", "Vertex").c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Derivative instructions require Fragment execution model: DPdx")); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/val/val_ext_inst_test.cpp b/3rdparty/spirv-tools/test/val/val_ext_inst_test.cpp new file mode 100644 index 000000000..ee304ba76 --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_ext_inst_test.cpp @@ -0,0 +1,5761 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Tests validation rules of GLSL.450.std and OpenCL.std extended instructions. +// Doesn't test OpenCL.std vector size 2, 3, 4, 8 or 16 rules (not supported +// by standard SPIR-V). + +#include +#include + +#include "gmock/gmock.h" +#include "unit_spirv.h" +#include "val_fixtures.h" + +namespace { + +using ::testing::HasSubstr; +using ::testing::Not; + +using ValidateExtInst = spvtest::ValidateBase; +using ValidateGlslStd450SqrtLike = spvtest::ValidateBase; +using ValidateGlslStd450FMinLike = spvtest::ValidateBase; +using ValidateGlslStd450FClampLike = spvtest::ValidateBase; +using ValidateGlslStd450SAbsLike = spvtest::ValidateBase; +using ValidateGlslStd450UMinLike = spvtest::ValidateBase; +using ValidateGlslStd450UClampLike = spvtest::ValidateBase; +using ValidateGlslStd450SinLike = spvtest::ValidateBase; +using ValidateGlslStd450PowLike = spvtest::ValidateBase; +using ValidateGlslStd450Pack = spvtest::ValidateBase; +using ValidateGlslStd450Unpack = spvtest::ValidateBase; +using ValidateOpenCLStdSqrtLike = spvtest::ValidateBase; +using ValidateOpenCLStdFMinLike = spvtest::ValidateBase; +using ValidateOpenCLStdFClampLike = spvtest::ValidateBase; +using ValidateOpenCLStdSAbsLike = spvtest::ValidateBase; +using ValidateOpenCLStdUMinLike = spvtest::ValidateBase; +using ValidateOpenCLStdUClampLike = spvtest::ValidateBase; +using ValidateOpenCLStdUMul24Like = spvtest::ValidateBase; +using ValidateOpenCLStdUMad24Like = spvtest::ValidateBase; +using ValidateOpenCLStdLengthLike = spvtest::ValidateBase; +using ValidateOpenCLStdDistanceLike = spvtest::ValidateBase; +using ValidateOpenCLStdNormalizeLike = spvtest::ValidateBase; +using ValidateOpenCLStdVStoreHalfLike = spvtest::ValidateBase; +using ValidateOpenCLStdVLoadHalfLike = spvtest::ValidateBase; +using ValidateOpenCLStdFractLike = spvtest::ValidateBase; +using ValidateOpenCLStdFrexpLike = spvtest::ValidateBase; +using ValidateOpenCLStdLdexpLike = spvtest::ValidateBase; +using ValidateOpenCLStdUpsampleLike = spvtest::ValidateBase; + +// Returns number of components in Pack/Unpack extended instructions. +// |ext_inst_name| is expected to be of the format "PackHalf2x16". +// Number of components is assumed to be single-digit. +uint32_t GetPackedNumComponents(const std::string& ext_inst_name) { + const size_t x_index = ext_inst_name.find_last_of('x'); + const std::string num_components_str = + ext_inst_name.substr(x_index - 1, x_index); + return uint32_t(std::stoul(num_components_str)); +} + +// Returns packed bit width in Pack/Unpack extended instructions. +// |ext_inst_name| is expected to be of the format "PackHalf2x16". +uint32_t GetPackedBitWidth(const std::string& ext_inst_name) { + const size_t x_index = ext_inst_name.find_last_of('x'); + const std::string packed_bit_width_str = ext_inst_name.substr(x_index + 1); + return uint32_t(std::stoul(packed_bit_width_str)); +} + +std::string GenerateShaderCode( + const std::string& body, + const std::string& capabilities_and_extensions = "", + const std::string& execution_model = "Fragment") { + std::ostringstream ss; + ss << R"( +OpCapability Shader +OpCapability Float16 +OpCapability Float64 +OpCapability Int16 +OpCapability Int64 +)"; + + ss << capabilities_and_extensions; + ss << "%extinst = OpExtInstImport \"GLSL.std.450\"\n"; + ss << "OpMemoryModel Logical GLSL450\n"; + ss << "OpEntryPoint " << execution_model << " %main \"main\"\n"; + + ss << R"( +%void = OpTypeVoid +%func = OpTypeFunction %void +%bool = OpTypeBool +%f16 = OpTypeFloat 16 +%f32 = OpTypeFloat 32 +%f64 = OpTypeFloat 64 +%u32 = OpTypeInt 32 0 +%s32 = OpTypeInt 32 1 +%u64 = OpTypeInt 64 0 +%s64 = OpTypeInt 64 1 +%u16 = OpTypeInt 16 0 +%s16 = OpTypeInt 16 1 +%f32vec2 = OpTypeVector %f32 2 +%f32vec3 = OpTypeVector %f32 3 +%f32vec4 = OpTypeVector %f32 4 +%f64vec2 = OpTypeVector %f64 2 +%f64vec3 = OpTypeVector %f64 3 +%f64vec4 = OpTypeVector %f64 4 +%u32vec2 = OpTypeVector %u32 2 +%u32vec3 = OpTypeVector %u32 3 +%s32vec2 = OpTypeVector %s32 2 +%u32vec4 = OpTypeVector %u32 4 +%s32vec4 = OpTypeVector %s32 4 +%u64vec2 = OpTypeVector %u64 2 +%s64vec2 = OpTypeVector %s64 2 +%f64mat22 = OpTypeMatrix %f64vec2 2 +%f32mat22 = OpTypeMatrix %f32vec2 2 +%f32mat23 = OpTypeMatrix %f32vec2 3 +%f32mat32 = OpTypeMatrix %f32vec3 2 +%f32mat33 = OpTypeMatrix %f32vec3 3 + +%f32_0 = OpConstant %f32 0 +%f32_1 = OpConstant %f32 1 +%f32_2 = OpConstant %f32 2 +%f32_3 = OpConstant %f32 3 +%f32_4 = OpConstant %f32 4 +%f32_h = OpConstant %f32 0.5 +%f32vec2_01 = OpConstantComposite %f32vec2 %f32_0 %f32_1 +%f32vec2_12 = OpConstantComposite %f32vec2 %f32_1 %f32_2 +%f32vec3_012 = OpConstantComposite %f32vec3 %f32_0 %f32_1 %f32_2 +%f32vec3_123 = OpConstantComposite %f32vec3 %f32_1 %f32_2 %f32_3 +%f32vec4_0123 = OpConstantComposite %f32vec4 %f32_0 %f32_1 %f32_2 %f32_3 +%f32vec4_1234 = OpConstantComposite %f32vec4 %f32_1 %f32_2 %f32_3 %f32_4 + +%f64_0 = OpConstant %f64 0 +%f64_1 = OpConstant %f64 1 +%f64_2 = OpConstant %f64 2 +%f64_3 = OpConstant %f64 3 +%f64vec2_01 = OpConstantComposite %f64vec2 %f64_0 %f64_1 +%f64vec3_012 = OpConstantComposite %f64vec3 %f64_0 %f64_1 %f64_2 +%f64vec4_0123 = OpConstantComposite %f64vec4 %f64_0 %f64_1 %f64_2 %f64_3 + +%f16_0 = OpConstant %f16 0 +%f16_1 = OpConstant %f16 1 + +%u32_0 = OpConstant %u32 0 +%u32_1 = OpConstant %u32 1 +%u32_2 = OpConstant %u32 2 +%u32_3 = OpConstant %u32 3 + +%s32_0 = OpConstant %s32 0 +%s32_1 = OpConstant %s32 1 +%s32_2 = OpConstant %s32 2 +%s32_3 = OpConstant %s32 3 + +%u64_0 = OpConstant %u64 0 +%u64_1 = OpConstant %u64 1 +%u64_2 = OpConstant %u64 2 +%u64_3 = OpConstant %u64 3 + +%s64_0 = OpConstant %s64 0 +%s64_1 = OpConstant %s64 1 +%s64_2 = OpConstant %s64 2 +%s64_3 = OpConstant %s64 3 + +%s32vec2_01 = OpConstantComposite %s32vec2 %s32_0 %s32_1 +%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1 + +%s32vec2_12 = OpConstantComposite %s32vec2 %s32_1 %s32_2 +%u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2 + +%s32vec4_0123 = OpConstantComposite %s32vec4 %s32_0 %s32_1 %s32_2 %s32_3 +%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3 + +%s64vec2_01 = OpConstantComposite %s64vec2 %s64_0 %s64_1 +%u64vec2_01 = OpConstantComposite %u64vec2 %u64_0 %u64_1 + +%f32mat22_1212 = OpConstantComposite %f32mat22 %f32vec2_12 %f32vec2_12 +%f32mat23_121212 = OpConstantComposite %f32mat23 %f32vec2_12 %f32vec2_12 %f32vec2_12 + +%f32_ptr_output = OpTypePointer Output %f32 +%f32vec2_ptr_output = OpTypePointer Output %f32vec2 + +%u32_ptr_output = OpTypePointer Output %u32 +%u32vec2_ptr_output = OpTypePointer Output %u32vec2 + +%u64_ptr_output = OpTypePointer Output %u64 + +%f32_output = OpVariable %f32_ptr_output Output +%f32vec2_output = OpVariable %f32vec2_ptr_output Output + +%u32_output = OpVariable %u32_ptr_output Output +%u32vec2_output = OpVariable %u32vec2_ptr_output Output + +%u64_output = OpVariable %u64_ptr_output Output + +%f32_ptr_input = OpTypePointer Input %f32 +%f32vec2_ptr_input = OpTypePointer Input %f32vec2 + +%u32_ptr_input = OpTypePointer Input %u32 +%u32vec2_ptr_input = OpTypePointer Input %u32vec2 + +%u64_ptr_input = OpTypePointer Input %u64 + +%f32_input = OpVariable %f32_ptr_input Input +%f32vec2_input = OpVariable %f32vec2_ptr_input Input + +%u32_input = OpVariable %u32_ptr_input Input +%u32vec2_input = OpVariable %u32vec2_ptr_input Input + +%u64_input = OpVariable %u64_ptr_input Input + +%struct_f32_f32 = OpTypeStruct %f32 %f32 +%struct_f32_f32_f32 = OpTypeStruct %f32 %f32 %f32 +%struct_f32_u32 = OpTypeStruct %f32 %u32 +%struct_f32_u32_f32 = OpTypeStruct %f32 %u32 %f32 +%struct_u32_f32 = OpTypeStruct %u32 %f32 +%struct_u32_u32 = OpTypeStruct %u32 %u32 +%struct_f32_f64 = OpTypeStruct %f32 %f64 +%struct_f32vec2_f32vec2 = OpTypeStruct %f32vec2 %f32vec2 +%struct_f32vec2_u32vec2 = OpTypeStruct %f32vec2 %u32vec2 + +%main = OpFunction %void None %func +%main_entry = OpLabel +)"; + + ss << body; + + ss << R"( +OpReturn +OpFunctionEnd)"; + + return ss.str(); +} + +std::string GenerateKernelCode( + const std::string& body, + const std::string& capabilities_and_extensions = "", + const std::string& memory_model = "Physical32") { + std::ostringstream ss; + ss << R"( +OpCapability Addresses +OpCapability Kernel +OpCapability Linkage +OpCapability GenericPointer +OpCapability Int8 +OpCapability Int16 +OpCapability Int64 +OpCapability Float16 +OpCapability Float64 +OpCapability Vector16 +OpCapability Matrix +)"; + + ss << capabilities_and_extensions; + ss << "%extinst = OpExtInstImport \"OpenCL.std\"\n"; + ss << "OpMemoryModel " << memory_model << " OpenCL\n"; + + ss << R"( +%void = OpTypeVoid +%func = OpTypeFunction %void +%bool = OpTypeBool +%f16 = OpTypeFloat 16 +%f32 = OpTypeFloat 32 +%f64 = OpTypeFloat 64 +%u32 = OpTypeInt 32 0 +%u64 = OpTypeInt 64 0 +%u16 = OpTypeInt 16 0 +%u8 = OpTypeInt 8 0 +%f32vec2 = OpTypeVector %f32 2 +%f32vec3 = OpTypeVector %f32 3 +%f32vec4 = OpTypeVector %f32 4 +%f32vec8 = OpTypeVector %f32 8 +%f16vec8 = OpTypeVector %f16 8 +%f32vec16 = OpTypeVector %f32 16 +%f64vec2 = OpTypeVector %f64 2 +%f64vec3 = OpTypeVector %f64 3 +%f64vec4 = OpTypeVector %f64 4 +%u32vec2 = OpTypeVector %u32 2 +%u32vec3 = OpTypeVector %u32 3 +%u32vec4 = OpTypeVector %u32 4 +%u32vec8 = OpTypeVector %u32 8 +%u64vec2 = OpTypeVector %u64 2 +%f64mat22 = OpTypeMatrix %f64vec2 2 +%f32mat22 = OpTypeMatrix %f32vec2 2 +%f32mat23 = OpTypeMatrix %f32vec2 3 +%f32mat32 = OpTypeMatrix %f32vec3 2 +%f32mat33 = OpTypeMatrix %f32vec3 3 + +%f32_0 = OpConstant %f32 0 +%f32_1 = OpConstant %f32 1 +%f32_2 = OpConstant %f32 2 +%f32_3 = OpConstant %f32 3 +%f32_4 = OpConstant %f32 4 +%f32_h = OpConstant %f32 0.5 +%f32vec2_01 = OpConstantComposite %f32vec2 %f32_0 %f32_1 +%f32vec2_12 = OpConstantComposite %f32vec2 %f32_1 %f32_2 +%f32vec3_012 = OpConstantComposite %f32vec3 %f32_0 %f32_1 %f32_2 +%f32vec3_123 = OpConstantComposite %f32vec3 %f32_1 %f32_2 %f32_3 +%f32vec4_0123 = OpConstantComposite %f32vec4 %f32_0 %f32_1 %f32_2 %f32_3 +%f32vec4_1234 = OpConstantComposite %f32vec4 %f32_1 %f32_2 %f32_3 %f32_4 +%f32vec8_01010101 = OpConstantComposite %f32vec8 %f32_0 %f32_1 %f32_0 %f32_1 %f32_0 %f32_1 %f32_0 %f32_1 + +%f64_0 = OpConstant %f64 0 +%f64_1 = OpConstant %f64 1 +%f64_2 = OpConstant %f64 2 +%f64_3 = OpConstant %f64 3 +%f64vec2_01 = OpConstantComposite %f64vec2 %f64_0 %f64_1 +%f64vec3_012 = OpConstantComposite %f64vec3 %f64_0 %f64_1 %f64_2 +%f64vec4_0123 = OpConstantComposite %f64vec4 %f64_0 %f64_1 %f64_2 %f64_3 + +%f16_0 = OpConstant %f16 0 +%f16_1 = OpConstant %f16 1 + +%u8_0 = OpConstant %u8 0 +%u8_1 = OpConstant %u8 1 +%u8_2 = OpConstant %u8 2 +%u8_3 = OpConstant %u8 3 + +%u16_0 = OpConstant %u16 0 +%u16_1 = OpConstant %u16 1 +%u16_2 = OpConstant %u16 2 +%u16_3 = OpConstant %u16 3 + +%u32_0 = OpConstant %u32 0 +%u32_1 = OpConstant %u32 1 +%u32_2 = OpConstant %u32 2 +%u32_3 = OpConstant %u32 3 +%u32_256 = OpConstant %u32 256 + +%u64_0 = OpConstant %u64 0 +%u64_1 = OpConstant %u64 1 +%u64_2 = OpConstant %u64 2 +%u64_3 = OpConstant %u64 3 +%u64_256 = OpConstant %u64 256 + +%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1 +%u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2 +%u32vec3_012 = OpConstantComposite %u32vec3 %u32_0 %u32_1 %u32_2 +%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3 + +%u64vec2_01 = OpConstantComposite %u64vec2 %u64_0 %u64_1 + +%f32mat22_1212 = OpConstantComposite %f32mat22 %f32vec2_12 %f32vec2_12 +%f32mat23_121212 = OpConstantComposite %f32mat23 %f32vec2_12 %f32vec2_12 %f32vec2_12 + +%struct_f32_f32 = OpTypeStruct %f32 %f32 +%struct_f32_f32_f32 = OpTypeStruct %f32 %f32 %f32 +%struct_f32_u32 = OpTypeStruct %f32 %u32 +%struct_f32_u32_f32 = OpTypeStruct %f32 %u32 %f32 +%struct_u32_f32 = OpTypeStruct %u32 %f32 +%struct_u32_u32 = OpTypeStruct %u32 %u32 +%struct_f32_f64 = OpTypeStruct %f32 %f64 +%struct_f32vec2_f32vec2 = OpTypeStruct %f32vec2 %f32vec2 +%struct_f32vec2_u32vec2 = OpTypeStruct %f32vec2 %u32vec2 + +%f16vec8_ptr_workgroup = OpTypePointer Workgroup %f16vec8 +%f16vec8_workgroup = OpVariable %f16vec8_ptr_workgroup Workgroup +%f16_ptr_workgroup = OpTypePointer Workgroup %f16 + +%u32vec8_ptr_workgroup = OpTypePointer Workgroup %u32vec8 +%u32vec8_workgroup = OpVariable %u32vec8_ptr_workgroup Workgroup +%u32_ptr_workgroup = OpTypePointer Workgroup %u32 + +%f32vec8_ptr_workgroup = OpTypePointer Workgroup %f32vec8 +%f32vec8_workgroup = OpVariable %f32vec8_ptr_workgroup Workgroup +%f32_ptr_workgroup = OpTypePointer Workgroup %f32 + +%u32arr = OpTypeArray %u32 %u32_256 +%u32arr_ptr_cross_workgroup = OpTypePointer CrossWorkgroup %u32arr +%u32arr_cross_workgroup = OpVariable %u32arr_ptr_cross_workgroup CrossWorkgroup +%u32_ptr_cross_workgroup = OpTypePointer CrossWorkgroup %u32 + +%f32arr = OpTypeArray %f32 %u32_256 +%f32arr_ptr_cross_workgroup = OpTypePointer CrossWorkgroup %f32arr +%f32arr_cross_workgroup = OpVariable %f32arr_ptr_cross_workgroup CrossWorkgroup +%f32_ptr_cross_workgroup = OpTypePointer CrossWorkgroup %f32 + +%f32vec2arr = OpTypeArray %f32vec2 %u32_256 +%f32vec2arr_ptr_cross_workgroup = OpTypePointer CrossWorkgroup %f32vec2arr +%f32vec2arr_cross_workgroup = OpVariable %f32vec2arr_ptr_cross_workgroup CrossWorkgroup +%f32vec2_ptr_cross_workgroup = OpTypePointer CrossWorkgroup %f32vec2 + +%struct_arr = OpTypeArray %struct_f32_f32 %u32_256 +%struct_arr_ptr_cross_workgroup = OpTypePointer CrossWorkgroup %struct_arr +%struct_arr_cross_workgroup = OpVariable %struct_arr_ptr_cross_workgroup CrossWorkgroup +%struct_ptr_cross_workgroup = OpTypePointer CrossWorkgroup %struct_f32_f32 + +%f16vec8_ptr_uniform_constant = OpTypePointer UniformConstant %f16vec8 +%f16vec8_uniform_constant = OpVariable %f16vec8_ptr_uniform_constant UniformConstant +%f16_ptr_uniform_constant = OpTypePointer UniformConstant %f16 + +%u32vec8_ptr_uniform_constant = OpTypePointer UniformConstant %u32vec8 +%u32vec8_uniform_constant = OpVariable %u32vec8_ptr_uniform_constant UniformConstant +%u32_ptr_uniform_constant = OpTypePointer UniformConstant %u32 + +%f32vec8_ptr_uniform_constant = OpTypePointer UniformConstant %f32vec8 +%f32vec8_uniform_constant = OpVariable %f32vec8_ptr_uniform_constant UniformConstant +%f32_ptr_uniform_constant = OpTypePointer UniformConstant %f32 + +%f16vec8_ptr_input = OpTypePointer Input %f16vec8 +%f16vec8_input = OpVariable %f16vec8_ptr_input Input +%f16_ptr_input = OpTypePointer Input %f16 + +%f32_ptr_generic = OpTypePointer Generic %f32 +%u32_ptr_generic = OpTypePointer Generic %u32 + +%f32_ptr_function = OpTypePointer Function %f32 +%f32vec2_ptr_function = OpTypePointer Function %f32vec2 +%u32_ptr_function = OpTypePointer Function %u32 +%u64_ptr_function = OpTypePointer Function %u64 +%u32vec2_ptr_function = OpTypePointer Function %u32vec2 + +%u8arr = OpTypeArray %u8 %u32_256 +%u8arr_ptr_uniform_constant = OpTypePointer UniformConstant %u8arr +%u8arr_uniform_constant = OpVariable %u8arr_ptr_uniform_constant UniformConstant +%u8_ptr_uniform_constant = OpTypePointer UniformConstant %u8 +%u8_ptr_generic = OpTypePointer Generic %u8 + +%main = OpFunction %void None %func +%main_entry = OpLabel +)"; + + ss << body; + + ss << R"( +OpReturn +OpFunctionEnd)"; + + return ss.str(); +} + +TEST_P(ValidateGlslStd450SqrtLike, Success) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %f32 %extinst " << ext_inst_name << " %f32_0\n"; + ss << "%val2 = OpExtInst %f32vec2 %extinst " << ext_inst_name + << " %f32vec2_01\n"; + ss << "%val3 = OpExtInst %f64 %extinst " << ext_inst_name << " %f64_0\n"; + CompileSuccessfully(GenerateShaderCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateGlslStd450SqrtLike, IntResultType) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + " %f32_0\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected Result Type to be a float scalar " + "or vector type")); +} + +TEST_P(ValidateGlslStd450SqrtLike, IntOperand) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %f32 %extinst " + ext_inst_name + " %u32_0\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected types of all operands to be equal to " + "Result Type")); +} + +INSTANTIATE_TEST_CASE_P(AllSqrtLike, ValidateGlslStd450SqrtLike, + ::testing::ValuesIn(std::vector{ + "Round", + "RoundEven", + "FAbs", + "Trunc", + "FSign", + "Floor", + "Ceil", + "Fract", + "Sqrt", + "InverseSqrt", + "Normalize", + }), ); + +TEST_P(ValidateGlslStd450FMinLike, Success) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %f32 %extinst " << ext_inst_name + << " %f32_0 %f32_1\n"; + ss << "%val2 = OpExtInst %f32vec2 %extinst " << ext_inst_name + << " %f32vec2_01 %f32vec2_12\n"; + ss << "%val3 = OpExtInst %f64 %extinst " << ext_inst_name + << " %f64_0 %f64_0\n"; + CompileSuccessfully(GenerateShaderCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateGlslStd450FMinLike, IntResultType) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + " %f32_0 %f32_1\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected Result Type to be a float scalar " + "or vector type")); +} + +TEST_P(ValidateGlslStd450FMinLike, IntOperand1) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %f32 %extinst " + ext_inst_name + " %u32_0 %f32_1\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected types of all operands to be equal to " + "Result Type")); +} + +TEST_P(ValidateGlslStd450FMinLike, IntOperand2) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %f32 %extinst " + ext_inst_name + " %f32_0 %u32_1\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected types of all operands to be equal to " + "Result Type")); +} + +INSTANTIATE_TEST_CASE_P(AllFMinLike, ValidateGlslStd450FMinLike, + ::testing::ValuesIn(std::vector{ + "FMin", + "FMax", + "Step", + "Reflect", + "NMin", + "NMax", + }), ); + +TEST_P(ValidateGlslStd450FClampLike, Success) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %f32 %extinst " << ext_inst_name + << " %f32_0 %f32_1 %f32_2\n"; + ss << "%val2 = OpExtInst %f32vec2 %extinst " << ext_inst_name + << " %f32vec2_01 %f32vec2_01 %f32vec2_12\n"; + ss << "%val3 = OpExtInst %f64 %extinst " << ext_inst_name + << " %f64_0 %f64_0 %f64_1\n"; + CompileSuccessfully(GenerateShaderCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateGlslStd450FClampLike, IntResultType) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + + " %f32_0 %f32_1 %f32_2\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected Result Type to be a float scalar " + "or vector type")); +} + +TEST_P(ValidateGlslStd450FClampLike, IntOperand1) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %f32 %extinst " + ext_inst_name + + " %u32_0 %f32_0 %f32_1\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected types of all operands to be equal to " + "Result Type")); +} + +TEST_P(ValidateGlslStd450FClampLike, IntOperand2) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %f32 %extinst " + ext_inst_name + + " %f32_0 %u32_0 %f32_1\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected types of all operands to be equal to " + "Result Type")); +} + +TEST_P(ValidateGlslStd450FClampLike, IntOperand3) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %f32 %extinst " + ext_inst_name + + " %f32_1 %f32_0 %u32_2\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected types of all operands to be equal to " + "Result Type")); +} + +INSTANTIATE_TEST_CASE_P(AllFClampLike, ValidateGlslStd450FClampLike, + ::testing::ValuesIn(std::vector{ + "FClamp", + "FMix", + "SmoothStep", + "Fma", + "FaceForward", + "NClamp", + }), ); + +TEST_P(ValidateGlslStd450SAbsLike, Success) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %s32 %extinst " << ext_inst_name << " %u32_1\n"; + ss << "%val2 = OpExtInst %s32 %extinst " << ext_inst_name << " %s32_1\n"; + ss << "%val3 = OpExtInst %u32 %extinst " << ext_inst_name << " %u32_1\n"; + ss << "%val4 = OpExtInst %u32 %extinst " << ext_inst_name << " %s32_1\n"; + ss << "%val5 = OpExtInst %s32vec2 %extinst " << ext_inst_name + << " %s32vec2_01\n"; + ss << "%val6 = OpExtInst %u32vec2 %extinst " << ext_inst_name + << " %u32vec2_01\n"; + ss << "%val7 = OpExtInst %u32vec2 %extinst " << ext_inst_name + << " %s32vec2_01\n"; + ss << "%val8 = OpExtInst %s32vec2 %extinst " << ext_inst_name + << " %u32vec2_01\n"; + CompileSuccessfully(GenerateShaderCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateGlslStd450SAbsLike, FloatResultType) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %f32 %extinst " + ext_inst_name + " %u32_0\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected Result Type to be an int scalar " + "or vector type")); +} + +TEST_P(ValidateGlslStd450SAbsLike, FloatOperand) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %s32 %extinst " + ext_inst_name + " %f32_0\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected all operands to be int scalars or " + "vectors")); +} + +TEST_P(ValidateGlslStd450SAbsLike, WrongDimOperand) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %s32 %extinst " + ext_inst_name + " %s32vec2_01\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected all operands to have the same dimension as " + "Result Type")); +} + +TEST_P(ValidateGlslStd450SAbsLike, WrongBitWidthOperand) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %s64 %extinst " + ext_inst_name + " %s32_0\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected all operands to have the same bit width as " + "Result Type")); +} + +INSTANTIATE_TEST_CASE_P(AllSAbsLike, ValidateGlslStd450SAbsLike, + ::testing::ValuesIn(std::vector{ + "SAbs", + "SSign", + "FindILsb", + "FindUMsb", + "FindSMsb", + }), ); + +TEST_F(ValidateExtInst, FindUMsbNot32Bit) { + const std::string body = R"( +%val1 = OpExtInst %s64 %extinst FindUMsb %u64_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 FindUMsb: this instruction is currently " + "limited to 32-bit width components")); +} + +TEST_F(ValidateExtInst, FindSMsbNot32Bit) { + const std::string body = R"( +%val1 = OpExtInst %s64 %extinst FindSMsb %u64_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 FindSMsb: this instruction is currently " + "limited to 32-bit width components")); +} + +TEST_P(ValidateGlslStd450UMinLike, Success) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %s32 %extinst " << ext_inst_name + << " %u32_1 %s32_2\n"; + ss << "%val2 = OpExtInst %s32 %extinst " << ext_inst_name + << " %s32_1 %u32_2\n"; + ss << "%val3 = OpExtInst %u32 %extinst " << ext_inst_name + << " %u32_1 %s32_2\n"; + ss << "%val4 = OpExtInst %u32 %extinst " << ext_inst_name + << " %s32_1 %u32_2\n"; + ss << "%val5 = OpExtInst %s32vec2 %extinst " << ext_inst_name + << " %s32vec2_01 %u32vec2_01\n"; + ss << "%val6 = OpExtInst %u32vec2 %extinst " << ext_inst_name + << " %u32vec2_01 %s32vec2_01\n"; + ss << "%val7 = OpExtInst %u32vec2 %extinst " << ext_inst_name + << " %s32vec2_01 %u32vec2_01\n"; + ss << "%val8 = OpExtInst %s32vec2 %extinst " << ext_inst_name + << " %u32vec2_01 %s32vec2_01\n"; + ss << "%val9 = OpExtInst %s64 %extinst " << ext_inst_name + << " %u64_1 %s64_0\n"; + CompileSuccessfully(GenerateShaderCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateGlslStd450UMinLike, FloatResultType) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %f32 %extinst " + ext_inst_name + " %u32_0 %u32_0\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected Result Type to be an int scalar " + "or vector type")); +} + +TEST_P(ValidateGlslStd450UMinLike, FloatOperand1) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %s32 %extinst " + ext_inst_name + " %f32_0 %u32_0\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected all operands to be int scalars or " + "vectors")); +} + +TEST_P(ValidateGlslStd450UMinLike, FloatOperand2) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %s32 %extinst " + ext_inst_name + " %u32_0 %f32_0\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected all operands to be int scalars or " + "vectors")); +} + +TEST_P(ValidateGlslStd450UMinLike, WrongDimOperand1) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %s32 %extinst " + ext_inst_name + + " %s32vec2_01 %s32_0\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected all operands to have the same dimension as " + "Result Type")); +} + +TEST_P(ValidateGlslStd450UMinLike, WrongDimOperand2) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %s32 %extinst " + ext_inst_name + + " %s32_0 %s32vec2_01\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected all operands to have the same dimension as " + "Result Type")); +} + +TEST_P(ValidateGlslStd450UMinLike, WrongBitWidthOperand1) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %s64 %extinst " + ext_inst_name + " %s32_0 %s64_0\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected all operands to have the same bit width as " + "Result Type")); +} + +TEST_P(ValidateGlslStd450UMinLike, WrongBitWidthOperand2) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %s64 %extinst " + ext_inst_name + " %s64_0 %s32_0\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected all operands to have the same bit width as " + "Result Type")); +} + +INSTANTIATE_TEST_CASE_P(AllUMinLike, ValidateGlslStd450UMinLike, + ::testing::ValuesIn(std::vector{ + "UMin", + "SMin", + "UMax", + "SMax", + }), ); + +TEST_P(ValidateGlslStd450UClampLike, Success) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %s32 %extinst " << ext_inst_name + << " %s32_0 %u32_1 %s32_2\n"; + ss << "%val2 = OpExtInst %s32 %extinst " << ext_inst_name + << " %u32_0 %s32_1 %u32_2\n"; + ss << "%val3 = OpExtInst %u32 %extinst " << ext_inst_name + << " %s32_0 %u32_1 %s32_2\n"; + ss << "%val4 = OpExtInst %u32 %extinst " << ext_inst_name + << " %u32_0 %s32_1 %u32_2\n"; + ss << "%val5 = OpExtInst %s32vec2 %extinst " << ext_inst_name + << " %s32vec2_01 %u32vec2_01 %u32vec2_12\n"; + ss << "%val6 = OpExtInst %u32vec2 %extinst " << ext_inst_name + << " %u32vec2_01 %s32vec2_01 %s32vec2_12\n"; + ss << "%val7 = OpExtInst %u32vec2 %extinst " << ext_inst_name + << " %s32vec2_01 %u32vec2_01 %u32vec2_12\n"; + ss << "%val8 = OpExtInst %s32vec2 %extinst " << ext_inst_name + << " %u32vec2_01 %s32vec2_01 %s32vec2_12\n"; + ss << "%val9 = OpExtInst %s64 %extinst " << ext_inst_name + << " %u64_1 %s64_0 %s64_1\n"; + CompileSuccessfully(GenerateShaderCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateGlslStd450UClampLike, FloatResultType) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %f32 %extinst " + ext_inst_name + + " %u32_0 %u32_0 %u32_1\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected Result Type to be an int scalar " + "or vector type")); +} + +TEST_P(ValidateGlslStd450UClampLike, FloatOperand1) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %s32 %extinst " + ext_inst_name + + " %f32_0 %u32_0 %u32_1\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected all operands to be int scalars or " + "vectors")); +} + +TEST_P(ValidateGlslStd450UClampLike, FloatOperand2) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %s32 %extinst " + ext_inst_name + + " %u32_0 %f32_0 %u32_1\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected all operands to be int scalars or " + "vectors")); +} + +TEST_P(ValidateGlslStd450UClampLike, FloatOperand3) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %s32 %extinst " + ext_inst_name + + " %u32_0 %u32_0 %f32_1\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected all operands to be int scalars or " + "vectors")); +} + +TEST_P(ValidateGlslStd450UClampLike, WrongDimOperand1) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %s32 %extinst " + ext_inst_name + + " %s32vec2_01 %s32_0 %u32_1\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected all operands to have the same dimension as " + "Result Type")); +} + +TEST_P(ValidateGlslStd450UClampLike, WrongDimOperand2) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %s32 %extinst " + ext_inst_name + + " %s32_0 %s32vec2_01 %u32_1\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected all operands to have the same dimension as " + "Result Type")); +} + +TEST_P(ValidateGlslStd450UClampLike, WrongDimOperand3) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %s32 %extinst " + ext_inst_name + + " %s32_0 %u32_1 %s32vec2_01\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected all operands to have the same dimension as " + "Result Type")); +} + +TEST_P(ValidateGlslStd450UClampLike, WrongBitWidthOperand1) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %s64 %extinst " + ext_inst_name + + " %s32_0 %s64_0 %s64_1\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected all operands to have the same bit width as " + "Result Type")); +} + +TEST_P(ValidateGlslStd450UClampLike, WrongBitWidthOperand2) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %s64 %extinst " + ext_inst_name + + " %s64_0 %s32_0 %s64_1\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected all operands to have the same bit width as " + "Result Type")); +} + +TEST_P(ValidateGlslStd450UClampLike, WrongBitWidthOperand3) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %s64 %extinst " + ext_inst_name + + " %s64_0 %s64_0 %s32_1\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected all operands to have the same bit width as " + "Result Type")); +} + +INSTANTIATE_TEST_CASE_P(AllUClampLike, ValidateGlslStd450UClampLike, + ::testing::ValuesIn(std::vector{ + "UClamp", + "SClamp", + }), ); + +TEST_P(ValidateGlslStd450SinLike, Success) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %f32 %extinst " << ext_inst_name << " %f32_0\n"; + ss << "%val2 = OpExtInst %f32vec2 %extinst " << ext_inst_name + << " %f32vec2_01\n"; + CompileSuccessfully(GenerateShaderCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateGlslStd450SinLike, IntResultType) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + " %f32_0\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected Result Type to be a 16 or 32-bit scalar " + "or vector float type")); +} + +TEST_P(ValidateGlslStd450SinLike, F64ResultType) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %f64 %extinst " + ext_inst_name + " %f32_0\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected Result Type to be a 16 or 32-bit scalar " + "or vector float type")); +} + +TEST_P(ValidateGlslStd450SinLike, IntOperand) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %f32 %extinst " + ext_inst_name + " %u32_0\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected types of all operands to be equal to " + "Result Type")); +} + +INSTANTIATE_TEST_CASE_P(AllSinLike, ValidateGlslStd450SinLike, + ::testing::ValuesIn(std::vector{ + "Radians", + "Degrees", + "Sin", + "Cos", + "Tan", + "Asin", + "Acos", + "Atan", + "Sinh", + "Cosh", + "Tanh", + "Asinh", + "Acosh", + "Atanh", + "Exp", + "Exp2", + "Log", + "Log2", + }), ); + +TEST_P(ValidateGlslStd450PowLike, Success) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %f32 %extinst " << ext_inst_name + << " %f32_1 %f32_1\n"; + ss << "%val2 = OpExtInst %f32vec2 %extinst " << ext_inst_name + << " %f32vec2_01 %f32vec2_12\n"; + CompileSuccessfully(GenerateShaderCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateGlslStd450PowLike, IntResultType) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + " %f32_1 %f32_0\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected Result Type to be a 16 or 32-bit scalar " + "or vector float type")); +} + +TEST_P(ValidateGlslStd450PowLike, F64ResultType) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %f64 %extinst " + ext_inst_name + " %f32_1 %f32_0\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected Result Type to be a 16 or 32-bit scalar " + "or vector float type")); +} + +TEST_P(ValidateGlslStd450PowLike, IntOperand1) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %f32 %extinst " + ext_inst_name + " %u32_0 %f32_1\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected types of all operands to be equal to " + "Result Type")); +} + +TEST_P(ValidateGlslStd450PowLike, IntOperand2) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %f32 %extinst " + ext_inst_name + " %f32_0 %u32_1\n"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 " + ext_inst_name + + ": expected types of all operands to be equal to " + "Result Type")); +} + +INSTANTIATE_TEST_CASE_P(AllPowLike, ValidateGlslStd450PowLike, + ::testing::ValuesIn(std::vector{ + "Atan2", + "Pow", + }), ); + +TEST_F(ValidateExtInst, GlslStd450DeterminantSuccess) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst Determinant %f32mat22_1212 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, GlslStd450DeterminantIncompatibleResultType) { + const std::string body = R"( +%val1 = OpExtInst %f64 %extinst Determinant %f32mat22_1212 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Determinant: " + "expected operand X component type to be equal to " + "Result Type")); +} + +TEST_F(ValidateExtInst, GlslStd450DeterminantNotMatrix) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst Determinant %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Determinant: " + "expected operand X to be a square matrix")); +} + +TEST_F(ValidateExtInst, GlslStd450DeterminantMatrixNotSquare) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst Determinant %f32mat23_121212 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Determinant: " + "expected operand X to be a square matrix")); +} + +TEST_F(ValidateExtInst, GlslStd450MatrixInverseSuccess) { + const std::string body = R"( +%val1 = OpExtInst %f32mat22 %extinst MatrixInverse %f32mat22_1212 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, GlslStd450MatrixInverseIncompatibleResultType) { + const std::string body = R"( +%val1 = OpExtInst %f32mat33 %extinst MatrixInverse %f32mat22_1212 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 MatrixInverse: " + "expected operand X type to be equal to " + "Result Type")); +} + +TEST_F(ValidateExtInst, GlslStd450MatrixInverseNotMatrix) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst MatrixInverse %f32mat22_1212 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 MatrixInverse: " + "expected Result Type to be a square matrix")); +} + +TEST_F(ValidateExtInst, GlslStd450MatrixInverseMatrixNotSquare) { + const std::string body = R"( +%val1 = OpExtInst %f32mat23 %extinst MatrixInverse %f32mat23_121212 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 MatrixInverse: " + "expected Result Type to be a square matrix")); +} + +TEST_F(ValidateExtInst, GlslStd450ModfSuccess) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst Modf %f32_h %f32_output +%val2 = OpExtInst %f32vec2 %extinst Modf %f32vec2_01 %f32vec2_output +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, GlslStd450ModfIntResultType) { + const std::string body = R"( +%val1 = OpExtInst %u32 %extinst Modf %f32_h %f32_output +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Modf: " + "expected Result Type to be a scalar or vector " + "float type")); +} + +TEST_F(ValidateExtInst, GlslStd450ModfXNotOfResultType) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst Modf %f64_0 %f32_output +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Modf: " + "expected operand X type to be equal to Result Type")); +} + +TEST_F(ValidateExtInst, GlslStd450ModfINotPointer) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst Modf %f32_h %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Modf: " + "expected operand I to be a pointer")); +} + +TEST_F(ValidateExtInst, GlslStd450ModfIDataNotOfResultType) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst Modf %f32_h %f32vec2_output +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Modf: " + "expected operand I data type to be equal to " + "Result Type")); +} + +TEST_F(ValidateExtInst, GlslStd450ModfStructSuccess) { + const std::string body = R"( +%val1 = OpExtInst %struct_f32_f32 %extinst ModfStruct %f32_h +%val2 = OpExtInst %struct_f32vec2_f32vec2 %extinst ModfStruct %f32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, GlslStd450ModfStructResultTypeNotStruct) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst ModfStruct %f32_h +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 ModfStruct: " + "expected Result Type to be a struct with two " + "identical scalar or vector float type members")); +} + +TEST_F(ValidateExtInst, GlslStd450ModfStructResultTypeStructWrongSize) { + const std::string body = R"( +%val1 = OpExtInst %struct_f32_f32_f32 %extinst ModfStruct %f32_h +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 ModfStruct: " + "expected Result Type to be a struct with two " + "identical scalar or vector float type members")); +} + +TEST_F(ValidateExtInst, GlslStd450ModfStructResultTypeStructWrongFirstMember) { + const std::string body = R"( +%val1 = OpExtInst %struct_u32_f32 %extinst ModfStruct %f32_h +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 ModfStruct: " + "expected Result Type to be a struct with two " + "identical scalar or vector float type members")); +} + +TEST_F(ValidateExtInst, GlslStd450ModfStructResultTypeStructMembersNotEqual) { + const std::string body = R"( +%val1 = OpExtInst %struct_f32_f64 %extinst ModfStruct %f32_h +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 ModfStruct: " + "expected Result Type to be a struct with two " + "identical scalar or vector float type members")); +} + +TEST_F(ValidateExtInst, GlslStd450ModfStructXWrongType) { + const std::string body = R"( +%val1 = OpExtInst %struct_f32_f32 %extinst ModfStruct %f64_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 ModfStruct: " + "expected operand X type to be equal to members of " + "Result Type struct")); +} + +TEST_F(ValidateExtInst, GlslStd450FrexpSuccess) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst Frexp %f32_h %u32_output +%val2 = OpExtInst %f32vec2 %extinst Frexp %f32vec2_01 %u32vec2_output +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, GlslStd450FrexpIntResultType) { + const std::string body = R"( +%val1 = OpExtInst %u32 %extinst Frexp %f32_h %u32_output +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Frexp: " + "expected Result Type to be a scalar or vector " + "float type")); +} + +TEST_F(ValidateExtInst, GlslStd450FrexpWrongXType) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst Frexp %u32_1 %u32_output +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Frexp: " + "expected operand X type to be equal to Result Type")); +} + +TEST_F(ValidateExtInst, GlslStd450FrexpExpNotPointer) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst Frexp %f32_1 %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Frexp: " + "expected operand Exp to be a pointer")); +} + +TEST_F(ValidateExtInst, GlslStd450FrexpExpNotInt32Pointer) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst Frexp %f32_1 %f32_output +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Frexp: " + "expected operand Exp data type to be a 32-bit int " + "scalar or vector type")); +} + +TEST_F(ValidateExtInst, GlslStd450FrexpExpWrongComponentNumber) { + const std::string body = R"( +%val1 = OpExtInst %f32vec2 %extinst Frexp %f32vec2_01 %u32_output +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Frexp: " + "expected operand Exp data type to have the same " + "component number as Result Type")); +} + +TEST_F(ValidateExtInst, GlslStd450LdexpSuccess) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst Ldexp %f32_h %u32_2 +%val2 = OpExtInst %f32vec2 %extinst Ldexp %f32vec2_01 %u32vec2_12 +%val3 = OpExtInst %f32 %extinst Ldexp %f32_h %u64_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, GlslStd450LdexpIntResultType) { + const std::string body = R"( +%val1 = OpExtInst %u32 %extinst Ldexp %f32_h %u32_2 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Ldexp: " + "expected Result Type to be a scalar or vector " + "float type")); +} + +TEST_F(ValidateExtInst, GlslStd450LdexpWrongXType) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst Ldexp %u32_1 %u32_2 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Ldexp: " + "expected operand X type to be equal to Result Type")); +} + +TEST_F(ValidateExtInst, GlslStd450LdexpFloatExp) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst Ldexp %f32_1 %f32_2 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Ldexp: " + "expected operand Exp to be a 32-bit int scalar " + "or vector type")); +} + +TEST_F(ValidateExtInst, GlslStd450LdexpExpWrongSize) { + const std::string body = R"( +%val1 = OpExtInst %f32vec2 %extinst Ldexp %f32vec2_12 %u32_2 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Ldexp: " + "expected operand Exp to have the same component " + "number as Result Type")); +} + +TEST_F(ValidateExtInst, GlslStd450FrexpStructSuccess) { + const std::string body = R"( +%val1 = OpExtInst %struct_f32_u32 %extinst FrexpStruct %f32_h +%val2 = OpExtInst %struct_f32vec2_u32vec2 %extinst FrexpStruct %f32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, GlslStd450FrexpStructResultTypeNotStruct) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst FrexpStruct %f32_h +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 FrexpStruct: " + "expected Result Type to be a struct with two members, " + "first member a float scalar or vector, second member " + "a 32-bit int scalar or vector with the same number of " + "components as the first member")); +} + +TEST_F(ValidateExtInst, GlslStd450FrexpStructResultTypeStructWrongSize) { + const std::string body = R"( +%val1 = OpExtInst %struct_f32_u32_f32 %extinst FrexpStruct %f32_h +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 FrexpStruct: " + "expected Result Type to be a struct with two members, " + "first member a float scalar or vector, second member " + "a 32-bit int scalar or vector with the same number of " + "components as the first member")); +} + +TEST_F(ValidateExtInst, GlslStd450FrexpStructResultTypeStructWrongMember1) { + const std::string body = R"( +%val1 = OpExtInst %struct_u32_u32 %extinst FrexpStruct %f32_h +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 FrexpStruct: " + "expected Result Type to be a struct with two members, " + "first member a float scalar or vector, second member " + "a 32-bit int scalar or vector with the same number of " + "components as the first member")); +} + +TEST_F(ValidateExtInst, GlslStd450FrexpStructResultTypeStructWrongMember2) { + const std::string body = R"( +%val1 = OpExtInst %struct_f32_f32 %extinst FrexpStruct %f32_h +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 FrexpStruct: " + "expected Result Type to be a struct with two members, " + "first member a float scalar or vector, second member " + "a 32-bit int scalar or vector with the same number of " + "components as the first member")); +} + +TEST_F(ValidateExtInst, GlslStd450FrexpStructXWrongType) { + const std::string body = R"( +%val1 = OpExtInst %struct_f32_u32 %extinst FrexpStruct %f64_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 FrexpStruct: " + "expected operand X type to be equal to the first " + "member of Result Type struct")); +} + +TEST_P(ValidateGlslStd450Pack, Success) { + const std::string ext_inst_name = GetParam(); + const uint32_t num_components = GetPackedNumComponents(ext_inst_name); + const uint32_t packed_bit_width = GetPackedBitWidth(ext_inst_name); + const uint32_t total_bit_width = num_components * packed_bit_width; + const std::string vec_str = + num_components == 2 ? " %f32vec2_01\n" : " %f32vec4_0123\n"; + + std::ostringstream body; + body << "%val1 = OpExtInst %u" << total_bit_width << " %extinst " + << ext_inst_name << vec_str; + body << "%val2 = OpExtInst %s" << total_bit_width << " %extinst " + << ext_inst_name << vec_str; + CompileSuccessfully(GenerateShaderCode(body.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateGlslStd450Pack, Float32ResultType) { + const std::string ext_inst_name = GetParam(); + const uint32_t num_components = GetPackedNumComponents(ext_inst_name); + const uint32_t packed_bit_width = GetPackedBitWidth(ext_inst_name); + const uint32_t total_bit_width = num_components * packed_bit_width; + const std::string vec_str = + num_components == 2 ? " %f32vec2_01\n" : " %f32vec4_0123\n"; + + std::ostringstream body; + body << "%val1 = OpExtInst %f" << total_bit_width << " %extinst " + << ext_inst_name << vec_str; + + std::ostringstream expected; + expected << "GLSL.std.450 " << ext_inst_name + << ": expected Result Type to be " << total_bit_width + << "-bit int scalar type"; + + CompileSuccessfully(GenerateShaderCode(body.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(expected.str())); +} + +TEST_P(ValidateGlslStd450Pack, Int16ResultType) { + const std::string ext_inst_name = GetParam(); + const uint32_t num_components = GetPackedNumComponents(ext_inst_name); + const uint32_t packed_bit_width = GetPackedBitWidth(ext_inst_name); + const uint32_t total_bit_width = num_components * packed_bit_width; + const std::string vec_str = + num_components == 2 ? " %f32vec2_01\n" : " %f32vec4_0123\n"; + + std::ostringstream body; + body << "%val1 = OpExtInst %u16 %extinst " << ext_inst_name << vec_str; + + std::ostringstream expected; + expected << "GLSL.std.450 " << ext_inst_name + << ": expected Result Type to be " << total_bit_width + << "-bit int scalar type"; + + CompileSuccessfully(GenerateShaderCode(body.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(expected.str())); +} + +TEST_P(ValidateGlslStd450Pack, VNotVector) { + const std::string ext_inst_name = GetParam(); + const uint32_t num_components = GetPackedNumComponents(ext_inst_name); + const uint32_t packed_bit_width = GetPackedBitWidth(ext_inst_name); + const uint32_t total_bit_width = num_components * packed_bit_width; + + std::ostringstream body; + body << "%val1 = OpExtInst %u" << total_bit_width << " %extinst " + << ext_inst_name << " %f32_1\n"; + + std::ostringstream expected; + expected << "GLSL.std.450 " << ext_inst_name + << ": expected operand V to be a 32-bit float vector of size " + << num_components; + + CompileSuccessfully(GenerateShaderCode(body.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(expected.str())); +} + +TEST_P(ValidateGlslStd450Pack, VNotFloatVector) { + const std::string ext_inst_name = GetParam(); + const uint32_t num_components = GetPackedNumComponents(ext_inst_name); + const uint32_t packed_bit_width = GetPackedBitWidth(ext_inst_name); + const uint32_t total_bit_width = num_components * packed_bit_width; + const std::string vec_str = + num_components == 2 ? " %u32vec2_01\n" : " %u32vec4_0123\n"; + + std::ostringstream body; + body << "%val1 = OpExtInst %u" << total_bit_width << " %extinst " + << ext_inst_name << vec_str; + + std::ostringstream expected; + expected << "GLSL.std.450 " << ext_inst_name + << ": expected operand V to be a 32-bit float vector of size " + << num_components; + + CompileSuccessfully(GenerateShaderCode(body.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(expected.str())); +} + +TEST_P(ValidateGlslStd450Pack, VNotFloat32Vector) { + const std::string ext_inst_name = GetParam(); + const uint32_t num_components = GetPackedNumComponents(ext_inst_name); + const uint32_t packed_bit_width = GetPackedBitWidth(ext_inst_name); + const uint32_t total_bit_width = num_components * packed_bit_width; + const std::string vec_str = + num_components == 2 ? " %f64vec2_01\n" : " %f64vec4_0123\n"; + + std::ostringstream body; + body << "%val1 = OpExtInst %u" << total_bit_width << " %extinst " + << ext_inst_name << vec_str; + + std::ostringstream expected; + expected << "GLSL.std.450 " << ext_inst_name + << ": expected operand V to be a 32-bit float vector of size " + << num_components; + + CompileSuccessfully(GenerateShaderCode(body.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(expected.str())); +} + +TEST_P(ValidateGlslStd450Pack, VWrongSizeVector) { + const std::string ext_inst_name = GetParam(); + const uint32_t num_components = GetPackedNumComponents(ext_inst_name); + const uint32_t packed_bit_width = GetPackedBitWidth(ext_inst_name); + const uint32_t total_bit_width = num_components * packed_bit_width; + const std::string vec_str = + num_components == 4 ? " %f32vec2_01\n" : " %f32vec4_0123\n"; + + std::ostringstream body; + body << "%val1 = OpExtInst %u" << total_bit_width << " %extinst " + << ext_inst_name << vec_str; + + std::ostringstream expected; + expected << "GLSL.std.450 " << ext_inst_name + << ": expected operand V to be a 32-bit float vector of size " + << num_components; + + CompileSuccessfully(GenerateShaderCode(body.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(expected.str())); +} + +INSTANTIATE_TEST_CASE_P(AllPack, ValidateGlslStd450Pack, + ::testing::ValuesIn(std::vector{ + "PackSnorm4x8", + "PackUnorm4x8", + "PackSnorm2x16", + "PackUnorm2x16", + "PackHalf2x16", + }), ); + +TEST_F(ValidateExtInst, PackDouble2x32Success) { + const std::string body = R"( +%val1 = OpExtInst %f64 %extinst PackDouble2x32 %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, PackDouble2x32Float32ResultType) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst PackDouble2x32 %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 PackDouble2x32: expected Result Type to " + "be 64-bit float scalar type")); +} + +TEST_F(ValidateExtInst, PackDouble2x32Int64ResultType) { + const std::string body = R"( +%val1 = OpExtInst %u64 %extinst PackDouble2x32 %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 PackDouble2x32: expected Result Type to " + "be 64-bit float scalar type")); +} + +TEST_F(ValidateExtInst, PackDouble2x32VNotVector) { + const std::string body = R"( +%val1 = OpExtInst %f64 %extinst PackDouble2x32 %u64_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 PackDouble2x32: expected operand V to be " + "a 32-bit int vector of size 2")); +} + +TEST_F(ValidateExtInst, PackDouble2x32VNotIntVector) { + const std::string body = R"( +%val1 = OpExtInst %f64 %extinst PackDouble2x32 %f32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 PackDouble2x32: expected operand V to be " + "a 32-bit int vector of size 2")); +} + +TEST_F(ValidateExtInst, PackDouble2x32VNotInt32Vector) { + const std::string body = R"( +%val1 = OpExtInst %f64 %extinst PackDouble2x32 %u64vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 PackDouble2x32: expected operand V to be " + "a 32-bit int vector of size 2")); +} + +TEST_F(ValidateExtInst, PackDouble2x32VWrongSize) { + const std::string body = R"( +%val1 = OpExtInst %f64 %extinst PackDouble2x32 %u32vec4_0123 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 PackDouble2x32: expected operand V to be " + "a 32-bit int vector of size 2")); +} + +TEST_P(ValidateGlslStd450Unpack, Success) { + const std::string ext_inst_name = GetParam(); + const uint32_t num_components = GetPackedNumComponents(ext_inst_name); + const uint32_t packed_bit_width = GetPackedBitWidth(ext_inst_name); + const uint32_t total_bit_width = num_components * packed_bit_width; + const std::string result_type_str = + num_components == 2 ? "%f32vec2" : " %f32vec4"; + + std::ostringstream body; + body << "%val1 = OpExtInst " << result_type_str << " %extinst " + << ext_inst_name << " %u" << total_bit_width << "_1\n"; + body << "%val2 = OpExtInst " << result_type_str << " %extinst " + << ext_inst_name << " %s" << total_bit_width << "_1\n"; + CompileSuccessfully(GenerateShaderCode(body.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateGlslStd450Unpack, ResultTypeNotVector) { + const std::string ext_inst_name = GetParam(); + const uint32_t num_components = GetPackedNumComponents(ext_inst_name); + const uint32_t packed_bit_width = GetPackedBitWidth(ext_inst_name); + const uint32_t total_bit_width = num_components * packed_bit_width; + const std::string result_type_str = "%f32"; + + std::ostringstream body; + body << "%val1 = OpExtInst " << result_type_str << " %extinst " + << ext_inst_name << " %u" << total_bit_width << "_1\n"; + + std::ostringstream expected; + expected << "GLSL.std.450 " << ext_inst_name + << ": expected Result Type to be a 32-bit float vector of size " + << num_components; + + CompileSuccessfully(GenerateShaderCode(body.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(expected.str())); +} + +TEST_P(ValidateGlslStd450Unpack, ResultTypeNotFloatVector) { + const std::string ext_inst_name = GetParam(); + const uint32_t num_components = GetPackedNumComponents(ext_inst_name); + const uint32_t packed_bit_width = GetPackedBitWidth(ext_inst_name); + const uint32_t total_bit_width = num_components * packed_bit_width; + const std::string result_type_str = + num_components == 2 ? "%u32vec2" : " %u32vec4"; + + std::ostringstream body; + body << "%val1 = OpExtInst " << result_type_str << " %extinst " + << ext_inst_name << " %u" << total_bit_width << "_1\n"; + + std::ostringstream expected; + expected << "GLSL.std.450 " << ext_inst_name + << ": expected Result Type to be a 32-bit float vector of size " + << num_components; + + CompileSuccessfully(GenerateShaderCode(body.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(expected.str())); +} + +TEST_P(ValidateGlslStd450Unpack, ResultTypeNotFloat32Vector) { + const std::string ext_inst_name = GetParam(); + const uint32_t num_components = GetPackedNumComponents(ext_inst_name); + const uint32_t packed_bit_width = GetPackedBitWidth(ext_inst_name); + const uint32_t total_bit_width = num_components * packed_bit_width; + const std::string result_type_str = + num_components == 2 ? "%f64vec2" : " %f64vec4"; + + std::ostringstream body; + body << "%val1 = OpExtInst " << result_type_str << " %extinst " + << ext_inst_name << " %u" << total_bit_width << "_1\n"; + + std::ostringstream expected; + expected << "GLSL.std.450 " << ext_inst_name + << ": expected Result Type to be a 32-bit float vector of size " + << num_components; + + CompileSuccessfully(GenerateShaderCode(body.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(expected.str())); +} + +TEST_P(ValidateGlslStd450Unpack, ResultTypeWrongSize) { + const std::string ext_inst_name = GetParam(); + const uint32_t num_components = GetPackedNumComponents(ext_inst_name); + const uint32_t packed_bit_width = GetPackedBitWidth(ext_inst_name); + const uint32_t total_bit_width = num_components * packed_bit_width; + const std::string result_type_str = + num_components == 4 ? "%f32vec2" : " %f32vec4"; + + std::ostringstream body; + body << "%val1 = OpExtInst " << result_type_str << " %extinst " + << ext_inst_name << " %u" << total_bit_width << "_1\n"; + + std::ostringstream expected; + expected << "GLSL.std.450 " << ext_inst_name + << ": expected Result Type to be a 32-bit float vector of size " + << num_components; + + CompileSuccessfully(GenerateShaderCode(body.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(expected.str())); +} + +TEST_P(ValidateGlslStd450Unpack, ResultPNotInt) { + const std::string ext_inst_name = GetParam(); + const uint32_t num_components = GetPackedNumComponents(ext_inst_name); + const uint32_t packed_bit_width = GetPackedBitWidth(ext_inst_name); + const uint32_t total_bit_width = num_components * packed_bit_width; + const std::string result_type_str = + num_components == 2 ? "%f32vec2" : " %f32vec4"; + + std::ostringstream body; + body << "%val1 = OpExtInst " << result_type_str << " %extinst " + << ext_inst_name << " %f" << total_bit_width << "_1\n"; + + std::ostringstream expected; + expected << "GLSL.std.450 " << ext_inst_name + << ": expected operand P to be a " << total_bit_width + << "-bit int scalar"; + + CompileSuccessfully(GenerateShaderCode(body.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(expected.str())); +} + +TEST_P(ValidateGlslStd450Unpack, ResultPWrongBitWidth) { + const std::string ext_inst_name = GetParam(); + const uint32_t num_components = GetPackedNumComponents(ext_inst_name); + const uint32_t packed_bit_width = GetPackedBitWidth(ext_inst_name); + const uint32_t total_bit_width = num_components * packed_bit_width; + const uint32_t wrong_bit_width = total_bit_width == 32 ? 64 : 32; + const std::string result_type_str = + num_components == 2 ? "%f32vec2" : " %f32vec4"; + + std::ostringstream body; + body << "%val1 = OpExtInst " << result_type_str << " %extinst " + << ext_inst_name << " %u" << wrong_bit_width << "_1\n"; + + std::ostringstream expected; + expected << "GLSL.std.450 " << ext_inst_name + << ": expected operand P to be a " << total_bit_width + << "-bit int scalar"; + + CompileSuccessfully(GenerateShaderCode(body.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(expected.str())); +} + +INSTANTIATE_TEST_CASE_P(AllUnpack, ValidateGlslStd450Unpack, + ::testing::ValuesIn(std::vector{ + "UnpackSnorm4x8", + "UnpackUnorm4x8", + "UnpackSnorm2x16", + "UnpackUnorm2x16", + "UnpackHalf2x16", + }), ); + +TEST_F(ValidateExtInst, UnpackDouble2x32Success) { + const std::string body = R"( +%val1 = OpExtInst %u32vec2 %extinst UnpackDouble2x32 %f64_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, UnpackDouble2x32ResultTypeNotVector) { + const std::string body = R"( +%val1 = OpExtInst %u64 %extinst UnpackDouble2x32 %f64_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 UnpackDouble2x32: expected Result Type " + "to be a 32-bit int vector of size 2")); +} + +TEST_F(ValidateExtInst, UnpackDouble2x32ResultTypeNotIntVector) { + const std::string body = R"( +%val1 = OpExtInst %f32vec2 %extinst UnpackDouble2x32 %f64_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 UnpackDouble2x32: expected Result Type " + "to be a 32-bit int vector of size 2")); +} + +TEST_F(ValidateExtInst, UnpackDouble2x32ResultTypeNotInt32Vector) { + const std::string body = R"( +%val1 = OpExtInst %u64vec2 %extinst UnpackDouble2x32 %f64_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 UnpackDouble2x32: expected Result Type " + "to be a 32-bit int vector of size 2")); +} + +TEST_F(ValidateExtInst, UnpackDouble2x32ResultTypeWrongSize) { + const std::string body = R"( +%val1 = OpExtInst %u32vec4 %extinst UnpackDouble2x32 %f64_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 UnpackDouble2x32: expected Result Type " + "to be a 32-bit int vector of size 2")); +} + +TEST_F(ValidateExtInst, UnpackDouble2x32VNotFloat) { + const std::string body = R"( +%val1 = OpExtInst %u32vec2 %extinst UnpackDouble2x32 %u64_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 UnpackDouble2x32: expected operand V to " + "be a 64-bit float scalar")); +} + +TEST_F(ValidateExtInst, UnpackDouble2x32VNotFloat64) { + const std::string body = R"( +%val1 = OpExtInst %u32vec2 %extinst UnpackDouble2x32 %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 UnpackDouble2x32: expected operand V to " + "be a 64-bit float scalar")); +} + +TEST_F(ValidateExtInst, GlslStd450LengthSuccess) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst Length %f32_1 +%val2 = OpExtInst %f32 %extinst Length %f32vec2_01 +%val3 = OpExtInst %f32 %extinst Length %f32vec4_0123 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, GlslStd450LengthIntResultType) { + const std::string body = R"( +%val1 = OpExtInst %u32 %extinst Length %f32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Length: " + "expected Result Type to be a float scalar type")); +} + +TEST_F(ValidateExtInst, GlslStd450LengthIntX) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst Length %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Length: " + "expected operand X to be of float scalar or " + "vector type")); +} + +TEST_F(ValidateExtInst, GlslStd450LengthDifferentType) { + const std::string body = R"( +%val1 = OpExtInst %f64 %extinst Length %f32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Length: " + "expected operand X component type to be equal to " + "Result Type")); +} + +TEST_F(ValidateExtInst, GlslStd450DistanceSuccess) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst Distance %f32_0 %f32_1 +%val2 = OpExtInst %f32 %extinst Distance %f32vec2_01 %f32vec2_12 +%val3 = OpExtInst %f32 %extinst Distance %f32vec4_0123 %f32vec4_1234 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, GlslStd450DistanceIntResultType) { + const std::string body = R"( +%val1 = OpExtInst %u32 %extinst Distance %f32vec2_01 %f32vec2_12 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Distance: " + "expected Result Type to be a float scalar type")); +} + +TEST_F(ValidateExtInst, GlslStd450DistanceIntP0) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst Distance %u32_0 %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Distance: " + "expected operand P0 to be of float scalar or " + "vector type")); +} + +TEST_F(ValidateExtInst, GlslStd450DistanceF64VectorP0) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst Distance %f64vec2_01 %f32vec2_12 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Distance: " + "expected operand P0 component type to be equal to " + "Result Type")); +} + +TEST_F(ValidateExtInst, GlslStd450DistanceIntP1) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst Distance %f32_0 %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Distance: " + "expected operand P1 to be of float scalar or " + "vector type")); +} + +TEST_F(ValidateExtInst, GlslStd450DistanceF64VectorP1) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst Distance %f32vec2_12 %f64vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Distance: " + "expected operand P1 component type to be equal to " + "Result Type")); +} + +TEST_F(ValidateExtInst, GlslStd450DistanceDifferentSize) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst Distance %f32vec2_01 %f32vec4_0123 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Distance: " + "expected operands P0 and P1 to have the same number " + "of components")); +} + +TEST_F(ValidateExtInst, GlslStd450CrossSuccess) { + const std::string body = R"( +%val1 = OpExtInst %f32vec3 %extinst Cross %f32vec3_012 %f32vec3_123 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, GlslStd450CrossIntVectorResultType) { + const std::string body = R"( +%val1 = OpExtInst %u32vec3 %extinst Cross %f32vec3_012 %f32vec3_123 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Cross: " + "expected Result Type to be a float vector type")); +} + +TEST_F(ValidateExtInst, GlslStd450CrossResultTypeWrongSize) { + const std::string body = R"( +%val1 = OpExtInst %f32vec2 %extinst Cross %f32vec3_012 %f32vec3_123 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Cross: " + "expected Result Type to have 3 components")); +} + +TEST_F(ValidateExtInst, GlslStd450CrossXWrongType) { + const std::string body = R"( +%val1 = OpExtInst %f32vec3 %extinst Cross %f64vec3_012 %f32vec3_123 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Cross: " + "expected operand X type to be equal to Result Type")); +} + +TEST_F(ValidateExtInst, GlslStd450CrossYWrongType) { + const std::string body = R"( +%val1 = OpExtInst %f32vec3 %extinst Cross %f32vec3_123 %f64vec3_012 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Cross: " + "expected operand Y type to be equal to Result Type")); +} + +TEST_F(ValidateExtInst, GlslStd450RefractSuccess) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst Refract %f32_1 %f32_1 %f32_1 +%val2 = OpExtInst %f32vec2 %extinst Refract %f32vec2_01 %f32vec2_01 %f16_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, GlslStd450RefractIntVectorResultType) { + const std::string body = R"( +%val1 = OpExtInst %u32vec2 %extinst Refract %f32vec2_01 %f32vec2_01 %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Refract: " + "expected Result Type to be a float scalar or " + "vector type")); +} + +TEST_F(ValidateExtInst, GlslStd450RefractIntVectorI) { + const std::string body = R"( +%val1 = OpExtInst %f32vec2 %extinst Refract %u32vec2_01 %f32vec2_01 %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Refract: " + "expected operand I to be of type equal to " + "Result Type")); +} + +TEST_F(ValidateExtInst, GlslStd450RefractIntVectorN) { + const std::string body = R"( +%val1 = OpExtInst %f32vec2 %extinst Refract %f32vec2_01 %u32vec2_01 %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Refract: " + "expected operand N to be of type equal to " + "Result Type")); +} + +TEST_F(ValidateExtInst, GlslStd450RefractIntEta) { + const std::string body = R"( +%val1 = OpExtInst %f32vec2 %extinst Refract %f32vec2_01 %f32vec2_01 %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Refract: " + "expected operand Eta to be a 16 or 32-bit " + "float scalar")); +} + +TEST_F(ValidateExtInst, GlslStd450RefractFloat64Eta) { + const std::string body = R"( +%val1 = OpExtInst %f32vec2 %extinst Refract %f32vec2_01 %f32vec2_01 %f64_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 Refract: " + "expected operand Eta to be a 16 or 32-bit " + "float scalar")); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtCentroidSuccess) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst InterpolateAtCentroid %f32_input +%val2 = OpExtInst %f32vec2 %extinst InterpolateAtCentroid %f32vec2_input +)"; + + CompileSuccessfully( + GenerateShaderCode(body, "OpCapability InterpolationFunction\n")); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtCentroidNoCapability) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst InterpolateAtCentroid %f32_input +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_CAPABILITY, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 InterpolateAtCentroid requires " + "capability InterpolationFunction")); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtCentroidIntResultType) { + const std::string body = R"( +%val1 = OpExtInst %u32 %extinst InterpolateAtCentroid %f32_input +)"; + + CompileSuccessfully( + GenerateShaderCode(body, "OpCapability InterpolationFunction\n")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 InterpolateAtCentroid: " + "expected Result Type to be a 32-bit float scalar " + "or vector type")); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtCentroidF64ResultType) { + const std::string body = R"( +%val1 = OpExtInst %f64 %extinst InterpolateAtCentroid %f32_input +)"; + + CompileSuccessfully( + GenerateShaderCode(body, "OpCapability InterpolationFunction\n")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 InterpolateAtCentroid: " + "expected Result Type to be a 32-bit float scalar " + "or vector type")); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtCentroidNotPointer) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst InterpolateAtCentroid %f32_1 +)"; + + CompileSuccessfully( + GenerateShaderCode(body, "OpCapability InterpolationFunction\n")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 InterpolateAtCentroid: " + "expected Interpolant to be a pointer")); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtCentroidWrongDataType) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst InterpolateAtCentroid %f32vec2_input +)"; + + CompileSuccessfully( + GenerateShaderCode(body, "OpCapability InterpolationFunction\n")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 InterpolateAtCentroid: " + "expected Interpolant data type to be equal to " + "Result Type")); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtCentroidWrongStorageClass) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst InterpolateAtCentroid %f32_output +)"; + + CompileSuccessfully( + GenerateShaderCode(body, "OpCapability InterpolationFunction\n")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 InterpolateAtCentroid: " + "expected Interpolant storage class to be Input")); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtCentroidWrongExecutionModel) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst InterpolateAtCentroid %f32_input +)"; + + CompileSuccessfully(GenerateShaderCode( + body, "OpCapability InterpolationFunction\n", "Vertex")); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 InterpolateAtCentroid requires " + "Fragment execution model")); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtSampleSuccess) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst InterpolateAtSample %f32_input %u32_1 +%val2 = OpExtInst %f32vec2 %extinst InterpolateAtSample %f32vec2_input %u32_1 +)"; + + CompileSuccessfully( + GenerateShaderCode(body, "OpCapability InterpolationFunction\n")); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtSampleNoCapability) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst InterpolateAtSample %f32_input %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_CAPABILITY, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 InterpolateAtSample requires " + "capability InterpolationFunction")); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtSampleIntResultType) { + const std::string body = R"( +%val1 = OpExtInst %u32 %extinst InterpolateAtSample %f32_input %u32_1 +)"; + + CompileSuccessfully( + GenerateShaderCode(body, "OpCapability InterpolationFunction\n")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 InterpolateAtSample: " + "expected Result Type to be a 32-bit float scalar " + "or vector type")); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtSampleF64ResultType) { + const std::string body = R"( +%val1 = OpExtInst %f64 %extinst InterpolateAtSample %f32_input %u32_1 +)"; + + CompileSuccessfully( + GenerateShaderCode(body, "OpCapability InterpolationFunction\n")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 InterpolateAtSample: " + "expected Result Type to be a 32-bit float scalar " + "or vector type")); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtSampleNotPointer) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst InterpolateAtSample %f32_1 %u32_1 +)"; + + CompileSuccessfully( + GenerateShaderCode(body, "OpCapability InterpolationFunction\n")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 InterpolateAtSample: " + "expected Interpolant to be a pointer")); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtSampleWrongDataType) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst InterpolateAtSample %f32vec2_input %u32_1 +)"; + + CompileSuccessfully( + GenerateShaderCode(body, "OpCapability InterpolationFunction\n")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 InterpolateAtSample: " + "expected Interpolant data type to be equal to " + "Result Type")); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtSampleWrongStorageClass) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst InterpolateAtSample %f32_output %u32_1 +)"; + + CompileSuccessfully( + GenerateShaderCode(body, "OpCapability InterpolationFunction\n")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 InterpolateAtSample: " + "expected Interpolant storage class to be Input")); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtSampleFloatSample) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst InterpolateAtSample %f32_input %f32_1 +)"; + + CompileSuccessfully( + GenerateShaderCode(body, "OpCapability InterpolationFunction\n")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 InterpolateAtSample: " + "expected Sample to be 32-bit integer")); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtSampleU64Sample) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst InterpolateAtSample %f32_input %u64_1 +)"; + + CompileSuccessfully( + GenerateShaderCode(body, "OpCapability InterpolationFunction\n")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 InterpolateAtSample: " + "expected Sample to be 32-bit integer")); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtSampleWrongExecutionModel) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst InterpolateAtSample %f32_input %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode( + body, "OpCapability InterpolationFunction\n", "Vertex")); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 InterpolateAtSample requires " + "Fragment execution model")); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtOffsetSuccess) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst InterpolateAtOffset %f32_input %f32vec2_01 +%val2 = OpExtInst %f32vec2 %extinst InterpolateAtOffset %f32vec2_input %f32vec2_01 +)"; + + CompileSuccessfully( + GenerateShaderCode(body, "OpCapability InterpolationFunction\n")); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtOffsetNoCapability) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst InterpolateAtOffset %f32_input %f32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_CAPABILITY, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 InterpolateAtOffset requires " + "capability InterpolationFunction")); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtOffsetIntResultType) { + const std::string body = R"( +%val1 = OpExtInst %u32 %extinst InterpolateAtOffset %f32_input %f32vec2_01 +)"; + + CompileSuccessfully( + GenerateShaderCode(body, "OpCapability InterpolationFunction\n")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 InterpolateAtOffset: " + "expected Result Type to be a 32-bit float scalar " + "or vector type")); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtOffsetF64ResultType) { + const std::string body = R"( +%val1 = OpExtInst %f64 %extinst InterpolateAtOffset %f32_input %f32vec2_01 +)"; + + CompileSuccessfully( + GenerateShaderCode(body, "OpCapability InterpolationFunction\n")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 InterpolateAtOffset: " + "expected Result Type to be a 32-bit float scalar " + "or vector type")); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtOffsetNotPointer) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst InterpolateAtOffset %f32_1 %f32vec2_01 +)"; + + CompileSuccessfully( + GenerateShaderCode(body, "OpCapability InterpolationFunction\n")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 InterpolateAtOffset: " + "expected Interpolant to be a pointer")); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtOffsetWrongDataType) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst InterpolateAtOffset %f32vec2_input %f32vec2_01 +)"; + + CompileSuccessfully( + GenerateShaderCode(body, "OpCapability InterpolationFunction\n")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 InterpolateAtOffset: " + "expected Interpolant data type to be equal to " + "Result Type")); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtOffsetWrongStorageClass) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst InterpolateAtOffset %f32_output %f32vec2_01 +)"; + + CompileSuccessfully( + GenerateShaderCode(body, "OpCapability InterpolationFunction\n")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 InterpolateAtOffset: " + "expected Interpolant storage class to be Input")); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtOffsetOffsetNotVector) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst InterpolateAtOffset %f32_input %f32_0 +)"; + + CompileSuccessfully( + GenerateShaderCode(body, "OpCapability InterpolationFunction\n")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 InterpolateAtOffset: " + "expected Offset to be a vector of 2 32-bit floats")); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtOffsetOffsetNotVector2) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst InterpolateAtOffset %f32_input %f32vec3_012 +)"; + + CompileSuccessfully( + GenerateShaderCode(body, "OpCapability InterpolationFunction\n")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 InterpolateAtOffset: " + "expected Offset to be a vector of 2 32-bit floats")); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtOffsetOffsetNotFloatVector) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst InterpolateAtOffset %f32_input %u32vec2_01 +)"; + + CompileSuccessfully( + GenerateShaderCode(body, "OpCapability InterpolationFunction\n")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 InterpolateAtOffset: " + "expected Offset to be a vector of 2 32-bit floats")); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtOffsetOffsetNotFloat32Vector) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst InterpolateAtOffset %f32_input %f64vec2_01 +)"; + + CompileSuccessfully( + GenerateShaderCode(body, "OpCapability InterpolationFunction\n")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 InterpolateAtOffset: " + "expected Offset to be a vector of 2 32-bit floats")); +} + +TEST_F(ValidateExtInst, GlslStd450InterpolateAtOffsetWrongExecutionModel) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst InterpolateAtOffset %f32_input %f32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode( + body, "OpCapability InterpolationFunction\n", "Vertex")); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("GLSL.std.450 InterpolateAtOffset requires " + "Fragment execution model")); +} + +TEST_P(ValidateOpenCLStdSqrtLike, Success) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %f32 %extinst " << ext_inst_name << " %f32_0\n"; + ss << "%val2 = OpExtInst %f32vec2 %extinst " << ext_inst_name + << " %f32vec2_01\n"; + ss << "%val3 = OpExtInst %f32vec4 %extinst " << ext_inst_name + << " %f32vec4_0123\n"; + ss << "%val4 = OpExtInst %f64 %extinst " << ext_inst_name << " %f64_0\n"; + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateOpenCLStdSqrtLike, IntResultType) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + " %f32_0\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected Result Type to be a float scalar " + "or vector type")); +} + +TEST_P(ValidateOpenCLStdSqrtLike, IntOperand) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %f32 %extinst " + ext_inst_name + " %u32_0\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected types of all operands to be equal to " + "Result Type")); +} + +INSTANTIATE_TEST_CASE_P( + AllSqrtLike, ValidateOpenCLStdSqrtLike, + ::testing::ValuesIn(std::vector{ + "acos", "acosh", "acospi", "asin", + "asinh", "asinpi", "atan", "atanh", + "atanpi", "cbrt", "ceil", "cos", + "cosh", "cospi", "erfc", "erf", + "exp", "exp2", "exp10", "expm1", + "fabs", "floor", "log", "log2", + "log10", "log1p", "logb", "rint", + "round", "rsqrt", "sin", "sinh", + "sinpi", "sqrt", "tan", "tanh", + "tanpi", "tgamma", "trunc", "half_cos", + "half_exp", "half_exp2", "half_exp10", "half_log", + "half_log2", "half_log10", "half_recip", "half_rsqrt", + "half_sin", "half_sqrt", "half_tan", "lgamma", + "native_cos", "native_exp", "native_exp2", "native_exp10", + "native_log", "native_log2", "native_log10", "native_recip", + "native_rsqrt", "native_sin", "native_sqrt", "native_tan", + "degrees", "radians", "sign", + }), ); + +TEST_P(ValidateOpenCLStdFMinLike, Success) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %f32 %extinst " << ext_inst_name + << " %f32_0 %f32_1\n"; + ss << "%val2 = OpExtInst %f32vec2 %extinst " << ext_inst_name + << " %f32vec2_01 %f32vec2_12\n"; + ss << "%val3 = OpExtInst %f64 %extinst " << ext_inst_name + << " %f64_0 %f64_0\n"; + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateOpenCLStdFMinLike, IntResultType) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + " %f32_0 %f32_1\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected Result Type to be a float scalar " + "or vector type")); +} + +TEST_P(ValidateOpenCLStdFMinLike, IntOperand1) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %f32 %extinst " + ext_inst_name + " %u32_0 %f32_1\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected types of all operands to be equal to " + "Result Type")); +} + +TEST_P(ValidateOpenCLStdFMinLike, IntOperand2) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %f32 %extinst " + ext_inst_name + " %f32_0 %u32_1\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected types of all operands to be equal to " + "Result Type")); +} + +INSTANTIATE_TEST_CASE_P(AllFMinLike, ValidateOpenCLStdFMinLike, + ::testing::ValuesIn(std::vector{ + "atan2", "atan2pi", "copysign", + "fdim", "fmax", "fmin", + "fmod", "maxmag", "minmag", + "hypot", "nextafter", "pow", + "powr", "remainder", "half_divide", + "half_powr", "native_divide", "native_powr", + "step", "fmax_common", "fmin_common", + }), ); + +TEST_P(ValidateOpenCLStdFClampLike, Success) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %f32 %extinst " << ext_inst_name + << " %f32_0 %f32_1 %f32_2\n"; + ss << "%val2 = OpExtInst %f32vec2 %extinst " << ext_inst_name + << " %f32vec2_01 %f32vec2_01 %f32vec2_12\n"; + ss << "%val3 = OpExtInst %f64 %extinst " << ext_inst_name + << " %f64_0 %f64_0 %f64_1\n"; + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateOpenCLStdFClampLike, IntResultType) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + + " %f32_0 %f32_1 %f32_2\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected Result Type to be a float scalar " + "or vector type")); +} + +TEST_P(ValidateOpenCLStdFClampLike, IntOperand1) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %f32 %extinst " + ext_inst_name + + " %u32_0 %f32_0 %f32_1\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected types of all operands to be equal to " + "Result Type")); +} + +TEST_P(ValidateOpenCLStdFClampLike, IntOperand2) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %f32 %extinst " + ext_inst_name + + " %f32_0 %u32_0 %f32_1\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected types of all operands to be equal to " + "Result Type")); +} + +TEST_P(ValidateOpenCLStdFClampLike, IntOperand3) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %f32 %extinst " + ext_inst_name + + " %f32_1 %f32_0 %u32_2\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected types of all operands to be equal to " + "Result Type")); +} + +INSTANTIATE_TEST_CASE_P(AllFClampLike, ValidateOpenCLStdFClampLike, + ::testing::ValuesIn(std::vector{ + "fma", + "mad", + "fclamp", + "mix", + "smoothstep", + }), ); + +TEST_P(ValidateOpenCLStdSAbsLike, Success) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %u32 %extinst " << ext_inst_name << " %u32_1\n"; + ss << "%val2 = OpExtInst %u32 %extinst " << ext_inst_name << " %u32_1\n"; + ss << "%val3 = OpExtInst %u32 %extinst " << ext_inst_name << " %u32_1\n"; + ss << "%val4 = OpExtInst %u32 %extinst " << ext_inst_name << " %u32_1\n"; + ss << "%val5 = OpExtInst %u32vec2 %extinst " << ext_inst_name + << " %u32vec2_01\n"; + ss << "%val6 = OpExtInst %u32vec2 %extinst " << ext_inst_name + << " %u32vec2_01\n"; + ss << "%val7 = OpExtInst %u32vec2 %extinst " << ext_inst_name + << " %u32vec2_01\n"; + ss << "%val8 = OpExtInst %u32vec2 %extinst " << ext_inst_name + << " %u32vec2_01\n"; + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateOpenCLStdSAbsLike, FloatResultType) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %f32 %extinst " + ext_inst_name + " %u32_0\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected Result Type to be an int scalar " + "or vector type")); +} + +TEST_P(ValidateOpenCLStdSAbsLike, FloatOperand) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + " %f32_0\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected types of all operands to be equal to Result Type")); +} + +TEST_P(ValidateOpenCLStdSAbsLike, U64Operand) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + " %u64_0\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected types of all operands to be equal to Result Type")); +} + +INSTANTIATE_TEST_CASE_P(AllSAbsLike, ValidateOpenCLStdSAbsLike, + ::testing::ValuesIn(std::vector{ + "s_abs", + "clz", + "ctz", + "popcount", + "u_abs", + }), ); + +TEST_P(ValidateOpenCLStdUMinLike, Success) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %u32 %extinst " << ext_inst_name + << " %u32_1 %u32_2\n"; + ss << "%val2 = OpExtInst %u32 %extinst " << ext_inst_name + << " %u32_1 %u32_2\n"; + ss << "%val3 = OpExtInst %u32 %extinst " << ext_inst_name + << " %u32_1 %u32_2\n"; + ss << "%val4 = OpExtInst %u32 %extinst " << ext_inst_name + << " %u32_1 %u32_2\n"; + ss << "%val5 = OpExtInst %u32vec2 %extinst " << ext_inst_name + << " %u32vec2_01 %u32vec2_01\n"; + ss << "%val6 = OpExtInst %u32vec2 %extinst " << ext_inst_name + << " %u32vec2_01 %u32vec2_01\n"; + ss << "%val7 = OpExtInst %u32vec2 %extinst " << ext_inst_name + << " %u32vec2_01 %u32vec2_01\n"; + ss << "%val8 = OpExtInst %u32vec2 %extinst " << ext_inst_name + << " %u32vec2_01 %u32vec2_01\n"; + ss << "%val9 = OpExtInst %u64 %extinst " << ext_inst_name + << " %u64_1 %u64_0\n"; + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateOpenCLStdUMinLike, FloatResultType) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %f32 %extinst " + ext_inst_name + " %u32_0 %u32_0\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected Result Type to be an int scalar " + "or vector type")); +} + +TEST_P(ValidateOpenCLStdUMinLike, FloatOperand1) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + " %f32_0 %u32_0\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected types of all operands to be equal to Result Type")); +} + +TEST_P(ValidateOpenCLStdUMinLike, FloatOperand2) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + " %u32_0 %f32_0\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected types of all operands to be equal to Result Type")); +} + +TEST_P(ValidateOpenCLStdUMinLike, U64Operand1) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + " %u64_0 %u32_0\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected types of all operands to be equal to Result Type")); +} + +TEST_P(ValidateOpenCLStdUMinLike, U64Operand2) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + " %u32_0 %u64_0\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected types of all operands to be equal to Result Type")); +} + +INSTANTIATE_TEST_CASE_P(AllUMinLike, ValidateOpenCLStdUMinLike, + ::testing::ValuesIn(std::vector{ + "s_max", + "u_max", + "s_min", + "u_min", + "s_abs_diff", + "s_add_sat", + "u_add_sat", + "s_mul_hi", + "rotate", + "s_sub_sat", + "u_sub_sat", + "s_hadd", + "u_hadd", + "s_rhadd", + "u_rhadd", + "u_abs_diff", + "u_mul_hi", + }), ); + +TEST_P(ValidateOpenCLStdUClampLike, Success) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %u32 %extinst " << ext_inst_name + << " %u32_0 %u32_1 %u32_2\n"; + ss << "%val2 = OpExtInst %u32 %extinst " << ext_inst_name + << " %u32_0 %u32_1 %u32_2\n"; + ss << "%val3 = OpExtInst %u32 %extinst " << ext_inst_name + << " %u32_0 %u32_1 %u32_2\n"; + ss << "%val4 = OpExtInst %u32 %extinst " << ext_inst_name + << " %u32_0 %u32_1 %u32_2\n"; + ss << "%val5 = OpExtInst %u32vec2 %extinst " << ext_inst_name + << " %u32vec2_01 %u32vec2_01 %u32vec2_12\n"; + ss << "%val6 = OpExtInst %u32vec2 %extinst " << ext_inst_name + << " %u32vec2_01 %u32vec2_01 %u32vec2_12\n"; + ss << "%val7 = OpExtInst %u32vec2 %extinst " << ext_inst_name + << " %u32vec2_01 %u32vec2_01 %u32vec2_12\n"; + ss << "%val8 = OpExtInst %u32vec2 %extinst " << ext_inst_name + << " %u32vec2_01 %u32vec2_01 %u32vec2_12\n"; + ss << "%val9 = OpExtInst %u64 %extinst " << ext_inst_name + << " %u64_1 %u64_0 %u64_1\n"; + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateOpenCLStdUClampLike, FloatResultType) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %f32 %extinst " + ext_inst_name + + " %u32_0 %u32_0 %u32_1\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected Result Type to be an int scalar " + "or vector type")); +} + +TEST_P(ValidateOpenCLStdUClampLike, FloatOperand1) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + + " %f32_0 %u32_0 %u32_1\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected types of all operands to be equal to Result Type")); +} + +TEST_P(ValidateOpenCLStdUClampLike, FloatOperand2) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + + " %u32_0 %f32_0 %u32_1\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected types of all operands to be equal to Result Type")); +} + +TEST_P(ValidateOpenCLStdUClampLike, FloatOperand3) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + + " %u32_0 %u32_0 %f32_1\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected types of all operands to be equal to Result Type")); +} + +TEST_P(ValidateOpenCLStdUClampLike, U64Operand1) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + + " %f32_0 %u32_0 %u64_1\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected types of all operands to be equal to Result Type")); +} + +TEST_P(ValidateOpenCLStdUClampLike, U64Operand2) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + + " %u32_0 %f32_0 %u64_1\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected types of all operands to be equal to Result Type")); +} + +TEST_P(ValidateOpenCLStdUClampLike, U64Operand3) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + + " %u32_0 %u32_0 %u64_1\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected types of all operands to be equal to Result Type")); +} + +INSTANTIATE_TEST_CASE_P(AllUClampLike, ValidateOpenCLStdUClampLike, + ::testing::ValuesIn(std::vector{ + "s_clamp", + "u_clamp", + "s_mad_hi", + "u_mad_sat", + "s_mad_sat", + "u_mad_hi", + }), ); + +// ------------------------------------------------------------- +TEST_P(ValidateOpenCLStdUMul24Like, Success) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %u32 %extinst " << ext_inst_name + << " %u32_1 %u32_2\n"; + ss << "%val2 = OpExtInst %u32 %extinst " << ext_inst_name + << " %u32_1 %u32_2\n"; + ss << "%val3 = OpExtInst %u32 %extinst " << ext_inst_name + << " %u32_1 %u32_2\n"; + ss << "%val4 = OpExtInst %u32 %extinst " << ext_inst_name + << " %u32_1 %u32_2\n"; + ss << "%val5 = OpExtInst %u32vec2 %extinst " << ext_inst_name + << " %u32vec2_01 %u32vec2_01\n"; + ss << "%val6 = OpExtInst %u32vec2 %extinst " << ext_inst_name + << " %u32vec2_01 %u32vec2_01\n"; + ss << "%val7 = OpExtInst %u32vec2 %extinst " << ext_inst_name + << " %u32vec2_01 %u32vec2_01\n"; + ss << "%val8 = OpExtInst %u32vec2 %extinst " << ext_inst_name + << " %u32vec2_01 %u32vec2_01\n"; + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateOpenCLStdUMul24Like, FloatResultType) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %f32 %extinst " + ext_inst_name + " %u32_0 %u32_0\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpenCL.std " + ext_inst_name + + ": expected Result Type to be a 32-bit int scalar or vector type")); +} + +TEST_P(ValidateOpenCLStdUMul24Like, U64ResultType) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %u64 %extinst " + ext_inst_name + " %u64_0 %u64\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpenCL.std " + ext_inst_name + + ": expected Result Type to be a 32-bit int scalar or vector type")); +} + +TEST_P(ValidateOpenCLStdUMul24Like, FloatOperand1) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + " %f32_0 %u32_0\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected types of all operands to be equal to Result Type")); +} + +TEST_P(ValidateOpenCLStdUMul24Like, FloatOperand2) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + " %u32_0 %f32_0\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected types of all operands to be equal to Result Type")); +} + +TEST_P(ValidateOpenCLStdUMul24Like, U64Operand1) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + " %u64_0 %u32_0\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected types of all operands to be equal to Result Type")); +} + +TEST_P(ValidateOpenCLStdUMul24Like, U64Operand2) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + " %u32_0 %u64_0\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected types of all operands to be equal to Result Type")); +} + +INSTANTIATE_TEST_CASE_P(AllUMul24Like, ValidateOpenCLStdUMul24Like, + ::testing::ValuesIn(std::vector{ + "s_mul24", + "u_mul24", + }), ); + +TEST_P(ValidateOpenCLStdUMad24Like, Success) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %u32 %extinst " << ext_inst_name + << " %u32_0 %u32_1 %u32_2\n"; + ss << "%val2 = OpExtInst %u32 %extinst " << ext_inst_name + << " %u32_0 %u32_1 %u32_2\n"; + ss << "%val3 = OpExtInst %u32 %extinst " << ext_inst_name + << " %u32_0 %u32_1 %u32_2\n"; + ss << "%val4 = OpExtInst %u32 %extinst " << ext_inst_name + << " %u32_0 %u32_1 %u32_2\n"; + ss << "%val5 = OpExtInst %u32vec2 %extinst " << ext_inst_name + << " %u32vec2_01 %u32vec2_01 %u32vec2_12\n"; + ss << "%val6 = OpExtInst %u32vec2 %extinst " << ext_inst_name + << " %u32vec2_01 %u32vec2_01 %u32vec2_12\n"; + ss << "%val7 = OpExtInst %u32vec2 %extinst " << ext_inst_name + << " %u32vec2_01 %u32vec2_01 %u32vec2_12\n"; + ss << "%val8 = OpExtInst %u32vec2 %extinst " << ext_inst_name + << " %u32vec2_01 %u32vec2_01 %u32vec2_12\n"; + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateOpenCLStdUMad24Like, FloatResultType) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %f32 %extinst " + ext_inst_name + + " %u32_0 %u32_0 %u32_1\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpenCL.std " + ext_inst_name + + ": expected Result Type to be a 32-bit int scalar or vector type")); +} + +TEST_P(ValidateOpenCLStdUMad24Like, U64ResultType) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %u64 %extinst " + ext_inst_name + + " %u64_0 %u64_0 %u64_1\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpenCL.std " + ext_inst_name + + ": expected Result Type to be a 32-bit int scalar or vector type")); +} + +TEST_P(ValidateOpenCLStdUMad24Like, FloatOperand1) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + + " %f32_0 %u32_0 %u32_1\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected types of all operands to be equal to Result Type")); +} + +TEST_P(ValidateOpenCLStdUMad24Like, FloatOperand2) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + + " %u32_0 %f32_0 %u32_1\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected types of all operands to be equal to Result Type")); +} + +TEST_P(ValidateOpenCLStdUMad24Like, FloatOperand3) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + + " %u32_0 %u32_0 %f32_1\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected types of all operands to be equal to Result Type")); +} + +TEST_P(ValidateOpenCLStdUMad24Like, U64Operand1) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + + " %f32_0 %u32_0 %u64_1\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected types of all operands to be equal to Result Type")); +} + +TEST_P(ValidateOpenCLStdUMad24Like, U64Operand2) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + + " %u32_0 %f32_0 %u64_1\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected types of all operands to be equal to Result Type")); +} + +TEST_P(ValidateOpenCLStdUMad24Like, U64Operand3) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + + " %u32_0 %u32_0 %u64_1\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected types of all operands to be equal to Result Type")); +} + +INSTANTIATE_TEST_CASE_P(AllUMad24Like, ValidateOpenCLStdUMad24Like, + ::testing::ValuesIn(std::vector{ + "s_mad24", + "u_mad24", + }), ); + +TEST_F(ValidateExtInst, OpenCLStdCrossSuccess) { + const std::string body = R"( +%val1 = OpExtInst %f32vec3 %extinst cross %f32vec3_012 %f32vec3_123 +%val2 = OpExtInst %f32vec4 %extinst cross %f32vec4_0123 %f32vec4_0123 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, OpenCLStdCrossIntVectorResultType) { + const std::string body = R"( +%val1 = OpExtInst %u32vec3 %extinst cross %f32vec3_012 %f32vec3_123 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std cross: " + "expected Result Type to be a float vector type")); +} + +TEST_F(ValidateExtInst, OpenCLStdCrossResultTypeWrongSize) { + const std::string body = R"( +%val1 = OpExtInst %f32vec2 %extinst cross %f32vec3_012 %f32vec3_123 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std cross: " + "expected Result Type to have 3 or 4 components")); +} + +TEST_F(ValidateExtInst, OpenCLStdCrossXWrongType) { + const std::string body = R"( +%val1 = OpExtInst %f32vec3 %extinst cross %f64vec3_012 %f32vec3_123 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std cross: " + "expected operand X type to be equal to Result Type")); +} + +TEST_F(ValidateExtInst, OpenCLStdCrossYWrongType) { + const std::string body = R"( +%val1 = OpExtInst %f32vec3 %extinst cross %f32vec3_123 %f64vec3_012 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std cross: " + "expected operand Y type to be equal to Result Type")); +} + +TEST_P(ValidateOpenCLStdLengthLike, Success) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %f32 %extinst " << ext_inst_name << " %f32vec2_01\n"; + ss << "%val2 = OpExtInst %f32 %extinst " << ext_inst_name + << " %f32vec4_0123\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateOpenCLStdLengthLike, IntResultType) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + " %f32vec2_01\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": " + "expected Result Type to be a float scalar type")); +} + +TEST_P(ValidateOpenCLStdLengthLike, IntX) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %f32 %extinst " + ext_inst_name + " %u32vec2_01\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": " + "expected operand P to be a float scalar or vector")); +} + +TEST_P(ValidateOpenCLStdLengthLike, VectorTooBig) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %f32 %extinst " + ext_inst_name + + " %f32vec8_01010101\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": " + "expected operand P to have no more than 4 components")); +} + +TEST_P(ValidateOpenCLStdLengthLike, DifferentType) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %f64 %extinst " + ext_inst_name + " %f32vec2_01\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": " + "expected operand P component type to be equal to " + "Result Type")); +} + +INSTANTIATE_TEST_CASE_P(AllLengthLike, ValidateOpenCLStdLengthLike, + ::testing::ValuesIn(std::vector{ + "length", + "fast_length", + }), ); + +TEST_P(ValidateOpenCLStdDistanceLike, Success) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %f32 %extinst " << ext_inst_name + << " %f32vec2_01 %f32vec2_01\n"; + ss << "%val2 = OpExtInst %f32 %extinst " << ext_inst_name + << " %f32vec4_0123 %f32vec4_1234\n"; + ss << "%val3 = OpExtInst %f32 %extinst " << ext_inst_name + << " %f32_0 %f32_1\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateOpenCLStdDistanceLike, IntResultType) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + + " %f32vec2_01 %f32vec2_12\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": " + "expected Result Type to be a float scalar type")); +} + +TEST_P(ValidateOpenCLStdDistanceLike, IntP0) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %f32 %extinst " + ext_inst_name + + " %u32vec2_01 %f32vec2_12\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": " + "expected operand P0 to be of float scalar or vector type")); +} + +TEST_P(ValidateOpenCLStdDistanceLike, VectorTooBig) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %f32 %extinst " + ext_inst_name + + " %f32vec8_01010101 %f32vec8_01010101\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": " + "expected operand P0 to have no more than 4 components")); +} + +TEST_P(ValidateOpenCLStdDistanceLike, F64P0) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %f32 %extinst " + ext_inst_name + + " %f64vec2_01 %f32vec2_12\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpenCL.std " + ext_inst_name + + ": " + "expected operand P0 component type to be equal to Result Type")); +} + +TEST_P(ValidateOpenCLStdDistanceLike, DifferentOperands) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %f64 %extinst " + ext_inst_name + + " %f64vec2_01 %f32vec2_12\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": " + "expected operands P0 and P1 to be of the same type")); +} + +INSTANTIATE_TEST_CASE_P(AllDistanceLike, ValidateOpenCLStdDistanceLike, + ::testing::ValuesIn(std::vector{ + "distance", + "fast_distance", + }), ); + +TEST_P(ValidateOpenCLStdNormalizeLike, Success) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %f32vec2 %extinst " << ext_inst_name + << " %f32vec2_01\n"; + ss << "%val2 = OpExtInst %f32vec4 %extinst " << ext_inst_name + << " %f32vec4_0123\n"; + ss << "%val3 = OpExtInst %f32 %extinst " << ext_inst_name << " %f32_2\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateOpenCLStdNormalizeLike, IntResultType) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %u32 %extinst " + ext_inst_name + " %f32_2\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": " + "expected Result Type to be a float scalar or vector type")); +} + +TEST_P(ValidateOpenCLStdNormalizeLike, VectorTooBig) { + const std::string ext_inst_name = GetParam(); + const std::string body = "%val1 = OpExtInst %f32vec8 %extinst " + + ext_inst_name + " %f32vec8_01010101\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": " + "expected Result Type to have no more than 4 components")); +} + +TEST_P(ValidateOpenCLStdNormalizeLike, DifferentType) { + const std::string ext_inst_name = GetParam(); + const std::string body = + "%val1 = OpExtInst %f64vec2 %extinst " + ext_inst_name + " %f32vec2_01\n"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": " + "expected operand P type to be equal to Result Type")); +} + +INSTANTIATE_TEST_CASE_P(AllNormalizeLike, ValidateOpenCLStdNormalizeLike, + ::testing::ValuesIn(std::vector{ + "normalize", + "fast_normalize", + }), ); + +TEST_F(ValidateExtInst, OpenCLStdBitselectSuccess) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst bitselect %f32_2 %f32_1 %f32_1 +%val2 = OpExtInst %f32vec4 %extinst bitselect %f32vec4_0123 %f32vec4_1234 %f32vec4_0123 +%val3 = OpExtInst %u32 %extinst bitselect %u32_2 %u32_1 %u32_1 +%val4 = OpExtInst %u32vec4 %extinst bitselect %u32vec4_0123 %u32vec4_0123 %u32vec4_0123 +%val5 = OpExtInst %u64 %extinst bitselect %u64_2 %u64_1 %u64_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, OpenCLStdBitselectWrongResultType) { + const std::string body = R"( +%val3 = OpExtInst %struct_f32_f32 %extinst bitselect %u32_2 %u32_1 %u32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpenCL.std bitselect: " + "expected Result Type to be an int or float scalar or vector type")); +} + +TEST_F(ValidateExtInst, OpenCLStdBitselectAWrongType) { + const std::string body = R"( +%val3 = OpExtInst %u32 %extinst bitselect %f32_2 %u32_1 %u32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std bitselect: " + "expected types of all operands to be equal to Result Type")); +} + +TEST_F(ValidateExtInst, OpenCLStdBitselectBWrongType) { + const std::string body = R"( +%val3 = OpExtInst %u32 %extinst bitselect %u32_2 %f32_1 %u32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std bitselect: " + "expected types of all operands to be equal to Result Type")); +} + +TEST_F(ValidateExtInst, OpenCLStdBitselectCWrongType) { + const std::string body = R"( +%val3 = OpExtInst %u32 %extinst bitselect %u32_2 %u32_1 %f32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std bitselect: " + "expected types of all operands to be equal to Result Type")); +} + +TEST_F(ValidateExtInst, OpenCLStdSelectSuccess) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst select %f32_2 %f32_1 %u32_1 +%val2 = OpExtInst %f32vec4 %extinst select %f32vec4_0123 %f32vec4_1234 %u32vec4_0123 +%val3 = OpExtInst %u32 %extinst select %u32_2 %u32_1 %u32_1 +%val4 = OpExtInst %u32vec4 %extinst select %u32vec4_0123 %u32vec4_0123 %u32vec4_0123 +%val5 = OpExtInst %u64 %extinst select %u64_2 %u64_1 %u64_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, OpenCLStdSelectWrongResultType) { + const std::string body = R"( +%val3 = OpExtInst %struct_f32_f32 %extinst select %u32_2 %u32_1 %u32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpenCL.std select: " + "expected Result Type to be an int or float scalar or vector type")); +} + +TEST_F(ValidateExtInst, OpenCLStdSelectAWrongType) { + const std::string body = R"( +%val3 = OpExtInst %u32 %extinst select %f32_2 %u32_1 %u32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std select: " + "expected operand A type to be equal to Result Type")); +} + +TEST_F(ValidateExtInst, OpenCLStdSelectBWrongType) { + const std::string body = R"( +%val3 = OpExtInst %u32 %extinst select %u32_2 %f32_1 %u32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std select: " + "expected operand B type to be equal to Result Type")); +} + +TEST_F(ValidateExtInst, OpenCLStdSelectCWrongType) { + const std::string body = R"( +%val3 = OpExtInst %f32 %extinst select %f32_2 %f32_1 %f32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std select: " + "expected operand C to be an int scalar or vector")); +} + +TEST_F(ValidateExtInst, OpenCLStdSelectCWrongComponentNumber) { + const std::string body = R"( +%val3 = OpExtInst %f32vec2 %extinst select %f32vec2_12 %f32vec2_01 %u32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std select: " + "expected operand C to have the same number of " + "components as Result Type")); +} + +TEST_F(ValidateExtInst, OpenCLStdSelectCWrongBitWidth) { + const std::string body = R"( +%val3 = OpExtInst %f32vec2 %extinst select %f32vec2_12 %f32vec2_01 %u64vec2_01 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpenCL.std select: " + "expected operand C to have the same bit width as Result Type")); +} + +TEST_P(ValidateOpenCLStdVStoreHalfLike, SuccessPhysical32) { + const std::string ext_inst_name = GetParam(); + const std::string rounding_mode = + ext_inst_name.substr(ext_inst_name.length() - 2) == "_r" ? " RTE" : ""; + + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f16_ptr_workgroup %f16vec8_workgroup %u32_1\n"; + if (std::string::npos == ext_inst_name.find("halfn")) { + ss << "%val1 = OpExtInst %void %extinst " << ext_inst_name + << " %f32_1 %u32_1 %ptr" << rounding_mode << "\n"; + ss << "%val2 = OpExtInst %void %extinst " << ext_inst_name + << " %f64_0 %u32_2 %ptr" << rounding_mode << "\n"; + } else { + ss << "%val1 = OpExtInst %void %extinst " << ext_inst_name + << " %f32vec2_01 %u32_1 %ptr" << rounding_mode << "\n"; + ss << "%val2 = OpExtInst %void %extinst " << ext_inst_name + << " %f32vec4_0123 %u32_0 %ptr" << rounding_mode << "\n"; + ss << "%val3 = OpExtInst %void %extinst " << ext_inst_name + << " %f64vec2_01 %u32_2 %ptr" << rounding_mode << "\n"; + } + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateOpenCLStdVStoreHalfLike, SuccessPhysical64) { + const std::string ext_inst_name = GetParam(); + const std::string rounding_mode = + ext_inst_name.substr(ext_inst_name.length() - 2) == "_r" ? " RTE" : ""; + + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f16_ptr_workgroup %f16vec8_workgroup %u32_1\n"; + if (std::string::npos == ext_inst_name.find("halfn")) { + ss << "%val1 = OpExtInst %void %extinst " << ext_inst_name + << " %f32_1 %u64_1 %ptr" << rounding_mode << "\n"; + ss << "%val2 = OpExtInst %void %extinst " << ext_inst_name + << " %f64_0 %u64_2 %ptr" << rounding_mode << "\n"; + } else { + ss << "%val1 = OpExtInst %void %extinst " << ext_inst_name + << " %f32vec2_01 %u64_1 %ptr" << rounding_mode << "\n"; + ss << "%val2 = OpExtInst %void %extinst " << ext_inst_name + << " %f32vec4_0123 %u64_0 %ptr" << rounding_mode << "\n"; + ss << "%val3 = OpExtInst %void %extinst " << ext_inst_name + << " %f64vec2_01 %u64_2 %ptr" << rounding_mode << "\n"; + } + + CompileSuccessfully(GenerateKernelCode(ss.str(), "", "Physical64")); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateOpenCLStdVStoreHalfLike, NonVoidResultType) { + const std::string ext_inst_name = GetParam(); + const std::string rounding_mode = + ext_inst_name.substr(ext_inst_name.length() - 2) == "_r" ? " RTE" : ""; + + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f16_ptr_workgroup %f16vec8_workgroup %u32_1\n"; + if (std::string::npos == ext_inst_name.find("halfn")) { + ss << "%val1 = OpExtInst %f32 %extinst " << ext_inst_name + << " %f32_1 %u32_1 %ptr" << rounding_mode << "\n"; + } else { + ss << "%val1 = OpExtInst %f32 %extinst " << ext_inst_name + << " %f32vec2_01 %u32_1 %ptr" << rounding_mode << "\n"; + } + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected Result Type to be void")); +} + +TEST_P(ValidateOpenCLStdVStoreHalfLike, WrongDataType) { + const std::string ext_inst_name = GetParam(); + const std::string rounding_mode = + ext_inst_name.substr(ext_inst_name.length() - 2) == "_r" ? " RTE" : ""; + + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f16_ptr_workgroup %f16vec8_workgroup %u32_1\n"; + if (std::string::npos == ext_inst_name.find("halfn")) { + ss << "%val1 = OpExtInst %void %extinst " << ext_inst_name + << " %f64vec2_01 %u32_1 %ptr" << rounding_mode << "\n"; + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected Data to be a 32 or 64-bit float scalar")); + } else { + ss << "%val1 = OpExtInst %void %extinst " << ext_inst_name + << " %f64_0 %u32_1 %ptr" << rounding_mode << "\n"; + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected Data to be a 32 or 64-bit float vector")); + } +} + +TEST_P(ValidateOpenCLStdVStoreHalfLike, AddressingModelLogical) { + const std::string ext_inst_name = GetParam(); + const std::string rounding_mode = + ext_inst_name.substr(ext_inst_name.length() - 2) == "_r" ? " RTE" : ""; + + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f16_ptr_workgroup %f16vec8_workgroup %u32_1\n"; + if (std::string::npos == ext_inst_name.find("halfn")) { + ss << "%val1 = OpExtInst %void %extinst " << ext_inst_name + << " %f32_0 %u32_1 %ptr" << rounding_mode << "\n"; + } else { + ss << "%val1 = OpExtInst %void %extinst " << ext_inst_name + << " %f32vec2_01 %u32_1 %ptr" << rounding_mode << "\n"; + } + + CompileSuccessfully(GenerateKernelCode(ss.str(), "", "Logical")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + " can only be used with physical addressing models")); +} + +TEST_P(ValidateOpenCLStdVStoreHalfLike, OffsetNotSizeT) { + const std::string ext_inst_name = GetParam(); + const std::string rounding_mode = + ext_inst_name.substr(ext_inst_name.length() - 2) == "_r" ? " RTE" : ""; + + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f16_ptr_workgroup %f16vec8_workgroup %u32_1\n"; + if (std::string::npos == ext_inst_name.find("halfn")) { + ss << "%val1 = OpExtInst %void %extinst " << ext_inst_name + << " %f32_0 %u32_1 %ptr" << rounding_mode << "\n"; + } else { + ss << "%val1 = OpExtInst %void %extinst " << ext_inst_name + << " %f32vec2_01 %u32_1 %ptr" << rounding_mode << "\n"; + } + + CompileSuccessfully(GenerateKernelCode(ss.str(), "", "Physical64")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": " + "expected operand Offset to be of type size_t (64-bit integer " + "for the addressing model used in the module)")); +} + +TEST_P(ValidateOpenCLStdVStoreHalfLike, PNotPointer) { + const std::string ext_inst_name = GetParam(); + const std::string rounding_mode = + ext_inst_name.substr(ext_inst_name.length() - 2) == "_r" ? " RTE" : ""; + + std::ostringstream ss; + if (std::string::npos == ext_inst_name.find("halfn")) { + ss << "%val1 = OpExtInst %void %extinst " << ext_inst_name + << " %f32_0 %u32_1 %f16_ptr_workgroup" << rounding_mode << "\n"; + } else { + ss << "%val1 = OpExtInst %void %extinst " << ext_inst_name + << " %f32vec2_01 %u32_1 %f16_ptr_workgroup" << rounding_mode << "\n"; + } + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected operand P to be a pointer")); +} + +TEST_P(ValidateOpenCLStdVStoreHalfLike, ConstPointer) { + const std::string ext_inst_name = GetParam(); + const std::string rounding_mode = + ext_inst_name.substr(ext_inst_name.length() - 2) == "_r" ? " RTE" : ""; + + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f16_ptr_uniform_constant " + "%f16vec8_uniform_constant %u32_1\n"; + if (std::string::npos == ext_inst_name.find("halfn")) { + ss << "%val1 = OpExtInst %void %extinst " << ext_inst_name + << " %f32_0 %u32_1 %ptr" << rounding_mode << "\n"; + } else { + ss << "%val1 = OpExtInst %void %extinst " << ext_inst_name + << " %f32vec2_01 %u32_1 %ptr" << rounding_mode << "\n"; + } + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected operand P storage class to be Generic, " + "CrossWorkgroup, Workgroup or Function")); +} + +TEST_P(ValidateOpenCLStdVStoreHalfLike, PDataTypeInt) { + const std::string ext_inst_name = GetParam(); + const std::string rounding_mode = + ext_inst_name.substr(ext_inst_name.length() - 2) == "_r" ? " RTE" : ""; + + std::ostringstream ss; + ss << "%ptr = OpAccessChain %u32_ptr_workgroup %u32vec8_workgroup %u32_1\n"; + if (std::string::npos == ext_inst_name.find("halfn")) { + ss << "%val1 = OpExtInst %void %extinst " << ext_inst_name + << " %f32_0 %u32_1 %ptr" << rounding_mode << "\n"; + } else { + ss << "%val1 = OpExtInst %void %extinst " << ext_inst_name + << " %f32vec2_01 %u32_1 %ptr" << rounding_mode << "\n"; + } + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected operand P data type to be 16-bit float scalar")); +} + +TEST_P(ValidateOpenCLStdVStoreHalfLike, PDataTypeFloat32) { + const std::string ext_inst_name = GetParam(); + const std::string rounding_mode = + ext_inst_name.substr(ext_inst_name.length() - 2) == "_r" ? " RTE" : ""; + + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f32_ptr_workgroup %f32vec8_workgroup %u32_1\n"; + if (std::string::npos == ext_inst_name.find("halfn")) { + ss << "%val1 = OpExtInst %void %extinst " << ext_inst_name + << " %f32_0 %u32_1 %ptr" << rounding_mode << "\n"; + } else { + ss << "%val1 = OpExtInst %void %extinst " << ext_inst_name + << " %f32vec2_01 %u32_1 %ptr" << rounding_mode << "\n"; + } + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected operand P data type to be 16-bit float scalar")); +} + +INSTANTIATE_TEST_CASE_P(AllVStoreHalfLike, ValidateOpenCLStdVStoreHalfLike, + ::testing::ValuesIn(std::vector{ + "vstore_half", + "vstore_half_r", + "vstore_halfn", + "vstore_halfn_r", + "vstorea_halfn", + "vstorea_halfn_r", + }), ); + +TEST_P(ValidateOpenCLStdVLoadHalfLike, SuccessPhysical32) { + const std::string ext_inst_name = GetParam(); + + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f16_ptr_workgroup %f16vec8_workgroup %u32_1\n"; + ss << "%val1 = OpExtInst %f32vec2 %extinst " << ext_inst_name + << " %u32_1 %ptr 2\n"; + ss << "%val2 = OpExtInst %f32vec3 %extinst " << ext_inst_name + << " %u32_1 %ptr 3\n"; + ss << "%val3 = OpExtInst %f32vec4 %extinst " << ext_inst_name + << " %u32_1 %ptr 4\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateOpenCLStdVLoadHalfLike, SuccessPhysical64) { + const std::string ext_inst_name = GetParam(); + + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f16_ptr_workgroup %f16vec8_workgroup %u32_1\n"; + ss << "%val1 = OpExtInst %f32vec2 %extinst " << ext_inst_name + << " %u64_1 %ptr 2\n"; + ss << "%val2 = OpExtInst %f32vec3 %extinst " << ext_inst_name + << " %u64_1 %ptr 3\n"; + ss << "%val3 = OpExtInst %f32vec4 %extinst " << ext_inst_name + << " %u64_1 %ptr 4\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str(), "", "Physical64")); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateOpenCLStdVLoadHalfLike, ResultTypeNotFloatVector) { + const std::string ext_inst_name = GetParam(); + + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f16_ptr_workgroup %f16vec8_workgroup %u32_1\n"; + ss << "%val1 = OpExtInst %f32 %extinst " << ext_inst_name + << " %u32_1 %ptr 1\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected Result Type to be a float vector type")); +} + +TEST_P(ValidateOpenCLStdVLoadHalfLike, AddressingModelLogical) { + const std::string ext_inst_name = GetParam(); + + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f16_ptr_workgroup %f16vec8_workgroup %u32_1\n"; + ss << "%val1 = OpExtInst %f32vec2 %extinst " << ext_inst_name + << " %u32_1 %ptr 2\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str(), "", "Logical")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + " can only be used with physical addressing models")); +} + +TEST_P(ValidateOpenCLStdVLoadHalfLike, OffsetNotSizeT) { + const std::string ext_inst_name = GetParam(); + + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f16_ptr_workgroup %f16vec8_workgroup %u32_1\n"; + ss << "%val1 = OpExtInst %f32vec2 %extinst " << ext_inst_name + << " %u64_1 %ptr 2\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected operand Offset to be of type size_t (32-bit " + "integer for the addressing model used in the module)")); +} + +TEST_P(ValidateOpenCLStdVLoadHalfLike, PNotPointer) { + const std::string ext_inst_name = GetParam(); + + std::ostringstream ss; + ss << "%val1 = OpExtInst %f32vec2 %extinst " << ext_inst_name + << " %u32_1 %f16_ptr_workgroup 2\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected operand P to be a pointer")); +} + +TEST_P(ValidateOpenCLStdVLoadHalfLike, OffsetWrongStorageType) { + const std::string ext_inst_name = GetParam(); + + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f16_ptr_input %f16vec8_input %u32_1\n"; + ss << "%val1 = OpExtInst %f32vec2 %extinst " << ext_inst_name + << " %u32_1 %ptr 2\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected operand P storage class to be UniformConstant, " + "Generic, CrossWorkgroup, Workgroup or Function")); +} + +TEST_P(ValidateOpenCLStdVLoadHalfLike, PDataTypeInt) { + const std::string ext_inst_name = GetParam(); + + std::ostringstream ss; + ss << "%ptr = OpAccessChain %u32_ptr_workgroup %u32vec8_workgroup %u32_1\n"; + ss << "%val1 = OpExtInst %f32vec2 %extinst " << ext_inst_name + << " %u32_1 %ptr 2\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected operand P data type to be 16-bit float scalar")); +} + +TEST_P(ValidateOpenCLStdVLoadHalfLike, PDataTypeFloat32) { + const std::string ext_inst_name = GetParam(); + + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f32_ptr_workgroup %f32vec8_workgroup %u32_1\n"; + ss << "%val1 = OpExtInst %f32vec2 %extinst " << ext_inst_name + << " %u32_1 %ptr 2\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected operand P data type to be 16-bit float scalar")); +} + +TEST_P(ValidateOpenCLStdVLoadHalfLike, WrongN) { + const std::string ext_inst_name = GetParam(); + + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f16_ptr_workgroup %f16vec8_workgroup %u32_1\n"; + ss << "%val1 = OpExtInst %f32vec2 %extinst " << ext_inst_name + << " %u32_1 %ptr 3\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected literal N to be equal to the number of " + "components of Result Type")); +} + +INSTANTIATE_TEST_CASE_P(AllVLoadHalfLike, ValidateOpenCLStdVLoadHalfLike, + ::testing::ValuesIn(std::vector{ + "vload_halfn", + "vloada_halfn", + }), ); + +TEST_F(ValidateExtInst, VLoadNSuccessFloatPhysical32) { + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f32_ptr_uniform_constant " + "%f32vec8_uniform_constant %u32_1\n"; + ss << "%val1 = OpExtInst %f32vec2 %extinst vloadn %u32_1 %ptr 2\n"; + ss << "%val2 = OpExtInst %f32vec3 %extinst vloadn %u32_1 %ptr 3\n"; + ss << "%val3 = OpExtInst %f32vec4 %extinst vloadn %u32_1 %ptr 4\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, VLoadNSuccessIntPhysical32) { + std::ostringstream ss; + ss << "%ptr = OpAccessChain %u32_ptr_uniform_constant " + "%u32vec8_uniform_constant %u32_1\n"; + ss << "%val1 = OpExtInst %u32vec2 %extinst vloadn %u32_1 %ptr 2\n"; + ss << "%val2 = OpExtInst %u32vec3 %extinst vloadn %u32_1 %ptr 3\n"; + ss << "%val3 = OpExtInst %u32vec4 %extinst vloadn %u32_1 %ptr 4\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, VLoadNSuccessFloatPhysical64) { + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f32_ptr_uniform_constant " + "%f32vec8_uniform_constant %u32_1\n"; + ss << "%val1 = OpExtInst %f32vec2 %extinst vloadn %u64_1 %ptr 2\n"; + ss << "%val2 = OpExtInst %f32vec3 %extinst vloadn %u64_1 %ptr 3\n"; + ss << "%val3 = OpExtInst %f32vec4 %extinst vloadn %u64_1 %ptr 4\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str(), "", "Physical64")); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, VLoadNSuccessIntPhysical64) { + std::ostringstream ss; + ss << "%ptr = OpAccessChain %u32_ptr_uniform_constant " + "%u32vec8_uniform_constant %u32_1\n"; + ss << "%val1 = OpExtInst %u32vec2 %extinst vloadn %u64_1 %ptr 2\n"; + ss << "%val2 = OpExtInst %u32vec3 %extinst vloadn %u64_1 %ptr 3\n"; + ss << "%val3 = OpExtInst %u32vec4 %extinst vloadn %u64_1 %ptr 4\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str(), "", "Physical64")); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, VLoadNWrongResultType) { + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f32_ptr_uniform_constant " + "%f32vec8_uniform_constant %u32_1\n"; + ss << "%val1 = OpExtInst %f32 %extinst vloadn %u32_1 %ptr 2\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std vloadn: " + "expected Result Type to be an int or float vector type")); +} + +TEST_F(ValidateExtInst, VLoadNAddressingModelLogical) { + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f32_ptr_uniform_constant " + "%f32vec8_uniform_constant %u32_1\n"; + ss << "%val1 = OpExtInst %f32vec2 %extinst vloadn %u32_1 %ptr 2\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str(), "", "Logical")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std vloadn can only be used with physical " + "addressing models")); +} + +TEST_F(ValidateExtInst, VLoadNOffsetNotSizeT) { + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f32_ptr_uniform_constant " + "%f32vec8_uniform_constant %u32_1\n"; + ss << "%val1 = OpExtInst %f32vec2 %extinst vloadn %u64_1 %ptr 2\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpenCL.std vloadn: expected operand Offset to be of type size_t " + "(32-bit integer for the addressing model used in the module)")); +} + +TEST_F(ValidateExtInst, VLoadNPNotPointer) { + std::ostringstream ss; + ss << "%val1 = OpExtInst %f32vec2 %extinst vloadn %u32_1 " + "%f32_ptr_uniform_constant 2\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std vloadn: expected operand P to be a pointer")); +} + +TEST_F(ValidateExtInst, VLoadNWrongStorageClass) { + std::ostringstream ss; + ss << "%ptr = OpAccessChain %u32_ptr_workgroup %u32vec8_workgroup %u32_1\n"; + ss << "%val1 = OpExtInst %u32vec2 %extinst vloadn %u32_1 %ptr 2\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std vloadn: expected operand P storage class " + "to be UniformConstant or Generic")); +} + +TEST_F(ValidateExtInst, VLoadNWrongComponentType) { + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f32_ptr_uniform_constant " + "%f32vec8_uniform_constant %u32_1\n"; + ss << "%val1 = OpExtInst %u32vec2 %extinst vloadn %u32_1 %ptr 2\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std vloadn: expected operand P data type to be " + "equal to component type of Result Type")); +} + +TEST_F(ValidateExtInst, VLoadNWrongN) { + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f32_ptr_uniform_constant " + "%f32vec8_uniform_constant %u32_1\n"; + ss << "%val1 = OpExtInst %f32vec2 %extinst vloadn %u32_1 %ptr 3\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std vloadn: expected literal N to be equal to " + "the number of components of Result Type")); +} + +TEST_F(ValidateExtInst, VLoadHalfSuccessPhysical32) { + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f16_ptr_uniform_constant " + "%f16vec8_uniform_constant %u32_1\n"; + ss << "%val1 = OpExtInst %f32 %extinst vload_half %u32_1 %ptr\n"; + ss << "%val2 = OpExtInst %f64 %extinst vload_half %u32_1 %ptr\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, VLoadHalfSuccessPhysical64) { + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f16_ptr_uniform_constant " + "%f16vec8_uniform_constant %u32_1\n"; + ss << "%val1 = OpExtInst %f32 %extinst vload_half %u64_1 %ptr\n"; + ss << "%val2 = OpExtInst %f64 %extinst vload_half %u64_1 %ptr\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str(), "", "Physical64")); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, VLoadHalfWrongResultType) { + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f16_ptr_uniform_constant " + "%f16vec8_uniform_constant %u32_1\n"; + ss << "%val1 = OpExtInst %u32 %extinst vload_half %u32_1 %ptr\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std vload_half: " + "expected Result Type to be a float scalar type")); +} + +TEST_F(ValidateExtInst, VLoadHalfAddressingModelLogical) { + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f16_ptr_uniform_constant " + "%f16vec8_uniform_constant %u32_1\n"; + ss << "%val1 = OpExtInst %f32 %extinst vload_half %u32_1 %ptr\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str(), "", "Logical")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std vload_half can only be used with physical " + "addressing models")); +} + +TEST_F(ValidateExtInst, VLoadHalfOffsetNotSizeT) { + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f16_ptr_uniform_constant " + "%f16vec8_uniform_constant %u32_1\n"; + ss << "%val1 = OpExtInst %f32 %extinst vload_half %u64_1 %ptr\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpenCL.std vload_half: expected operand Offset to be of type size_t " + "(32-bit integer for the addressing model used in the module)")); +} + +TEST_F(ValidateExtInst, VLoadHalfPNotPointer) { + std::ostringstream ss; + ss << "%val1 = OpExtInst %f32 %extinst vload_half %u32_1 " + "%f16_ptr_uniform_constant\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std vload_half: expected operand P to be a pointer")); +} + +TEST_F(ValidateExtInst, VLoadHalfWrongStorageClass) { + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f16_ptr_input %f16vec8_input %u32_1\n"; + ss << "%val1 = OpExtInst %f32 %extinst vload_half %u32_1 %ptr\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpenCL.std vload_half: expected operand P storage class to be " + "UniformConstant, Generic, CrossWorkgroup, Workgroup or Function")); +} + +TEST_F(ValidateExtInst, VLoadHalfPDataTypeInt) { + std::ostringstream ss; + ss << "%ptr = OpAccessChain %u32_ptr_uniform_constant " + "%u32vec8_uniform_constant %u32_1\n"; + ss << "%val1 = OpExtInst %f32 %extinst vload_half %u32_1 %ptr\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std vload_half: expected operand P data type " + "to be 16-bit float scalar")); +} + +TEST_F(ValidateExtInst, VLoadHalfPDataTypeFloat32) { + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f32_ptr_uniform_constant " + "%f32vec8_uniform_constant %u32_1\n"; + ss << "%val1 = OpExtInst %f32 %extinst vload_half %u32_1 %ptr\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std vload_half: expected operand P data type " + "to be 16-bit float scalar")); +} + +TEST_F(ValidateExtInst, VStoreNSuccessFloatPhysical32) { + std::ostringstream ss; + ss << "%ptr_w = OpAccessChain %f32_ptr_workgroup %f32vec8_workgroup %u32_1\n"; + ss << "%ptr_g = OpPtrCastToGeneric %f32_ptr_generic %ptr_w\n"; + ss << "%val1 = OpExtInst %void %extinst vstoren %f32vec2_01 %u32_1 %ptr_g\n"; + ss << "%val2 = OpExtInst %void %extinst vstoren %f32vec4_0123 %u32_1 " + "%ptr_g\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, VStoreNSuccessFloatPhysical64) { + std::ostringstream ss; + ss << "%ptr_w = OpAccessChain %f32_ptr_workgroup %f32vec8_workgroup %u32_1\n"; + ss << "%ptr_g = OpPtrCastToGeneric %f32_ptr_generic %ptr_w\n"; + ss << "%val1 = OpExtInst %void %extinst vstoren %f32vec2_01 %u64_1 %ptr_g\n"; + ss << "%val2 = OpExtInst %void %extinst vstoren %f32vec4_0123 %u64_1 " + "%ptr_g\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str(), "", "Physical64")); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, VStoreNSuccessIntPhysical32) { + std::ostringstream ss; + ss << "%ptr_w = OpAccessChain %u32_ptr_workgroup %u32vec8_workgroup %u32_1\n"; + ss << "%ptr_g = OpPtrCastToGeneric %u32_ptr_generic %ptr_w\n"; + ss << "%val1 = OpExtInst %void %extinst vstoren %u32vec2_01 %u32_1 %ptr_g\n"; + ss << "%val2 = OpExtInst %void %extinst vstoren %u32vec4_0123 %u32_1 " + "%ptr_g\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, VStoreNSuccessIntPhysical64) { + std::ostringstream ss; + ss << "%ptr_w = OpAccessChain %u32_ptr_workgroup %u32vec8_workgroup %u32_1\n"; + ss << "%ptr_g = OpPtrCastToGeneric %u32_ptr_generic %ptr_w\n"; + ss << "%val1 = OpExtInst %void %extinst vstoren %u32vec2_01 %u64_1 %ptr_g\n"; + ss << "%val2 = OpExtInst %void %extinst vstoren %u32vec4_0123 %u64_1 " + "%ptr_g\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str(), "", "Physical64")); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, VStoreNResultTypeNotVoid) { + std::ostringstream ss; + ss << "%ptr_w = OpAccessChain %f32_ptr_workgroup %f32vec8_workgroup %u32_1\n"; + ss << "%ptr_g = OpPtrCastToGeneric %f32_ptr_generic %ptr_w\n"; + ss << "%val1 = OpExtInst %f32 %extinst vstoren %f32vec2_01 %u32_1 %ptr_g\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std vstoren: expected Result Type to be void")); +} + +TEST_F(ValidateExtInst, VStoreNDataWrongType) { + std::ostringstream ss; + ss << "%ptr_w = OpAccessChain %f32_ptr_workgroup %f32vec8_workgroup %u32_1\n"; + ss << "%ptr_g = OpPtrCastToGeneric %f32_ptr_generic %ptr_w\n"; + ss << "%val1 = OpExtInst %void %extinst vstoren %f32_1 %u32_1 %ptr_g\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpenCL.std vstoren: expected Data to be an int or float vector")); +} + +TEST_F(ValidateExtInst, VStoreNAddressingModelLogical) { + std::ostringstream ss; + ss << "%ptr_w = OpAccessChain %f32_ptr_workgroup %f32vec8_workgroup %u32_1\n"; + ss << "%ptr_g = OpPtrCastToGeneric %f32_ptr_generic %ptr_w\n"; + ss << "%val1 = OpExtInst %void %extinst vstoren %f32vec2_01 %u32_1 %ptr_g\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str(), "", "Logical")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std vstoren can only be used with physical " + "addressing models")); +} + +TEST_F(ValidateExtInst, VStoreNOffsetNotSizeT) { + std::ostringstream ss; + ss << "%ptr_w = OpAccessChain %f32_ptr_workgroup %f32vec8_workgroup %u32_1\n"; + ss << "%ptr_g = OpPtrCastToGeneric %f32_ptr_generic %ptr_w\n"; + ss << "%val1 = OpExtInst %void %extinst vstoren %f32vec2_01 %u32_1 %ptr_g\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str(), "", "Physical64")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpenCL.std vstoren: expected operand Offset to be of type size_t " + "(64-bit integer for the addressing model used in the module)")); +} + +TEST_F(ValidateExtInst, VStoreNPNotPointer) { + std::ostringstream ss; + ss << "%val1 = OpExtInst %void %extinst vstoren %f32vec2_01 %u32_1 " + "%f32_ptr_generic\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std vstoren: expected operand P to be a pointer")); +} + +TEST_F(ValidateExtInst, VStoreNPNotGeneric) { + std::ostringstream ss; + ss << "%ptr_w = OpAccessChain %f32_ptr_workgroup %f32vec8_workgroup %u32_1\n"; + ss << "%val1 = OpExtInst %void %extinst vstoren %f32vec2_01 %u32_1 %ptr_w\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std vstoren: expected operand P storage class " + "to be Generic")); +} + +TEST_F(ValidateExtInst, VStorePWrongDataType) { + std::ostringstream ss; + ss << "%ptr_w = OpAccessChain %f32_ptr_workgroup %f32vec8_workgroup %u32_1\n"; + ss << "%ptr_g = OpPtrCastToGeneric %f32_ptr_generic %ptr_w\n"; + ss << "%val1 = OpExtInst %void %extinst vstoren %u32vec2_01 %u32_1 %ptr_g\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std vstoren: expected operand P data type to " + "be equal to the type of operand Data components")); +} + +TEST_F(ValidateExtInst, OpenCLStdShuffleSuccess) { + const std::string body = R"( +%val1 = OpExtInst %f32vec2 %extinst shuffle %f32vec4_0123 %u32vec2_01 +%val2 = OpExtInst %f32vec4 %extinst shuffle %f32vec4_0123 %u32vec4_0123 +%val3 = OpExtInst %u32vec2 %extinst shuffle %u32vec4_0123 %u32vec2_01 +%val4 = OpExtInst %u32vec4 %extinst shuffle %u32vec4_0123 %u32vec4_0123 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, OpenCLStdShuffleWrongResultType) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst shuffle %f32vec4_0123 %u32vec2_01 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std shuffle: " + "expected Result Type to be an int or float vector type")); +} + +TEST_F(ValidateExtInst, OpenCLStdShuffleResultTypeInvalidNumComponents) { + const std::string body = R"( +%val1 = OpExtInst %f32vec3 %extinst shuffle %f32vec4_0123 %u32vec3_012 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std shuffle: " + "expected Result Type to have 2, 4, 8 or 16 components")); +} + +TEST_F(ValidateExtInst, OpenCLStdShuffleXWrongType) { + const std::string body = R"( +%val1 = OpExtInst %f32vec2 %extinst shuffle %f32_0 %u32vec2_01 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std shuffle: " + "expected operand X to be an int or float vector")); +} + +TEST_F(ValidateExtInst, OpenCLStdShuffleXInvalidNumComponents) { + const std::string body = R"( +%val1 = OpExtInst %f32vec2 %extinst shuffle %f32vec3_012 %u32vec2_01 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std shuffle: " + "expected operand X to have 2, 4, 8 or 16 components")); +} + +TEST_F(ValidateExtInst, OpenCLStdShuffleXInvalidComponentType) { + const std::string body = R"( +%val1 = OpExtInst %f32vec2 %extinst shuffle %f64vec4_0123 %u32vec2_01 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpenCL.std shuffle: " + "expected operand X and Result Type to have equal component types")); +} + +TEST_F(ValidateExtInst, OpenCLStdShuffleShuffleMaskNotIntVector) { + const std::string body = R"( +%val1 = OpExtInst %f32vec2 %extinst shuffle %f32vec4_0123 %f32vec2_01 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std shuffle: " + "expected operand Shuffle Mask to be an int vector")); +} + +TEST_F(ValidateExtInst, OpenCLStdShuffleShuffleMaskInvalidNumComponents) { + const std::string body = R"( +%val1 = OpExtInst %f32vec4 %extinst shuffle %f32vec4_0123 %u32vec2_01 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std shuffle: " + "expected operand Shuffle Mask to have the same number " + "of components as Result Type")); +} + +TEST_F(ValidateExtInst, OpenCLStdShuffleShuffleMaskInvalidBitWidth) { + const std::string body = R"( +%val1 = OpExtInst %f64vec2 %extinst shuffle %f64vec4_0123 %u32vec2_01 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std shuffle: " + "expected operand Shuffle Mask components to have the " + "same bit width as Result Type components")); +} + +TEST_F(ValidateExtInst, OpenCLStdShuffle2Success) { + const std::string body = R"( +%val1 = OpExtInst %f32vec2 %extinst shuffle2 %f32vec4_0123 %f32vec4_0123 %u32vec2_01 +%val2 = OpExtInst %f32vec4 %extinst shuffle2 %f32vec4_0123 %f32vec4_0123 %u32vec4_0123 +%val3 = OpExtInst %u32vec2 %extinst shuffle2 %u32vec4_0123 %u32vec4_0123 %u32vec2_01 +%val4 = OpExtInst %u32vec4 %extinst shuffle2 %u32vec4_0123 %u32vec4_0123 %u32vec4_0123 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, OpenCLStdShuffle2WrongResultType) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst shuffle2 %f32vec4_0123 %f32vec4_0123 %u32vec2_01 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std shuffle2: " + "expected Result Type to be an int or float vector type")); +} + +TEST_F(ValidateExtInst, OpenCLStdShuffle2ResultTypeInvalidNumComponents) { + const std::string body = R"( +%val1 = OpExtInst %f32vec3 %extinst shuffle2 %f32vec4_0123 %f32vec4_0123 %u32vec3_012 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std shuffle2: " + "expected Result Type to have 2, 4, 8 or 16 components")); +} + +TEST_F(ValidateExtInst, OpenCLStdShuffle2XWrongType) { + const std::string body = R"( +%val1 = OpExtInst %f32vec2 %extinst shuffle2 %f32_0 %f32_0 %u32vec2_01 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std shuffle2: " + "expected operand X to be an int or float vector")); +} + +TEST_F(ValidateExtInst, OpenCLStdShuffle2YTypeDifferentFromX) { + const std::string body = R"( +%val1 = OpExtInst %f32vec2 %extinst shuffle2 %f32vec2_01 %f32vec4_0123 %u32vec2_01 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std shuffle2: " + "expected operands X and Y to be of the same type")); +} + +TEST_F(ValidateExtInst, OpenCLStdShuffle2XInvalidNumComponents) { + const std::string body = R"( +%val1 = OpExtInst %f32vec2 %extinst shuffle2 %f32vec3_012 %f32vec3_012 %u32vec2_01 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std shuffle2: " + "expected operand X to have 2, 4, 8 or 16 components")); +} + +TEST_F(ValidateExtInst, OpenCLStdShuffle2XInvalidComponentType) { + const std::string body = R"( +%val1 = OpExtInst %f32vec2 %extinst shuffle2 %f64vec4_0123 %f64vec4_0123 %u32vec2_01 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpenCL.std shuffle2: " + "expected operand X and Result Type to have equal component types")); +} + +TEST_F(ValidateExtInst, OpenCLStdShuffle2ShuffleMaskNotIntVector) { + const std::string body = R"( +%val1 = OpExtInst %f32vec2 %extinst shuffle2 %f32vec4_0123 %f32vec4_0123 %f32vec2_01 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std shuffle2: " + "expected operand Shuffle Mask to be an int vector")); +} + +TEST_F(ValidateExtInst, OpenCLStdShuffle2ShuffleMaskInvalidNumComponents) { + const std::string body = R"( +%val1 = OpExtInst %f32vec4 %extinst shuffle2 %f32vec4_0123 %f32vec4_0123 %u32vec2_01 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std shuffle2: " + "expected operand Shuffle Mask to have the same number " + "of components as Result Type")); +} + +TEST_F(ValidateExtInst, OpenCLStdShuffle2ShuffleMaskInvalidBitWidth) { + const std::string body = R"( +%val1 = OpExtInst %f64vec2 %extinst shuffle2 %f64vec4_0123 %f64vec4_0123 %u32vec2_01 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std shuffle2: " + "expected operand Shuffle Mask components to have the " + "same bit width as Result Type components")); +} + +TEST_F(ValidateExtInst, OpenCLStdPrintfSuccess) { + const std::string body = R"( +%format = OpAccessChain %u8_ptr_uniform_constant %u8arr_uniform_constant %u32_0 +%val1 = OpExtInst %u32 %extinst printf %format %u32_0 %u32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, OpenCLStdPrintfBoolResultType) { + const std::string body = R"( +%format = OpAccessChain %u8_ptr_uniform_constant %u8arr_uniform_constant %u32_0 +%val1 = OpExtInst %bool %extinst printf %format %u32_0 %u32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpenCL.std printf: expected Result Type to be a 32-bit int type")); +} + +TEST_F(ValidateExtInst, OpenCLStdPrintfU64ResultType) { + const std::string body = R"( +%format = OpAccessChain %u8_ptr_uniform_constant %u8arr_uniform_constant %u32_0 +%val1 = OpExtInst %u64 %extinst printf %format %u32_0 %u32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpenCL.std printf: expected Result Type to be a 32-bit int type")); +} + +TEST_F(ValidateExtInst, OpenCLStdPrintfFormatNotPointer) { + const std::string body = R"( +%val1 = OpExtInst %u32 %extinst printf %u8_ptr_uniform_constant %u32_0 %u32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std printf: expected operand Format to be a pointer")); +} + +TEST_F(ValidateExtInst, OpenCLStdPrintfFormatNotUniformConstStorageClass) { + const std::string body = R"( +%format_const = OpAccessChain %u8_ptr_uniform_constant %u8arr_uniform_constant %u32_0 +%format = OpBitcast %u8_ptr_generic %format_const +%val1 = OpExtInst %u32 %extinst printf %format %u32_0 %u32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std printf: expected Format storage class to " + "be UniformConstant")); +} + +TEST_F(ValidateExtInst, OpenCLStdPrintfFormatNotU8Pointer) { + const std::string body = R"( +%format = OpAccessChain %u32_ptr_uniform_constant %u32vec8_uniform_constant %u32_0 +%val1 = OpExtInst %u32 %extinst printf %format %u32_0 %u32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpenCL.std printf: expected Format data type to be 8-bit int")); +} + +TEST_F(ValidateExtInst, OpenCLStdPrefetchU32Success) { + const std::string body = R"( +%ptr = OpAccessChain %u32_ptr_cross_workgroup %u32arr_cross_workgroup %u32_0 +%val1 = OpExtInst %void %extinst prefetch %ptr %u32_256 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, OpenCLStdPrefetchU32Physical64Success) { + const std::string body = R"( +%ptr = OpAccessChain %u32_ptr_cross_workgroup %u32arr_cross_workgroup %u32_0 +%val1 = OpExtInst %void %extinst prefetch %ptr %u64_256 +)"; + + CompileSuccessfully(GenerateKernelCode(body, "", "Physical64")); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, OpenCLStdPrefetchF32Success) { + const std::string body = R"( +%ptr = OpAccessChain %f32_ptr_cross_workgroup %f32arr_cross_workgroup %u32_0 +%val1 = OpExtInst %void %extinst prefetch %ptr %u32_256 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, OpenCLStdPrefetchF32Vec2Success) { + const std::string body = R"( +%ptr = OpAccessChain %f32vec2_ptr_cross_workgroup %f32vec2arr_cross_workgroup %u32_0 +%val1 = OpExtInst %void %extinst prefetch %ptr %u32_256 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, OpenCLStdPrefetchResultTypeNotVoid) { + const std::string body = R"( +%ptr = OpAccessChain %u32_ptr_cross_workgroup %u32arr_cross_workgroup %u32_0 +%val1 = OpExtInst %u32 %extinst prefetch %ptr %u32_256 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std prefetch: expected Result Type to be void")); +} + +TEST_F(ValidateExtInst, OpenCLStdPrefetchPtrNotPointer) { + const std::string body = R"( +%val1 = OpExtInst %void %extinst prefetch %u32_ptr_cross_workgroup %u32_256 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std prefetch: expected operand Ptr to be a pointer")); +} + +TEST_F(ValidateExtInst, OpenCLStdPrefetchPtrNotCrossWorkgroup) { + const std::string body = R"( +%ptr = OpAccessChain %u8_ptr_uniform_constant %u8arr_uniform_constant %u32_0 +%val1 = OpExtInst %void %extinst prefetch %ptr %u32_256 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std prefetch: expected operand Ptr storage " + "class to be CrossWorkgroup")); +} + +TEST_F(ValidateExtInst, OpenCLStdPrefetchInvalidDataType) { + const std::string body = R"( +%ptr = OpAccessChain %struct_ptr_cross_workgroup %struct_arr_cross_workgroup %u32_0 +%val1 = OpExtInst %void %extinst prefetch %ptr %u32_256 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std prefetch: expected Ptr data type to be int " + "or float scalar or vector")); +} + +TEST_F(ValidateExtInst, OpenCLStdPrefetchAddressingModelLogical) { + const std::string body = R"( +%ptr = OpAccessChain %u32_ptr_cross_workgroup %u32arr_cross_workgroup %u32_0 +%val1 = OpExtInst %void %extinst prefetch %ptr %u32_256 +)"; + + CompileSuccessfully(GenerateKernelCode(body, "", "Logical")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std prefetch can only be used with physical " + "addressing models")); +} + +TEST_F(ValidateExtInst, OpenCLStdPrefetchNumElementsNotSizeT) { + const std::string body = R"( +%ptr = OpAccessChain %f32_ptr_cross_workgroup %f32arr_cross_workgroup %u32_0 +%val1 = OpExtInst %void %extinst prefetch %ptr %u32_256 +)"; + + CompileSuccessfully(GenerateKernelCode(body, "", "Physical64")); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std prefetch: expected operand Num Elements to " + "be of type size_t (64-bit integer for the addressing " + "model used in the module)")); +} + +TEST_P(ValidateOpenCLStdFractLike, Success) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%var_f32 = OpVariable %f32_ptr_function Function\n"; + ss << "%var_f32vec2 = OpVariable %f32vec2_ptr_function Function\n"; + ss << "%val1 = OpExtInst %f32 %extinst " << ext_inst_name + << " %f32_0 %var_f32\n"; + ss << "%val2 = OpExtInst %f32vec2 %extinst " << ext_inst_name + << " %f32vec2_01 %var_f32vec2\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateOpenCLStdFractLike, IntResultType) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%var_f32 = OpVariable %f32_ptr_function Function\n"; + ss << "%val1 = OpExtInst %u32 %extinst " << ext_inst_name + << " %f32_0 %var_f32\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected Result Type to be a float scalar or vector type")); +} + +TEST_P(ValidateOpenCLStdFractLike, XWrongType) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%var_f32 = OpVariable %f32_ptr_function Function\n"; + ss << "%val1 = OpExtInst %f32 %extinst " << ext_inst_name + << " %f64_0 %var_f32\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected type of operand X to be equal to Result Type")); +} + +TEST_P(ValidateOpenCLStdFractLike, NotPointer) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%var_f32 = OpVariable %f32_ptr_function Function\n"; + ss << "%val1 = OpExtInst %f32 %extinst " << ext_inst_name + << " %f32_0 %f32_1\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected the last operand to be a pointer")); +} + +TEST_P(ValidateOpenCLStdFractLike, PointerInvalidStorageClass) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f32_ptr_uniform_constant " + "%f32vec8_uniform_constant %u32_1\n"; + ss << "%val1 = OpExtInst %f32 %extinst " << ext_inst_name << " %f32_0 %ptr\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected storage class of the pointer to be " + "Generic, CrossWorkgroup, Workgroup or Function")); +} + +TEST_P(ValidateOpenCLStdFractLike, PointerWrongDataType) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%var_u32 = OpVariable %u32_ptr_function Function\n"; + ss << "%val1 = OpExtInst %f32 %extinst " << ext_inst_name + << " %f32_0 %var_u32\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpenCL.std " + ext_inst_name + + ": expected data type of the pointer to be equal to Result Type")); +} + +INSTANTIATE_TEST_CASE_P(AllFractLike, ValidateOpenCLStdFractLike, + ::testing::ValuesIn(std::vector{ + "fract", + "modf", + "sincos", + }), ); + +TEST_F(ValidateExtInst, OpenCLStdRemquoSuccess) { + const std::string body = R"( +%var_f32 = OpVariable %f32_ptr_function Function +%var_f32vec2 = OpVariable %f32vec2_ptr_function Function +%val1 = OpExtInst %f32 %extinst remquo %f32_3 %f32_2 %var_f32 +%val2 = OpExtInst %f32vec2 %extinst remquo %f32vec2_01 %f32vec2_12 %var_f32vec2 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, OpenCLStdRemquoIntResultType) { + const std::string body = R"( +%var_f32 = OpVariable %f32_ptr_function Function +%val1 = OpExtInst %u32 %extinst remquo %f32_3 %f32_2 %var_f32 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std remquo: " + "expected Result Type to be a float scalar or vector type")); +} + +TEST_F(ValidateExtInst, OpenCLStdRemquoXWrongType) { + const std::string body = R"( +%var_f32 = OpVariable %f32_ptr_function Function +%val1 = OpExtInst %f32 %extinst remquo %u32_3 %f32_2 %var_f32 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std remquo: " + "expected type of operand X to be equal to Result Type")); +} + +TEST_F(ValidateExtInst, OpenCLStdRemquoYWrongType) { + const std::string body = R"( +%var_f32 = OpVariable %f32_ptr_function Function +%val1 = OpExtInst %f32 %extinst remquo %f32_3 %u32_2 %var_f32 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std remquo: " + "expected type of operand Y to be equal to Result Type")); +} + +TEST_F(ValidateExtInst, OpenCLStdRemquoNotPointer) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst remquo %f32_3 %f32_2 %f32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std remquo: " + "expected the last operand to be a pointer")); +} + +TEST_F(ValidateExtInst, OpenCLStdRemquoPointerWrongStorageClass) { + const std::string body = R"( +%ptr = OpAccessChain %f32_ptr_uniform_constant %f32vec8_uniform_constant %u32_1 +%val1 = OpExtInst %f32 %extinst remquo %f32_3 %f32_2 %ptr +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std remquo: " + "expected storage class of the pointer to be Generic, " + "CrossWorkgroup, Workgroup or Function")); +} + +TEST_F(ValidateExtInst, OpenCLStdRemquoPointerWrongDataType) { + const std::string body = R"( +%var_u32 = OpVariable %u32_ptr_function Function +%val1 = OpExtInst %f32 %extinst remquo %f32_3 %f32_2 %var_u32 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpenCL.std remquo: " + "expected data type of the pointer to be equal to Result Type")); +} + +TEST_P(ValidateOpenCLStdFrexpLike, Success) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%var_u32 = OpVariable %u32_ptr_function Function\n"; + ss << "%var_u32vec2 = OpVariable %u32vec2_ptr_function Function\n"; + ss << "%val1 = OpExtInst %f32 %extinst " << ext_inst_name + << " %f32_0 %var_u32\n"; + ss << "%val2 = OpExtInst %f32vec2 %extinst " << ext_inst_name + << " %f32vec2_01 %var_u32vec2\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateOpenCLStdFrexpLike, IntResultType) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%var_u32 = OpVariable %u32_ptr_function Function\n"; + ss << "%val1 = OpExtInst %u32 %extinst " << ext_inst_name + << " %f32_0 %var_u32\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected Result Type to be a float scalar or vector type")); +} + +TEST_P(ValidateOpenCLStdFrexpLike, XWrongType) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%var_u32 = OpVariable %u32_ptr_function Function\n"; + ss << "%val1 = OpExtInst %f32 %extinst " << ext_inst_name + << " %f64_0 %var_u32\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected type of operand X to be equal to Result Type")); +} + +TEST_P(ValidateOpenCLStdFrexpLike, NotPointer) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %f32 %extinst " << ext_inst_name + << " %f32_0 %u32_1\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected the last operand to be a pointer")); +} + +TEST_P(ValidateOpenCLStdFrexpLike, PointerInvalidStorageClass) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%ptr = OpAccessChain %f32_ptr_uniform_constant " + "%f32vec8_uniform_constant %u32_1\n"; + ss << "%val1 = OpExtInst %f32 %extinst " << ext_inst_name << " %f32_0 %ptr\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected storage class of the pointer to be " + "Generic, CrossWorkgroup, Workgroup or Function")); +} + +TEST_P(ValidateOpenCLStdFrexpLike, PointerDataTypeFloat) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%var_f32 = OpVariable %f32_ptr_function Function\n"; + ss << "%val1 = OpExtInst %f32 %extinst " << ext_inst_name + << " %f32_0 %var_f32\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected data type of the pointer to be a 32-bit " + "int scalar or vector type")); +} + +TEST_P(ValidateOpenCLStdFrexpLike, PointerDataTypeU64) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%var_u64 = OpVariable %u64_ptr_function Function\n"; + ss << "%val1 = OpExtInst %f32 %extinst " << ext_inst_name + << " %f32_0 %var_u64\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected data type of the pointer to be a 32-bit " + "int scalar or vector type")); +} + +TEST_P(ValidateOpenCLStdFrexpLike, PointerDataTypeDiffSize) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%var_u32 = OpVariable %u32_ptr_function Function\n"; + ss << "%val1 = OpExtInst %f32vec2 %extinst " << ext_inst_name + << " %f32vec2_01 %var_u32\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected data type of the pointer to have the same " + "number of components as Result Type")); +} + +INSTANTIATE_TEST_CASE_P(AllFrexpLike, ValidateOpenCLStdFrexpLike, + ::testing::ValuesIn(std::vector{ + "frexp", + "lgamma_r", + }), ); + +TEST_F(ValidateExtInst, OpenCLStdIlogbSuccess) { + const std::string body = R"( +%val1 = OpExtInst %u32 %extinst ilogb %f32_3 +%val2 = OpExtInst %u32vec2 %extinst ilogb %f32vec2_12 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, OpenCLStdIlogbFloatResultType) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst ilogb %f32_3 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpenCL.std ilogb: " + "expected Result Type to be a 32-bit int scalar or vector type")); +} + +TEST_F(ValidateExtInst, OpenCLStdIlogbIntX) { + const std::string body = R"( +%val1 = OpExtInst %u32 %extinst ilogb %u32_3 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std ilogb: " + "expected operand X to be a float scalar or vector")); +} + +TEST_F(ValidateExtInst, OpenCLStdIlogbDiffSize) { + const std::string body = R"( +%val2 = OpExtInst %u32vec2 %extinst ilogb %f32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std ilogb: " + "expected operand X to have the same number of " + "components as Result Type")); +} + +TEST_F(ValidateExtInst, OpenCLStdNanSuccess) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst nan %u32_3 +%val2 = OpExtInst %f32vec2 %extinst nan %u32vec2_12 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtInst, OpenCLStdNanIntResultType) { + const std::string body = R"( +%val1 = OpExtInst %u32 %extinst nan %u32_3 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std nan: " + "expected Result Type to be a float scalar or vector type")); +} + +TEST_F(ValidateExtInst, OpenCLStdNanFloatNancode) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst nan %f32_3 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std nan: " + "expected Nancode to be an int scalar or vector type")); +} + +TEST_F(ValidateExtInst, OpenCLStdNanFloatDiffSize) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst nan %u32vec2_12 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std nan: " + "expected Nancode to have the same number of " + "components as Result Type")); +} + +TEST_F(ValidateExtInst, OpenCLStdNanFloatDiffBitWidth) { + const std::string body = R"( +%val1 = OpExtInst %f64 %extinst nan %u32_2 +)"; + + CompileSuccessfully(GenerateKernelCode(body)); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std nan: " + "expected Nancode to have the same bit width as Result Type")); +} + +TEST_P(ValidateOpenCLStdLdexpLike, Success) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %f32 %extinst " << ext_inst_name + << " %f32_0 %u32_1\n"; + ss << "%val2 = OpExtInst %f32vec2 %extinst " << ext_inst_name + << " %f32vec2_12 %u32vec2_12\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateOpenCLStdLdexpLike, IntResultType) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %u32 %extinst " << ext_inst_name + << " %f32_0 %u32_1\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected Result Type to be a float scalar or vector type")); +} + +TEST_P(ValidateOpenCLStdLdexpLike, XWrongType) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %f32 %extinst " << ext_inst_name + << " %u32_0 %u32_1\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected type of operand X to be equal to Result Type")); +} + +TEST_P(ValidateOpenCLStdLdexpLike, ExponentNotInt) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %f32 %extinst " << ext_inst_name + << " %f32_0 %f32_1\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected the exponent to be a 32-bit int scalar or vector")); +} + +TEST_P(ValidateOpenCLStdLdexpLike, ExponentNotInt32) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %f32 %extinst " << ext_inst_name + << " %f32_0 %u64_1\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected the exponent to be a 32-bit int scalar or vector")); +} + +TEST_P(ValidateOpenCLStdLdexpLike, ExponentWrongSize) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %f32 %extinst " << ext_inst_name + << " %f32_0 %u32vec2_01\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected the exponent to have the same number of " + "components as Result Type")); +} + +INSTANTIATE_TEST_CASE_P(AllLdexpLike, ValidateOpenCLStdLdexpLike, + ::testing::ValuesIn(std::vector{ + "ldexp", + "pown", + "rootn", + }), ); + +TEST_P(ValidateOpenCLStdUpsampleLike, Success) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %u16 %extinst " << ext_inst_name << " %u8_1 %u8_2\n"; + ss << "%val2 = OpExtInst %u32 %extinst " << ext_inst_name + << " %u16_1 %u16_2\n"; + ss << "%val3 = OpExtInst %u64 %extinst " << ext_inst_name + << " %u32_1 %u32_2\n"; + ss << "%val4 = OpExtInst %u64vec2 %extinst " << ext_inst_name + << " %u32vec2_01 %u32vec2_01\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateOpenCLStdUpsampleLike, FloatResultType) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %f64 %extinst " << ext_inst_name + << " %u32_1 %u32_2\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected Result Type to be an int scalar or vector type")); +} + +TEST_P(ValidateOpenCLStdUpsampleLike, InvalidResultTypeBitWidth) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %u8 %extinst " << ext_inst_name << " %u8_1 %u8_2\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpenCL.std " + ext_inst_name + + ": expected bit width of Result Type components to be 16, 32 or 64")); +} + +TEST_P(ValidateOpenCLStdUpsampleLike, LoHiDiffType) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %u64 %extinst " << ext_inst_name + << " %u32_1 %u16_2\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected Hi and Lo operands to have the same type")); +} + +TEST_P(ValidateOpenCLStdUpsampleLike, DiffNumberOfComponents) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %u64vec2 %extinst " << ext_inst_name + << " %u32_1 %u32_2\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected Hi and Lo operands to have the same number " + "of components as Result Type")); +} + +TEST_P(ValidateOpenCLStdUpsampleLike, HiLoWrongBitWidth) { + const std::string ext_inst_name = GetParam(); + std::ostringstream ss; + ss << "%val1 = OpExtInst %u64 %extinst " << ext_inst_name + << " %u16_1 %u16_2\n"; + + CompileSuccessfully(GenerateKernelCode(ss.str())); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpenCL.std " + ext_inst_name + + ": expected bit width of components of Hi and Lo operands to " + "be half of the bit width of components of Result Type")); +} + +INSTANTIATE_TEST_CASE_P(AllUpsampleLike, ValidateOpenCLStdUpsampleLike, + ::testing::ValuesIn(std::vector{ + "u_upsample", + "s_upsample", + }), ); + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/val/val_extensions_test.cpp b/3rdparty/spirv-tools/test/val/val_extensions_test.cpp new file mode 100644 index 000000000..d5ecacda6 --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_extensions_test.cpp @@ -0,0 +1,319 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Tests for OpExtension validator rules. + +#include + +#include "enum_string_mapping.h" +#include "extensions.h" +#include "gmock/gmock.h" +#include "spirv_target_env.h" +#include "test_fixture.h" +#include "unit_spirv.h" +#include "val_fixtures.h" + +namespace { + +using ::libspirv::Extension; + +using ::testing::HasSubstr; +using ::testing::Not; +using ::testing::Values; +using ::testing::ValuesIn; + +using std::string; + +using ValidateKnownExtensions = spvtest::ValidateBase; +using ValidateUnknownExtensions = spvtest::ValidateBase; +using ValidateExtensionCapabilities = spvtest::ValidateBase; + +// Returns expected error string if |extension| is not recognized. +string GetErrorString(const std::string& extension) { + return "Found unrecognized extension " + extension; +} + +INSTANTIATE_TEST_CASE_P( + ExpectSuccess, ValidateKnownExtensions, + Values( + // Match the order as published on the SPIR-V Registry. + "SPV_AMD_shader_explicit_vertex_parameter", + "SPV_AMD_shader_trinary_minmax", "SPV_AMD_gcn_shader", + "SPV_KHR_shader_ballot", "SPV_AMD_shader_ballot", + "SPV_AMD_gpu_shader_half_float", "SPV_KHR_shader_draw_parameters", + "SPV_KHR_subgroup_vote", "SPV_KHR_16bit_storage", + "SPV_KHR_device_group", "SPV_KHR_multiview", + "SPV_NVX_multiview_per_view_attributes", "SPV_NV_viewport_array2", + "SPV_NV_stereo_view_rendering", "SPV_NV_sample_mask_override_coverage", + "SPV_NV_geometry_shader_passthrough", "SPV_AMD_texture_gather_bias_lod", + "SPV_KHR_storage_buffer_storage_class", "SPV_KHR_variable_pointers", + "SPV_AMD_gpu_shader_int16", "SPV_KHR_post_depth_coverage", + "SPV_KHR_shader_atomic_counter_ops", "SPV_EXT_shader_stencil_export", + "SPV_EXT_shader_viewport_index_layer", + "SPV_AMD_shader_image_load_store_lod", "SPV_AMD_shader_fragment_mask", + "SPV_GOOGLE_decorate_string", "SPV_GOOGLE_hlsl_functionality1", + "SPV_NV_shader_subgroup_partitioned", "SPV_EXT_descriptor_indexing")); + +INSTANTIATE_TEST_CASE_P(FailSilently, ValidateUnknownExtensions, + Values("ERROR_unknown_extension", "SPV_KHR_", + "SPV_KHR_shader_ballot_ERROR")); + +TEST_P(ValidateKnownExtensions, ExpectSuccess) { + const std::string extension = GetParam(); + const string str = + "OpCapability Shader\nOpCapability Linkage\nOpExtension \"" + extension + + "\"\nOpMemoryModel Logical GLSL450"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), Not(HasSubstr(GetErrorString(extension)))); +} + +TEST_P(ValidateUnknownExtensions, FailSilently) { + const std::string extension = GetParam(); + const string str = + "OpCapability Shader\nOpCapability Linkage\nOpExtension \"" + extension + + "\"\nOpMemoryModel Logical GLSL450"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(GetErrorString(extension))); +} + +TEST_F(ValidateExtensionCapabilities, DeclCapabilitySuccess) { + const string str = + "OpCapability Shader\nOpCapability Linkage\nOpCapability DeviceGroup\n" + "OpExtension \"SPV_KHR_device_group\"" + "\nOpMemoryModel Logical GLSL450"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateExtensionCapabilities, DeclCapabilityFailure) { + const string str = + "OpCapability Shader\nOpCapability Linkage\nOpCapability DeviceGroup\n" + "\nOpMemoryModel Logical GLSL450"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_ERROR_MISSING_EXTENSION, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("1st operand of Capability")); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("requires one of these extensions")); + EXPECT_THAT(getDiagnosticString(), HasSubstr("SPV_KHR_device_group")); +} + +using ValidateAMDShaderBallotCapabilities = spvtest::ValidateBase; + +// Returns a vector of strings for the prefix of a SPIR-V assembly shader +// that can use the group instructions introduced by SPV_AMD_shader_ballot. +std::vector ShaderPartsForAMDShaderBallot() { + return std::vector{R"( + OpCapability Shader + OpCapability Linkage + )", + R"( + OpMemoryModel Logical GLSL450 + %float = OpTypeFloat 32 + %uint = OpTypeInt 32 0 + %int = OpTypeInt 32 1 + %scope = OpConstant %uint 3 + %uint_const = OpConstant %uint 42 + %int_const = OpConstant %uint 45 + %float_const = OpConstant %float 3.5 + + %void = OpTypeVoid + %fn_ty = OpTypeFunction %void + %fn = OpFunction %void None %fn_ty + %entry = OpLabel + )"}; +} + +// Returns a list of SPIR-V assembly strings, where each uses only types +// and IDs that can fit with a shader made from parts from the result +// of ShaderPartsForAMDShaderBallot. +std::vector AMDShaderBallotGroupInstructions() { + return std::vector{ + "%iadd_reduce = OpGroupIAddNonUniformAMD %uint %scope Reduce %uint_const", + "%iadd_iscan = OpGroupIAddNonUniformAMD %uint %scope InclusiveScan " + "%uint_const", + "%iadd_escan = OpGroupIAddNonUniformAMD %uint %scope ExclusiveScan " + "%uint_const", + + "%fadd_reduce = OpGroupFAddNonUniformAMD %float %scope Reduce " + "%float_const", + "%fadd_iscan = OpGroupFAddNonUniformAMD %float %scope InclusiveScan " + "%float_const", + "%fadd_escan = OpGroupFAddNonUniformAMD %float %scope ExclusiveScan " + "%float_const", + + "%fmin_reduce = OpGroupFMinNonUniformAMD %float %scope Reduce " + "%float_const", + "%fmin_iscan = OpGroupFMinNonUniformAMD %float %scope InclusiveScan " + "%float_const", + "%fmin_escan = OpGroupFMinNonUniformAMD %float %scope ExclusiveScan " + "%float_const", + + "%umin_reduce = OpGroupUMinNonUniformAMD %uint %scope Reduce %uint_const", + "%umin_iscan = OpGroupUMinNonUniformAMD %uint %scope InclusiveScan " + "%uint_const", + "%umin_escan = OpGroupUMinNonUniformAMD %uint %scope ExclusiveScan " + "%uint_const", + + "%smin_reduce = OpGroupUMinNonUniformAMD %int %scope Reduce %int_const", + "%smin_iscan = OpGroupUMinNonUniformAMD %int %scope InclusiveScan " + "%int_const", + "%smin_escan = OpGroupUMinNonUniformAMD %int %scope ExclusiveScan " + "%int_const", + + "%fmax_reduce = OpGroupFMaxNonUniformAMD %float %scope Reduce " + "%float_const", + "%fmax_iscan = OpGroupFMaxNonUniformAMD %float %scope InclusiveScan " + "%float_const", + "%fmax_escan = OpGroupFMaxNonUniformAMD %float %scope ExclusiveScan " + "%float_const", + + "%umax_reduce = OpGroupUMaxNonUniformAMD %uint %scope Reduce %uint_const", + "%umax_iscan = OpGroupUMaxNonUniformAMD %uint %scope InclusiveScan " + "%uint_const", + "%umax_escan = OpGroupUMaxNonUniformAMD %uint %scope ExclusiveScan " + "%uint_const", + + "%smax_reduce = OpGroupUMaxNonUniformAMD %int %scope Reduce %int_const", + "%smax_iscan = OpGroupUMaxNonUniformAMD %int %scope InclusiveScan " + "%int_const", + "%smax_escan = OpGroupUMaxNonUniformAMD %int %scope ExclusiveScan " + "%int_const"}; +} + +TEST_P(ValidateAMDShaderBallotCapabilities, ExpectSuccess) { + // Succeed because the module specifies the SPV_AMD_shader_ballot extension. + auto parts = ShaderPartsForAMDShaderBallot(); + + const string assembly = parts[0] + "OpExtension \"SPV_AMD_shader_ballot\"\n" + + parts[1] + GetParam() + "\nOpReturn OpFunctionEnd"; + + CompileSuccessfully(assembly.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString(); +} + +INSTANTIATE_TEST_CASE_P(ExpectSuccess, ValidateAMDShaderBallotCapabilities, + ValuesIn(AMDShaderBallotGroupInstructions())); + +TEST_P(ValidateAMDShaderBallotCapabilities, ExpectFailure) { + // Fail because the module does not specify the SPV_AMD_shader_ballot + // extension. + auto parts = ShaderPartsForAMDShaderBallot(); + + const string assembly = + parts[0] + parts[1] + GetParam() + "\nOpReturn OpFunctionEnd"; + + CompileSuccessfully(assembly.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_CAPABILITY, ValidateInstructions()); + + // Make sure we get an appropriate error message. + // Find just the opcode name, skipping over the "Op" part. + auto prefix_with_opcode = GetParam().substr(GetParam().find("Group")); + auto opcode = prefix_with_opcode.substr(0, prefix_with_opcode.find(' ')); + EXPECT_THAT(getDiagnosticString(), + HasSubstr(string("Opcode " + opcode + + " requires one of these capabilities: Groups"))); +} + +INSTANTIATE_TEST_CASE_P(ExpectFailure, ValidateAMDShaderBallotCapabilities, + ValuesIn(AMDShaderBallotGroupInstructions())); + +struct ExtIntoCoreCase { + const char* ext; + const char* cap; + const char* builtin; + spv_target_env env; + bool success; +}; + +using ValidateExtIntoCore = spvtest::ValidateBase; + +// Make sure that we don't panic about missing extensions for using +// functionalities that introduced in extensions but became core SPIR-V later. + +TEST_P(ValidateExtIntoCore, DoNotAskForExtensionInLaterVersion) { + const string code = string(R"( + OpCapability Shader + OpCapability )") + + GetParam().cap + R"( + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %main "main" %builtin + OpDecorate %builtin BuiltIn )" + GetParam().builtin + R"( + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Input_int = OpTypePointer Input %int + %builtin = OpVariable %_ptr_Input_int Input + %main = OpFunction %void None %3 + %5 = OpLabel + %18 = OpLoad %int %builtin + OpReturn + OpFunctionEnd)"; + + CompileSuccessfully(code.c_str(), GetParam().env); + if (GetParam().success) { + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(GetParam().env)); + } else { + ASSERT_NE(SPV_SUCCESS, ValidateInstructions(GetParam().env)); + const string message = getDiagnosticString(); + if (spvIsVulkanEnv(GetParam().env)) { + EXPECT_THAT(message, HasSubstr(string(GetParam().cap) + + " is not allowed by Vulkan")); + EXPECT_THAT(message, HasSubstr(string("or requires extension"))); + } else { + EXPECT_THAT(message, + HasSubstr(string("requires one of these extensions: ") + + GetParam().ext)); + } + } +} + +// clang-format off +INSTANTIATE_TEST_CASE_P( + KHR_extensions, ValidateExtIntoCore, + ValuesIn(std::vector{ + // SPV_KHR_shader_draw_parameters became core SPIR-V 1.3 + {"SPV_KHR_shader_draw_parameters", "DrawParameters", "BaseVertex", SPV_ENV_UNIVERSAL_1_3, true}, + {"SPV_KHR_shader_draw_parameters", "DrawParameters", "BaseVertex", SPV_ENV_UNIVERSAL_1_2, false}, + {"SPV_KHR_shader_draw_parameters", "DrawParameters", "BaseVertex", SPV_ENV_UNIVERSAL_1_1, false}, + {"SPV_KHR_shader_draw_parameters", "DrawParameters", "BaseVertex", SPV_ENV_UNIVERSAL_1_0, false}, + {"SPV_KHR_shader_draw_parameters", "DrawParameters", "BaseVertex", SPV_ENV_VULKAN_1_1, true}, + {"SPV_KHR_shader_draw_parameters", "DrawParameters", "BaseVertex", SPV_ENV_VULKAN_1_0, false}, + + {"SPV_KHR_shader_draw_parameters", "DrawParameters", "BaseInstance", SPV_ENV_UNIVERSAL_1_3, true}, + {"SPV_KHR_shader_draw_parameters", "DrawParameters", "BaseInstance", SPV_ENV_VULKAN_1_0, false}, + + {"SPV_KHR_shader_draw_parameters", "DrawParameters", "DrawIndex", SPV_ENV_UNIVERSAL_1_3, true}, + {"SPV_KHR_shader_draw_parameters", "DrawParameters", "DrawIndex", SPV_ENV_UNIVERSAL_1_1, false}, + + // SPV_KHR_multiview became core SPIR-V 1.3 + {"SPV_KHR_multiview", "MultiView", "ViewIndex", SPV_ENV_UNIVERSAL_1_3, true}, + {"SPV_KHR_multiview", "MultiView", "ViewIndex", SPV_ENV_UNIVERSAL_1_2, false}, + {"SPV_KHR_multiview", "MultiView", "ViewIndex", SPV_ENV_UNIVERSAL_1_1, false}, + {"SPV_KHR_multiview", "MultiView", "ViewIndex", SPV_ENV_UNIVERSAL_1_0, false}, + {"SPV_KHR_multiview", "MultiView", "ViewIndex", SPV_ENV_VULKAN_1_1, true}, + {"SPV_KHR_multiview", "MultiView", "ViewIndex", SPV_ENV_VULKAN_1_0, false}, + + // SPV_KHR_device_group became core SPIR-V 1.3 + {"SPV_KHR_device_group", "DeviceGroup", "DeviceIndex", SPV_ENV_UNIVERSAL_1_3, true}, + {"SPV_KHR_device_group", "DeviceGroup", "DeviceIndex", SPV_ENV_UNIVERSAL_1_2, false}, + {"SPV_KHR_device_group", "DeviceGroup", "DeviceIndex", SPV_ENV_UNIVERSAL_1_1, false}, + {"SPV_KHR_device_group", "DeviceGroup", "DeviceIndex", SPV_ENV_UNIVERSAL_1_0, false}, + {"SPV_KHR_device_group", "DeviceGroup", "DeviceIndex", SPV_ENV_VULKAN_1_1, true}, + {"SPV_KHR_device_group", "DeviceGroup", "DeviceIndex", SPV_ENV_VULKAN_1_0, false}, + })); +// clang-format on + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/val/val_fixtures.h b/3rdparty/spirv-tools/test/val/val_fixtures.h new file mode 100644 index 000000000..3f53e9676 --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_fixtures.h @@ -0,0 +1,138 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Common validation fixtures for unit tests + +#ifndef LIBSPIRV_TEST_VALIDATE_FIXTURES_H_ +#define LIBSPIRV_TEST_VALIDATE_FIXTURES_H_ + +#include "source/val/validation_state.h" +#include "test_fixture.h" +#include "unit_spirv.h" + +namespace spvtest { + +template +class ValidateBase : public ::testing::Test, + public ::testing::WithParamInterface { + public: + ValidateBase(); + + virtual void TearDown(); + + // Returns the a spv_const_binary struct + spv_const_binary get_const_binary(); + + void CompileSuccessfully(std::string code, + spv_target_env env = SPV_ENV_UNIVERSAL_1_0); + + // Overwrites the word at index 'index' with the given word. + // For testing purposes, it is often useful to be able to manipulate the + // assembled binary before running the validator on it. + // This function overwrites the word at the given index with a new word. + void OverwriteAssembledBinary(uint32_t index, uint32_t word); + + // Performs validation on the SPIR-V code and compares the result of the + // spvValidate function + spv_result_t ValidateInstructions(spv_target_env env = SPV_ENV_UNIVERSAL_1_0); + + // Performs validation. Returns the status and stores validation state into + // the vstate_ member. + spv_result_t ValidateAndRetrieveValidationState( + spv_target_env env = SPV_ENV_UNIVERSAL_1_0); + + std::string getDiagnosticString(); + spv_position_t getErrorPosition(); + spv_validator_options getValidatorOptions(); + + spv_binary binary_; + spv_diagnostic diagnostic_; + spv_validator_options options_; + std::unique_ptr vstate_; +}; + +template +ValidateBase::ValidateBase() : binary_(), diagnostic_() { + // Initialize to default command line options. Different tests can then + // specialize specific options as necessary. + options_ = spvValidatorOptionsCreate(); +} + +template +spv_const_binary ValidateBase::get_const_binary() { + return spv_const_binary(binary_); +} + +template +void ValidateBase::TearDown() { + if (diagnostic_) { + spvDiagnosticPrint(diagnostic_); + } + spvDiagnosticDestroy(diagnostic_); + spvBinaryDestroy(binary_); + spvValidatorOptionsDestroy(options_); +} + +template +void ValidateBase::CompileSuccessfully(std::string code, + spv_target_env env) { + spv_diagnostic diagnostic = nullptr; + ASSERT_EQ(SPV_SUCCESS, + spvTextToBinary(ScopedContext(env).context, code.c_str(), + code.size(), &binary_, &diagnostic)) + << "ERROR: " << diagnostic->error + << "\nSPIR-V could not be compiled into binary:\n" + << code; +} + +template +void ValidateBase::OverwriteAssembledBinary(uint32_t index, uint32_t word) { + ASSERT_TRUE(index < binary_->wordCount) + << "OverwriteAssembledBinary: The given index is larger than the binary " + "word count."; + binary_->code[index] = word; +} + +template +spv_result_t ValidateBase::ValidateInstructions(spv_target_env env) { + return spvValidateWithOptions(ScopedContext(env).context, options_, + get_const_binary(), &diagnostic_); +} + +template +spv_result_t ValidateBase::ValidateAndRetrieveValidationState( + spv_target_env env) { + return spvtools::ValidateBinaryAndKeepValidationState( + ScopedContext(env).context, options_, get_const_binary()->code, + get_const_binary()->wordCount, &diagnostic_, &vstate_); +} + +template +std::string ValidateBase::getDiagnosticString() { + return diagnostic_ == nullptr ? std::string() + : std::string(diagnostic_->error); +} + +template +spv_validator_options ValidateBase::getValidatorOptions() { + return options_; +} + +template +spv_position_t ValidateBase::getErrorPosition() { + return diagnostic_ == nullptr ? spv_position_t() : diagnostic_->position; +} + +} // namespace spvtest +#endif diff --git a/3rdparty/spirv-tools/test/val/val_id_test.cpp b/3rdparty/spirv-tools/test/val/val_id_test.cpp new file mode 100644 index 000000000..195a65cf2 --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_id_test.cpp @@ -0,0 +1,4766 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "gmock/gmock.h" +#include "test_fixture.h" +#include "unit_spirv.h" +#include "val_fixtures.h" + +// NOTE: The tests in this file are ONLY testing ID usage, there for the input +// SPIR-V does not follow the logical layout rules from the spec in all cases in +// order to makes the tests smaller. Validation of the whole module is handled +// in stages, ID validation is only one of these stages. All validation stages +// are stand alone. + +namespace { + +using spvtest::ScopedContext; +using std::ostringstream; +using std::string; +using std::vector; +using ::testing::HasSubstr; +using ::testing::ValuesIn; + +using ValidateIdWithMessage = spvtest::ValidateBase; + +string kGLSL450MemoryModel = R"( + OpCapability Shader + OpCapability Linkage + OpCapability Addresses + OpCapability Pipes + OpCapability LiteralSampler + OpCapability DeviceEnqueue + OpCapability Vector16 + OpCapability Int8 + OpCapability Int16 + OpCapability Int64 + OpCapability Float64 + OpMemoryModel Logical GLSL450 +)"; + +string kOpenCLMemoryModel32 = R"( + OpCapability Addresses + OpCapability Linkage + OpCapability Kernel +%1 = OpExtInstImport "OpenCL.std" + OpMemoryModel Physical32 OpenCL +)"; + +string kOpenCLMemoryModel64 = R"( + OpCapability Addresses + OpCapability Linkage + OpCapability Kernel + OpCapability Int64 +%1 = OpExtInstImport "OpenCL.std" + OpMemoryModel Physical64 OpenCL +)"; + +string sampledImageSetup = R"( + %void = OpTypeVoid + %typeFuncVoid = OpTypeFunction %void + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 + %image_type = OpTypeImage %float 2D 0 0 0 1 Unknown +%_ptr_UniformConstant_img = OpTypePointer UniformConstant %image_type + %tex = OpVariable %_ptr_UniformConstant_img UniformConstant + %sampler_type = OpTypeSampler +%_ptr_UniformConstant_sam = OpTypePointer UniformConstant %sampler_type + %s = OpVariable %_ptr_UniformConstant_sam UniformConstant + %sampled_image_type = OpTypeSampledImage %image_type + %v2float = OpTypeVector %float 2 + %float_1 = OpConstant %float 1 + %float_2 = OpConstant %float 2 + %const_vec_1_1 = OpConstantComposite %v2float %float_1 %float_1 + %const_vec_2_2 = OpConstantComposite %v2float %float_2 %float_2 + %bool_type = OpTypeBool + %spec_true = OpSpecConstantTrue %bool_type + %main = OpFunction %void None %typeFuncVoid + %label_1 = OpLabel + %image_inst = OpLoad %image_type %tex + %sampler_inst = OpLoad %sampler_type %s +)"; + +string BranchConditionalSetup = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 140 + OpName %main "main" + + ; type definitions + %bool = OpTypeBool + %uint = OpTypeInt 32 0 + %int = OpTypeInt 32 1 + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 + + ; constants + %i0 = OpConstant %int 0 + %i1 = OpConstant %int 1 + %f0 = OpConstant %float 0 + %f1 = OpConstant %float 1 + + + ; main function header + %void = OpTypeVoid + %voidfunc = OpTypeFunction %void + %main = OpFunction %void None %voidfunc + %lmain = OpLabel +)"; + +string BranchConditionalTail = R"( + %target_t = OpLabel + OpNop + OpBranch %end + %target_f = OpLabel + OpNop + OpBranch %end + + %end = OpLabel + + OpReturn + OpFunctionEnd +)"; + +// TODO: OpUndef + +TEST_F(ValidateIdWithMessage, OpName) { + string spirv = kGLSL450MemoryModel + R"( + OpName %2 "name" +%1 = OpTypeInt 32 0 +%2 = OpTypePointer UniformConstant %1 +%3 = OpVariable %2 UniformConstant)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, OpMemberNameGood) { + string spirv = kGLSL450MemoryModel + R"( + OpMemberName %2 0 "foo" +%1 = OpTypeInt 32 0 +%2 = OpTypeStruct %1)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpMemberNameTypeBad) { + string spirv = kGLSL450MemoryModel + R"( + OpMemberName %1 0 "foo" +%1 = OpTypeInt 32 0)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpMemberName Type '1' is not a struct type.")); +} +TEST_F(ValidateIdWithMessage, OpMemberNameMemberBad) { + string spirv = kGLSL450MemoryModel + R"( + OpMemberName %1 1 "foo" +%2 = OpTypeInt 32 0 +%1 = OpTypeStruct %2)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpMemberName Member '1' index is larger than " + "Type '1's member count.")); +} + +TEST_F(ValidateIdWithMessage, OpLineGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpString "/path/to/source.file" + OpLine %1 0 0 +%2 = OpTypeInt 32 0 +%3 = OpTypePointer Input %2 +%4 = OpVariable %3 Input)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, OpLineFileBad) { + string spirv = kGLSL450MemoryModel + R"( + %1 = OpTypeInt 32 0 + OpLine %1 0 0 + )"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpLine Target '1' is not an OpString.")); +} + +TEST_F(ValidateIdWithMessage, OpDecorateGood) { + string spirv = kGLSL450MemoryModel + R"( + OpDecorate %2 GLSLShared +%1 = OpTypeInt 64 0 +%2 = OpTypeStruct %1 %1)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpDecorateBad) { + string spirv = kGLSL450MemoryModel + R"( +OpDecorate %1 GLSLShared)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("forward referenced IDs have not been defined")); +} + +TEST_F(ValidateIdWithMessage, OpMemberDecorateGood) { + string spirv = kGLSL450MemoryModel + R"( + OpMemberDecorate %2 0 Uniform +%1 = OpTypeInt 32 0 +%2 = OpTypeStruct %1 %1)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpMemberDecorateBad) { + string spirv = kGLSL450MemoryModel + R"( + OpMemberDecorate %1 0 Uniform +%1 = OpTypeInt 32 0)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpMemberDecorate Structure type '1' is not a struct type.")); +} +TEST_F(ValidateIdWithMessage, OpMemberDecorateMemberBad) { + string spirv = kGLSL450MemoryModel + R"( + OpMemberDecorate %1 3 Uniform +%int = OpTypeInt 32 0 +%1 = OpTypeStruct %int %int)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Index 3 provided in OpMemberDecorate for struct " + "1 is out of bounds. The structure has 2 members. " + "Largest valid index is 1.")); +} + +TEST_F(ValidateIdWithMessage, OpGroupDecorateGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpDecorationGroup + OpDecorate %1 Uniform + OpDecorate %1 GLSLShared + OpGroupDecorate %1 %3 %4 +%2 = OpTypeInt 32 0 +%3 = OpConstant %2 42 +%4 = OpConstant %2 23)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpDecorationGroupBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpDecorationGroup + OpDecorate %1 Uniform + OpDecorate %1 GLSLShared + OpMemberDecorate %1 0 Constant + )"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Result id of OpDecorationGroup can only " + "be targeted by OpName, OpGroupDecorate, " + "OpDecorate, and OpGroupMemberDecorate")); +} +TEST_F(ValidateIdWithMessage, OpGroupDecorateDecorationGroupBad) { + string spirv = R"( + OpCapability Shader + OpCapability Linkage + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpGroupDecorate %1 %2 %3 +%2 = OpTypeInt 32 0 +%3 = OpConstant %2 42)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpGroupDecorate Decoration group '1' is not a " + "decoration group.")); +} +TEST_F(ValidateIdWithMessage, OpGroupDecorateTargetBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpDecorationGroup + OpDecorate %1 Uniform + OpDecorate %1 GLSLShared + OpGroupDecorate %1 %3 +%2 = OpTypeInt 32 0)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("forward referenced IDs have not been defined")); +} +TEST_F(ValidateIdWithMessage, OpGroupMemberDecorateDecorationGroupBad) { + string spirv = R"( + OpCapability Shader + OpCapability Linkage + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpGroupMemberDecorate %1 %2 0 +%2 = OpTypeInt 32 0)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpGroupMemberDecorate Decoration group '1' is " + "not a decoration group.")); +} +TEST_F(ValidateIdWithMessage, OpGroupMemberDecorateIdNotStructBad) { + string spirv = kGLSL450MemoryModel + R"( + %1 = OpDecorationGroup + OpGroupMemberDecorate %1 %2 0 +%2 = OpTypeInt 32 0)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpGroupMemberDecorate Structure type '2' is not " + "a struct type.")); +} +TEST_F(ValidateIdWithMessage, OpGroupMemberDecorateIndexOutOfBoundBad) { + string spirv = kGLSL450MemoryModel + R"( + OpDecorate %1 Offset 0 + %1 = OpDecorationGroup + OpGroupMemberDecorate %1 %struct 3 +%float = OpTypeFloat 32 +%struct = OpTypeStruct %float %float %float +)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Index 3 provided in OpGroupMemberDecorate for struct " + " 2 is out of bounds. The structure has 3 members. " + "Largest valid index is 2.")); +} + +// TODO: OpExtInst + +TEST_F(ValidateIdWithMessage, OpEntryPointGood) { + string spirv = kGLSL450MemoryModel + R"( + OpEntryPoint GLCompute %3 "" +%1 = OpTypeVoid +%2 = OpTypeFunction %1 +%3 = OpFunction %1 None %2 +%4 = OpLabel + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpEntryPointFunctionBad) { + string spirv = kGLSL450MemoryModel + R"( + OpEntryPoint GLCompute %1 "" +%1 = OpTypeVoid)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpEntryPoint Entry Point '1' is not a function.")); +} +TEST_F(ValidateIdWithMessage, OpEntryPointParameterCountBad) { + string spirv = kGLSL450MemoryModel + R"( + OpEntryPoint GLCompute %3 "" +%1 = OpTypeVoid +%2 = OpTypeFunction %1 %1 +%3 = OpFunction %1 None %2 +%4 = OpLabel + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpEntryPoint Entry Point '1's function parameter " + "count is not zero")); +} +TEST_F(ValidateIdWithMessage, OpEntryPointReturnTypeBad) { + string spirv = kGLSL450MemoryModel + R"( + OpEntryPoint GLCompute %3 "" +%1 = OpTypeInt 32 0 +%ret = OpConstant %1 0 +%2 = OpTypeFunction %1 +%3 = OpFunction %1 None %2 +%4 = OpLabel + OpReturnValue %ret + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpEntryPoint Entry Point '1's function return " + "type is not void.")); +} + +TEST_F(ValidateIdWithMessage, OpEntryPointInterfaceIsNotVariableTypeBad) { + string spirv = R"( + OpCapability Shader + OpCapability Geometry + OpMemoryModel Logical GLSL450 + OpEntryPoint Geometry %main "main" %ptr_builtin_1 + OpMemberDecorate %struct_1 0 BuiltIn InvocationId + %int = OpTypeInt 32 1 + %void = OpTypeVoid + %func = OpTypeFunction %void + %struct_1 = OpTypeStruct %int +%ptr_builtin_1 = OpTypePointer Input %struct_1 + %main = OpFunction %void None %func + %5 = OpLabel + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Interfaces passed to OpEntryPoint must be of type " + "OpTypeVariable. Found OpTypePointer.")); +} + +TEST_F(ValidateIdWithMessage, OpEntryPointInterfaceStorageClassBad) { + string spirv = R"( + OpCapability Shader + OpCapability Geometry + OpMemoryModel Logical GLSL450 + OpEntryPoint Geometry %main "main" %in_1 + OpMemberDecorate %struct_1 0 BuiltIn InvocationId + %int = OpTypeInt 32 1 + %void = OpTypeVoid + %func = OpTypeFunction %void + %struct_1 = OpTypeStruct %int +%ptr_builtin_1 = OpTypePointer Uniform %struct_1 + %in_1 = OpVariable %ptr_builtin_1 Uniform + %main = OpFunction %void None %func + %5 = OpLabel + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpEntryPoint interfaces must be OpVariables with " + "Storage Class of Input(1) or Output(3). Found Storage " + "Class 2 for Entry Point id 1.")); +} + +TEST_F(ValidateIdWithMessage, OpExecutionModeGood) { + string spirv = kGLSL450MemoryModel + R"( + OpEntryPoint GLCompute %3 "" + OpExecutionMode %3 LocalSize 1 1 1 +%1 = OpTypeVoid +%2 = OpTypeFunction %1 +%3 = OpFunction %1 None %2 +%4 = OpLabel + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, OpExecutionModeEntryPointMissing) { + string spirv = kGLSL450MemoryModel + R"( + OpExecutionMode %3 LocalSize 1 1 1 +%1 = OpTypeVoid +%2 = OpTypeFunction %1 +%3 = OpFunction %1 None %2 +%4 = OpLabel + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpExecutionMode Entry Point '1' is not the Entry " + "Point operand of an OpEntryPoint.")); +} + +TEST_F(ValidateIdWithMessage, OpExecutionModeEntryPointBad) { + string spirv = kGLSL450MemoryModel + R"( + OpEntryPoint GLCompute %3 "" %a + OpExecutionMode %a LocalSize 1 1 1 +%void = OpTypeVoid +%ptr = OpTypePointer Input %void +%a = OpVariable %ptr Input +%2 = OpTypeFunction %void +%3 = OpFunction %void None %2 +%4 = OpLabel + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpExecutionMode Entry Point '2' is not the Entry " + "Point operand of an OpEntryPoint.")); +} + +TEST_F(ValidateIdWithMessage, OpTypeVectorFloat) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeFloat 32 +%2 = OpTypeVector %1 4)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, OpTypeVectorInt) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpTypeVector %1 4)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, OpTypeVectorUInt) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 64 0 +%2 = OpTypeVector %1 4)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, OpTypeVectorBool) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeBool +%2 = OpTypeVector %1 4)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, OpTypeVectorComponentTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeFloat 32 +%2 = OpTypePointer UniformConstant %1 +%3 = OpTypeVector %2 4)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpTypeVector Component Type '2' is not a scalar type.")); +} + +TEST_F(ValidateIdWithMessage, OpTypeMatrixGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeFloat 32 +%2 = OpTypeVector %1 2 +%3 = OpTypeMatrix %2 3)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpTypeMatrixColumnTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpTypeMatrix %1 3)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Columns in a matrix must be of type vector.")); +} + +TEST_F(ValidateIdWithMessage, OpTypeSamplerGood) { + // In Rev31, OpTypeSampler takes no arguments. + string spirv = kGLSL450MemoryModel + R"( +%s = OpTypeSampler)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, OpTypeArrayGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpConstant %1 1 +%3 = OpTypeArray %1 %2)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, OpTypeArrayElementTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpConstant %1 1 +%3 = OpTypeArray %2 %2)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpTypeArray Element Type '2' is not a type.")); +} + +// Signed or unsigned. +enum Signed { kSigned, kUnsigned }; + +// Creates an assembly snippet declaring OpTypeArray with the given length. +string MakeArrayLength(const string& len, Signed isSigned, int width) { + ostringstream ss; + ss << R"( + OpCapability Shader + OpCapability Linkage + OpCapability Int16 + OpCapability Int64 + )"; + ss << "OpMemoryModel Logical GLSL450\n"; + ss << " %t = OpTypeInt " << width << (isSigned == kSigned ? " 1" : " 0"); + ss << " %l = OpConstant %t " << len; + ss << " %a = OpTypeArray %t %l"; + return ss.str(); +} + +// Tests OpTypeArray. Parameter is the width (in bits) of the array-length's +// type. +class OpTypeArrayLengthTest + : public spvtest::TextToBinaryTestBase<::testing::TestWithParam> { + protected: + OpTypeArrayLengthTest() + : position_(spv_position_t{0, 0, 0}), + diagnostic_(spvDiagnosticCreate(&position_, "")) {} + + ~OpTypeArrayLengthTest() { spvDiagnosticDestroy(diagnostic_); } + + // Runs spvValidate() on v, printing any errors via spvDiagnosticPrint(). + spv_result_t Val(const SpirvVector& v, const std::string& expected_err = "") { + spv_const_binary_t cbinary{v.data(), v.size()}; + const auto status = + spvValidate(ScopedContext().context, &cbinary, &diagnostic_); + if (status != SPV_SUCCESS) { + spvDiagnosticPrint(diagnostic_); + EXPECT_THAT(std::string(diagnostic_->error), HasSubstr(expected_err)); + } + return status; + } + + private: + spv_position_t position_; // For creating diagnostic_. + spv_diagnostic diagnostic_; +}; + +TEST_P(OpTypeArrayLengthTest, LengthPositive) { + const int width = GetParam(); + EXPECT_EQ(SPV_SUCCESS, + Val(CompileSuccessfully(MakeArrayLength("1", kSigned, width)))); + EXPECT_EQ(SPV_SUCCESS, + Val(CompileSuccessfully(MakeArrayLength("1", kUnsigned, width)))); + EXPECT_EQ(SPV_SUCCESS, + Val(CompileSuccessfully(MakeArrayLength("2", kSigned, width)))); + EXPECT_EQ(SPV_SUCCESS, + Val(CompileSuccessfully(MakeArrayLength("2", kUnsigned, width)))); + EXPECT_EQ(SPV_SUCCESS, + Val(CompileSuccessfully(MakeArrayLength("55", kSigned, width)))); + EXPECT_EQ(SPV_SUCCESS, + Val(CompileSuccessfully(MakeArrayLength("55", kUnsigned, width)))); + const string fpad(width / 4 - 1, 'F'); + EXPECT_EQ( + SPV_SUCCESS, + Val(CompileSuccessfully(MakeArrayLength("0x7" + fpad, kSigned, width)))); + EXPECT_EQ(SPV_SUCCESS, Val(CompileSuccessfully( + MakeArrayLength("0xF" + fpad, kUnsigned, width)))); +} + +TEST_P(OpTypeArrayLengthTest, LengthZero) { + const int width = GetParam(); + EXPECT_EQ( + SPV_ERROR_INVALID_ID, + Val(CompileSuccessfully(MakeArrayLength("0", kSigned, width)), + "OpTypeArray Length '2' default value must be at least 1.")); + EXPECT_EQ( + SPV_ERROR_INVALID_ID, + Val(CompileSuccessfully(MakeArrayLength("0", kUnsigned, width)), + "OpTypeArray Length '2' default value must be at least 1.")); +} + +TEST_P(OpTypeArrayLengthTest, LengthNegative) { + const int width = GetParam(); + EXPECT_EQ( + SPV_ERROR_INVALID_ID, + Val(CompileSuccessfully(MakeArrayLength("-1", kSigned, width)), + "OpTypeArray Length '2' default value must be at least 1.")); + EXPECT_EQ( + SPV_ERROR_INVALID_ID, + Val(CompileSuccessfully(MakeArrayLength("-2", kSigned, width)), + "OpTypeArray Length '2' default value must be at least 1.")); + EXPECT_EQ( + SPV_ERROR_INVALID_ID, + Val(CompileSuccessfully(MakeArrayLength("-123", kSigned, width)), + "OpTypeArray Length '2' default value must be at least 1.")); + const string neg_max = "0x8" + string(width / 4 - 1, '0'); + EXPECT_EQ( + SPV_ERROR_INVALID_ID, + Val(CompileSuccessfully(MakeArrayLength(neg_max, kSigned, width)), + "OpTypeArray Length '2' default value must be at least 1.")); +} + +// The only valid widths for integers are 8, 16, 32, and 64. +// Since the Int8 capability requires the Kernel capability, and the Kernel +// capability prohibits usage of signed integers, we can skip 8-bit integers +// here since the purpose of these tests is to check the validity of +// OpTypeArray, not OpTypeInt. +INSTANTIATE_TEST_CASE_P(Widths, OpTypeArrayLengthTest, + ValuesIn(vector{16, 32, 64})); + +TEST_F(ValidateIdWithMessage, OpTypeArrayLengthNull) { + string spirv = kGLSL450MemoryModel + R"( +%i32 = OpTypeInt 32 0 +%len = OpConstantNull %i32 +%ary = OpTypeArray %i32 %len)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpTypeArray Length '2' default value must be at least 1.")); +} + +TEST_F(ValidateIdWithMessage, OpTypeArrayLengthSpecConst) { + string spirv = kGLSL450MemoryModel + R"( +%i32 = OpTypeInt 32 0 +%len = OpSpecConstant %i32 2 +%ary = OpTypeArray %i32 %len)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, OpTypeArrayLengthSpecConstOp) { + string spirv = kGLSL450MemoryModel + R"( +%i32 = OpTypeInt 32 0 +%c1 = OpConstant %i32 1 +%c2 = OpConstant %i32 2 +%len = OpSpecConstantOp %i32 IAdd %c1 %c2 +%ary = OpTypeArray %i32 %len)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, OpTypeRuntimeArrayGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpTypeRuntimeArray %1)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpTypeRuntimeArrayBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpConstant %1 0 +%3 = OpTypeRuntimeArray %2)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpTypeRuntimeArray Element Type '2' is not a type.")); +} +// TODO: Object of this type can only be created with OpVariable using the +// Unifrom Storage Class + +TEST_F(ValidateIdWithMessage, OpTypeStructGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpTypeFloat 64 +%3 = OpTypePointer Input %1 +%4 = OpTypeStruct %1 %2 %3)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpTypeStructMemberTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpTypeFloat 64 +%3 = OpConstant %2 0.0 +%4 = OpTypeStruct %1 %2 %3)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpTypeStruct Member Type '3' is not a type.")); +} + +TEST_F(ValidateIdWithMessage, OpTypePointerGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpTypePointer Input %1)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpTypePointerBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpConstant %1 0 +%3 = OpTypePointer Input %2)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpTypePointer Type '2' is not a type.")); +} + +TEST_F(ValidateIdWithMessage, OpTypeFunctionGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeFunction %1)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpTypeFunctionReturnTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpConstant %1 0 +%3 = OpTypeFunction %2)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpTypeFunction Return Type '2' is not a type.")); +} +TEST_F(ValidateIdWithMessage, OpTypeFunctionParameterBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpConstant %2 0 +%4 = OpTypeFunction %1 %2 %3)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpTypeFunction Parameter Type '3' is not a type.")); +} + +TEST_F(ValidateIdWithMessage, OpTypePipeGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeFloat 32 +%2 = OpTypeVector %1 16 +%3 = OpTypePipe ReadOnly)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, OpConstantTrueGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeBool +%2 = OpConstantTrue %1)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpConstantTrueBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpConstantTrue %1)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpConstantTrue Result Type '1' is not a boolean type.")); +} + +TEST_F(ValidateIdWithMessage, OpConstantFalseGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeBool +%2 = OpConstantTrue %1)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpConstantFalseBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpConstantFalse %1)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpConstantFalse Result Type '1' is not a boolean type.")); +} + +TEST_F(ValidateIdWithMessage, OpConstantGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpConstant %1 1)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpConstantBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpConstant !1 !0)"; + // The expected failure code is implementation dependent (currently + // INVALID_BINARY because the binary parser catches these cases) and may + // change over time, but this must always fail. + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, OpConstantCompositeVectorGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeFloat 32 +%2 = OpTypeVector %1 4 +%3 = OpConstant %1 3.14 +%4 = OpConstantComposite %2 %3 %3 %3 %3)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpConstantCompositeVectorWithUndefGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeFloat 32 +%2 = OpTypeVector %1 4 +%3 = OpConstant %1 3.14 +%9 = OpUndef %1 +%4 = OpConstantComposite %2 %3 %3 %3 %9)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpConstantCompositeVectorResultTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeFloat 32 +%2 = OpTypeVector %1 4 +%3 = OpConstant %1 3.14 +%4 = OpConstantComposite %1 %3 %3 %3 %3)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpConstantComposite Result Type '1' is not a composite type.")); +} +TEST_F(ValidateIdWithMessage, OpConstantCompositeVectorConstituentTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeFloat 32 +%2 = OpTypeVector %1 4 +%4 = OpTypeInt 32 0 +%3 = OpConstant %1 3.14 +%5 = OpConstant %4 42 ; bad type for constant value +%6 = OpConstantComposite %2 %3 %5 %3 %3)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpConstantComposite Constituent '5's type does not match " + "Result Type '2's vector element type.")); +} +TEST_F(ValidateIdWithMessage, + OpConstantCompositeVectorConstituentUndefTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeFloat 32 +%2 = OpTypeVector %1 4 +%4 = OpTypeInt 32 0 +%3 = OpConstant %1 3.14 +%5 = OpUndef %4 ; bad type for undef value +%6 = OpConstantComposite %2 %3 %5 %3 %3)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpConstantComposite Constituent '5's type does not match " + "Result Type '2's vector element type.")); +} +TEST_F(ValidateIdWithMessage, OpConstantCompositeMatrixGood) { + string spirv = kGLSL450MemoryModel + R"( + %1 = OpTypeFloat 32 + %2 = OpTypeVector %1 4 + %3 = OpTypeMatrix %2 4 + %4 = OpConstant %1 1.0 + %5 = OpConstant %1 0.0 + %6 = OpConstantComposite %2 %4 %5 %5 %5 + %7 = OpConstantComposite %2 %5 %4 %5 %5 + %8 = OpConstantComposite %2 %5 %5 %4 %5 + %9 = OpConstantComposite %2 %5 %5 %5 %4 +%10 = OpConstantComposite %3 %6 %7 %8 %9)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpConstantCompositeMatrixUndefGood) { + string spirv = kGLSL450MemoryModel + R"( + %1 = OpTypeFloat 32 + %2 = OpTypeVector %1 4 + %3 = OpTypeMatrix %2 4 + %4 = OpConstant %1 1.0 + %5 = OpConstant %1 0.0 + %6 = OpConstantComposite %2 %4 %5 %5 %5 + %7 = OpConstantComposite %2 %5 %4 %5 %5 + %8 = OpConstantComposite %2 %5 %5 %4 %5 + %9 = OpUndef %2 +%10 = OpConstantComposite %3 %6 %7 %8 %9)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpConstantCompositeMatrixConstituentTypeBad) { + string spirv = kGLSL450MemoryModel + R"( + %1 = OpTypeFloat 32 + %2 = OpTypeVector %1 4 +%11 = OpTypeVector %1 3 + %3 = OpTypeMatrix %2 4 + %4 = OpConstant %1 1.0 + %5 = OpConstant %1 0.0 + %6 = OpConstantComposite %2 %4 %5 %5 %5 + %7 = OpConstantComposite %2 %5 %4 %5 %5 + %8 = OpConstantComposite %2 %5 %5 %4 %5 + %9 = OpConstantComposite %11 %5 %5 %5 +%10 = OpConstantComposite %3 %6 %7 %8 %9)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpConstantComposite Constituent '10' vector " + "component count does not match Result Type '4's " + "vector component count.")); +} +TEST_F(ValidateIdWithMessage, + OpConstantCompositeMatrixConstituentUndefTypeBad) { + string spirv = kGLSL450MemoryModel + R"( + %1 = OpTypeFloat 32 + %2 = OpTypeVector %1 4 +%11 = OpTypeVector %1 3 + %3 = OpTypeMatrix %2 4 + %4 = OpConstant %1 1.0 + %5 = OpConstant %1 0.0 + %6 = OpConstantComposite %2 %4 %5 %5 %5 + %7 = OpConstantComposite %2 %5 %4 %5 %5 + %8 = OpConstantComposite %2 %5 %5 %4 %5 + %9 = OpUndef %11 +%10 = OpConstantComposite %3 %6 %7 %8 %9)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpConstantComposite Constituent '10' vector " + "component count does not match Result Type '4's " + "vector component count.")); +} +TEST_F(ValidateIdWithMessage, OpConstantCompositeMatrixColumnTypeBad) { + string spirv = kGLSL450MemoryModel + R"( + %1 = OpTypeInt 32 0 + %2 = OpTypeFloat 32 + %3 = OpTypeVector %1 2 + %4 = OpTypeVector %3 2 + %5 = OpTypeMatrix %2 2 + %6 = OpConstant %1 42 + %7 = OpConstant %2 3.14 + %8 = OpConstantComposite %3 %6 %6 + %9 = OpConstantComposite %4 %7 %7 +%10 = OpConstantComposite %5 %8 %9)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Columns in a matrix must be of type vector.")); +} +TEST_F(ValidateIdWithMessage, OpConstantCompositeArrayGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpConstant %1 4 +%3 = OpTypeArray %1 %2 +%4 = OpConstantComposite %3 %2 %2 %2 %2)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpConstantCompositeArrayWithUndefGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpConstant %1 4 +%9 = OpUndef %1 +%3 = OpTypeArray %1 %2 +%4 = OpConstantComposite %3 %2 %2 %2 %9)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpConstantCompositeArrayConstConstituentBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpConstant %1 4 +%3 = OpTypeArray %1 %2 +%4 = OpConstantComposite %3 %2 %2 %2 %1)"; // Uses a type as operand + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpConstantComposite Constituent '1' is not a " + "constant or undef.")); +} +TEST_F(ValidateIdWithMessage, OpConstantCompositeArrayConstituentTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpConstant %1 4 +%3 = OpTypeArray %1 %2 +%5 = OpTypeFloat 32 +%6 = OpConstant %5 3.14 ; bad type for const value +%4 = OpConstantComposite %3 %2 %2 %2 %6)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpConstantComposite Constituent '5's type does " + "not match Result Type '3's array element type.")); +} +TEST_F(ValidateIdWithMessage, OpConstantCompositeArrayConstituentUndefTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpConstant %1 4 +%3 = OpTypeArray %1 %2 +%5 = OpTypeFloat 32 +%6 = OpUndef %5 ; bad type for undef +%4 = OpConstantComposite %3 %2 %2 %2 %6)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpConstantComposite Constituent '5's type does " + "not match Result Type '3's array element type.")); +} +TEST_F(ValidateIdWithMessage, OpConstantCompositeStructGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpTypeInt 64 0 +%3 = OpTypeStruct %1 %1 %2 +%4 = OpConstant %1 42 +%5 = OpConstant %2 4300000000 +%6 = OpConstantComposite %3 %4 %4 %5)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpConstantCompositeStructUndefGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpTypeInt 64 0 +%3 = OpTypeStruct %1 %1 %2 +%4 = OpConstant %1 42 +%5 = OpUndef %2 +%6 = OpConstantComposite %3 %4 %4 %5)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpConstantCompositeStructMemberTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpTypeInt 64 0 +%3 = OpTypeStruct %1 %1 %2 +%4 = OpConstant %1 42 +%5 = OpConstant %2 4300000000 +%6 = OpConstantComposite %3 %4 %5 %4)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpConstantComposite Constituent '5' type does " + "not match the Result Type '3's member type.")); +} + +TEST_F(ValidateIdWithMessage, OpConstantCompositeStructMemberUndefTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpTypeInt 64 0 +%3 = OpTypeStruct %1 %1 %2 +%4 = OpConstant %1 42 +%5 = OpUndef %2 +%6 = OpConstantComposite %3 %4 %5 %4)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpConstantComposite Constituent '5' type does " + "not match the Result Type '3's member type.")); +} + +TEST_F(ValidateIdWithMessage, OpConstantSamplerGood) { + string spirv = kGLSL450MemoryModel + R"( +%float = OpTypeFloat 32 +%samplerType = OpTypeSampler +%3 = OpConstantSampler %samplerType ClampToEdge 0 Nearest)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpConstantSamplerResultTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeFloat 32 +%2 = OpConstantSampler %1 Clamp 0 Nearest)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpConstantSampler Result Type '1' is not a sampler type.")); +} + +TEST_F(ValidateIdWithMessage, OpConstantNullGood) { + string spirv = kGLSL450MemoryModel + R"( + %1 = OpTypeBool + %2 = OpConstantNull %1 + %3 = OpTypeInt 32 0 + %4 = OpConstantNull %3 + %5 = OpTypeFloat 32 + %6 = OpConstantNull %5 + %7 = OpTypePointer UniformConstant %3 + %8 = OpConstantNull %7 + %9 = OpTypeEvent +%10 = OpConstantNull %9 +%11 = OpTypeDeviceEvent +%12 = OpConstantNull %11 +%13 = OpTypeReserveId +%14 = OpConstantNull %13 +%15 = OpTypeQueue +%16 = OpConstantNull %15 +%17 = OpTypeVector %5 2 +%18 = OpConstantNull %17 +%19 = OpTypeMatrix %17 2 +%20 = OpConstantNull %19 +%25 = OpConstant %3 8 +%21 = OpTypeArray %3 %25 +%22 = OpConstantNull %21 +%23 = OpTypeStruct %3 %5 %1 +%24 = OpConstantNull %23 +%26 = OpTypeArray %17 %25 +%27 = OpConstantNull %26 +%28 = OpTypeStruct %7 %26 %26 %1 +%29 = OpConstantNull %28 +)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, OpConstantNullBasicBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpConstantNull %1)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpConstantNull Result Type '1' cannot have a null value.")); +} + +TEST_F(ValidateIdWithMessage, OpConstantNullArrayBad) { + string spirv = kGLSL450MemoryModel + R"( +%2 = OpTypeInt 32 0 +%3 = OpTypeSampler +%4 = OpConstant %2 4 +%5 = OpTypeArray %3 %4 +%6 = OpConstantNull %5)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpConstantNull Result Type '4' cannot have a null value.")); +} + +TEST_F(ValidateIdWithMessage, OpConstantNullStructBad) { + string spirv = kGLSL450MemoryModel + R"( +%2 = OpTypeSampler +%3 = OpTypeStruct %2 %2 +%4 = OpConstantNull %3)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpConstantNull Result Type '2' cannot have a null value.")); +} + +TEST_F(ValidateIdWithMessage, OpConstantNullRuntimeArrayBad) { + string spirv = kGLSL450MemoryModel + R"( +%bool = OpTypeBool +%array = OpTypeRuntimeArray %bool +%null = OpConstantNull %array)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpConstantNull Result Type '2' cannot have a null value.")); +} + +TEST_F(ValidateIdWithMessage, OpSpecConstantTrueGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeBool +%2 = OpSpecConstantTrue %1)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpSpecConstantTrueBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpSpecConstantTrue %1)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Specialization constant must be a boolean type.")); +} + +TEST_F(ValidateIdWithMessage, OpSpecConstantFalseGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeBool +%2 = OpSpecConstantFalse %1)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpSpecConstantFalseBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpSpecConstantFalse %1)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Specialization constant must be a boolean type.")); +} + +TEST_F(ValidateIdWithMessage, OpSpecConstantGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeFloat 32 +%2 = OpSpecConstant %1 42)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpSpecConstantBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpSpecConstant !1 !4)"; + // The expected failure code is implementation dependent (currently + // INVALID_BINARY because the binary parser catches these cases) and may + // change over time, but this must always fail. + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Type Id 1 is not a scalar numeric type")); +} + +// Valid: SpecConstantComposite specializes to a vector. +TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeVectorGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeFloat 32 +%2 = OpTypeVector %1 4 +%3 = OpSpecConstant %1 3.14 +%4 = OpConstant %1 3.14 +%5 = OpSpecConstantComposite %2 %3 %3 %4 %4)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Valid: Vector of floats and Undefs. +TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeVectorWithUndefGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeFloat 32 +%2 = OpTypeVector %1 4 +%3 = OpSpecConstant %1 3.14 +%5 = OpConstant %1 3.14 +%9 = OpUndef %1 +%4 = OpSpecConstantComposite %2 %3 %5 %3 %9)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid: result type is float. +TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeVectorResultTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeFloat 32 +%2 = OpTypeVector %1 4 +%3 = OpSpecConstant %1 3.14 +%4 = OpSpecConstantComposite %1 %3 %3 %3 %3)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("is not a composite type")); +} + +// Invalid: Vector contains a mix of Int and Float. +TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeVectorConstituentTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeFloat 32 +%2 = OpTypeVector %1 4 +%4 = OpTypeInt 32 0 +%3 = OpSpecConstant %1 3.14 +%5 = OpConstant %4 42 ; bad type for constant value +%6 = OpSpecConstantComposite %2 %3 %5 %3 %3)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpSpecConstantComposite Constituent '5's type " + "does not match Result Type '2's vector element " + "type.")); +} + +// Invalid: Constituent is not a constant +TEST_F(ValidateIdWithMessage, + OpSpecConstantCompositeVectorConstituentNotConstantBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeFloat 32 +%2 = OpTypeVector %1 4 +%3 = OpTypeInt 32 0 +%4 = OpSpecConstant %1 3.14 +%6 = OpSpecConstantComposite %2 %3 %4 %4 %4)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpSpecConstantComposite Constituent '3' is not a " + "constant or undef.")); +} + +// Invalid: Vector contains a mix of Undef-int and Float. +TEST_F(ValidateIdWithMessage, + OpSpecConstantCompositeVectorConstituentUndefTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeFloat 32 +%2 = OpTypeVector %1 4 +%4 = OpTypeInt 32 0 +%3 = OpSpecConstant %1 3.14 +%5 = OpUndef %4 ; bad type for undef value +%6 = OpSpecConstantComposite %2 %3 %5 %3 %3)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpSpecConstantComposite Constituent '5's type " + "does not match Result Type '2's vector element " + "type.")); +} + +// Invalid: Vector expects 3 components, but 4 specified. +TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeVectorNumComponentsBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeFloat 32 +%2 = OpTypeVector %1 3 +%3 = OpConstant %1 3.14 +%5 = OpSpecConstant %1 4.0 +%6 = OpSpecConstantComposite %2 %3 %5 %3 %3)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpSpecConstantComposite Constituent count does " + "not match Result Type '2's vector component " + "count.")); +} + +// Valid: 4x4 matrix of floats +TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeMatrixGood) { + string spirv = kGLSL450MemoryModel + R"( + %1 = OpTypeFloat 32 + %2 = OpTypeVector %1 4 + %3 = OpTypeMatrix %2 4 + %4 = OpConstant %1 1.0 + %5 = OpSpecConstant %1 0.0 + %6 = OpSpecConstantComposite %2 %4 %5 %5 %5 + %7 = OpSpecConstantComposite %2 %5 %4 %5 %5 + %8 = OpSpecConstantComposite %2 %5 %5 %4 %5 + %9 = OpSpecConstantComposite %2 %5 %5 %5 %4 +%10 = OpSpecConstantComposite %3 %6 %7 %8 %9)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Valid: Matrix in which one column is Undef +TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeMatrixUndefGood) { + string spirv = kGLSL450MemoryModel + R"( + %1 = OpTypeFloat 32 + %2 = OpTypeVector %1 4 + %3 = OpTypeMatrix %2 4 + %4 = OpConstant %1 1.0 + %5 = OpSpecConstant %1 0.0 + %6 = OpSpecConstantComposite %2 %4 %5 %5 %5 + %7 = OpSpecConstantComposite %2 %5 %4 %5 %5 + %8 = OpSpecConstantComposite %2 %5 %5 %4 %5 + %9 = OpUndef %2 +%10 = OpSpecConstantComposite %3 %6 %7 %8 %9)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid: Matrix in which the sizes of column vectors are not equal. +TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeMatrixConstituentTypeBad) { + string spirv = kGLSL450MemoryModel + R"( + %1 = OpTypeFloat 32 + %2 = OpTypeVector %1 4 + %3 = OpTypeVector %1 3 + %4 = OpTypeMatrix %2 4 + %5 = OpSpecConstant %1 1.0 + %6 = OpConstant %1 0.0 + %7 = OpSpecConstantComposite %2 %5 %6 %6 %6 + %8 = OpSpecConstantComposite %2 %6 %5 %6 %6 + %9 = OpSpecConstantComposite %2 %6 %6 %5 %6 + %10 = OpSpecConstantComposite %3 %6 %6 %6 +%11 = OpSpecConstantComposite %4 %7 %8 %9 %10)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpSpecConstantComposite Constituent '10' vector " + "component count does not match Result Type '4's " + "vector component count.")); +} + +// Invalid: Matrix type expects 4 columns but only 3 specified. +TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeMatrixNumColsBad) { + string spirv = kGLSL450MemoryModel + R"( + %1 = OpTypeFloat 32 + %2 = OpTypeVector %1 4 + %3 = OpTypeMatrix %2 4 + %4 = OpSpecConstant %1 1.0 + %5 = OpConstant %1 0.0 + %6 = OpSpecConstantComposite %2 %4 %5 %5 %5 + %7 = OpSpecConstantComposite %2 %5 %4 %5 %5 + %8 = OpSpecConstantComposite %2 %5 %5 %4 %5 +%10 = OpSpecConstantComposite %3 %6 %7 %8)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpSpecConstantComposite Constituent count does " + "not match Result Type '3's matrix column count.")); +} + +// Invalid: Composite contains a non-const/undef component +TEST_F(ValidateIdWithMessage, + OpSpecConstantCompositeMatrixConstituentNotConstBad) { + string spirv = kGLSL450MemoryModel + R"( + %1 = OpTypeFloat 32 + %2 = OpConstant %1 0.0 + %3 = OpTypeVector %1 4 + %4 = OpTypeMatrix %3 4 + %5 = OpSpecConstantComposite %3 %2 %2 %2 %2 + %6 = OpSpecConstantComposite %4 %5 %5 %5 %1)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpSpecConstantComposite Constituent '1' is not a " + "constant composite or undef.")); +} + +// Invalid: Composite contains a column that is *not* a vector (it's an array) +TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeMatrixColTypeBad) { + string spirv = kGLSL450MemoryModel + R"( + %1 = OpTypeFloat 32 + %2 = OpTypeInt 32 0 + %3 = OpSpecConstant %2 4 + %4 = OpConstant %1 0.0 + %5 = OpTypeVector %1 4 + %6 = OpTypeArray %2 %3 + %7 = OpTypeMatrix %5 4 + %8 = OpSpecConstantComposite %6 %3 %3 %3 %3 + %9 = OpSpecConstantComposite %5 %4 %4 %4 %4 + %10 = OpSpecConstantComposite %7 %9 %9 %9 %8)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpSpecConstantComposite Constituent '8' type " + "does not match Result Type '7's matrix column " + "type.")); +} + +// Invalid: Matrix with an Undef column of the wrong size. +TEST_F(ValidateIdWithMessage, + OpSpecConstantCompositeMatrixConstituentUndefTypeBad) { + string spirv = kGLSL450MemoryModel + R"( + %1 = OpTypeFloat 32 + %2 = OpTypeVector %1 4 + %3 = OpTypeVector %1 3 + %4 = OpTypeMatrix %2 4 + %5 = OpSpecConstant %1 1.0 + %6 = OpSpecConstant %1 0.0 + %7 = OpSpecConstantComposite %2 %5 %6 %6 %6 + %8 = OpSpecConstantComposite %2 %6 %5 %6 %6 + %9 = OpSpecConstantComposite %2 %6 %6 %5 %6 + %10 = OpUndef %3 + %11 = OpSpecConstantComposite %4 %7 %8 %9 %10)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpSpecConstantComposite Constituent '10' vector " + "component count does not match Result Type '4's " + "vector component count.")); +} + +// Invalid: Matrix in which some columns are Int and some are Float. +TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeMatrixColumnTypeBad) { + string spirv = kGLSL450MemoryModel + R"( + %1 = OpTypeInt 32 0 + %2 = OpTypeFloat 32 + %3 = OpTypeVector %1 2 + %4 = OpTypeVector %2 2 + %5 = OpTypeMatrix %4 2 + %6 = OpSpecConstant %1 42 + %7 = OpConstant %2 3.14 + %8 = OpSpecConstantComposite %3 %6 %6 + %9 = OpSpecConstantComposite %4 %7 %7 +%10 = OpSpecConstantComposite %5 %8 %9)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpSpecConstantComposite Constituent '8' " + "component type does not match Result Type '5's " + "matrix column component type.")); +} + +// Valid: Array of integers +TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeArrayGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpSpecConstant %1 4 +%5 = OpConstant %1 5 +%3 = OpTypeArray %1 %2 +%6 = OpTypeArray %1 %5 +%4 = OpSpecConstantComposite %3 %2 %2 %2 %2 +%7 = OpSpecConstantComposite %3 %5 %5 %5 %5)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid: Expecting an array of 4 components, but 3 specified. +TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeArrayNumComponentsBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpSpecConstant %1 4 +%3 = OpTypeArray %1 %2 +%4 = OpSpecConstantComposite %3 %2 %2 %2)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpSpecConstantComposite Constituent count does not " + "match Result Type '3's array length.")); +} + +// Valid: Array of Integers and Undef-int +TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeArrayWithUndefGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpSpecConstant %1 4 +%9 = OpUndef %1 +%3 = OpTypeArray %1 %2 +%4 = OpSpecConstantComposite %3 %2 %2 %2 %9)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid: Array uses a type as operand. +TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeArrayConstConstituentBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpConstant %1 4 +%3 = OpTypeArray %1 %2 +%4 = OpSpecConstantComposite %3 %2 %2 %2 %1)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpSpecConstantComposite Constituent '1' is not a " + "constant or undef.")); +} + +// Invalid: Array has a mix of Int and Float components. +TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeArrayConstituentTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpConstant %1 4 +%3 = OpTypeArray %1 %2 +%4 = OpTypeFloat 32 +%5 = OpSpecConstant %4 3.14 ; bad type for const value +%6 = OpSpecConstantComposite %3 %2 %2 %2 %5)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpSpecConstantComposite Constituent '5's type " + "does not match Result Type '3's array element " + "type.")); +} + +// Invalid: Array has a mix of Int and Undef-float. +TEST_F(ValidateIdWithMessage, + OpSpecConstantCompositeArrayConstituentUndefTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpSpecConstant %1 4 +%3 = OpTypeArray %1 %2 +%5 = OpTypeFloat 32 +%6 = OpUndef %5 ; bad type for undef +%4 = OpSpecConstantComposite %3 %2 %2 %2 %6)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpSpecConstantComposite Constituent '5's type " + "does not match Result Type '3's array element " + "type.")); +} + +// Valid: Struct of {Int32,Int32,Int64}. +TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeStructGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpTypeInt 64 0 +%3 = OpTypeStruct %1 %1 %2 +%4 = OpConstant %1 42 +%5 = OpSpecConstant %2 4300000000 +%6 = OpSpecConstantComposite %3 %4 %4 %5)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid: missing one int32 struct member. +TEST_F(ValidateIdWithMessage, + OpSpecConstantCompositeStructMissingComponentBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%3 = OpTypeStruct %1 %1 %1 +%4 = OpConstant %1 42 +%5 = OpSpecConstant %1 430 +%6 = OpSpecConstantComposite %3 %4 %5)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpSpecConstantComposite Constituent '2' count " + "does not match Result Type '2's struct member " + "count.")); +} + +// Valid: Struct uses Undef-int64. +TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeStructUndefGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpTypeInt 64 0 +%3 = OpTypeStruct %1 %1 %2 +%4 = OpSpecConstant %1 42 +%5 = OpUndef %2 +%6 = OpSpecConstantComposite %3 %4 %4 %5)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid: Composite contains non-const/undef component. +TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeStructNonConstBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpTypeInt 64 0 +%3 = OpTypeStruct %1 %1 %2 +%4 = OpSpecConstant %1 42 +%5 = OpUndef %2 +%6 = OpSpecConstantComposite %3 %4 %1 %5)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpSpecConstantComposite Constituent '1' is not a " + "constant or undef.")); +} + +// Invalid: Struct component type does not match expected specialization type. +// Second component was expected to be Int32, but got Int64. +TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeStructMemberTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpTypeInt 64 0 +%3 = OpTypeStruct %1 %1 %2 +%4 = OpConstant %1 42 +%5 = OpSpecConstant %2 4300000000 +%6 = OpSpecConstantComposite %3 %4 %5 %4)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpSpecConstantComposite Constituent '5' type " + "does not match the Result Type '3's member " + "type.")); +} + +// Invalid: Undef-int64 used when Int32 was expected. +TEST_F(ValidateIdWithMessage, OpSpecConstantCompositeStructMemberUndefTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpTypeInt 64 0 +%3 = OpTypeStruct %1 %1 %2 +%4 = OpSpecConstant %1 42 +%5 = OpUndef %2 +%6 = OpSpecConstantComposite %3 %4 %5 %4)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpSpecConstantComposite Constituent '5' type " + "does not match the Result Type '3's member " + "type.")); +} + +// TODO: OpSpecConstantOp + +TEST_F(ValidateIdWithMessage, OpVariableGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpTypePointer Input %1 +%3 = OpVariable %2 Input)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpVariableInitializerConstantGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpTypePointer Input %1 +%3 = OpConstant %1 42 +%4 = OpVariable %2 Input %3)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpVariableInitializerGlobalVariableGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpTypePointer Uniform %1 +%3 = OpVariable %2 Uniform +%4 = OpTypePointer Uniform %2 ; pointer to pointer +%5 = OpVariable %4 Uniform %3)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +// TODO: Positive test OpVariable with OpConstantNull of OpTypePointer +TEST_F(ValidateIdWithMessage, OpVariableResultTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpVariable %1 Input)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpVariable Result Type '1' is not a pointer type.")); +} +TEST_F(ValidateIdWithMessage, OpVariableInitializerIsTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpTypePointer Input %1 +%3 = OpVariable %2 Input %2)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpVariable Initializer '2' is not a constant or " + "module-scope variable")); +} + +TEST_F(ValidateIdWithMessage, OpVariableInitializerIsFunctionVarBad) { + string spirv = kGLSL450MemoryModel + R"( +%int = OpTypeInt 32 0 +%ptrint = OpTypePointer Function %int +%ptrptrint = OpTypePointer Function %ptrint +%void = OpTypeVoid +%fnty = OpTypeFunction %void +%main = OpFunction %void None %fnty +%entry = OpLabel +%var = OpVariable %ptrint Function +%varinit = OpVariable %ptrptrint Function %var ; Can't initialize function variable. +OpReturn +OpFunctionEnd +)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpVariable Initializer '8' is not a constant or " + "module-scope variable")); +} + +TEST_F(ValidateIdWithMessage, OpVariableInitializerIsModuleVarGood) { + string spirv = kGLSL450MemoryModel + R"( +%int = OpTypeInt 32 0 +%ptrint = OpTypePointer Uniform %int +%mvar = OpVariable %ptrint Uniform +%ptrptrint = OpTypePointer Function %ptrint +%void = OpTypeVoid +%fnty = OpTypeFunction %void +%main = OpFunction %void None %fnty +%entry = OpLabel +%goodvar = OpVariable %ptrptrint Function %mvar ; This is ok +OpReturn +OpFunctionEnd +)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, OpLoadGood) { + string spirv = kGLSL450MemoryModel + R"( + %1 = OpTypeVoid + %2 = OpTypeInt 32 0 + %3 = OpTypePointer UniformConstant %2 + %4 = OpTypeFunction %1 + %5 = OpVariable %3 UniformConstant + %6 = OpFunction %1 None %4 + %7 = OpLabel + %8 = OpLoad %2 %5 + %9 = OpReturn +%10 = OpFunctionEnd +)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// TODO: Add tests that exercise VariablePointersStorageBuffer instead of +// VariablePointers. +void createVariablePointerSpirvProgram(std::ostringstream* spirv, + std::string result_strategy, + bool use_varptr_cap, + bool add_helper_function) { + *spirv << "OpCapability Shader "; + if (use_varptr_cap) { + *spirv << "OpCapability VariablePointers "; + *spirv << "OpExtension \"SPV_KHR_variable_pointers\" "; + } + *spirv << R"( + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %main "main" + %void = OpTypeVoid + %voidf = OpTypeFunction %void + %bool = OpTypeBool + %i32 = OpTypeInt 32 1 + %f32 = OpTypeFloat 32 + %f32ptr = OpTypePointer Uniform %f32 + %i = OpConstant %i32 1 + %zero = OpConstant %i32 0 + %float_1 = OpConstant %f32 1.0 + %ptr1 = OpVariable %f32ptr Uniform + %ptr2 = OpVariable %f32ptr Uniform + )"; + if (add_helper_function) { + *spirv << R"( + ; //////////////////////////////////////////////////////////// + ;;;; Function that returns a pointer + ; //////////////////////////////////////////////////////////// + %selector_func_type = OpTypeFunction %f32ptr %bool %f32ptr %f32ptr + %choose_input_func = OpFunction %f32ptr None %selector_func_type + %is_neg_param = OpFunctionParameter %bool + %first_ptr_param = OpFunctionParameter %f32ptr + %second_ptr_param = OpFunctionParameter %f32ptr + %selector_func_begin = OpLabel + %result_ptr = OpSelect %f32ptr %is_neg_param %first_ptr_param %second_ptr_param + OpReturnValue %result_ptr + OpFunctionEnd + )"; + } + *spirv << R"( + %main = OpFunction %void None %voidf + %label = OpLabel + )"; + *spirv << result_strategy; + *spirv << R"( + OpReturn + OpFunctionEnd + )"; +} + +// With the VariablePointer Capability, OpLoad should allow loading a +// VaiablePointer. In this test the variable pointer is obtained by an OpSelect +TEST_F(ValidateIdWithMessage, OpLoadVarPtrOpSelectGood) { + std::string result_strategy = R"( + %isneg = OpSLessThan %bool %i %zero + %varptr = OpSelect %f32ptr %isneg %ptr1 %ptr2 + %result = OpLoad %f32 %varptr + )"; + + std::ostringstream spirv; + createVariablePointerSpirvProgram(&spirv, result_strategy, + true /* Add VariablePointers Capability? */, + false /* Use Helper Function? */); + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Without the VariablePointers Capability, OpLoad will not allow loading +// through a variable pointer. +// Disabled since using OpSelect with pointers without VariablePointers will +// fail LogicalsPass. +TEST_F(ValidateIdWithMessage, DISABLED_OpLoadVarPtrOpSelectBad) { + std::string result_strategy = R"( + %isneg = OpSLessThan %bool %i %zero + %varptr = OpSelect %f32ptr %isneg %ptr1 %ptr2 + %result = OpLoad %f32 %varptr + )"; + + std::ostringstream spirv; + createVariablePointerSpirvProgram(&spirv, result_strategy, + false /* Add VariablePointers Capability?*/, + false /* Use Helper Function? */); + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("is not a logical pointer.")); +} + +// With the VariablePointer Capability, OpLoad should allow loading a +// VaiablePointer. In this test the variable pointer is obtained by an OpPhi +TEST_F(ValidateIdWithMessage, OpLoadVarPtrOpPhiGood) { + std::string result_strategy = R"( + %is_neg = OpSLessThan %bool %i %zero + OpSelectionMerge %end_label None + OpBranchConditional %is_neg %take_ptr_1 %take_ptr_2 + %take_ptr_1 = OpLabel + OpBranch %end_label + %take_ptr_2 = OpLabel + OpBranch %end_label + %end_label = OpLabel + %varptr = OpPhi %f32ptr %ptr1 %take_ptr_1 %ptr2 %take_ptr_2 + %result = OpLoad %f32 %varptr + )"; + + std::ostringstream spirv; + createVariablePointerSpirvProgram(&spirv, result_strategy, + true /* Add VariablePointers Capability?*/, + false /* Use Helper Function? */); + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Without the VariablePointers Capability, OpLoad will not allow loading +// through a variable pointer. +TEST_F(ValidateIdWithMessage, OpLoadVarPtrOpPhiBad) { + std::string result_strategy = R"( + %is_neg = OpSLessThan %bool %i %zero + OpSelectionMerge %end_label None + OpBranchConditional %is_neg %take_ptr_1 %take_ptr_2 + %take_ptr_1 = OpLabel + OpBranch %end_label + %take_ptr_2 = OpLabel + OpBranch %end_label + %end_label = OpLabel + %varptr = OpPhi %f32ptr %ptr1 %take_ptr_1 %ptr2 %take_ptr_2 + %result = OpLoad %f32 %varptr + )"; + + std::ostringstream spirv; + createVariablePointerSpirvProgram(&spirv, result_strategy, + false /* Add VariablePointers Capability?*/, + false /* Use Helper Function? */); + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("is not a logical pointer")); +} + +// With the VariablePointer Capability, OpLoad should allow loading through a +// VaiablePointer. In this test the variable pointer is obtained from an +// OpFunctionCall (return value from a function) +TEST_F(ValidateIdWithMessage, OpLoadVarPtrOpFunctionCallGood) { + std::ostringstream spirv; + std::string result_strategy = R"( + %isneg = OpSLessThan %bool %i %zero + %varptr = OpFunctionCall %f32ptr %choose_input_func %isneg %ptr1 %ptr2 + %result = OpLoad %f32 %varptr + )"; + + createVariablePointerSpirvProgram(&spirv, result_strategy, + true /* Add VariablePointers Capability?*/, + true /* Use Helper Function? */); + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, OpLoadResultTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypePointer UniformConstant %2 +%4 = OpTypeFunction %1 +%5 = OpVariable %3 UniformConstant +%6 = OpFunction %1 None %4 +%7 = OpLabel +%8 = OpLoad %3 %5 + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpLoad Result Type '3' does not match Pointer " + " '5's type.")); +} + +TEST_F(ValidateIdWithMessage, OpLoadPointerBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypePointer UniformConstant %2 +%4 = OpTypeFunction %1 +%5 = OpFunction %1 None %4 +%6 = OpLabel +%7 = OpLoad %2 %8 + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + // Prove that SSA checks trigger for a bad Id value. + // The next test case show the not-a-logical-pointer case. + EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 8 has not been defined")); +} + +// Disabled as bitcasting type to object is now not valid. +TEST_F(ValidateIdWithMessage, DISABLED_OpLoadLogicalPointerBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypeFloat 32 +%4 = OpTypePointer UniformConstant %2 +%5 = OpTypePointer UniformConstant %3 +%6 = OpTypeFunction %1 +%7 = OpFunction %1 None %6 +%8 = OpLabel +%9 = OpBitcast %5 %4 ; Not valid in logical addressing +%10 = OpLoad %3 %9 ; Should trigger message + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + // Once we start checking bitcasts, we might catch that + // as the error first, instead of catching it here. + // I don't know if it's possible to generate a bad case + // if/when the validator is complete. + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpLoad Pointer '9' is not a logical pointer.")); +} + +TEST_F(ValidateIdWithMessage, OpStoreGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypePointer Uniform %2 +%4 = OpTypeFunction %1 +%5 = OpConstant %2 42 +%6 = OpVariable %3 UniformConstant +%7 = OpFunction %1 None %4 +%8 = OpLabel + OpStore %6 %5 + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpStorePointerBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypePointer UniformConstant %2 +%4 = OpTypeFunction %1 +%5 = OpConstant %2 42 +%6 = OpVariable %3 UniformConstant +%7 = OpFunction %1 None %4 +%8 = OpLabel + OpStore %3 %5 + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpStore Pointer '3' is not a logical pointer.")); +} + +// Disabled as bitcasting type to object is now not valid. +TEST_F(ValidateIdWithMessage, DISABLED_OpStoreLogicalPointerBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypeFloat 32 +%4 = OpTypePointer UniformConstant %2 +%5 = OpTypePointer UniformConstant %3 +%6 = OpTypeFunction %1 +%7 = OpConstantNull %5 +%8 = OpFunction %1 None %6 +%9 = OpLabel +%10 = OpBitcast %5 %4 ; Not valid in logical addressing +%11 = OpStore %10 %7 ; Should trigger message + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpStore Pointer '10' is not a logical pointer.")); +} + +// Without the VariablePointer Capability, OpStore should may not store +// through a variable pointer. +// Disabled since using OpSelect with pointers without VariablePointers will +// fail LogicalsPass. +TEST_F(ValidateIdWithMessage, DISABLED_OpStoreVarPtrBad) { + std::string result_strategy = R"( + %isneg = OpSLessThan %bool %i %zero + %varptr = OpSelect %f32ptr %isneg %ptr1 %ptr2 + OpStore %varptr %float_1 + )"; + + std::ostringstream spirv; + createVariablePointerSpirvProgram( + &spirv, result_strategy, false /* Add VariablePointers Capability? */, + false /* Use Helper Function? */); + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("is not a logical pointer.")); +} + +// With the VariablePointer Capability, OpStore should allow storing through a +// variable pointer. +TEST_F(ValidateIdWithMessage, OpStoreVarPtrGood) { + std::string result_strategy = R"( + %isneg = OpSLessThan %bool %i %zero + %varptr = OpSelect %f32ptr %isneg %ptr1 %ptr2 + OpStore %varptr %float_1 + )"; + + std::ostringstream spirv; + createVariablePointerSpirvProgram(&spirv, result_strategy, + true /* Add VariablePointers Capability? */, + false /* Use Helper Function? */); + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, OpStoreObjectGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypePointer Uniform %2 +%4 = OpTypeFunction %1 +%5 = OpConstant %2 42 +%6 = OpVariable %3 UniformConstant +%7 = OpFunction %1 None %4 +%8 = OpLabel + OpStore %6 %7 + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpStore Object '7's type is void.")); +} +TEST_F(ValidateIdWithMessage, OpStoreTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%9 = OpTypeFloat 32 +%3 = OpTypePointer Uniform %2 +%4 = OpTypeFunction %1 +%5 = OpConstant %9 3.14 +%6 = OpVariable %3 UniformConstant +%7 = OpFunction %1 None %4 +%8 = OpLabel + OpStore %6 %5 + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpStore Pointer '7's type does not match Object " + " '6's type.")); +} + +// The next series of test check test a relaxation of the rules for stores to +// structs. The first test checks that we get a failure when the option is not +// set to relax the rule. +// TODO: Add tests for layout compatible arrays and matricies when the validator +// relaxes the rules for them as well. Also need test to check for layout +// decorations specific to those types. +TEST_F(ValidateIdWithMessage, OpStoreTypeBadStruct) { + string spirv = kGLSL450MemoryModel + R"( + OpMemberDecorate %1 0 Offset 0 + OpMemberDecorate %1 1 Offset 4 + OpMemberDecorate %2 0 Offset 0 + OpMemberDecorate %2 1 Offset 4 +%3 = OpTypeVoid +%4 = OpTypeFloat 32 +%1 = OpTypeStruct %4 %4 +%5 = OpTypePointer Uniform %1 +%2 = OpTypeStruct %4 %4 +%6 = OpTypeFunction %3 +%7 = OpConstant %4 3.14 +%8 = OpVariable %5 Uniform +%9 = OpFunction %3 None %6 +%10 = OpLabel +%11 = OpCompositeConstruct %2 %7 %7 + OpStore %8 %11 + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpStore Pointer '8's type does not match Object " + " '11's type.")); +} + +// Same code as the last test. The difference is that we relax the rule. +// Because the structs %3 and %5 are defined the same way. +TEST_F(ValidateIdWithMessage, OpStoreTypeRelaxedStruct) { + string spirv = kGLSL450MemoryModel + R"( + OpMemberDecorate %1 0 Offset 0 + OpMemberDecorate %1 1 Offset 4 + OpMemberDecorate %2 0 Offset 0 + OpMemberDecorate %2 1 Offset 4 +%3 = OpTypeVoid +%4 = OpTypeFloat 32 +%1 = OpTypeStruct %4 %4 +%5 = OpTypePointer Uniform %1 +%2 = OpTypeStruct %4 %4 +%6 = OpTypeFunction %3 +%7 = OpConstant %4 3.14 +%8 = OpVariable %5 Uniform +%9 = OpFunction %3 None %6 +%10 = OpLabel +%11 = OpCompositeConstruct %2 %7 %7 + OpStore %8 %11 + OpReturn + OpFunctionEnd)"; + spvValidatorOptionsSetRelaxStoreStruct(options_, true); + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Same code as the last test excect for an extra decoration on one of the +// members. With the relaxed rules, the code is still valid. +TEST_F(ValidateIdWithMessage, OpStoreTypeRelaxedStructWithExtraDecoration) { + string spirv = kGLSL450MemoryModel + R"( + OpMemberDecorate %1 0 Offset 0 + OpMemberDecorate %1 1 Offset 4 + OpMemberDecorate %1 0 RelaxedPrecision + OpMemberDecorate %2 0 Offset 0 + OpMemberDecorate %2 1 Offset 4 +%3 = OpTypeVoid +%4 = OpTypeFloat 32 +%1 = OpTypeStruct %4 %4 +%5 = OpTypePointer Uniform %1 +%2 = OpTypeStruct %4 %4 +%6 = OpTypeFunction %3 +%7 = OpConstant %4 3.14 +%8 = OpVariable %5 Uniform +%9 = OpFunction %3 None %6 +%10 = OpLabel +%11 = OpCompositeConstruct %2 %7 %7 + OpStore %8 %11 + OpReturn + OpFunctionEnd)"; + spvValidatorOptionsSetRelaxStoreStruct(options_, true); + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// This test check that we recursively traverse the struct to check if they are +// interchangable. +TEST_F(ValidateIdWithMessage, OpStoreTypeRelaxedNestedStruct) { + string spirv = kGLSL450MemoryModel + R"( + OpMemberDecorate %1 0 Offset 0 + OpMemberDecorate %1 1 Offset 4 + OpMemberDecorate %2 0 Offset 0 + OpMemberDecorate %2 1 Offset 8 + OpMemberDecorate %3 0 Offset 0 + OpMemberDecorate %3 1 Offset 4 + OpMemberDecorate %4 0 Offset 0 + OpMemberDecorate %4 1 Offset 8 +%5 = OpTypeVoid +%6 = OpTypeInt 32 0 +%7 = OpTypeFloat 32 +%1 = OpTypeStruct %7 %6 +%2 = OpTypeStruct %1 %1 +%8 = OpTypePointer Uniform %2 +%3 = OpTypeStruct %7 %6 +%4 = OpTypeStruct %3 %3 +%9 = OpTypeFunction %5 +%10 = OpConstant %6 7 +%11 = OpConstant %7 3.14 +%12 = OpConstantComposite %3 %11 %10 +%13 = OpVariable %8 Uniform +%14 = OpFunction %5 None %9 +%15 = OpLabel +%16 = OpCompositeConstruct %4 %12 %12 + OpStore %13 %16 + OpReturn + OpFunctionEnd)"; + spvValidatorOptionsSetRelaxStoreStruct(options_, true); + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// This test check that the even with the relaxed rules an error is identified +// if the members of the struct are in a different order. +TEST_F(ValidateIdWithMessage, OpStoreTypeBadRelaxedStruct1) { + string spirv = kGLSL450MemoryModel + R"( + OpMemberDecorate %1 0 Offset 0 + OpMemberDecorate %1 1 Offset 4 + OpMemberDecorate %2 0 Offset 0 + OpMemberDecorate %2 1 Offset 8 + OpMemberDecorate %3 0 Offset 0 + OpMemberDecorate %3 1 Offset 4 + OpMemberDecorate %4 0 Offset 0 + OpMemberDecorate %4 1 Offset 8 +%5 = OpTypeVoid +%6 = OpTypeInt 32 0 +%7 = OpTypeFloat 32 +%1 = OpTypeStruct %6 %7 +%2 = OpTypeStruct %1 %1 +%8 = OpTypePointer Uniform %2 +%3 = OpTypeStruct %7 %6 +%4 = OpTypeStruct %3 %3 +%9 = OpTypeFunction %5 +%10 = OpConstant %6 7 +%11 = OpConstant %7 3.14 +%12 = OpConstantComposite %3 %11 %10 +%13 = OpVariable %8 Uniform +%14 = OpFunction %5 None %9 +%15 = OpLabel +%16 = OpCompositeConstruct %4 %12 %12 + OpStore %13 %16 + OpReturn + OpFunctionEnd)"; + spvValidatorOptionsSetRelaxStoreStruct(options_, true); + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpStore Pointer '13's layout does not match Object " + " '16's layout.")); +} + +// This test check that the even with the relaxed rules an error is identified +// if the members of the struct are at different offsets. +TEST_F(ValidateIdWithMessage, OpStoreTypeBadRelaxedStruct2) { + string spirv = kGLSL450MemoryModel + R"( + OpMemberDecorate %1 0 Offset 4 + OpMemberDecorate %1 1 Offset 0 + OpMemberDecorate %2 0 Offset 0 + OpMemberDecorate %2 1 Offset 8 + OpMemberDecorate %3 0 Offset 0 + OpMemberDecorate %3 1 Offset 4 + OpMemberDecorate %4 0 Offset 0 + OpMemberDecorate %4 1 Offset 8 +%5 = OpTypeVoid +%6 = OpTypeInt 32 0 +%7 = OpTypeFloat 32 +%1 = OpTypeStruct %7 %6 +%2 = OpTypeStruct %1 %1 +%8 = OpTypePointer Uniform %2 +%3 = OpTypeStruct %7 %6 +%4 = OpTypeStruct %3 %3 +%9 = OpTypeFunction %5 +%10 = OpConstant %6 7 +%11 = OpConstant %7 3.14 +%12 = OpConstantComposite %3 %11 %10 +%13 = OpVariable %8 Uniform +%14 = OpFunction %5 None %9 +%15 = OpLabel +%16 = OpCompositeConstruct %4 %12 %12 + OpStore %13 %16 + OpReturn + OpFunctionEnd)"; + spvValidatorOptionsSetRelaxStoreStruct(options_, true); + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpStore Pointer '13's layout does not match Object " + " '16's layout.")); +} + +TEST_F(ValidateIdWithMessage, OpStoreTypeRelaxedLogicalPointerReturnPointer) { + const string spirv = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 +%1 = OpTypeInt 32 1 +%2 = OpTypePointer Function %1 +%3 = OpTypeFunction %2 %2 +%4 = OpFunction %2 None %3 +%5 = OpFunctionParameter %2 +%6 = OpLabel + OpReturnValue %5 + OpFunctionEnd)"; + + spvValidatorOptionsSetRelaxLogicalPointer(options_, true); + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, OpStoreTypeRelaxedLogicalPointerAllocPointer) { + const string spirv = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + %1 = OpTypeVoid + %2 = OpTypeInt 32 1 + %3 = OpTypeFunction %1 ; void(void) + %4 = OpTypePointer Uniform %2 ; int* + %5 = OpTypePointer Private %4 ; int** (Private) + %6 = OpTypePointer Function %4 ; int** (Function) + %7 = OpVariable %5 Private + %8 = OpFunction %1 None %3 + %9 = OpLabel +%10 = OpVariable %6 Function + OpReturn + OpFunctionEnd)"; + + spvValidatorOptionsSetRelaxLogicalPointer(options_, true); + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, OpStoreVoid) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypePointer Uniform %2 +%4 = OpTypeFunction %1 +%6 = OpVariable %3 UniformConstant +%7 = OpFunction %1 None %4 +%8 = OpLabel +%9 = OpFunctionCall %1 %7 + OpStore %6 %9 + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpStore Object '8's type is void.")); +} + +TEST_F(ValidateIdWithMessage, OpStoreLabel) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypePointer Uniform %2 +%4 = OpTypeFunction %1 +%6 = OpVariable %3 UniformConstant +%7 = OpFunction %1 None %4 +%8 = OpLabel + OpStore %6 %8 + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpStore Object '7' is not an object.")); +} + +// TODO: enable when this bug is fixed: +// https://cvs.khronos.org/bugzilla/show_bug.cgi?id=15404 +TEST_F(ValidateIdWithMessage, DISABLED_OpStoreFunction) { + string spirv = kGLSL450MemoryModel + R"( +%2 = OpTypeInt 32 0 +%3 = OpTypePointer UniformConstant %2 +%4 = OpTypeFunction %2 +%5 = OpConstant %2 123 +%6 = OpVariable %3 UniformConstant +%7 = OpFunction %2 None %4 +%8 = OpLabel + OpStore %6 %7 + OpReturnValue %5 + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, OpStoreBuiltin) { + string spirv = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %main "main" %gl_GlobalInvocationID + OpExecutionMode %main LocalSize 1 1 1 + OpSource GLSL 450 + OpName %main "main" + + OpName %gl_GlobalInvocationID "gl_GlobalInvocationID" + OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId + + %int = OpTypeInt 32 1 + %uint = OpTypeInt 32 0 + %v3uint = OpTypeVector %uint 3 +%_ptr_Input_v3uint = OpTypePointer Input %v3uint +%gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input + + %zero = OpConstant %uint 0 + %v3uint_000 = OpConstantComposite %v3uint %zero %zero %zero + + %void = OpTypeVoid + %voidfunc = OpTypeFunction %void + %main = OpFunction %void None %voidfunc + %lmain = OpLabel + + OpStore %gl_GlobalInvocationID %v3uint_000 + + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("storage class is read-only")); +} + +TEST_F(ValidateIdWithMessage, OpCopyMemoryGood) { + string spirv = kGLSL450MemoryModel + R"( + %1 = OpTypeVoid + %2 = OpTypeInt 32 0 + %3 = OpTypePointer UniformConstant %2 + %4 = OpConstant %2 42 + %5 = OpVariable %3 UniformConstant %4 + %6 = OpTypePointer Function %2 + %7 = OpTypeFunction %1 + %8 = OpFunction %1 None %7 + %9 = OpLabel +%10 = OpVariable %6 Function + OpCopyMemory %10 %5 None + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpCopyMemoryBad) { + string spirv = kGLSL450MemoryModel + R"( + %1 = OpTypeVoid + %2 = OpTypeInt 32 0 + %3 = OpTypePointer UniformConstant %2 + %4 = OpConstant %2 42 + %5 = OpVariable %3 UniformConstant %4 +%11 = OpTypeFloat 32 + %6 = OpTypePointer Function %11 + %7 = OpTypeFunction %1 + %8 = OpFunction %1 None %7 + %9 = OpLabel +%10 = OpVariable %6 Function + OpCopyMemory %10 %5 None + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpCopyMemory Target '5's type does not match " + "Source '2's type.")); +} + +// TODO: OpCopyMemorySized +TEST_F(ValidateIdWithMessage, OpCopyMemorySizedGood) { + string spirv = kGLSL450MemoryModel + R"( + %1 = OpTypeVoid + %2 = OpTypeInt 32 0 + %3 = OpTypePointer UniformConstant %2 + %4 = OpTypePointer Function %2 + %5 = OpConstant %2 4 + %6 = OpVariable %3 UniformConstant %5 + %7 = OpTypeFunction %1 + %8 = OpFunction %1 None %7 + %9 = OpLabel +%10 = OpVariable %4 Function + OpCopyMemorySized %10 %6 %5 None + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpCopyMemorySizedTargetBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypePointer UniformConstant %2 +%4 = OpTypePointer Function %2 +%5 = OpConstant %2 4 +%6 = OpVariable %3 UniformConstant %5 +%7 = OpTypeFunction %1 +%8 = OpFunction %1 None %7 +%9 = OpLabel + OpCopyMemorySized %9 %6 %5 None + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpCopyMemorySized Target '9' is not a pointer.")); +} +TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSourceBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypePointer UniformConstant %2 +%4 = OpTypePointer Function %2 +%5 = OpConstant %2 4 +%6 = OpTypeFunction %1 +%7 = OpFunction %1 None %6 +%8 = OpLabel +%9 = OpVariable %4 Function + OpCopyMemorySized %9 %6 %5 None + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpCopyMemorySized Source '6' is not a pointer.")); +} +TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeBad) { + string spirv = kGLSL450MemoryModel + R"( + %1 = OpTypeVoid + %2 = OpTypeInt 32 0 + %3 = OpTypePointer UniformConstant %2 + %4 = OpTypePointer Function %2 + %5 = OpConstant %2 4 + %6 = OpVariable %3 UniformConstant %5 + %7 = OpTypeFunction %1 + %8 = OpFunction %1 None %7 + %9 = OpLabel +%10 = OpVariable %4 Function + OpCopyMemorySized %10 %6 %6 None + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpCopyMemorySized Size '6's variable type is not " + "an integer type.")); +} +TEST_F(ValidateIdWithMessage, OpCopyMemorySizedSizeTypeBad) { + string spirv = kGLSL450MemoryModel + R"( + %1 = OpTypeVoid + %2 = OpTypeInt 32 0 + %3 = OpTypePointer UniformConstant %2 + %4 = OpTypePointer Function %2 + %5 = OpConstant %2 4 + %6 = OpVariable %3 UniformConstant %5 + %7 = OpTypeFunction %1 +%11 = OpTypeFloat 32 +%12 = OpConstant %11 1.0 + %8 = OpFunction %1 None %7 + %9 = OpLabel +%10 = OpVariable %4 Function + OpCopyMemorySized %10 %6 %12 None + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpCopyMemorySized Size '9's type is not an integer type.")); +} + +const char kDeeplyNestedStructureSetup[] = R"( +%void = OpTypeVoid +%void_f = OpTypeFunction %void +%int = OpTypeInt 32 0 +%float = OpTypeFloat 32 +%v3float = OpTypeVector %float 3 +%mat4x3 = OpTypeMatrix %v3float 4 +%_ptr_Private_mat4x3 = OpTypePointer Private %mat4x3 +%_ptr_Private_float = OpTypePointer Private %float +%my_matrix = OpVariable %_ptr_Private_mat4x3 Private +%my_float_var = OpVariable %_ptr_Private_float Private +%_ptr_Function_float = OpTypePointer Function %float +%int_0 = OpConstant %int 0 +%int_1 = OpConstant %int 1 +%int_2 = OpConstant %int 2 +%int_3 = OpConstant %int 3 +%int_5 = OpConstant %int 5 + +; Making the following nested structures. +; +; struct S { +; bool b; +; vec4 v[5]; +; int i; +; mat4x3 m[5]; +; } +; uniform blockName { +; S s; +; bool cond; +; RunTimeArray arr; +; } + +%f32arr = OpTypeRuntimeArray %float +%bool = OpTypeBool +%v4float = OpTypeVector %float 4 +%array5_mat4x3 = OpTypeArray %mat4x3 %int_5 +%array5_vec4 = OpTypeArray %v4float %int_5 +%_ptr_Uniform_float = OpTypePointer Uniform %float +%_ptr_Function_vec4 = OpTypePointer Function %v4float +%_ptr_Uniform_vec4 = OpTypePointer Uniform %v4float +%struct_s = OpTypeStruct %bool %array5_vec4 %int %array5_mat4x3 +%struct_blockName = OpTypeStruct %struct_s %bool %f32arr +%_ptr_Uniform_blockName = OpTypePointer Uniform %struct_blockName +%_ptr_Uniform_struct_s = OpTypePointer Uniform %struct_s +%_ptr_Uniform_array5_mat4x3 = OpTypePointer Uniform %array5_mat4x3 +%_ptr_Uniform_mat4x3 = OpTypePointer Uniform %mat4x3 +%_ptr_Uniform_v3float = OpTypePointer Uniform %v3float +%blockName_var = OpVariable %_ptr_Uniform_blockName Uniform +%spec_int = OpSpecConstant %int 2 +%func = OpFunction %void None %void_f +%my_label = OpLabel +)"; + +// In what follows, Access Chain Instruction refers to one of the following: +// OpAccessChain, OpInBoundsAccessChain, OpPtrAccessChain, and +// OpInBoundsPtrAccessChain +using AccessChainInstructionTest = spvtest::ValidateBase; + +// Determines whether the access chain instruction requires the 'element id' +// argument. +bool AccessChainRequiresElemId(const std::string& instr) { + return (instr == "OpPtrAccessChain" || instr == "OpInBoundsPtrAccessChain"); +} + +// Valid: Access a float in a matrix using an access chain instruction. +TEST_P(AccessChainInstructionTest, AccessChainGood) { + const std::string instr = GetParam(); + const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + + "%float_entry = " + instr + + R"( %_ptr_Private_float %my_matrix )" + elem + + R"(%int_0 %int_1 + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid. The result type of an access chain instruction must be a pointer. +TEST_P(AccessChainInstructionTest, AccessChainResultTypeBad) { + const std::string instr = GetParam(); + const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( +%float_entry = )" + + instr + R"( %float %my_matrix )" + elem + R"(%int_0 %int_1 +OpReturn +OpFunctionEnd + )"; + + const std::string expected_err = "The Result Type of " + instr + + " '36' must be " + "OpTypePointer. Found OpTypeFloat."; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(expected_err)); +} + +// Invalid. The base type of an access chain instruction must be a pointer. +TEST_P(AccessChainInstructionTest, AccessChainBaseTypeVoidBad) { + const std::string instr = GetParam(); + const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( +%float_entry = )" + + instr + " %_ptr_Private_float %void " + elem + + R"(%int_0 %int_1 +OpReturn +OpFunctionEnd + )"; + const std::string expected_err = "The Base '1' in " + instr + + " instruction must " + "be a pointer."; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(expected_err)); +} + +// Invalid. The base type of an access chain instruction must be a pointer. +TEST_P(AccessChainInstructionTest, AccessChainBaseTypeNonPtrVariableBad) { + const std::string instr = GetParam(); + const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( +%entry = )" + + instr + + R"( %_ptr_Private_float %_ptr_Private_float )" + elem + + R"(%int_0 %int_1 +OpReturn +OpFunctionEnd + )"; + const std::string expected_err = "The Base '8' in " + instr + + " instruction must " + "be a pointer."; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(expected_err)); +} + +// Invalid: The storage class of Base and Result do not match. +TEST_P(AccessChainInstructionTest, + AccessChainResultAndBaseStorageClassDoesntMatchBad) { + const std::string instr = GetParam(); + const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( +%entry = )" + + instr + + R"( %_ptr_Function_float %my_matrix )" + elem + + R"(%int_0 %int_1 +OpReturn +OpFunctionEnd + )"; + const std::string expected_err = + "The result pointer storage class and base pointer storage class in " + + instr + " do not match."; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(expected_err)); +} + +// Invalid. The base type of an access chain instruction must point to a +// composite object. +TEST_P(AccessChainInstructionTest, + AccessChainBasePtrNotPointingToCompositeBad) { + const std::string instr = GetParam(); + const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( +%entry = )" + + instr + + R"( %_ptr_Private_float %my_float_var )" + elem + R"(%int_0 +OpReturn +OpFunctionEnd + )"; + const std::string expected_err = instr + + " reached non-composite type while " + "indexes still remain to be traversed."; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(expected_err)); +} + +// Valid. No Indexes were passed to the access chain instruction. The Result +// Type is the same as the Base type. +TEST_P(AccessChainInstructionTest, AccessChainNoIndexesGood) { + const std::string instr = GetParam(); + const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( +%entry = )" + + instr + + R"( %_ptr_Private_float %my_float_var )" + elem + R"( +OpReturn +OpFunctionEnd + )"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid. No Indexes were passed to the access chain instruction, but the +// Result Type is different from the Base type. +TEST_P(AccessChainInstructionTest, AccessChainNoIndexesBad) { + const std::string instr = GetParam(); + const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( +%entry = )" + + instr + + R"( %_ptr_Private_mat4x3 %my_float_var )" + elem + R"( +OpReturn +OpFunctionEnd + )"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("result type (OpTypeMatrix) does not match the type that " + "results from indexing into the base (OpTypeFloat).")); +} + +// Valid: 255 indexes passed to the access chain instruction. Limit is 255. +TEST_P(AccessChainInstructionTest, AccessChainTooManyIndexesGood) { + const std::string instr = GetParam(); + const std::string elem = AccessChainRequiresElemId(instr) ? " %int_0 " : ""; + int depth = 255; + std::string header = kGLSL450MemoryModel + kDeeplyNestedStructureSetup; + header.erase(header.find("%func")); + std::ostringstream spirv; + spirv << header << "\n"; + + // Build nested structures. Struct 'i' contains struct 'i-1' + spirv << "%s_depth_1 = OpTypeStruct %float\n"; + for (int i = 2; i <= depth; ++i) { + spirv << "%s_depth_" << i << " = OpTypeStruct %s_depth_" << i - 1 << "\n"; + } + + // Define Pointer and Variable to use for the AccessChain instruction. + spirv << "%_ptr_Uniform_deep_struct = OpTypePointer Uniform %s_depth_" + << depth << "\n"; + spirv << "%deep_var = OpVariable %_ptr_Uniform_deep_struct Uniform\n"; + + // Function Start + spirv << R"( + %func = OpFunction %void None %void_f + %my_label = OpLabel + )"; + + // AccessChain with 'n' indexes (n = depth) + spirv << "%entry = " << instr << " %_ptr_Uniform_float %deep_var" << elem; + for (int i = 0; i < depth; ++i) { + spirv << " %int_0"; + } + + // Function end + spirv << R"( + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid: 256 indexes passed to the access chain instruction. Limit is 255. +TEST_P(AccessChainInstructionTest, AccessChainTooManyIndexesBad) { + const std::string instr = GetParam(); + const std::string elem = AccessChainRequiresElemId(instr) ? " %int_0 " : ""; + std::ostringstream spirv; + spirv << kGLSL450MemoryModel << kDeeplyNestedStructureSetup; + spirv << "%entry = " << instr << " %_ptr_Private_float %my_matrix" << elem; + for (int i = 0; i < 256; ++i) { + spirv << " %int_0"; + } + spirv << R"( + OpReturn + OpFunctionEnd + )"; + const std::string expected_err = "The number of indexes in " + instr + + " may not exceed 255. Found 256 indexes."; + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(expected_err)); +} + +// Valid: 10 indexes passed to the access chain instruction. (Custom limit: 10) +TEST_P(AccessChainInstructionTest, CustomizedAccessChainTooManyIndexesGood) { + const std::string instr = GetParam(); + const std::string elem = AccessChainRequiresElemId(instr) ? " %int_0 " : ""; + int depth = 10; + std::string header = kGLSL450MemoryModel + kDeeplyNestedStructureSetup; + header.erase(header.find("%func")); + std::ostringstream spirv; + spirv << header << "\n"; + + // Build nested structures. Struct 'i' contains struct 'i-1' + spirv << "%s_depth_1 = OpTypeStruct %float\n"; + for (int i = 2; i <= depth; ++i) { + spirv << "%s_depth_" << i << " = OpTypeStruct %s_depth_" << i - 1 << "\n"; + } + + // Define Pointer and Variable to use for the AccessChain instruction. + spirv << "%_ptr_Uniform_deep_struct = OpTypePointer Uniform %s_depth_" + << depth << "\n"; + spirv << "%deep_var = OpVariable %_ptr_Uniform_deep_struct Uniform\n"; + + // Function Start + spirv << R"( + %func = OpFunction %void None %void_f + %my_label = OpLabel + )"; + + // AccessChain with 'n' indexes (n = depth) + spirv << "%entry = " << instr << " %_ptr_Uniform_float %deep_var" << elem; + for (int i = 0; i < depth; ++i) { + spirv << " %int_0"; + } + + // Function end + spirv << R"( + OpReturn + OpFunctionEnd + )"; + + spvValidatorOptionsSetUniversalLimit( + options_, spv_validator_limit_max_access_chain_indexes, 10u); + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid: 11 indexes passed to the access chain instruction. Custom Limit:10 +TEST_P(AccessChainInstructionTest, CustomizedAccessChainTooManyIndexesBad) { + const std::string instr = GetParam(); + const std::string elem = AccessChainRequiresElemId(instr) ? " %int_0 " : ""; + std::ostringstream spirv; + spirv << kGLSL450MemoryModel << kDeeplyNestedStructureSetup; + spirv << "%entry = " << instr << " %_ptr_Private_float %my_matrix" << elem; + for (int i = 0; i < 11; ++i) { + spirv << " %int_0"; + } + spirv << R"( + OpReturn + OpFunctionEnd + )"; + const std::string expected_err = "The number of indexes in " + instr + + " may not exceed 10. Found 11 indexes."; + spvValidatorOptionsSetUniversalLimit( + options_, spv_validator_limit_max_access_chain_indexes, 10u); + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(expected_err)); +} + +// Invalid: Index passed to the access chain instruction is float (must be +// integer). +TEST_P(AccessChainInstructionTest, AccessChainUndefinedIndexBad) { + const std::string instr = GetParam(); + const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; + string spirv = + kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( +%entry = )" + instr + + R"( %_ptr_Private_float %my_matrix )" + elem + R"(%float %int_1 +OpReturn +OpFunctionEnd + )"; + const std::string expected_err = + "Indexes passed to " + instr + " must be of type integer."; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(expected_err)); +} + +// Invalid: The index argument that indexes into a struct must be of type +// OpConstant. +TEST_P(AccessChainInstructionTest, AccessChainStructIndexNotConstantBad) { + const std::string instr = GetParam(); + const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( +%f = )" + + instr + R"( %_ptr_Uniform_float %blockName_var )" + elem + + R"(%int_0 %spec_int %int_2 +OpReturn +OpFunctionEnd + )"; + const std::string expected_err = + "The passed to " + instr + + " to index into a structure must be an OpConstant."; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(expected_err)); +} + +// Invalid: Indexing up to a vec4 granularity, but result type expected float. +TEST_P(AccessChainInstructionTest, + AccessChainStructResultTypeDoesntMatchIndexedTypeBad) { + const std::string instr = GetParam(); + const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( +%entry = )" + + instr + + R"( %_ptr_Uniform_float %blockName_var )" + elem + + R"(%int_0 %int_1 %int_2 +OpReturn +OpFunctionEnd + )"; + const std::string expected_err = instr + + " result type (OpTypeFloat) does not match " + "the type that results from indexing into " + "the base (OpTypeVector)."; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(expected_err)); +} + +// Invalid: Reach non-composite type (bool) when unused indexes remain. +TEST_P(AccessChainInstructionTest, AccessChainStructTooManyIndexesBad) { + const std::string instr = GetParam(); + const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( +%entry = )" + + instr + + R"( %_ptr_Uniform_float %blockName_var )" + elem + + R"(%int_0 %int_2 %int_2 +OpReturn +OpFunctionEnd + )"; + const std::string expected_err = instr + + " reached non-composite type while " + "indexes still remain to be traversed."; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(expected_err)); +} + +// Invalid: Trying to find index 3 of the struct that has only 3 members. +TEST_P(AccessChainInstructionTest, AccessChainStructIndexOutOfBoundBad) { + const std::string instr = GetParam(); + const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( +%entry = )" + + instr + + R"( %_ptr_Uniform_float %blockName_var )" + elem + + R"(%int_3 %int_2 %int_2 +OpReturn +OpFunctionEnd + )"; + const std::string expected_err = "Index is out of bounds: " + instr + + " can not find index 3 into the structure " + " '26'. This structure has 3 members. " + "Largest valid index is 2."; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(expected_err)); +} + +// Valid: Tests that we can index into Struct, Array, Matrix, and Vector! +TEST_P(AccessChainInstructionTest, AccessChainIndexIntoAllTypesGood) { + // indexes that we are passing are: 0, 3, 1, 2, 0 + // 0 will select the struct_s within the base struct (blockName) + // 3 will select the Array that contains 5 matrices + // 1 will select the Matrix that is at index 1 of the array + // 2 will select the column (which is a vector) within the matrix at index 2 + // 0 will select the element at the index 0 of the vector. (which is a float). + const std::string instr = GetParam(); + const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; + ostringstream spirv; + spirv << kGLSL450MemoryModel << kDeeplyNestedStructureSetup << std::endl; + spirv << "%ss = " << instr << " %_ptr_Uniform_struct_s %blockName_var " + << elem << "%int_0" << std::endl; + spirv << "%sa = " << instr << " %_ptr_Uniform_array5_mat4x3 %blockName_var " + << elem << "%int_0 %int_3" << std::endl; + spirv << "%sm = " << instr << " %_ptr_Uniform_mat4x3 %blockName_var " << elem + << "%int_0 %int_3 %int_1" << std::endl; + spirv << "%sc = " << instr << " %_ptr_Uniform_v3float %blockName_var " << elem + << "%int_0 %int_3 %int_1 %int_2" << std::endl; + spirv << "%entry = " << instr << " %_ptr_Uniform_float %blockName_var " + << elem << "%int_0 %int_3 %int_1 %int_2 %int_0" << std::endl; + spirv << R"( +OpReturn +OpFunctionEnd + )"; + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Valid: Access an element of OpTypeRuntimeArray. +TEST_P(AccessChainInstructionTest, AccessChainIndexIntoRuntimeArrayGood) { + const std::string instr = GetParam(); + const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( +%runtime_arr_entry = )" + + instr + + R"( %_ptr_Uniform_float %blockName_var )" + elem + + R"(%int_2 %int_0 +OpReturn +OpFunctionEnd + )"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid: Unused index when accessing OpTypeRuntimeArray. +TEST_P(AccessChainInstructionTest, AccessChainIndexIntoRuntimeArrayBad) { + const std::string instr = GetParam(); + const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( +%runtime_arr_entry = )" + + instr + + R"( %_ptr_Uniform_float %blockName_var )" + elem + + R"(%int_2 %int_0 %int_1 +OpReturn +OpFunctionEnd + )"; + const std::string expected_err = + instr + + " reached non-composite type while indexes still remain to be traversed."; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(expected_err)); +} + +// Invalid: Reached scalar type before arguments to the access chain instruction +// finished. +TEST_P(AccessChainInstructionTest, AccessChainMatrixMoreArgsThanNeededBad) { + const std::string instr = GetParam(); + const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( +%entry = )" + + instr + + R"( %_ptr_Private_float %my_matrix )" + elem + + R"(%int_0 %int_1 %int_0 +OpReturn +OpFunctionEnd + )"; + const std::string expected_err = instr + + " reached non-composite type while " + "indexes still remain to be traversed."; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(expected_err)); +} + +// Invalid: The result type and the type indexed into do not match. +TEST_P(AccessChainInstructionTest, + AccessChainResultTypeDoesntMatchIndexedTypeBad) { + const std::string instr = GetParam(); + const std::string elem = AccessChainRequiresElemId(instr) ? "%int_0 " : ""; + string spirv = kGLSL450MemoryModel + kDeeplyNestedStructureSetup + R"( +%entry = )" + + instr + + R"( %_ptr_Private_mat4x3 %my_matrix )" + elem + + R"(%int_0 %int_1 +OpReturn +OpFunctionEnd + )"; + const std::string expected_err = instr + + " result type (OpTypeMatrix) does not match " + "the type that results from indexing into " + "the base (OpTypeFloat)."; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(expected_err)); +} + +// Run tests for Access Chain Instructions. +INSTANTIATE_TEST_CASE_P( + CheckAccessChainInstructions, AccessChainInstructionTest, + ::testing::Values("OpAccessChain", "OpInBoundsAccessChain", + "OpPtrAccessChain", "OpInBoundsPtrAccessChain")); + +// TODO: OpArrayLength +// TODO: OpImagePointer +// TODO: OpGenericPtrMemSemantics + +TEST_F(ValidateIdWithMessage, OpFunctionGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypeFunction %1 %2 %2 +%4 = OpFunction %1 None %3 +%5 = OpLabel + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpFunctionResultTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpConstant %2 42 +%4 = OpTypeFunction %1 %2 %2 +%5 = OpFunction %2 None %4 +%6 = OpLabel + OpReturnValue %3 + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpFunction Result Type '2' does not match the " + "Function Type '2's return type.")); +} +TEST_F(ValidateIdWithMessage, OpReturnValueTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeInt 32 0 +%2 = OpTypeFloat 32 +%3 = OpConstant %2 0 +%4 = OpTypeFunction %1 +%5 = OpFunction %1 None %4 +%6 = OpLabel + OpReturnValue %3 + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpReturnValue Value '3's type does not match " + "OpFunction's return type.")); +} +TEST_F(ValidateIdWithMessage, OpFunctionFunctionTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%4 = OpFunction %1 None %2 +%5 = OpLabel + OpReturn +OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpFunction Function Type '2' is not a function type.")); +} + +TEST_F(ValidateIdWithMessage, OpFunctionParameterGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypeFunction %1 %2 +%4 = OpFunction %1 None %3 +%5 = OpFunctionParameter %2 +%6 = OpLabel + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpFunctionParameterMultipleGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypeFunction %1 %2 %2 +%4 = OpFunction %1 None %3 +%5 = OpFunctionParameter %2 +%6 = OpFunctionParameter %2 +%7 = OpLabel + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpFunctionParameterResultTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypeFunction %1 %2 +%4 = OpFunction %1 None %3 +%5 = OpFunctionParameter %1 +%6 = OpLabel + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpFunctionParameter Result Type '1' does not match the " + "OpTypeFunction parameter type of the same index.")); +} + +TEST_F(ValidateIdWithMessage, OpFunctionCallGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypeFunction %2 %2 +%4 = OpTypeFunction %1 +%5 = OpConstant %2 42 ;21 + +%6 = OpFunction %2 None %3 +%7 = OpFunctionParameter %2 +%8 = OpLabel + OpReturnValue %7 + OpFunctionEnd + +%10 = OpFunction %1 None %4 +%11 = OpLabel +%12 = OpFunctionCall %2 %6 %5 + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpFunctionCallResultTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypeFunction %2 %2 +%4 = OpTypeFunction %1 +%5 = OpConstant %2 42 ;21 + +%6 = OpFunction %2 None %3 +%7 = OpFunctionParameter %2 +%8 = OpLabel +%9 = OpIAdd %2 %7 %7 + OpReturnValue %9 + OpFunctionEnd + +%10 = OpFunction %1 None %4 +%11 = OpLabel +%12 = OpFunctionCall %1 %6 %5 + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpFunctionCall Result Type '1's type does not " + "match Function '2's return type.")); +} +TEST_F(ValidateIdWithMessage, OpFunctionCallFunctionBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypeFunction %2 %2 +%4 = OpTypeFunction %1 +%5 = OpConstant %2 42 ;21 + +%10 = OpFunction %1 None %4 +%11 = OpLabel +%12 = OpFunctionCall %2 %5 %5 + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpFunctionCall Function '5' is not a function.")); +} +TEST_F(ValidateIdWithMessage, OpFunctionCallArgumentTypeBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypeFunction %2 %2 +%4 = OpTypeFunction %1 +%5 = OpConstant %2 42 + +%13 = OpTypeFloat 32 +%14 = OpConstant %13 3.14 + +%6 = OpFunction %2 None %3 +%7 = OpFunctionParameter %2 +%8 = OpLabel +%9 = OpIAdd %2 %7 %7 + OpReturnValue %9 + OpFunctionEnd + +%10 = OpFunction %1 None %4 +%11 = OpLabel +%12 = OpFunctionCall %2 %6 %14 + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpFunctionCall Argument '7's type does not match " + "Function '2's parameter type.")); +} + +// Valid: OpSampledImage result is used in the same block by +// OpImageSampleImplictLod +TEST_F(ValidateIdWithMessage, OpSampledImageGood) { + string spirv = kGLSL450MemoryModel + sampledImageSetup + R"( +%smpld_img = OpSampledImage %sampled_image_type %image_inst %sampler_inst +%si_lod = OpImageSampleImplicitLod %v4float %smpld_img %const_vec_1_1 + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid: OpSampledImage result is defined in one block and used in a +// different block. +TEST_F(ValidateIdWithMessage, OpSampledImageUsedInDifferentBlockBad) { + string spirv = kGLSL450MemoryModel + sampledImageSetup + R"( +%smpld_img = OpSampledImage %sampled_image_type %image_inst %sampler_inst +OpBranch %label_2 +%label_2 = OpLabel +%si_lod = OpImageSampleImplicitLod %v4float %smpld_img %const_vec_1_1 +OpReturn +OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("All OpSampledImage instructions must be in the same block in " + "which their Result are consumed. OpSampledImage Result " + "Type '23' has a consumer in a different basic block. The " + "consumer instruction is '25'.")); +} + +// Invalid: OpSampledImage result is used by OpSelect +// Note: According to the Spec, OpSelect parameters must be either a scalar or a +// vector. Therefore, OpTypeSampledImage is an illegal parameter for OpSelect. +// However, the OpSelect validation does not catch this today. Therefore, it is +// caught by the OpSampledImage validation. If the OpSelect validation code is +// updated, the error message for this test may change. +// +// Disabled since OpSelect catches this now. +TEST_F(ValidateIdWithMessage, DISABLED_OpSampledImageUsedInOpSelectBad) { + string spirv = kGLSL450MemoryModel + sampledImageSetup + R"( +%smpld_img = OpSampledImage %sampled_image_type %image_inst %sampler_inst +%select_img = OpSelect %sampled_image_type %spec_true %smpld_img %smpld_img +OpReturn +OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Result from OpSampledImage instruction must not " + "appear as operands of OpSelect. Found result " + "'23' as an operand of '24'.")); +} + +// Valid: Get a float in a matrix using CompositeExtract. +// Valid: Insert float into a matrix using CompositeInsert. +TEST_F(ValidateIdWithMessage, CompositeExtractInsertGood) { + ostringstream spirv; + spirv << kGLSL450MemoryModel << kDeeplyNestedStructureSetup << std::endl; + spirv << "%matrix = OpLoad %mat4x3 %my_matrix" << std::endl; + spirv << "%float_entry = OpCompositeExtract %float %matrix 0 1" << std::endl; + + // To test CompositeInsert, insert the object back in after extraction. + spirv << "%new_composite = OpCompositeInsert %mat4x3 %float_entry %matrix 0 1" + << std::endl; + spirv << R"(OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +#if 0 +TEST_F(ValidateIdWithMessage, OpFunctionCallArgumentCountBar) { + const char *spirv = R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypeFunction %2 %2 +%4 = OpTypeFunction %1 +%5 = OpConstant %2 42 ;21 + +%6 = OpFunction %2 None %3 +%7 = OpFunctionParameter %2 +%8 = OpLabel +%9 = OpLoad %2 %7 + OpReturnValue %9 + OpFunctionEnd + +%10 = OpFunction %1 None %4 +%11 = OpLabel + OpReturn +%12 = OpFunctionCall %2 %6 %5 + OpFunctionEnd)"; + CHECK(spirv, SPV_ERROR_INVALID_ID); +} +#endif + +// TODO: The many things that changed with how images are used. +// TODO: OpTextureSample +// TODO: OpTextureSampleDref +// TODO: OpTextureSampleLod +// TODO: OpTextureSampleProj +// TODO: OpTextureSampleGrad +// TODO: OpTextureSampleOffset +// TODO: OpTextureSampleProjLod +// TODO: OpTextureSampleProjGrad +// TODO: OpTextureSampleLodOffset +// TODO: OpTextureSampleProjOffset +// TODO: OpTextureSampleGradOffset +// TODO: OpTextureSampleProjLodOffset +// TODO: OpTextureSampleProjGradOffset +// TODO: OpTextureFetchTexelLod +// TODO: OpTextureFetchTexelOffset +// TODO: OpTextureFetchSample +// TODO: OpTextureFetchTexel +// TODO: OpTextureGather +// TODO: OpTextureGatherOffset +// TODO: OpTextureGatherOffsets +// TODO: OpTextureQuerySizeLod +// TODO: OpTextureQuerySize +// TODO: OpTextureQueryLevels +// TODO: OpTextureQuerySamples +// TODO: OpConvertUToF +// TODO: OpConvertFToS +// TODO: OpConvertSToF +// TODO: OpConvertUToF +// TODO: OpUConvert +// TODO: OpSConvert +// TODO: OpFConvert +// TODO: OpConvertPtrToU +// TODO: OpConvertUToPtr +// TODO: OpPtrCastToGeneric +// TODO: OpGenericCastToPtr +// TODO: OpBitcast +// TODO: OpGenericCastToPtrExplicit +// TODO: OpSatConvertSToU +// TODO: OpSatConvertUToS +// TODO: OpVectorExtractDynamic +// TODO: OpVectorInsertDynamic + +TEST_F(ValidateIdWithMessage, OpVectorShuffleIntGood) { + string spirv = kGLSL450MemoryModel + R"( +%int = OpTypeInt 32 0 +%ivec3 = OpTypeVector %int 3 +%ivec4 = OpTypeVector %int 4 +%ptr_ivec3 = OpTypePointer Function %ivec3 +%undef = OpUndef %ivec4 +%int_42 = OpConstant %int 42 +%int_0 = OpConstant %int 0 +%int_2 = OpConstant %int 2 +%1 = OpConstantComposite %ivec3 %int_42 %int_0 %int_2 +%2 = OpTypeFunction %ivec3 +%3 = OpFunction %ivec3 None %2 +%4 = OpLabel +%var = OpVariable %ptr_ivec3 Function %1 +%5 = OpLoad %ivec3 %var +%6 = OpVectorShuffle %ivec3 %5 %undef 2 1 0 + OpReturnValue %6 + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, OpVectorShuffleFloatGood) { + string spirv = kGLSL450MemoryModel + R"( +%float = OpTypeFloat 32 +%vec2 = OpTypeVector %float 2 +%vec3 = OpTypeVector %float 3 +%vec4 = OpTypeVector %float 4 +%ptr_vec2 = OpTypePointer Function %vec2 +%ptr_vec3 = OpTypePointer Function %vec3 +%float_1 = OpConstant %float 1 +%float_2 = OpConstant %float 2 +%1 = OpConstantComposite %vec2 %float_2 %float_1 +%2 = OpConstantComposite %vec3 %float_1 %float_2 %float_2 +%3 = OpTypeFunction %vec4 +%4 = OpFunction %vec4 None %3 +%5 = OpLabel +%var = OpVariable %ptr_vec2 Function %1 +%var2 = OpVariable %ptr_vec3 Function %2 +%6 = OpLoad %vec2 %var +%7 = OpLoad %vec3 %var2 +%8 = OpVectorShuffle %vec4 %6 %7 4 3 1 0xffffffff + OpReturnValue %8 + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, OpVectorShuffleScalarResultType) { + string spirv = kGLSL450MemoryModel + R"( +%float = OpTypeFloat 32 +%vec2 = OpTypeVector %float 2 +%ptr_vec2 = OpTypePointer Function %vec2 +%float_1 = OpConstant %float 1 +%float_2 = OpConstant %float 2 +%1 = OpConstantComposite %vec2 %float_2 %float_1 +%2 = OpTypeFunction %float +%3 = OpFunction %float None %2 +%4 = OpLabel +%var = OpVariable %ptr_vec2 Function %1 +%5 = OpLoad %vec2 %var +%6 = OpVectorShuffle %float %5 %5 0 + OpReturnValue %6 + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Result Type of OpVectorShuffle must be OpTypeVector.")); +} + +TEST_F(ValidateIdWithMessage, OpVectorShuffleComponentCount) { + string spirv = kGLSL450MemoryModel + R"( +%int = OpTypeInt 32 0 +%ivec3 = OpTypeVector %int 3 +%ptr_ivec3 = OpTypePointer Function %ivec3 +%int_42 = OpConstant %int 42 +%int_0 = OpConstant %int 0 +%int_2 = OpConstant %int 2 +%1 = OpConstantComposite %ivec3 %int_42 %int_0 %int_2 +%2 = OpTypeFunction %ivec3 +%3 = OpFunction %ivec3 None %2 +%4 = OpLabel +%var = OpVariable %ptr_ivec3 Function %1 +%5 = OpLoad %ivec3 %var +%6 = OpVectorShuffle %ivec3 %5 %5 0 1 + OpReturnValue %6 + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpVectorShuffle component literals count does not match " + "Result Type '2's vector component count.")); +} + +TEST_F(ValidateIdWithMessage, OpVectorShuffleVector1Type) { + string spirv = kGLSL450MemoryModel + R"( +%int = OpTypeInt 32 0 +%ivec2 = OpTypeVector %int 2 +%ptr_int = OpTypePointer Function %int +%undef = OpUndef %ivec2 +%int_42 = OpConstant %int 42 +%2 = OpTypeFunction %ivec2 +%3 = OpFunction %ivec2 None %2 +%4 = OpLabel +%var = OpVariable %ptr_int Function %int_42 +%5 = OpLoad %int %var +%6 = OpVectorShuffle %ivec2 %5 %undef 0 0 + OpReturnValue %6 + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("The type of Vector 1 must be OpTypeVector.")); +} + +TEST_F(ValidateIdWithMessage, OpVectorShuffleVector2Type) { + string spirv = kGLSL450MemoryModel + R"( +%int = OpTypeInt 32 0 +%ivec2 = OpTypeVector %int 2 +%ptr_ivec2 = OpTypePointer Function %ivec2 +%undef = OpUndef %int +%int_42 = OpConstant %int 42 +%1 = OpConstantComposite %ivec2 %int_42 %int_42 +%2 = OpTypeFunction %ivec2 +%3 = OpFunction %ivec2 None %2 +%4 = OpLabel +%var = OpVariable %ptr_ivec2 Function %1 +%5 = OpLoad %ivec2 %var +%6 = OpVectorShuffle %ivec2 %5 %undef 0 1 + OpReturnValue %6 + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("The type of Vector 2 must be OpTypeVector.")); +} + +TEST_F(ValidateIdWithMessage, OpVectorShuffleVector1ComponentType) { + string spirv = kGLSL450MemoryModel + R"( +%int = OpTypeInt 32 0 +%ivec3 = OpTypeVector %int 3 +%ptr_ivec3 = OpTypePointer Function %ivec3 +%int_42 = OpConstant %int 42 +%int_0 = OpConstant %int 0 +%int_2 = OpConstant %int 2 +%float = OpTypeFloat 32 +%vec3 = OpTypeVector %float 3 +%vec4 = OpTypeVector %float 4 +%ptr_vec3 = OpTypePointer Function %vec3 +%float_1 = OpConstant %float 1 +%float_2 = OpConstant %float 2 +%1 = OpConstantComposite %ivec3 %int_42 %int_0 %int_2 +%2 = OpConstantComposite %vec3 %float_1 %float_2 %float_2 +%3 = OpTypeFunction %vec4 +%4 = OpFunction %vec4 None %3 +%5 = OpLabel +%var = OpVariable %ptr_ivec3 Function %1 +%var2 = OpVariable %ptr_vec3 Function %2 +%6 = OpLoad %ivec3 %var +%7 = OpLoad %vec3 %var2 +%8 = OpVectorShuffle %vec4 %6 %7 4 3 1 0 + OpReturnValue %8 + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("The Component Type of Vector 1 must be the same as " + "ResultType.")); +} + +TEST_F(ValidateIdWithMessage, OpVectorShuffleVector2ComponentType) { + string spirv = kGLSL450MemoryModel + R"( +%int = OpTypeInt 32 0 +%ivec3 = OpTypeVector %int 3 +%ptr_ivec3 = OpTypePointer Function %ivec3 +%int_42 = OpConstant %int 42 +%int_0 = OpConstant %int 0 +%int_2 = OpConstant %int 2 +%float = OpTypeFloat 32 +%vec3 = OpTypeVector %float 3 +%vec4 = OpTypeVector %float 4 +%ptr_vec3 = OpTypePointer Function %vec3 +%float_1 = OpConstant %float 1 +%float_2 = OpConstant %float 2 +%1 = OpConstantComposite %ivec3 %int_42 %int_0 %int_2 +%2 = OpConstantComposite %vec3 %float_1 %float_2 %float_2 +%3 = OpTypeFunction %vec4 +%4 = OpFunction %vec4 None %3 +%5 = OpLabel +%var = OpVariable %ptr_ivec3 Function %1 +%var2 = OpVariable %ptr_vec3 Function %2 +%6 = OpLoad %vec3 %var2 +%7 = OpLoad %ivec3 %var +%8 = OpVectorShuffle %vec4 %6 %7 4 3 1 0 + OpReturnValue %8 + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("The Component Type of Vector 2 must be the same as " + "ResultType.")); +} + +TEST_F(ValidateIdWithMessage, OpVectorShuffleLiterals) { + string spirv = kGLSL450MemoryModel + R"( +%float = OpTypeFloat 32 +%vec2 = OpTypeVector %float 2 +%vec3 = OpTypeVector %float 3 +%vec4 = OpTypeVector %float 4 +%ptr_vec2 = OpTypePointer Function %vec2 +%ptr_vec3 = OpTypePointer Function %vec3 +%float_1 = OpConstant %float 1 +%float_2 = OpConstant %float 2 +%1 = OpConstantComposite %vec2 %float_2 %float_1 +%2 = OpConstantComposite %vec3 %float_1 %float_2 %float_2 +%3 = OpTypeFunction %vec4 +%4 = OpFunction %vec4 None %3 +%5 = OpLabel +%var = OpVariable %ptr_vec2 Function %1 +%var2 = OpVariable %ptr_vec3 Function %2 +%6 = OpLoad %vec2 %var +%7 = OpLoad %vec3 %var2 +%8 = OpVectorShuffle %vec4 %6 %7 0 5 2 6 + OpReturnValue %8 + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Component literal value 5 is greater than 4.")); +} + +// TODO: OpCompositeConstruct +// TODO: OpCompositeExtract +// TODO: OpCompositeInsert +// TODO: OpCopyObject +// TODO: OpTranspose +// TODO: OpSNegate +// TODO: OpFNegate +// TODO: OpNot +// TODO: OpIAdd +// TODO: OpFAdd +// TODO: OpISub +// TODO: OpFSub +// TODO: OpIMul +// TODO: OpFMul +// TODO: OpUDiv +// TODO: OpSDiv +// TODO: OpFDiv +// TODO: OpUMod +// TODO: OpSRem +// TODO: OpSMod +// TODO: OpFRem +// TODO: OpFMod +// TODO: OpVectorTimesScalar +// TODO: OpMatrixTimesScalar +// TODO: OpVectorTimesMatrix +// TODO: OpMatrixTimesVector +// TODO: OpMatrixTimesMatrix +// TODO: OpOuterProduct +// TODO: OpDot +// TODO: OpShiftRightLogical +// TODO: OpShiftRightArithmetic +// TODO: OpShiftLeftLogical +// TODO: OpBitwiseOr +// TODO: OpBitwiseXor +// TODO: OpBitwiseAnd +// TODO: OpAny +// TODO: OpAll +// TODO: OpIsNan +// TODO: OpIsInf +// TODO: OpIsFinite +// TODO: OpIsNormal +// TODO: OpSignBitSet +// TODO: OpLessOrGreater +// TODO: OpOrdered +// TODO: OpUnordered +// TODO: OpLogicalOr +// TODO: OpLogicalXor +// TODO: OpLogicalAnd +// TODO: OpSelect +// TODO: OpIEqual +// TODO: OpFOrdEqual +// TODO: OpFUnordEqual +// TODO: OpINotEqual +// TODO: OpFOrdNotEqual +// TODO: OpFUnordNotEqual +// TODO: OpULessThan +// TODO: OpSLessThan +// TODO: OpFOrdLessThan +// TODO: OpFUnordLessThan +// TODO: OpUGreaterThan +// TODO: OpSGreaterThan +// TODO: OpFOrdGreaterThan +// TODO: OpFUnordGreaterThan +// TODO: OpULessThanEqual +// TODO: OpSLessThanEqual +// TODO: OpFOrdLessThanEqual +// TODO: OpFUnordLessThanEqual +// TODO: OpUGreaterThanEqual +// TODO: OpSGreaterThanEqual +// TODO: OpFOrdGreaterThanEqual +// TODO: OpFUnordGreaterThanEqual +// TODO: OpDPdx +// TODO: OpDPdy +// TODO: OpFWidth +// TODO: OpDPdxFine +// TODO: OpDPdyFine +// TODO: OpFwidthFine +// TODO: OpDPdxCoarse +// TODO: OpDPdyCoarse +// TODO: OpFwidthCoarse +// TODO: OpLoopMerge +// TODO: OpSelectionMerge +// TODO: OpBranch + +TEST_F(ValidateIdWithMessage, OpPhiNotAType) { + string spirv = kOpenCLMemoryModel32 + R"( +%2 = OpTypeBool +%3 = OpConstantTrue %2 +%4 = OpTypeVoid +%5 = OpTypeFunction %4 +%6 = OpFunction %4 None %5 +%7 = OpLabel +OpBranch %8 +%8 = OpLabel +%9 = OpPhi %3 %3 %7 +OpReturn +OpFunctionEnd + )"; + + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpPhi's type 3 is not a type instruction.")); +} + +TEST_F(ValidateIdWithMessage, OpPhiSamePredecessor) { + string spirv = kOpenCLMemoryModel32 + R"( +%2 = OpTypeBool +%3 = OpConstantTrue %2 +%4 = OpTypeVoid +%5 = OpTypeFunction %4 +%6 = OpFunction %4 None %5 +%7 = OpLabel +OpBranchConditional %3 %8 %8 +%8 = OpLabel +%9 = OpPhi %2 %3 %7 +OpReturn +OpFunctionEnd + )"; + + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, OpPhiOddArgumentNumber) { + string spirv = kOpenCLMemoryModel32 + R"( +%2 = OpTypeBool +%3 = OpConstantTrue %2 +%4 = OpTypeVoid +%5 = OpTypeFunction %4 +%6 = OpFunction %4 None %5 +%7 = OpLabel +OpBranch %8 +%8 = OpLabel +%9 = OpPhi %2 %3 +OpReturn +OpFunctionEnd + )"; + + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpPhi does not have an equal number of incoming " + "values and basic blocks.")); +} + +TEST_F(ValidateIdWithMessage, OpPhiTooFewPredecessors) { + string spirv = kOpenCLMemoryModel32 + R"( +%2 = OpTypeBool +%3 = OpConstantTrue %2 +%4 = OpTypeVoid +%5 = OpTypeFunction %4 +%6 = OpFunction %4 None %5 +%7 = OpLabel +OpBranch %8 +%8 = OpLabel +%9 = OpPhi %2 +OpReturn +OpFunctionEnd + )"; + + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpPhi's number of incoming blocks (0) does not match " + "block's predecessor count (1).")); +} + +TEST_F(ValidateIdWithMessage, OpPhiTooManyPredecessors) { + string spirv = kOpenCLMemoryModel32 + R"( +%2 = OpTypeBool +%3 = OpConstantTrue %2 +%4 = OpTypeVoid +%5 = OpTypeFunction %4 +%6 = OpFunction %4 None %5 +%7 = OpLabel +OpBranch %8 +%9 = OpLabel +OpReturn +%8 = OpLabel +%10 = OpPhi %2 %3 %7 %3 %9 +OpReturn +OpFunctionEnd + )"; + + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpPhi's number of incoming blocks (2) does not match " + "block's predecessor count (1).")); +} + +TEST_F(ValidateIdWithMessage, OpPhiMismatchedTypes) { + string spirv = kOpenCLMemoryModel32 + R"( +%2 = OpTypeBool +%3 = OpConstantTrue %2 +%4 = OpTypeVoid +%5 = OpTypeInt 32 0 +%6 = OpConstant %5 0 +%7 = OpTypeFunction %4 +%8 = OpFunction %4 None %7 +%9 = OpLabel +OpBranchConditional %3 %10 %11 +%11 = OpLabel +OpBranch %10 +%10 = OpLabel +%12 = OpPhi %2 %3 %9 %6 %11 +OpReturn +OpFunctionEnd + )"; + + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpPhi's result type 2 does not match incoming " + "value 6 type 5.")); +} + +TEST_F(ValidateIdWithMessage, OpPhiPredecessorNotABlock) { + string spirv = kOpenCLMemoryModel32 + R"( +%2 = OpTypeBool +%3 = OpConstantTrue %2 +%4 = OpTypeVoid +%5 = OpTypeFunction %4 +%6 = OpFunction %4 None %5 +%7 = OpLabel +OpBranchConditional %3 %8 %9 +%9 = OpLabel +OpBranch %11 +%11 = OpLabel +OpBranch %8 +%8 = OpLabel +%10 = OpPhi %2 %3 %7 %3 %3 +OpReturn +OpFunctionEnd + )"; + + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpPhi's incoming basic block 3 is not an OpLabel.")); +} + +TEST_F(ValidateIdWithMessage, OpPhiNotAPredecessor) { + string spirv = kOpenCLMemoryModel32 + R"( +%2 = OpTypeBool +%3 = OpConstantTrue %2 +%4 = OpTypeVoid +%5 = OpTypeFunction %4 +%6 = OpFunction %4 None %5 +%7 = OpLabel +OpBranchConditional %3 %8 %9 +%9 = OpLabel +OpBranch %11 +%11 = OpLabel +OpBranch %8 +%8 = OpLabel +%10 = OpPhi %2 %3 %7 %3 %9 +OpReturn +OpFunctionEnd + )"; + + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpPhi's incoming basic block 9 is not a " + "predecessor of 8.")); +} + +TEST_F(ValidateIdWithMessage, OpBranchConditionalGood) { + string spirv = BranchConditionalSetup + R"( + %branch_cond = OpINotEqual %bool %i0 %i1 + OpSelectionMerge %end None + OpBranchConditional %branch_cond %target_t %target_f + )" + BranchConditionalTail; + + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState()); +} + +TEST_F(ValidateIdWithMessage, OpBranchConditionalWithWeightsGood) { + string spirv = BranchConditionalSetup + R"( + %branch_cond = OpINotEqual %bool %i0 %i1 + OpSelectionMerge %end None + OpBranchConditional %branch_cond %target_t %target_f 1 1 + )" + BranchConditionalTail; + + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState()); +} + +TEST_F(ValidateIdWithMessage, OpBranchConditional_CondIsScalarInt) { + string spirv = BranchConditionalSetup + R"( + OpSelectionMerge %end None + OpBranchConditional %i0 %target_t %target_f + )" + BranchConditionalTail; + + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Condition operand for OpBranchConditional must be of boolean type")); +} + +TEST_F(ValidateIdWithMessage, OpBranchConditional_TrueTargetIsNotLabel) { + string spirv = BranchConditionalSetup + R"( + OpSelectionMerge %end None + OpBranchConditional %i0 %i0 %target_f + )" + BranchConditionalTail; + + CompileSuccessfully(spirv.c_str()); + // EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + // EXPECT_THAT( + // getDiagnosticString(), + // HasSubstr("The 'True Label' operand for OpBranchConditional must be the + // ID of an OpLabel instruction")); + + // xxxnsubtil: this is actually caught by the ID validation instead + EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("are referenced but not defined in function")); +} + +TEST_F(ValidateIdWithMessage, OpBranchConditional_FalseTargetIsNotLabel) { + string spirv = BranchConditionalSetup + R"( + OpSelectionMerge %end None + OpBranchConditional %i0 %target_t %i0 + )" + BranchConditionalTail; + + CompileSuccessfully(spirv.c_str()); + // EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + // EXPECT_THAT( + // getDiagnosticString(), + // HasSubstr("The 'False Label' operand for OpBranchConditional must be + // the ID of an OpLabel instruction")); + + // xxxnsubtil: this is actually caught by the ID validation + EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("are referenced but not defined in function")); +} + +TEST_F(ValidateIdWithMessage, OpBranchConditional_NotEnoughWeights) { + string spirv = BranchConditionalSetup + R"( + %branch_cond = OpINotEqual %bool %i0 %i1 + OpSelectionMerge %end None + OpBranchConditional %branch_cond %target_t %target_f 1 + )" + BranchConditionalTail; + + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpBranchConditional requires either 3 or 5 parameters")); +} + +TEST_F(ValidateIdWithMessage, OpBranchConditional_TooManyWeights) { + string spirv = BranchConditionalSetup + R"( + %branch_cond = OpINotEqual %bool %i0 %i1 + OpSelectionMerge %end None + OpBranchConditional %branch_cond %target_t %target_f 1 2 3 + )" + BranchConditionalTail; + + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpBranchConditional requires either 3 or 5 parameters")); +} + +// TODO: OpSwitch + +TEST_F(ValidateIdWithMessage, OpReturnValueConstantGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypeFunction %2 +%4 = OpConstant %2 42 +%5 = OpFunction %2 None %3 +%6 = OpLabel + OpReturnValue %4 + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, OpReturnValueVariableGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 ;10 +%3 = OpTypeFunction %2 +%8 = OpTypePointer Function %2 ;18 +%4 = OpConstant %2 42 ;22 +%5 = OpFunction %2 None %3 ;27 +%6 = OpLabel ;29 +%7 = OpVariable %8 Function %4 ;34 +%9 = OpLoad %2 %7 + OpReturnValue %9 ;36 + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, OpReturnValueExpressionGood) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypeFunction %2 +%4 = OpConstant %2 42 +%5 = OpFunction %2 None %3 +%6 = OpLabel +%7 = OpIAdd %2 %4 %4 + OpReturnValue %7 + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, OpReturnValueIsType) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypeFunction %2 +%5 = OpFunction %2 None %3 +%6 = OpLabel + OpReturnValue %1 + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpReturnValue Value '1' does not represent a value.")); +} + +TEST_F(ValidateIdWithMessage, OpReturnValueIsLabel) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypeFunction %2 +%5 = OpFunction %2 None %3 +%6 = OpLabel + OpReturnValue %6 + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpReturnValue Value '5' does not represent a value.")); +} + +TEST_F(ValidateIdWithMessage, OpReturnValueIsVoid) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypeFunction %1 +%5 = OpFunction %1 None %3 +%6 = OpLabel +%7 = OpFunctionCall %1 %5 + OpReturnValue %7 + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpReturnValue value's type '1' is missing or void.")); +} + +TEST_F(ValidateIdWithMessage, OpReturnValueIsVariableInPhysical) { + // It's valid to return a pointer in a physical addressing model. + string spirv = kGLSL450MemoryModel + R"( + OpMemoryModel Physical32 OpenCL +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypePointer Private %2 +%4 = OpTypeFunction %3 +%5 = OpFunction %3 None %4 +%6 = OpLabel +%7 = OpVariable %3 Function + OpReturnValue %7 + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, OpReturnValueIsVariableInLogical) { + // It's invalid to return a pointer in a physical addressing model. + string spirv = kGLSL450MemoryModel + R"( + OpMemoryModel Logical GLSL450 +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypePointer Private %2 +%4 = OpTypeFunction %3 +%5 = OpFunction %3 None %4 +%6 = OpLabel +%7 = OpVariable %3 Function + OpReturnValue %7 + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpReturnValue value's type '3' is a pointer, " + "which is invalid in the Logical addressing model.")); +} + +// With the VariablePointer Capability, the return value of a function is +// allowed to be a pointer. +TEST_F(ValidateIdWithMessage, OpReturnValueVarPtrGood) { + std::ostringstream spirv; + createVariablePointerSpirvProgram(&spirv, + "" /* Instructions to add to "main" */, + true /* Add VariablePointers Capability?*/, + true /* Use Helper Function? */); + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Without the VariablePointer Capability, the return value of a function is +// *not* allowed to be a pointer. +// Disabled since using OpSelect with pointers without VariablePointers will +// fail LogicalsPass. +TEST_F(ValidateIdWithMessage, DISABLED_OpReturnValueVarPtrBad) { + std::ostringstream spirv; + createVariablePointerSpirvProgram(&spirv, + "" /* Instructions to add to "main" */, + false /* Add VariablePointers Capability?*/, + true /* Use Helper Function? */); + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpReturnValue value's type '7' is a pointer, " + "which is invalid in the Logical addressing model.")); +} + +// TODO: enable when this bug is fixed: +// https://cvs.khronos.org/bugzilla/show_bug.cgi?id=15404 +TEST_F(ValidateIdWithMessage, DISABLED_OpReturnValueIsFunction) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeInt 32 0 +%3 = OpTypeFunction %2 +%5 = OpFunction %2 None %3 +%6 = OpLabel + OpReturnValue %5 + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, UndefinedTypeId) { + string spirv = kGLSL450MemoryModel + R"( +%s = OpTypeStruct %i32 +)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Forward reference operands in an OpTypeStruct must " + "first be declared using OpTypeForwardPointer.")); +} + +TEST_F(ValidateIdWithMessage, UndefinedIdScope) { + string spirv = kGLSL450MemoryModel + R"( +%u32 = OpTypeInt 32 0 +%memsem = OpConstant %u32 0 +%void = OpTypeVoid +%void_f = OpTypeFunction %void +%f = OpFunction %void None %void_f +%l = OpLabel + OpMemoryBarrier %undef %memsem + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 7 has not been defined")); +} + +TEST_F(ValidateIdWithMessage, UndefinedIdMemSem) { + string spirv = kGLSL450MemoryModel + R"( +%u32 = OpTypeInt 32 0 +%scope = OpConstant %u32 0 +%void = OpTypeVoid +%void_f = OpTypeFunction %void +%f = OpFunction %void None %void_f +%l = OpLabel + OpMemoryBarrier %scope %undef + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("ID 7 has not been defined")); +} + +TEST_F(ValidateIdWithMessage, + KernelOpEntryPointAndOpInBoundsPtrAccessChainGood) { + string spirv = kOpenCLMemoryModel32 + R"( + OpEntryPoint Kernel %2 "simple_kernel" + OpSource OpenCL_C 200000 + OpDecorate %3 BuiltIn GlobalInvocationId + OpDecorate %3 Constant + OpDecorate %4 FuncParamAttr NoCapture + OpDecorate %3 LinkageAttributes "__spirv_GlobalInvocationId" Import + %5 = OpTypeInt 32 0 + %6 = OpTypeVector %5 3 + %7 = OpTypePointer UniformConstant %6 + %3 = OpVariable %7 UniformConstant + %8 = OpTypeVoid + %9 = OpTypeStruct %5 +%10 = OpTypePointer CrossWorkgroup %9 +%11 = OpTypeFunction %8 %10 +%12 = OpConstant %5 0 +%13 = OpTypePointer CrossWorkgroup %5 +%14 = OpConstant %5 42 + %2 = OpFunction %8 None %11 + %4 = OpFunctionParameter %10 +%15 = OpLabel +%16 = OpLoad %6 %3 Aligned 0 +%17 = OpCompositeExtract %5 %16 0 +%18 = OpInBoundsPtrAccessChain %13 %4 %17 %12 + OpStore %18 %14 Aligned 4 + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, OpPtrAccessChainGood) { + string spirv = kOpenCLMemoryModel64 + R"( + OpEntryPoint Kernel %2 "another_kernel" + OpSource OpenCL_C 200000 + OpDecorate %3 BuiltIn GlobalInvocationId + OpDecorate %3 Constant + OpDecorate %4 FuncParamAttr NoCapture + OpDecorate %3 LinkageAttributes "__spirv_GlobalInvocationId" Import + %5 = OpTypeInt 64 0 + %6 = OpTypeVector %5 3 + %7 = OpTypePointer UniformConstant %6 + %3 = OpVariable %7 UniformConstant + %8 = OpTypeVoid + %9 = OpTypeInt 32 0 +%10 = OpTypeStruct %9 +%11 = OpTypePointer CrossWorkgroup %10 +%12 = OpTypeFunction %8 %11 +%13 = OpConstant %5 4294967295 +%14 = OpConstant %9 0 +%15 = OpTypePointer CrossWorkgroup %9 +%16 = OpConstant %9 42 + %2 = OpFunction %8 None %12 + %4 = OpFunctionParameter %11 +%17 = OpLabel +%18 = OpLoad %6 %3 Aligned 0 +%19 = OpCompositeExtract %5 %18 0 +%20 = OpBitwiseAnd %5 %19 %13 +%21 = OpPtrAccessChain %15 %4 %20 %14 + OpStore %21 %16 Aligned 4 + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateIdWithMessage, OpLoadBitcastPointerGood) { + string spirv = kOpenCLMemoryModel64 + R"( +%2 = OpTypeVoid +%3 = OpTypeInt 32 0 +%4 = OpTypeFloat 32 +%5 = OpTypePointer UniformConstant %3 +%6 = OpTypePointer UniformConstant %4 +%7 = OpVariable %5 UniformConstant +%8 = OpTypeFunction %2 +%9 = OpFunction %2 None %8 +%10 = OpLabel +%11 = OpBitcast %6 %7 +%12 = OpLoad %4 %11 + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpLoadBitcastNonPointerBad) { + string spirv = kOpenCLMemoryModel64 + R"( +%2 = OpTypeVoid +%3 = OpTypeInt 32 0 +%4 = OpTypeFloat 32 +%5 = OpTypePointer UniformConstant %3 +%6 = OpTypeFunction %2 +%7 = OpVariable %5 UniformConstant +%8 = OpFunction %2 None %6 +%9 = OpLabel +%10 = OpLoad %3 %7 +%11 = OpBitcast %4 %10 +%12 = OpLoad %3 %11 + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpLoad type for pointer '11' is not a pointer type.")); +} +TEST_F(ValidateIdWithMessage, OpStoreBitcastPointerGood) { + string spirv = kOpenCLMemoryModel64 + R"( +%2 = OpTypeVoid +%3 = OpTypeInt 32 0 +%4 = OpTypeFloat 32 +%5 = OpTypePointer Function %3 +%6 = OpTypePointer Function %4 +%7 = OpTypeFunction %2 +%8 = OpConstant %3 42 +%9 = OpFunction %2 None %7 +%10 = OpLabel +%11 = OpVariable %6 Function +%12 = OpBitcast %5 %11 + OpStore %12 %8 + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} +TEST_F(ValidateIdWithMessage, OpStoreBitcastNonPointerBad) { + string spirv = kOpenCLMemoryModel64 + R"( +%2 = OpTypeVoid +%3 = OpTypeInt 32 0 +%4 = OpTypeFloat 32 +%5 = OpTypePointer Function %4 +%6 = OpTypeFunction %2 +%7 = OpConstant %4 42 +%8 = OpFunction %2 None %6 +%9 = OpLabel +%10 = OpVariable %5 Function +%11 = OpBitcast %3 %7 + OpStore %11 %7 + OpReturn + OpFunctionEnd)"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpStore type for pointer '11' is not a pointer type.")); +} + +// Result resulting from an instruction within a function may not be used +// outside that function. +TEST_F(ValidateIdWithMessage, ResultIdUsedOutsideOfFunctionBad) { + string spirv = kGLSL450MemoryModel + R"( +%1 = OpTypeVoid +%2 = OpTypeFunction %1 +%3 = OpTypeInt 32 0 +%4 = OpTypePointer Function %3 +%5 = OpFunction %1 None %2 +%6 = OpLabel +%7 = OpVariable %4 Function +OpReturn +OpFunctionEnd +%8 = OpFunction %1 None %2 +%9 = OpLabel +%10 = OpLoad %3 %7 +OpReturn +OpFunctionEnd + )"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "ID 7 defined in block 6 does not dominate its use in block 9")); +} + +TEST_F(ValidateIdWithMessage, SpecIdTargetNotSpecializationConstant) { + string spirv = kGLSL450MemoryModel + R"( +OpDecorate %1 SpecId 200 +%void = OpTypeVoid +%2 = OpTypeFunction %void +%int = OpTypeInt 32 0 +%1 = OpConstant %int 3 +%main = OpFunction %1 None %2 +%4 = OpLabel +OpReturnValue %1 +OpFunctionEnd + )"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpDecorate SpectId decoration target '1' is not a " + "scalar specialization constant.")); +} + +TEST_F(ValidateIdWithMessage, SpecIdTargetOpSpecConstantOpBad) { + string spirv = kGLSL450MemoryModel + R"( +OpDecorate %1 SpecId 200 +%void = OpTypeVoid +%2 = OpTypeFunction %void +%int = OpTypeInt 32 0 +%3 = OpConstant %int 1 +%4 = OpConstant %int 2 +%1 = OpSpecConstantOp %int IAdd %3 %4 +%main = OpFunction %1 None %2 +%6 = OpLabel +OpReturnValue %3 +OpFunctionEnd + )"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpDecorate SpectId decoration target '1' is not a " + "scalar specialization constant.")); +} + +TEST_F(ValidateIdWithMessage, SpecIdTargetOpSpecConstantCompositeBad) { + string spirv = kGLSL450MemoryModel + R"( +OpDecorate %1 SpecId 200 +%void = OpTypeVoid +%2 = OpTypeFunction %void +%int = OpTypeInt 32 0 +%3 = OpConstant %int 1 +%1 = OpSpecConstantComposite %int +%main = OpFunction %1 None %2 +%4 = OpLabel +OpReturnValue %3 +OpFunctionEnd + )"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpDecorate SpectId decoration target '1' is not a " + "scalar specialization constant.")); +} + +TEST_F(ValidateIdWithMessage, SpecIdTargetGood) { + string spirv = kGLSL450MemoryModel + R"( +OpDecorate %3 SpecId 200 +OpDecorate %4 SpecId 201 +OpDecorate %5 SpecId 202 +%1 = OpTypeVoid +%2 = OpTypeFunction %1 +%int = OpTypeInt 32 0 +%bool = OpTypeBool +%3 = OpSpecConstant %int 3 +%4 = OpSpecConstantTrue %bool +%5 = OpSpecConstantFalse %bool +%main = OpFunction %1 None %2 +%6 = OpLabel +OpReturn +OpFunctionEnd + )"; + CompileSuccessfully(spirv.c_str()); + EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState()); +} + +// TODO: OpLifetimeStart +// TODO: OpLifetimeStop +// TODO: OpAtomicInit +// TODO: OpAtomicLoad +// TODO: OpAtomicStore +// TODO: OpAtomicExchange +// TODO: OpAtomicCompareExchange +// TODO: OpAtomicCompareExchangeWeak +// TODO: OpAtomicIIncrement +// TODO: OpAtomicIDecrement +// TODO: OpAtomicIAdd +// TODO: OpAtomicISub +// TODO: OpAtomicUMin +// TODO: OpAtomicUMax +// TODO: OpAtomicAnd +// TODO: OpAtomicOr +// TODO: OpAtomicXor +// TODO: OpAtomicIMin +// TODO: OpAtomicIMax +// TODO: OpEmitStreamVertex +// TODO: OpEndStreamPrimitive +// TODO: OpAsyncGroupCopy +// TODO: OpWaitGroupEvents +// TODO: OpGroupAll +// TODO: OpGroupAny +// TODO: OpGroupBroadcast +// TODO: OpGroupIAdd +// TODO: OpGroupFAdd +// TODO: OpGroupFMin +// TODO: OpGroupUMin +// TODO: OpGroupSMin +// TODO: OpGroupFMax +// TODO: OpGroupUMax +// TODO: OpGroupSMax +// TODO: OpEnqueueMarker +// TODO: OpEnqueueKernel +// TODO: OpGetKernelNDrangeSubGroupCount +// TODO: OpGetKernelNDrangeMaxSubGroupSize +// TODO: OpGetKernelWorkGroupSize +// TODO: OpGetKernelPreferredWorkGroupSizeMultiple +// TODO: OpRetainEvent +// TODO: OpReleaseEvent +// TODO: OpCreateUserEvent +// TODO: OpIsValidEvent +// TODO: OpSetUserEventStatus +// TODO: OpCaptureEventProfilingInfo +// TODO: OpGetDefaultQueue +// TODO: OpBuildNDRange +// TODO: OpReadPipe +// TODO: OpWritePipe +// TODO: OpReservedReadPipe +// TODO: OpReservedWritePipe +// TODO: OpReserveReadPipePackets +// TODO: OpReserveWritePipePackets +// TODO: OpCommitReadPipe +// TODO: OpCommitWritePipe +// TODO: OpIsValidReserveId +// TODO: OpGetNumPipePackets +// TODO: OpGetMaxPipePackets +// TODO: OpGroupReserveReadPipePackets +// TODO: OpGroupReserveWritePipePackets +// TODO: OpGroupCommitReadPipe +// TODO: OpGroupCommitWritePipe + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/val/val_image_test.cpp b/3rdparty/spirv-tools/test/val/val_image_test.cpp new file mode 100644 index 000000000..bad2a4349 --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_image_test.cpp @@ -0,0 +1,4029 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Tests for unique type declaration rules validator. + +#include +#include + +#include "gmock/gmock.h" +#include "unit_spirv.h" +#include "val_fixtures.h" + +namespace { + +using ::testing::HasSubstr; +using ::testing::Not; + +using ValidateImage = spvtest::ValidateBase; + +std::string GenerateShaderCode( + const std::string& body, + const std::string& capabilities_and_extensions = "", + const std::string& execution_model = "Fragment", + const spv_target_env env = SPV_ENV_UNIVERSAL_1_0) { + std::ostringstream ss; + ss << R"( +OpCapability Shader +OpCapability InputAttachment +OpCapability ImageGatherExtended +OpCapability MinLod +OpCapability Sampled1D +OpCapability ImageQuery +OpCapability Int64 +OpCapability Float64 +OpCapability SparseResidency +)"; + + if (env == SPV_ENV_UNIVERSAL_1_0) { + ss << "OpCapability SampledRect\n"; + } + + ss << capabilities_and_extensions; + ss << "OpMemoryModel Logical GLSL450\n"; + ss << "OpEntryPoint " << execution_model << " %main \"main\"\n"; + + ss << R"( +%void = OpTypeVoid +%func = OpTypeFunction %void +%bool = OpTypeBool +%f32 = OpTypeFloat 32 +%f64 = OpTypeFloat 64 +%u32 = OpTypeInt 32 0 +%s32 = OpTypeInt 32 1 +%u64 = OpTypeInt 64 0 +%s32vec2 = OpTypeVector %s32 2 +%u32vec2 = OpTypeVector %u32 2 +%f32vec2 = OpTypeVector %f32 2 +%u32vec3 = OpTypeVector %u32 3 +%s32vec3 = OpTypeVector %s32 3 +%f32vec3 = OpTypeVector %f32 3 +%u32vec4 = OpTypeVector %u32 4 +%s32vec4 = OpTypeVector %s32 4 +%f32vec4 = OpTypeVector %f32 4 + +%f32_0 = OpConstant %f32 0 +%f32_1 = OpConstant %f32 1 +%f32_0_5 = OpConstant %f32 0.5 +%f32_0_25 = OpConstant %f32 0.25 +%f32_0_75 = OpConstant %f32 0.75 + +%f64_0 = OpConstant %f64 0 +%f64_1 = OpConstant %f64 1 + +%s32_0 = OpConstant %s32 0 +%s32_1 = OpConstant %s32 1 +%s32_2 = OpConstant %s32 2 +%s32_3 = OpConstant %s32 3 +%s32_4 = OpConstant %s32 4 +%s32_m1 = OpConstant %s32 -1 + +%u32_0 = OpConstant %u32 0 +%u32_1 = OpConstant %u32 1 +%u32_2 = OpConstant %u32 2 +%u32_3 = OpConstant %u32 3 +%u32_4 = OpConstant %u32 4 + +%u64_0 = OpConstant %u64 0 + +%u32vec2arr4 = OpTypeArray %u32vec2 %u32_4 +%u32vec2arr3 = OpTypeArray %u32vec2 %u32_3 +%u32arr4 = OpTypeArray %u32 %u32_4 +%u32vec3arr4 = OpTypeArray %u32vec3 %u32_4 + +%struct_u32_f32vec4 = OpTypeStruct %u32 %f32vec4 +%struct_u64_f32vec4 = OpTypeStruct %u64 %f32vec4 +%struct_u32_u32vec4 = OpTypeStruct %u32 %u32vec4 +%struct_u32_f32vec3 = OpTypeStruct %u32 %f32vec3 +%struct_f32_f32vec4 = OpTypeStruct %f32 %f32vec4 +%struct_u32_u32 = OpTypeStruct %u32 %u32 +%struct_f32_f32 = OpTypeStruct %f32 %f32 +%struct_u32 = OpTypeStruct %u32 +%struct_u32_f32_u32 = OpTypeStruct %u32 %f32 %u32 +%struct_u32_f32vec4_u32 = OpTypeStruct %u32 %f32vec4 %u32 +%struct_u32_u32arr4 = OpTypeStruct %u32 %u32arr4 + +%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1 +%u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2 +%u32vec3_012 = OpConstantComposite %u32vec3 %u32_0 %u32_1 %u32_2 +%u32vec3_123 = OpConstantComposite %u32vec3 %u32_1 %u32_2 %u32_3 +%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3 +%u32vec4_1234 = OpConstantComposite %u32vec4 %u32_1 %u32_2 %u32_3 %u32_4 + +%s32vec2_01 = OpConstantComposite %s32vec2 %s32_0 %s32_1 +%s32vec2_12 = OpConstantComposite %s32vec2 %s32_1 %s32_2 +%s32vec3_012 = OpConstantComposite %s32vec3 %s32_0 %s32_1 %s32_2 +%s32vec3_123 = OpConstantComposite %s32vec3 %s32_1 %s32_2 %s32_3 +%s32vec4_0123 = OpConstantComposite %s32vec4 %s32_0 %s32_1 %s32_2 %s32_3 +%s32vec4_1234 = OpConstantComposite %s32vec4 %s32_1 %s32_2 %s32_3 %s32_4 + +%f32vec2_00 = OpConstantComposite %f32vec2 %f32_0 %f32_0 +%f32vec2_01 = OpConstantComposite %f32vec2 %f32_0 %f32_1 +%f32vec2_10 = OpConstantComposite %f32vec2 %f32_1 %f32_0 +%f32vec2_11 = OpConstantComposite %f32vec2 %f32_1 %f32_1 +%f32vec2_hh = OpConstantComposite %f32vec2 %f32_0_5 %f32_0_5 + +%f32vec3_000 = OpConstantComposite %f32vec3 %f32_0 %f32_0 %f32_0 +%f32vec3_hhh = OpConstantComposite %f32vec3 %f32_0_5 %f32_0_5 %f32_0_5 + +%f32vec4_0000 = OpConstantComposite %f32vec4 %f32_0 %f32_0 %f32_0 %f32_0 + +%const_offsets = OpConstantComposite %u32vec2arr4 %u32vec2_01 %u32vec2_12 %u32vec2_01 %u32vec2_12 +%const_offsets3x2 = OpConstantComposite %u32vec2arr3 %u32vec2_01 %u32vec2_12 %u32vec2_01 +%const_offsets4xu = OpConstantComposite %u32arr4 %u32_0 %u32_0 %u32_0 %u32_0 +%const_offsets4x3 = OpConstantComposite %u32vec3arr4 %u32vec3_012 %u32vec3_012 %u32vec3_012 %u32vec3_012 + +%type_image_f32_1d_0001 = OpTypeImage %f32 1D 0 0 0 1 Unknown +%ptr_image_f32_1d_0001 = OpTypePointer UniformConstant %type_image_f32_1d_0001 +%uniform_image_f32_1d_0001 = OpVariable %ptr_image_f32_1d_0001 UniformConstant +%type_sampled_image_f32_1d_0001 = OpTypeSampledImage %type_image_f32_1d_0001 + +%type_image_f32_1d_0002_rgba32f = OpTypeImage %f32 1D 0 0 0 2 Rgba32f +%ptr_image_f32_1d_0002_rgba32f = OpTypePointer UniformConstant %type_image_f32_1d_0002_rgba32f +%uniform_image_f32_1d_0002_rgba32f = OpVariable %ptr_image_f32_1d_0002_rgba32f UniformConstant +%type_sampled_image_f32_1d_0002_rgba32f = OpTypeSampledImage %type_image_f32_1d_0002_rgba32f + +%type_image_f32_2d_0001 = OpTypeImage %f32 2D 0 0 0 1 Unknown +%ptr_image_f32_2d_0001 = OpTypePointer UniformConstant %type_image_f32_2d_0001 +%uniform_image_f32_2d_0001 = OpVariable %ptr_image_f32_2d_0001 UniformConstant +%type_sampled_image_f32_2d_0001 = OpTypeSampledImage %type_image_f32_2d_0001 + +%type_image_f32_2d_0010 = OpTypeImage %f32 2D 0 0 1 0 Unknown +%ptr_image_f32_2d_0010 = OpTypePointer UniformConstant %type_image_f32_2d_0010 +%uniform_image_f32_2d_0010 = OpVariable %ptr_image_f32_2d_0010 UniformConstant +%type_sampled_image_f32_2d_0010 = OpTypeSampledImage %type_image_f32_2d_0010 + +%type_image_u32_2d_0001 = OpTypeImage %u32 2D 0 0 0 1 Unknown +%ptr_image_u32_2d_0001 = OpTypePointer UniformConstant %type_image_u32_2d_0001 +%uniform_image_u32_2d_0001 = OpVariable %ptr_image_u32_2d_0001 UniformConstant +%type_sampled_image_u32_2d_0001 = OpTypeSampledImage %type_image_u32_2d_0001 + +%type_image_u32_2d_0000 = OpTypeImage %u32 2D 0 0 0 0 Unknown +%ptr_image_u32_2d_0000 = OpTypePointer UniformConstant %type_image_u32_2d_0000 +%uniform_image_u32_2d_0000 = OpVariable %ptr_image_u32_2d_0000 UniformConstant +%type_sampled_image_u32_2d_0000 = OpTypeSampledImage %type_image_u32_2d_0000 + +%type_image_s32_3d_0001 = OpTypeImage %s32 3D 0 0 0 1 Unknown +%ptr_image_s32_3d_0001 = OpTypePointer UniformConstant %type_image_s32_3d_0001 +%uniform_image_s32_3d_0001 = OpVariable %ptr_image_s32_3d_0001 UniformConstant +%type_sampled_image_s32_3d_0001 = OpTypeSampledImage %type_image_s32_3d_0001 + +%type_image_f32_2d_0002 = OpTypeImage %f32 2D 0 0 0 2 Unknown +%ptr_image_f32_2d_0002 = OpTypePointer UniformConstant %type_image_f32_2d_0002 +%uniform_image_f32_2d_0002 = OpVariable %ptr_image_f32_2d_0002 UniformConstant +%type_sampled_image_f32_2d_0002 = OpTypeSampledImage %type_image_f32_2d_0002 + +%type_image_f32_spd_0002 = OpTypeImage %f32 SubpassData 0 0 0 2 Unknown +%ptr_image_f32_spd_0002 = OpTypePointer UniformConstant %type_image_f32_spd_0002 +%uniform_image_f32_spd_0002 = OpVariable %ptr_image_f32_spd_0002 UniformConstant +%type_sampled_image_f32_spd_0002 = OpTypeSampledImage %type_image_f32_spd_0002 + +%type_image_f32_3d_0111 = OpTypeImage %f32 3D 0 1 1 1 Unknown +%ptr_image_f32_3d_0111 = OpTypePointer UniformConstant %type_image_f32_3d_0111 +%uniform_image_f32_3d_0111 = OpVariable %ptr_image_f32_3d_0111 UniformConstant +%type_sampled_image_f32_3d_0111 = OpTypeSampledImage %type_image_f32_3d_0111 + +%type_image_f32_cube_0101 = OpTypeImage %f32 Cube 0 1 0 1 Unknown +%ptr_image_f32_cube_0101 = OpTypePointer UniformConstant %type_image_f32_cube_0101 +%uniform_image_f32_cube_0101 = OpVariable %ptr_image_f32_cube_0101 UniformConstant +%type_sampled_image_f32_cube_0101 = OpTypeSampledImage %type_image_f32_cube_0101 + +%type_image_f32_cube_0102_rgba32f = OpTypeImage %f32 Cube 0 1 0 2 Rgba32f +%ptr_image_f32_cube_0102_rgba32f = OpTypePointer UniformConstant %type_image_f32_cube_0102_rgba32f +%uniform_image_f32_cube_0102_rgba32f = OpVariable %ptr_image_f32_cube_0102_rgba32f UniformConstant +%type_sampled_image_f32_cube_0102_rgba32f = OpTypeSampledImage %type_image_f32_cube_0102_rgba32f + +%type_sampler = OpTypeSampler +%ptr_sampler = OpTypePointer UniformConstant %type_sampler +%uniform_sampler = OpVariable %ptr_sampler UniformConstant +)"; + + if (env == SPV_ENV_UNIVERSAL_1_0) { + ss << R"( +%type_image_void_2d_0001 = OpTypeImage %void 2D 0 0 0 1 Unknown +%ptr_image_void_2d_0001 = OpTypePointer UniformConstant %type_image_void_2d_0001 +%uniform_image_void_2d_0001 = OpVariable %ptr_image_void_2d_0001 UniformConstant +%type_sampled_image_void_2d_0001 = OpTypeSampledImage %type_image_void_2d_0001 + +%type_image_void_2d_0002 = OpTypeImage %void 2D 0 0 0 2 Unknown +%ptr_image_void_2d_0002 = OpTypePointer UniformConstant %type_image_void_2d_0002 +%uniform_image_void_2d_0002 = OpVariable %ptr_image_void_2d_0002 UniformConstant +%type_sampled_image_void_2d_0002 = OpTypeSampledImage %type_image_void_2d_0002 + +%type_image_f32_rect_0001 = OpTypeImage %f32 Rect 0 0 0 1 Unknown +%ptr_image_f32_rect_0001 = OpTypePointer UniformConstant %type_image_f32_rect_0001 +%uniform_image_f32_rect_0001 = OpVariable %ptr_image_f32_rect_0001 UniformConstant +%type_sampled_image_f32_rect_0001 = OpTypeSampledImage %type_image_f32_rect_0001 +)"; + } + + ss << R"( +%main = OpFunction %void None %func +%main_entry = OpLabel +)"; + + ss << body; + + ss << R"( +OpReturn +OpFunctionEnd)"; + + return ss.str(); +} + +std::string GenerateKernelCode( + const std::string& body, + const std::string& capabilities_and_extensions = "") { + std::ostringstream ss; + ss << R"( +OpCapability Addresses +OpCapability Kernel +OpCapability Linkage +OpCapability ImageQuery +OpCapability ImageGatherExtended +OpCapability InputAttachment +OpCapability SampledRect +)"; + + ss << capabilities_and_extensions; + ss << R"( +OpMemoryModel Physical32 OpenCL +%void = OpTypeVoid +%func = OpTypeFunction %void +%bool = OpTypeBool +%f32 = OpTypeFloat 32 +%u32 = OpTypeInt 32 0 +%u32vec2 = OpTypeVector %u32 2 +%f32vec2 = OpTypeVector %f32 2 +%u32vec3 = OpTypeVector %u32 3 +%f32vec3 = OpTypeVector %f32 3 +%u32vec4 = OpTypeVector %u32 4 +%f32vec4 = OpTypeVector %f32 4 + +%f32_0 = OpConstant %f32 0 +%f32_1 = OpConstant %f32 1 +%f32_0_5 = OpConstant %f32 0.5 +%f32_0_25 = OpConstant %f32 0.25 +%f32_0_75 = OpConstant %f32 0.75 + +%u32_0 = OpConstant %u32 0 +%u32_1 = OpConstant %u32 1 +%u32_2 = OpConstant %u32 2 +%u32_3 = OpConstant %u32 3 +%u32_4 = OpConstant %u32 4 + +%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1 +%u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2 +%u32vec3_012 = OpConstantComposite %u32vec3 %u32_0 %u32_1 %u32_2 +%u32vec3_123 = OpConstantComposite %u32vec3 %u32_1 %u32_2 %u32_3 +%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3 +%u32vec4_1234 = OpConstantComposite %u32vec4 %u32_1 %u32_2 %u32_3 %u32_4 + +%f32vec2_00 = OpConstantComposite %f32vec2 %f32_0 %f32_0 +%f32vec2_01 = OpConstantComposite %f32vec2 %f32_0 %f32_1 +%f32vec2_10 = OpConstantComposite %f32vec2 %f32_1 %f32_0 +%f32vec2_11 = OpConstantComposite %f32vec2 %f32_1 %f32_1 +%f32vec2_hh = OpConstantComposite %f32vec2 %f32_0_5 %f32_0_5 + +%f32vec3_000 = OpConstantComposite %f32vec3 %f32_0 %f32_0 %f32_0 +%f32vec3_hhh = OpConstantComposite %f32vec3 %f32_0_5 %f32_0_5 %f32_0_5 + +%f32vec4_0000 = OpConstantComposite %f32vec4 %f32_0 %f32_0 %f32_0 %f32_0 + +%type_image_f32_2d_0001 = OpTypeImage %f32 2D 0 0 0 1 Unknown +%ptr_image_f32_2d_0001 = OpTypePointer UniformConstant %type_image_f32_2d_0001 +%uniform_image_f32_2d_0001 = OpVariable %ptr_image_f32_2d_0001 UniformConstant +%type_sampled_image_f32_2d_0001 = OpTypeSampledImage %type_image_f32_2d_0001 + +%type_image_f32_2d_0010 = OpTypeImage %f32 2D 0 0 1 0 Unknown +%ptr_image_f32_2d_0010 = OpTypePointer UniformConstant %type_image_f32_2d_0010 +%uniform_image_f32_2d_0010 = OpVariable %ptr_image_f32_2d_0010 UniformConstant +%type_sampled_image_f32_2d_0010 = OpTypeSampledImage %type_image_f32_2d_0010 + +%type_image_f32_3d_0010 = OpTypeImage %f32 3D 0 0 1 0 Unknown +%ptr_image_f32_3d_0010 = OpTypePointer UniformConstant %type_image_f32_3d_0010 +%uniform_image_f32_3d_0010 = OpVariable %ptr_image_f32_3d_0010 UniformConstant +%type_sampled_image_f32_3d_0010 = OpTypeSampledImage %type_image_f32_3d_0010 + +%type_image_f32_rect_0001 = OpTypeImage %f32 Rect 0 0 0 1 Unknown +%ptr_image_f32_rect_0001 = OpTypePointer UniformConstant %type_image_f32_rect_0001 +%uniform_image_f32_rect_0001 = OpVariable %ptr_image_f32_rect_0001 UniformConstant +%type_sampled_image_f32_rect_0001 = OpTypeSampledImage %type_image_f32_rect_0001 + +%type_sampler = OpTypeSampler +%ptr_sampler = OpTypePointer UniformConstant %type_sampler +%uniform_sampler = OpVariable %ptr_sampler UniformConstant + +%main = OpFunction %void None %func +%main_entry = OpLabel +)"; + + ss << body; + ss << R"( +OpReturn +OpFunctionEnd)"; + + return ss.str(); +} + +std::string GetShaderHeader( + const std::string& capabilities_and_extensions = "") { + std::ostringstream ss; + ss << R"( +OpCapability Shader +OpCapability Int64 +)"; + + ss << capabilities_and_extensions; + + ss << R"( +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +%void = OpTypeVoid +%func = OpTypeFunction %void +%bool = OpTypeBool +%f32 = OpTypeFloat 32 +%u32 = OpTypeInt 32 0 +%u64 = OpTypeInt 64 0 +%s32 = OpTypeInt 32 1 +)"; + + return ss.str(); +} + +TEST_F(ValidateImage, TypeImageWrongSampledType) { + const std::string code = GetShaderHeader() + R"( +%img_type = OpTypeImage %bool 2D 0 0 0 1 Unknown +)"; + + CompileSuccessfully(code.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("TypeImage: expected Sampled Type to be either void or " + "numerical scalar " + "type")); +} + +TEST_F(ValidateImage, TypeImageVoidSampledTypeVulkan) { + const std::string code = GetShaderHeader() + R"( +%img_type = OpTypeImage %void 2D 0 0 0 1 Unknown +)"; + + const spv_target_env env = SPV_ENV_VULKAN_1_0; + CompileSuccessfully(code, env); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(env)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("TypeImage: expected Sampled Type to be a 32-bit int " + "or float scalar type for Vulkan environment")); +} + +TEST_F(ValidateImage, TypeImageU64SampledTypeVulkan) { + const std::string code = GetShaderHeader() + R"( +%img_type = OpTypeImage %u64 2D 0 0 0 1 Unknown +)"; + + const spv_target_env env = SPV_ENV_VULKAN_1_0; + CompileSuccessfully(code, env); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(env)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("TypeImage: expected Sampled Type to be a 32-bit int " + "or float scalar type for Vulkan environment")); +} + +TEST_F(ValidateImage, TypeImageWrongDepth) { + const std::string code = GetShaderHeader() + R"( +%img_type = OpTypeImage %f32 2D 3 0 0 1 Unknown +)"; + + CompileSuccessfully(code.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("TypeImage: invalid Depth 3 (must be 0, 1 or 2)")); +} + +TEST_F(ValidateImage, TypeImageWrongArrayed) { + const std::string code = GetShaderHeader() + R"( +%img_type = OpTypeImage %f32 2D 0 2 0 1 Unknown +)"; + + CompileSuccessfully(code.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("TypeImage: invalid Arrayed 2 (must be 0 or 1)")); +} + +TEST_F(ValidateImage, TypeImageWrongMS) { + const std::string code = GetShaderHeader() + R"( +%img_type = OpTypeImage %f32 2D 0 0 2 1 Unknown +)"; + + CompileSuccessfully(code.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("TypeImage: invalid MS 2 (must be 0 or 1)")); +} + +TEST_F(ValidateImage, TypeImageWrongSampled) { + const std::string code = GetShaderHeader() + R"( +%img_type = OpTypeImage %f32 2D 0 0 0 3 Unknown +)"; + + CompileSuccessfully(code.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("TypeImage: invalid Sampled 3 (must be 0, 1 or 2)")); +} + +TEST_F(ValidateImage, TypeImageWrongSampledForSubpassData) { + const std::string code = GetShaderHeader("OpCapability InputAttachment\n") + + R"( +%img_type = OpTypeImage %f32 SubpassData 0 0 0 1 Unknown +)"; + + CompileSuccessfully(code.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("TypeImage: Dim SubpassData requires Sampled to be 2")); +} + +TEST_F(ValidateImage, TypeImageWrongFormatForSubpassData) { + const std::string code = GetShaderHeader("OpCapability InputAttachment\n") + + R"( +%img_type = OpTypeImage %f32 SubpassData 0 0 0 2 Rgba32f +)"; + + CompileSuccessfully(code.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("TypeImage: Dim SubpassData requires format Unknown")); +} + +TEST_F(ValidateImage, TypeSampledImageNotImage) { + const std::string code = GetShaderHeader() + R"( +%simg_type = OpTypeSampledImage %f32 +)"; + + CompileSuccessfully(code.c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("TypeSampledImage: expected Image to be of type OpTypeImage")); +} + +TEST_F(ValidateImage, SampledImageSuccess) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, SampledImageVulkanSuccess) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +)"; + + const spv_target_env env = SPV_ENV_VULKAN_1_0; + CompileSuccessfully(GenerateShaderCode(body, "", "Fragment", env), env); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(env)); +} + +TEST_F(ValidateImage, SampledImageWrongResultType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_image_f32_2d_0001 %img %sampler +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Result Type to be OpTypeSampledImage: SampledImage")); +} + +TEST_F(ValidateImage, SampledImageNotImage) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg1 = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%simg2 = OpSampledImage %type_sampled_image_f32_2d_0001 %simg1 %sampler +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Image to be of type OpTypeImage: SampledImage")); +} + +TEST_F(ValidateImage, SampledImageImageNotForSampling) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0002 %uniform_image_f32_2d_0002 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0002 %img %sampler +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected Image 'Sampled' parameter to be 0 or 1: SampledImage")); +} + +TEST_F(ValidateImage, SampledImageVulkanUnknownSampled) { + const std::string body = R"( +%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_u32_2d_0000 %img %sampler +)"; + + const spv_target_env env = SPV_ENV_VULKAN_1_0; + CompileSuccessfully(GenerateShaderCode(body, "", "Fragment", env), env); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(env)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Image 'Sampled' parameter to be 1 for Vulkan " + "environment: SampledImage")); +} + +TEST_F(ValidateImage, SampledImageNotSampler) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %img +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Sampler to be of type OpTypeSampler: SampledImage")); +} + +TEST_F(ValidateImage, SampleImplicitLodSuccess) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec2_hh +%res2 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec2_hh Bias %f32_0_25 +%res4 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec2_hh ConstOffset %s32vec2_01 +%res5 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec2_hh Offset %s32vec2_01 +%res6 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec2_hh MinLod %f32_0_5 +%res7 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec2_hh Bias|Offset|MinLod %f32_0_25 %s32vec2_01 %f32_0_5 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, SampleImplicitLodWrongResultType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleImplicitLod %f32 %simg %f32vec2_hh +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type to be int or float vector type: " + "ImageSampleImplicitLod")); +} + +TEST_F(ValidateImage, SampleImplicitLodWrongNumComponentsResultType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleImplicitLod %f32vec3 %simg %f32vec2_hh +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type to have 4 components: " + "ImageSampleImplicitLod")); +} + +TEST_F(ValidateImage, SampleImplicitLodNotSampledImage) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%res1 = OpImageSampleImplicitLod %f32vec4 %img %f32vec2_hh +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Sampled Image to be of type OpTypeSampledImage: " + "ImageSampleImplicitLod")); +} + +TEST_F(ValidateImage, SampleImplicitLodWrongSampledType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleImplicitLod %u32vec4 %simg %f32vec2_00 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Image 'Sampled Type' to be the same as " + "Result Type components: " + "ImageSampleImplicitLod")); +} + +TEST_F(ValidateImage, SampleImplicitLodVoidSampledType) { + const std::string body = R"( +%img = OpLoad %type_image_void_2d_0001 %uniform_image_void_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_void_2d_0001 %img %sampler +%res1 = OpImageSampleImplicitLod %u32vec4 %simg %f32vec2_00 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, SampleImplicitLodWrongCoordinateType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleImplicitLod %f32vec4 %simg %img +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Coordinate to be float scalar or vector: " + "ImageSampleImplicitLod")); +} + +TEST_F(ValidateImage, SampleImplicitLodCoordinateSizeTooSmall) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleImplicitLod %f32vec4 %simg %f32_0_5 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Coordinate to have at least 2 components, " + "but given only 1: " + "ImageSampleImplicitLod")); +} + +TEST_F(ValidateImage, SampleExplicitLodSuccessShader) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleExplicitLod %f32vec4 %simg %f32vec4_0000 Lod %f32_1 +%res2 = OpImageSampleExplicitLod %f32vec4 %simg %f32vec2_hh Grad %f32vec2_10 %f32vec2_01 +%res3 = OpImageSampleExplicitLod %f32vec4 %simg %f32vec2_hh ConstOffset %s32vec2_01 +%res4 = OpImageSampleExplicitLod %f32vec4 %simg %f32vec3_hhh Offset %s32vec2_01 +%res5 = OpImageSampleExplicitLod %f32vec4 %simg %f32vec2_hh Grad|Offset|MinLod %f32vec2_10 %f32vec2_01 %s32vec2_01 %f32_0_5 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, SampleExplicitLodSuccessKernel) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleExplicitLod %f32vec4 %simg %u32vec4_0123 Lod %f32_1 +%res2 = OpImageSampleExplicitLod %f32vec4 %simg %u32vec2_01 Grad %f32vec2_10 %f32vec2_01 +%res3 = OpImageSampleExplicitLod %f32vec4 %simg %f32vec2_hh ConstOffset %u32vec2_01 +%res4 = OpImageSampleExplicitLod %f32vec4 %simg %u32vec2_01 Offset %u32vec2_01 +%res5 = OpImageSampleExplicitLod %f32vec4 %simg %f32vec2_hh Grad|Offset %f32vec2_10 %f32vec2_01 %u32vec2_01 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, SampleExplicitLodSuccessCubeArrayed) { + const std::string body = R"( +%img = OpLoad %type_image_f32_cube_0101 %uniform_image_f32_cube_0101 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_cube_0101 %img %sampler +%res1 = OpImageSampleExplicitLod %f32vec4 %simg %f32vec4_0000 Grad %f32vec3_hhh %f32vec3_hhh +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, SampleExplicitLodWrongResultType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleExplicitLod %f32 %simg %f32vec2_hh Lod %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type to be int or float vector type: " + "ImageSampleExplicitLod")); +} + +TEST_F(ValidateImage, SampleExplicitLodWrongNumComponentsResultType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleExplicitLod %f32vec3 %simg %f32vec2_hh Lod %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type to have 4 components: " + "ImageSampleExplicitLod")); +} + +TEST_F(ValidateImage, SampleExplicitLodNotSampledImage) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%res1 = OpImageSampleExplicitLod %f32vec4 %img %f32vec2_hh Lod %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Sampled Image to be of type OpTypeSampledImage: " + "ImageSampleExplicitLod")); +} + +TEST_F(ValidateImage, SampleExplicitLodWrongSampledType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleExplicitLod %u32vec4 %simg %f32vec2_00 Lod %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Image 'Sampled Type' to be the same as " + "Result Type components: " + "ImageSampleExplicitLod")); +} + +TEST_F(ValidateImage, SampleExplicitLodVoidSampledType) { + const std::string body = R"( +%img = OpLoad %type_image_void_2d_0001 %uniform_image_void_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_void_2d_0001 %img %sampler +%res1 = OpImageSampleExplicitLod %u32vec4 %simg %f32vec2_00 Lod %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, SampleExplicitLodWrongCoordinateType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleExplicitLod %f32vec4 %simg %img Lod %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Coordinate to be float scalar or vector: " + "ImageSampleExplicitLod")); +} + +TEST_F(ValidateImage, SampleExplicitLodCoordinateSizeTooSmall) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleExplicitLod %f32vec4 %simg %f32_0_5 Lod %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Coordinate to have at least 2 components, " + "but given only 1: " + "ImageSampleExplicitLod")); +} + +TEST_F(ValidateImage, SampleExplicitLodBias) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleExplicitLod %f32vec4 %simg %f32vec2_00 Bias|Lod %f32_1 %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Image Operand Bias can only be used with ImplicitLod opcodes: " + "ImageSampleExplicitLod")); +} + +TEST_F(ValidateImage, LodAndGrad) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleExplicitLod %f32vec4 %simg %f32vec2_00 Lod|Grad %f32_1 %f32vec2_hh %f32vec2_hh +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Image Operand bits Lod and Grad cannot be set at the same time: " + "ImageSampleExplicitLod")); +} + +TEST_F(ValidateImage, ImplicitLodWithLod) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res2 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec2_hh Lod %f32_0_5 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Image Operand Lod can only be used with ExplicitLod opcodes " + "and OpImageFetch: ImageSampleImplicitLod")); +} + +TEST_F(ValidateImage, LodWrongType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleExplicitLod %f32vec4 %simg %f32vec2_00 Lod %f32vec2_hh)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Image Operand Lod to be float scalar when " + "used with ExplicitLod: ImageSampleExplicitLod")); +} + +TEST_F(ValidateImage, LodWrongDim) { + const std::string body = R"( +%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_rect_0001 %img %sampler +%res1 = OpImageSampleExplicitLod %f32vec4 %simg %f32vec2_00 Lod %f32_0)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Image Operand Lod requires 'Dim' parameter to be 1D, " + "2D, 3D or Cube: " + "ImageSampleExplicitLod")); +} + +TEST_F(ValidateImage, LodMultisampled) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0010 %uniform_image_f32_2d_0010 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0010 %img %sampler +%res1 = OpImageSampleExplicitLod %f32vec4 %simg %f32vec2_00 Lod %f32_0)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Image Operand Lod requires 'MS' parameter to be 0: " + "ImageSampleExplicitLod")); +} + +TEST_F(ValidateImage, MinLodIncompatible) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleExplicitLod %f32vec4 %simg %f32vec2_00 Lod|MinLod %f32_0 %f32_0)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Image Operand MinLod can only be used with ImplicitLod opcodes or " + "together with Image Operand Grad: ImageSampleExplicitLod")); +} + +TEST_F(ValidateImage, ImplicitLodWithGrad) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res2 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec2_hh Grad %f32vec2_hh %f32vec2_hh +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Image Operand Grad can only be used with ExplicitLod opcodes: " + "ImageSampleImplicitLod")); +} + +TEST_F(ValidateImage, SampleImplicitLod3DArrayedMultisampledSuccess) { + const std::string body = R"( +%img = OpLoad %type_image_f32_3d_0111 %uniform_image_f32_3d_0111 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_3d_0111 %img %sampler +%res1 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec4_0000 +%res2 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec4_0000 ConstOffset %s32vec3_012 +%res3 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec4_0000 Offset %s32vec3_012 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, SampleImplicitLodCubeArrayedSuccess) { + const std::string body = R"( +%img = OpLoad %type_image_f32_cube_0101 %uniform_image_f32_cube_0101 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_cube_0101 %img %sampler +%res1 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec4_0000 +%res2 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec4_0000 Bias %f32_0_25 +%res4 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec4_0000 MinLod %f32_0_5 +%res5 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec4_0000 Bias|MinLod %f32_0_25 %f32_0_5 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, SampleImplicitLodBiasWrongType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res2 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec2_hh Bias %u32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Image Operand Bias to be float scalar: " + "ImageSampleImplicitLod")); +} + +TEST_F(ValidateImage, SampleImplicitLodBiasWrongDim) { + const std::string body = R"( +%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_rect_0001 %img %sampler +%res2 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec2_hh Bias %f32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Image Operand Bias requires 'Dim' parameter to be 1D, " + "2D, 3D or Cube: " + "ImageSampleImplicitLod")); +} + +TEST_F(ValidateImage, SampleImplicitLodBiasMultisampled) { + const std::string body = R"( +%img = OpLoad %type_image_f32_3d_0111 %uniform_image_f32_3d_0111 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_3d_0111 %img %sampler +%res1 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec4_0000 Bias %f32_0_25 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Image Operand Bias requires 'MS' parameter to be 0: " + "ImageSampleImplicitLod")); +} + +TEST_F(ValidateImage, SampleExplicitLodGradDxWrongType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_cube_0101 %uniform_image_f32_cube_0101 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_cube_0101 %img %sampler +%res1 = OpImageSampleExplicitLod %f32vec4 %simg %f32vec4_0000 Grad %s32vec3_012 %f32vec3_hhh +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected both Image Operand Grad ids to be float " + "scalars or vectors: " + "ImageSampleExplicitLod")); +} + +TEST_F(ValidateImage, SampleExplicitLodGradDyWrongType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_cube_0101 %uniform_image_f32_cube_0101 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_cube_0101 %img %sampler +%res1 = OpImageSampleExplicitLod %f32vec4 %simg %f32vec4_0000 Grad %f32vec3_hhh %s32vec3_012 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected both Image Operand Grad ids to be float " + "scalars or vectors: " + "ImageSampleExplicitLod")); +} + +TEST_F(ValidateImage, SampleExplicitLodGradDxWrongSize) { + const std::string body = R"( +%img = OpLoad %type_image_f32_cube_0101 %uniform_image_f32_cube_0101 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_cube_0101 %img %sampler +%res1 = OpImageSampleExplicitLod %f32vec4 %simg %f32vec4_0000 Grad %f32vec2_00 %f32vec3_hhh +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected Image Operand Grad dx to have 3 components, but given 2: " + "ImageSampleExplicitLod")); +} + +TEST_F(ValidateImage, SampleExplicitLodGradDyWrongSize) { + const std::string body = R"( +%img = OpLoad %type_image_f32_cube_0101 %uniform_image_f32_cube_0101 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_cube_0101 %img %sampler +%res1 = OpImageSampleExplicitLod %f32vec4 %simg %f32vec4_0000 Grad %f32vec3_hhh %f32vec2_00 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected Image Operand Grad dy to have 3 components, but given 2: " + "ImageSampleExplicitLod")); +} + +TEST_F(ValidateImage, SampleExplicitLodGradMultisampled) { + const std::string body = R"( +%img = OpLoad %type_image_f32_3d_0111 %uniform_image_f32_3d_0111 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_3d_0111 %img %sampler +%res1 = OpImageSampleExplicitLod %f32vec4 %simg %f32vec4_0000 Grad %f32vec3_000 %f32vec3_000 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Image Operand Grad requires 'MS' parameter to be 0: " + "ImageSampleExplicitLod")); +} + +TEST_F(ValidateImage, SampleImplicitLodConstOffsetCubeDim) { + const std::string body = R"( +%img = OpLoad %type_image_f32_cube_0101 %uniform_image_f32_cube_0101 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_cube_0101 %img %sampler +%res4 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec4_0000 ConstOffset %s32vec3_012 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Image Operand ConstOffset cannot be used with Cube Image 'Dim': " + "ImageSampleImplicitLod")); +} + +TEST_F(ValidateImage, SampleImplicitLodConstOffsetWrongType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_3d_0111 %uniform_image_f32_3d_0111 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_3d_0111 %img %sampler +%res4 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec4_0000 ConstOffset %f32vec3_000 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected Image Operand ConstOffset to be int scalar or vector: " + "ImageSampleImplicitLod")); +} + +TEST_F(ValidateImage, SampleImplicitLodConstOffsetWrongSize) { + const std::string body = R"( +%img = OpLoad %type_image_f32_3d_0111 %uniform_image_f32_3d_0111 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_3d_0111 %img %sampler +%res4 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec4_0000 ConstOffset %s32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Image Operand ConstOffset to have 3 " + "components, but given 2: " + "ImageSampleImplicitLod")); +} + +TEST_F(ValidateImage, SampleImplicitLodConstOffsetNotConst) { + const std::string body = R"( +%img = OpLoad %type_image_f32_3d_0111 %uniform_image_f32_3d_0111 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_3d_0111 %img %sampler +%offset = OpSNegate %s32vec3 %s32vec3_012 +%res4 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec4_0000 ConstOffset %offset +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Image Operand ConstOffset to be a const object: " + "ImageSampleImplicitLod")); +} + +TEST_F(ValidateImage, SampleImplicitLodOffsetCubeDim) { + const std::string body = R"( +%img = OpLoad %type_image_f32_cube_0101 %uniform_image_f32_cube_0101 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_cube_0101 %img %sampler +%res4 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec4_0000 Offset %s32vec3_012 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Image Operand Offset cannot be used with Cube Image 'Dim': " + "ImageSampleImplicitLod")); +} + +TEST_F(ValidateImage, SampleImplicitLodOffsetWrongType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_3d_0111 %uniform_image_f32_3d_0111 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_3d_0111 %img %sampler +%res4 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec4_0000 Offset %f32vec3_000 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Image Operand Offset to be int scalar or vector: " + "ImageSampleImplicitLod")); +} + +TEST_F(ValidateImage, SampleImplicitLodOffsetWrongSize) { + const std::string body = R"( +%img = OpLoad %type_image_f32_3d_0111 %uniform_image_f32_3d_0111 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_3d_0111 %img %sampler +%res4 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec4_0000 Offset %s32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected Image Operand Offset to have 3 components, but given 2: " + "ImageSampleImplicitLod")); +} + +TEST_F(ValidateImage, SampleImplicitLodMoreThanOneOffset) { + const std::string body = R"( +%img = OpLoad %type_image_f32_3d_0111 %uniform_image_f32_3d_0111 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_3d_0111 %img %sampler +%res4 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec4_0000 ConstOffset|Offset %s32vec3_012 %s32vec3_012 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Image Operands Offset, ConstOffset, ConstOffsets " + "cannot be used together: " + "ImageSampleImplicitLod")); +} + +TEST_F(ValidateImage, SampleImplicitLodMinLodWrongType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_cube_0101 %uniform_image_f32_cube_0101 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_cube_0101 %img %sampler +%res1 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec4_0000 MinLod %s32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Image Operand MinLod to be float scalar: " + "ImageSampleImplicitLod")); +} + +TEST_F(ValidateImage, SampleImplicitLodMinLodWrongDim) { + const std::string body = R"( +%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_rect_0001 %img %sampler +%res2 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec2_hh MinLod %f32_0_25 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Image Operand MinLod requires 'Dim' parameter to be " + "1D, 2D, 3D or Cube: " + "ImageSampleImplicitLod")); +} + +TEST_F(ValidateImage, SampleImplicitLodMinLodMultisampled) { + const std::string body = R"( +%img = OpLoad %type_image_f32_3d_0111 %uniform_image_f32_3d_0111 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_3d_0111 %img %sampler +%res1 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec4_0000 MinLod %f32_0_25 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Image Operand MinLod requires 'MS' parameter to be 0: " + "ImageSampleImplicitLod")); +} + +TEST_F(ValidateImage, SampleProjExplicitLodSuccess2D) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleProjExplicitLod %f32vec4 %simg %f32vec3_hhh Lod %f32_1 +%res3 = OpImageSampleProjExplicitLod %f32vec4 %simg %f32vec3_hhh Grad %f32vec2_10 %f32vec2_01 +%res4 = OpImageSampleProjExplicitLod %f32vec4 %simg %f32vec3_hhh ConstOffset %s32vec2_01 +%res5 = OpImageSampleProjExplicitLod %f32vec4 %simg %f32vec3_hhh Offset %s32vec2_01 +%res7 = OpImageSampleProjExplicitLod %f32vec4 %simg %f32vec3_hhh Grad|Offset %f32vec2_10 %f32vec2_01 %s32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, SampleProjExplicitLodSuccessRect) { + const std::string body = R"( +%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_rect_0001 %img %sampler +%res1 = OpImageSampleProjExplicitLod %f32vec4 %simg %f32vec3_hhh Grad %f32vec2_10 %f32vec2_01 +%res2 = OpImageSampleProjExplicitLod %f32vec4 %simg %f32vec3_hhh Grad|Offset %f32vec2_10 %f32vec2_01 %s32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, SampleProjExplicitLodWrongResultType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleProjExplicitLod %f32 %simg %f32vec3_hhh Lod %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type to be int or float vector type: " + "ImageSampleProjExplicitLod")); +} + +TEST_F(ValidateImage, SampleProjExplicitLodWrongNumComponentsResultType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleProjExplicitLod %f32vec3 %simg %f32vec3_hhh Lod %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type to have 4 components: " + "ImageSampleProjExplicitLod")); +} + +TEST_F(ValidateImage, SampleProjExplicitLodNotSampledImage) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%res1 = OpImageSampleProjExplicitLod %f32vec4 %img %f32vec3_hhh Lod %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Sampled Image to be of type OpTypeSampledImage: " + "ImageSampleProjExplicitLod")); +} + +TEST_F(ValidateImage, SampleProjExplicitLodWrongSampledType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleProjExplicitLod %u32vec4 %simg %f32vec3_hhh Lod %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Image 'Sampled Type' to be the same as " + "Result Type components: " + "ImageSampleProjExplicitLod")); +} + +TEST_F(ValidateImage, SampleProjExplicitLodVoidSampledType) { + const std::string body = R"( +%img = OpLoad %type_image_void_2d_0001 %uniform_image_void_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_void_2d_0001 %img %sampler +%res1 = OpImageSampleProjExplicitLod %u32vec4 %simg %f32vec3_hhh Lod %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, SampleProjExplicitLodWrongCoordinateType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleProjExplicitLod %f32vec4 %simg %img Lod %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Coordinate to be float scalar or vector: " + "ImageSampleProjExplicitLod")); +} + +TEST_F(ValidateImage, SampleProjExplicitLodCoordinateSizeTooSmall) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleProjExplicitLod %f32vec4 %simg %f32vec2_hh Lod %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Coordinate to have at least 3 components, " + "but given only 2: " + "ImageSampleProjExplicitLod")); +} + +TEST_F(ValidateImage, SampleProjImplicitLodSuccess) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleProjImplicitLod %f32vec4 %simg %f32vec3_hhh +%res2 = OpImageSampleProjImplicitLod %f32vec4 %simg %f32vec3_hhh Bias %f32_0_25 +%res4 = OpImageSampleProjImplicitLod %f32vec4 %simg %f32vec3_hhh ConstOffset %s32vec2_01 +%res5 = OpImageSampleProjImplicitLod %f32vec4 %simg %f32vec3_hhh Offset %s32vec2_01 +%res6 = OpImageSampleProjImplicitLod %f32vec4 %simg %f32vec3_hhh MinLod %f32_0_5 +%res7 = OpImageSampleProjImplicitLod %f32vec4 %simg %f32vec3_hhh Bias|Offset|MinLod %f32_0_25 %s32vec2_01 %f32_0_5 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, SampleProjImplicitLodWrongResultType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleProjImplicitLod %f32 %simg %f32vec3_hhh +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type to be int or float vector type: " + "ImageSampleProjImplicitLod")); +} + +TEST_F(ValidateImage, SampleProjImplicitLodWrongNumComponentsResultType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleProjImplicitLod %f32vec3 %simg %f32vec3_hhh +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type to have 4 components: " + "ImageSampleProjImplicitLod")); +} + +TEST_F(ValidateImage, SampleProjImplicitLodNotSampledImage) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%res1 = OpImageSampleProjImplicitLod %f32vec4 %img %f32vec3_hhh +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Sampled Image to be of type OpTypeSampledImage: " + "ImageSampleProjImplicitLod")); +} + +TEST_F(ValidateImage, SampleProjImplicitLodWrongSampledType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleProjImplicitLod %u32vec4 %simg %f32vec3_hhh +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Image 'Sampled Type' to be the same as " + "Result Type components: " + "ImageSampleProjImplicitLod")); +} + +TEST_F(ValidateImage, SampleProjImplicitLodVoidSampledType) { + const std::string body = R"( +%img = OpLoad %type_image_void_2d_0001 %uniform_image_void_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_void_2d_0001 %img %sampler +%res1 = OpImageSampleProjImplicitLod %u32vec4 %simg %f32vec3_hhh +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, SampleProjImplicitLodWrongCoordinateType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleProjImplicitLod %f32vec4 %simg %img +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Coordinate to be float scalar or vector: " + "ImageSampleProjImplicitLod")); +} + +TEST_F(ValidateImage, SampleProjImplicitLodCoordinateSizeTooSmall) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleProjImplicitLod %f32vec4 %simg %f32vec2_hh +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Coordinate to have at least 3 components, " + "but given only 2: " + "ImageSampleProjImplicitLod")); +} + +TEST_F(ValidateImage, SampleDrefImplicitLodSuccess) { + const std::string body = R"( +%img = OpLoad %type_image_u32_2d_0001 %uniform_image_u32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_u32_2d_0001 %img %sampler +%res1 = OpImageSampleDrefImplicitLod %u32 %simg %f32vec2_hh %f32_1 +%res2 = OpImageSampleDrefImplicitLod %u32 %simg %f32vec2_hh %f32_1 Bias %f32_0_25 +%res4 = OpImageSampleDrefImplicitLod %u32 %simg %f32vec2_hh %f32_1 ConstOffset %s32vec2_01 +%res5 = OpImageSampleDrefImplicitLod %u32 %simg %f32vec2_hh %f32_1 Offset %s32vec2_01 +%res6 = OpImageSampleDrefImplicitLod %u32 %simg %f32vec2_hh %f32_1 MinLod %f32_0_5 +%res7 = OpImageSampleDrefImplicitLod %u32 %simg %f32vec2_hh %f32_1 Bias|Offset|MinLod %f32_0_25 %s32vec2_01 %f32_0_5 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, SampleDrefImplicitLodWrongResultType) { + const std::string body = R"( +%img = OpLoad %type_image_void_2d_0001 %uniform_image_void_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_void_2d_0001 %img %sampler +%res1 = OpImageSampleDrefImplicitLod %void %simg %f32vec2_hh %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type to be int or float scalar type: " + "ImageSampleDrefImplicitLod")); +} + +TEST_F(ValidateImage, SampleDrefImplicitLodNotSampledImage) { + const std::string body = R"( +%img = OpLoad %type_image_u32_2d_0001 %uniform_image_u32_2d_0001 +%res1 = OpImageSampleDrefImplicitLod %u32 %img %f32vec2_hh %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Sampled Image to be of type OpTypeSampledImage: " + "ImageSampleDrefImplicitLod")); +} + +TEST_F(ValidateImage, SampleDrefImplicitLodWrongSampledType) { + const std::string body = R"( +%img = OpLoad %type_image_u32_2d_0001 %uniform_image_u32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_u32_2d_0001 %img %sampler +%res1 = OpImageSampleDrefImplicitLod %f32 %simg %f32vec2_00 %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Image 'Sampled Type' to be the same as Result Type: " + "ImageSampleDrefImplicitLod")); +} + +TEST_F(ValidateImage, SampleDrefImplicitLodVoidSampledType) { + const std::string body = R"( +%img = OpLoad %type_image_void_2d_0001 %uniform_image_void_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_void_2d_0001 %img %sampler +%res1 = OpImageSampleDrefImplicitLod %u32 %simg %f32vec2_00 %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Image 'Sampled Type' to be the same as Result Type: " + "ImageSampleDrefImplicitLod")); +} + +TEST_F(ValidateImage, SampleDrefImplicitLodWrongCoordinateType) { + const std::string body = R"( +%img = OpLoad %type_image_u32_2d_0001 %uniform_image_u32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_u32_2d_0001 %img %sampler +%res1 = OpImageSampleDrefImplicitLod %u32 %simg %img %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Coordinate to be float scalar or vector: " + "ImageSampleDrefImplicitLod")); +} + +TEST_F(ValidateImage, SampleDrefImplicitLodCoordinateSizeTooSmall) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleDrefImplicitLod %f32 %simg %f32_0_5 %f32_0_5 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Coordinate to have at least 2 components, " + "but given only 1: " + "ImageSampleDrefImplicitLod")); +} + +TEST_F(ValidateImage, SampleDrefImplicitLodWrongDrefType) { + const std::string body = R"( +%img = OpLoad %type_image_u32_2d_0001 %uniform_image_u32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_u32_2d_0001 %img %sampler +%res1 = OpImageSampleDrefImplicitLod %u32 %simg %f32vec2_00 %f64_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ImageSampleDrefImplicitLod: " + "Expected Dref to be of 32-bit float type")); +} + +TEST_F(ValidateImage, SampleDrefExplicitLodSuccess) { + const std::string body = R"( +%img = OpLoad %type_image_s32_3d_0001 %uniform_image_s32_3d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_s32_3d_0001 %img %sampler +%res1 = OpImageSampleDrefExplicitLod %s32 %simg %f32vec4_0000 %f32_1 Lod %f32_1 +%res3 = OpImageSampleDrefExplicitLod %s32 %simg %f32vec3_hhh %f32_1 Grad %f32vec3_hhh %f32vec3_hhh +%res4 = OpImageSampleDrefExplicitLod %s32 %simg %f32vec3_hhh %f32_1 ConstOffset %s32vec3_012 +%res5 = OpImageSampleDrefExplicitLod %s32 %simg %f32vec4_0000 %f32_1 Offset %s32vec3_012 +%res7 = OpImageSampleDrefExplicitLod %s32 %simg %f32vec3_hhh %f32_1 Grad|Offset %f32vec3_hhh %f32vec3_hhh %s32vec3_012 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, SampleDrefExplicitLodWrongResultType) { + const std::string body = R"( +%img = OpLoad %type_image_s32_3d_0001 %uniform_image_s32_3d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_s32_3d_0001 %img %sampler +%res1 = OpImageSampleDrefExplicitLod %bool %simg %f32vec3_hhh %s32_1 Lod %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type to be int or float scalar type: " + "ImageSampleDrefExplicitLod")); +} + +TEST_F(ValidateImage, SampleDrefExplicitLodNotSampledImage) { + const std::string body = R"( +%img = OpLoad %type_image_s32_3d_0001 %uniform_image_s32_3d_0001 +%res1 = OpImageSampleDrefExplicitLod %s32 %img %f32vec3_hhh %s32_1 Lod %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Sampled Image to be of type OpTypeSampledImage: " + "ImageSampleDrefExplicitLod")); +} + +TEST_F(ValidateImage, SampleDrefExplicitLodWrongSampledType) { + const std::string body = R"( +%img = OpLoad %type_image_s32_3d_0001 %uniform_image_s32_3d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_s32_3d_0001 %img %sampler +%res1 = OpImageSampleDrefExplicitLod %f32 %simg %f32vec3_hhh %s32_1 Lod %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Image 'Sampled Type' to be the same as Result Type: " + "ImageSampleDrefExplicitLod")); +} + +TEST_F(ValidateImage, SampleDrefExplicitLodVoidSampledType) { + const std::string body = R"( +%img = OpLoad %type_image_void_2d_0001 %uniform_image_void_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_void_2d_0001 %img %sampler +%res1 = OpImageSampleDrefExplicitLod %u32 %simg %f32vec2_00 %s32_1 Lod %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Image 'Sampled Type' to be the same as Result Type: " + "ImageSampleDrefExplicitLod")); +} + +TEST_F(ValidateImage, SampleDrefExplicitLodWrongCoordinateType) { + const std::string body = R"( +%img = OpLoad %type_image_s32_3d_0001 %uniform_image_s32_3d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_s32_3d_0001 %img %sampler +%res1 = OpImageSampleDrefExplicitLod %s32 %simg %img %s32_1 Lod %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Coordinate to be float scalar or vector: " + "ImageSampleDrefExplicitLod")); +} + +TEST_F(ValidateImage, SampleDrefExplicitLodCoordinateSizeTooSmall) { + const std::string body = R"( +%img = OpLoad %type_image_s32_3d_0001 %uniform_image_s32_3d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_s32_3d_0001 %img %sampler +%res1 = OpImageSampleDrefExplicitLod %s32 %simg %f32vec2_hh %s32_1 Lod %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Coordinate to have at least 3 components, " + "but given only 2: " + "ImageSampleDrefExplicitLod")); +} + +TEST_F(ValidateImage, SampleDrefExplicitLodWrongDrefType) { + const std::string body = R"( +%img = OpLoad %type_image_s32_3d_0001 %uniform_image_s32_3d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_s32_3d_0001 %img %sampler +%res1 = OpImageSampleDrefExplicitLod %s32 %simg %f32vec3_hhh %u32_1 Lod %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ImageSampleDrefExplicitLod: " + "Expected Dref to be of 32-bit float type")); +} + +TEST_F(ValidateImage, SampleProjDrefImplicitLodSuccess) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleProjDrefImplicitLod %f32 %simg %f32vec3_hhh %f32_0_5 +%res2 = OpImageSampleProjDrefImplicitLod %f32 %simg %f32vec3_hhh %f32_0_5 Bias %f32_0_25 +%res4 = OpImageSampleProjDrefImplicitLod %f32 %simg %f32vec3_hhh %f32_0_5 ConstOffset %s32vec2_01 +%res5 = OpImageSampleProjDrefImplicitLod %f32 %simg %f32vec3_hhh %f32_0_5 Offset %s32vec2_01 +%res6 = OpImageSampleProjDrefImplicitLod %f32 %simg %f32vec3_hhh %f32_0_5 MinLod %f32_0_5 +%res7 = OpImageSampleProjDrefImplicitLod %f32 %simg %f32vec3_hhh %f32_0_5 Bias|Offset|MinLod %f32_0_25 %s32vec2_01 %f32_0_5 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, SampleProjDrefImplicitLodWrongResultType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleProjDrefImplicitLod %void %simg %f32vec3_hhh %f32_0_5 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type to be int or float scalar type: " + "ImageSampleProjDrefImplicitLod")); +} + +TEST_F(ValidateImage, SampleProjDrefImplicitLodNotSampledImage) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%res1 = OpImageSampleProjDrefImplicitLod %f32 %img %f32vec3_hhh %f32_0_5 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Sampled Image to be of type OpTypeSampledImage: " + "ImageSampleProjDrefImplicitLod")); +} + +TEST_F(ValidateImage, SampleProjDrefImplicitLodWrongSampledType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleProjDrefImplicitLod %u32 %simg %f32vec3_hhh %f32_0_5 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Image 'Sampled Type' to be the same as Result Type: " + "ImageSampleProjDrefImplicitLod")); +} + +TEST_F(ValidateImage, SampleProjDrefImplicitLodVoidSampledType) { + const std::string body = R"( +%img = OpLoad %type_image_void_2d_0001 %uniform_image_void_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_void_2d_0001 %img %sampler +%res1 = OpImageSampleProjDrefImplicitLod %u32 %simg %f32vec3_hhh %f32_0_5 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Image 'Sampled Type' to be the same as Result Type: " + "ImageSampleProjDrefImplicitLod")); +} + +TEST_F(ValidateImage, SampleProjDrefImplicitLodWrongCoordinateType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleProjDrefImplicitLod %f32 %simg %img %f32_0_5 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Coordinate to be float scalar or vector: " + "ImageSampleProjDrefImplicitLod")); +} + +TEST_F(ValidateImage, SampleProjDrefImplicitLodCoordinateSizeTooSmall) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleProjDrefImplicitLod %f32 %simg %f32vec2_hh %f32_0_5 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Coordinate to have at least 3 components, " + "but given only 2: " + "ImageSampleProjDrefImplicitLod")); +} + +TEST_F(ValidateImage, SampleProjDrefImplicitLodWrongDrefType) { + const std::string body = R"( +%img = OpLoad %type_image_u32_2d_0001 %uniform_image_u32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_u32_2d_0001 %img %sampler +%res1 = OpImageSampleProjDrefImplicitLod %u32 %simg %f32vec3_hhh %f32vec4_0000 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ImageSampleProjDrefImplicitLod: " + "Expected Dref to be of 32-bit float type")); +} + +TEST_F(ValidateImage, SampleProjDrefExplicitLodSuccess) { + const std::string body = R"( +%img = OpLoad %type_image_f32_1d_0001 %uniform_image_f32_1d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_1d_0001 %img %sampler +%res1 = OpImageSampleProjDrefExplicitLod %f32 %simg %f32vec2_hh %f32_0_5 Lod %f32_1 +%res2 = OpImageSampleProjDrefExplicitLod %f32 %simg %f32vec3_hhh %f32_0_5 Grad %f32_0_5 %f32_0_5 +%res3 = OpImageSampleProjDrefExplicitLod %f32 %simg %f32vec2_hh %f32_0_5 ConstOffset %s32_1 +%res4 = OpImageSampleProjDrefExplicitLod %f32 %simg %f32vec2_hh %f32_0_5 Offset %s32_1 +%res5 = OpImageSampleProjDrefExplicitLod %f32 %simg %f32vec2_hh %f32_0_5 Grad|Offset %f32_0_5 %f32_0_5 %s32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, SampleProjDrefExplicitLodWrongResultType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_1d_0001 %uniform_image_f32_1d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_1d_0001 %img %sampler +%res1 = OpImageSampleProjDrefExplicitLod %bool %simg %f32vec2_hh %f32_0_5 Lod %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type to be int or float scalar type: " + "ImageSampleProjDrefExplicitLod")); +} + +TEST_F(ValidateImage, SampleProjDrefExplicitLodNotSampledImage) { + const std::string body = R"( +%img = OpLoad %type_image_f32_1d_0001 %uniform_image_f32_1d_0001 +%res1 = OpImageSampleProjDrefExplicitLod %f32 %img %f32vec2_hh %f32_0_5 Lod %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Sampled Image to be of type OpTypeSampledImage: " + "ImageSampleProjDrefExplicitLod")); +} + +TEST_F(ValidateImage, SampleProjDrefExplicitLodWrongSampledType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_1d_0001 %uniform_image_f32_1d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_1d_0001 %img %sampler +%res1 = OpImageSampleProjDrefExplicitLod %u32 %simg %f32vec2_hh %f32_0_5 Lod %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Image 'Sampled Type' to be the same as Result Type: " + "ImageSampleProjDrefExplicitLod")); +} + +TEST_F(ValidateImage, SampleProjDrefExplicitLodVoidSampledType) { + const std::string body = R"( +%img = OpLoad %type_image_void_2d_0001 %uniform_image_void_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_void_2d_0001 %img %sampler +%res1 = OpImageSampleProjDrefExplicitLod %u32 %simg %f32vec3_hhh %f32_0_5 Lod %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Image 'Sampled Type' to be the same as Result Type: " + "ImageSampleProjDrefExplicitLod")); +} + +TEST_F(ValidateImage, SampleProjDrefExplicitLodWrongCoordinateType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_1d_0001 %uniform_image_f32_1d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_1d_0001 %img %sampler +%res1 = OpImageSampleProjDrefExplicitLod %f32 %simg %img %f32_0_5 Lod %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Coordinate to be float scalar or vector: " + "ImageSampleProjDrefExplicitLod")); +} + +TEST_F(ValidateImage, SampleProjDrefExplicitLodCoordinateSizeTooSmall) { + const std::string body = R"( +%img = OpLoad %type_image_f32_1d_0001 %uniform_image_f32_1d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_1d_0001 %img %sampler +%res1 = OpImageSampleProjDrefExplicitLod %f32 %simg %f32_0_5 %f32_0_5 Lod %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Coordinate to have at least 2 components, " + "but given only 1: " + "ImageSampleProjDrefExplicitLod")); +} + +TEST_F(ValidateImage, FetchSuccess) { + const std::string body = R"( +%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001 +%res1 = OpImageFetch %f32vec4 %img %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, FetchWrongResultType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001 +%res1 = OpImageFetch %f32 %img %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type to be int or float vector type: " + "ImageFetch")); +} + +TEST_F(ValidateImage, FetchWrongNumComponentsResultType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001 +%res1 = OpImageFetch %f32vec3 %img %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Result Type to have 4 components: ImageFetch")); +} + +TEST_F(ValidateImage, FetchNotImage) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageFetch %f32vec4 %simg %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Image to be of type OpTypeImage: ImageFetch")); +} + +TEST_F(ValidateImage, FetchNotSampled) { + const std::string body = R"( +%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000 +%res1 = OpImageFetch %u32vec4 %img %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Image 'Sampled' parameter to be 1: ImageFetch")); +} + +TEST_F(ValidateImage, FetchCube) { + const std::string body = R"( +%img = OpLoad %type_image_f32_cube_0101 %uniform_image_f32_cube_0101 +%res1 = OpImageFetch %f32vec4 %img %u32vec3_012 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Image 'Dim' cannot be Cube: ImageFetch")); +} + +TEST_F(ValidateImage, FetchWrongSampledType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001 +%res1 = OpImageFetch %u32vec4 %img %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Image 'Sampled Type' to be the same as " + "Result Type components: " + "ImageFetch")); +} + +TEST_F(ValidateImage, FetchVoidSampledType) { + const std::string body = R"( +%img = OpLoad %type_image_void_2d_0001 %uniform_image_void_2d_0001 +%res1 = OpImageFetch %f32vec4 %img %u32vec2_01 +%res2 = OpImageFetch %u32vec4 %img %u32vec2_01 +%res3 = OpImageFetch %s32vec4 %img %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, FetchWrongCoordinateType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001 +%res1 = OpImageFetch %f32vec4 %img %f32vec2_00 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Coordinate to be int scalar or vector: " + "ImageFetch")); +} + +TEST_F(ValidateImage, FetchCoordinateSizeTooSmall) { + const std::string body = R"( +%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001 +%res1 = OpImageFetch %f32vec4 %img %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Coordinate to have at least 2 components, " + "but given only 1: " + "ImageFetch")); +} + +TEST_F(ValidateImage, FetchLodNotInt) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%res1 = OpImageFetch %f32vec4 %img %u32vec2_01 Lod %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Image Operand Lod to be int scalar when used " + "with OpImageFetch")); +} + +TEST_F(ValidateImage, GatherSuccess) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageGather %f32vec4 %simg %f32vec4_0000 %u32_1 +%res2 = OpImageGather %f32vec4 %simg %f32vec4_0000 %u32_1 ConstOffsets %const_offsets +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, GatherWrongResultType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_cube_0101 %uniform_image_f32_cube_0101 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_cube_0101 %img %sampler +%res1 = OpImageGather %f32 %simg %f32vec4_0000 %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type to be int or float vector type: " + "ImageGather")); +} + +TEST_F(ValidateImage, GatherWrongNumComponentsResultType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_cube_0101 %uniform_image_f32_cube_0101 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_cube_0101 %img %sampler +%res1 = OpImageGather %f32vec3 %simg %f32vec4_0000 %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type to have 4 components: " + "ImageGather")); +} + +TEST_F(ValidateImage, GatherNotSampledImage) { + const std::string body = R"( +%img = OpLoad %type_image_f32_cube_0101 %uniform_image_f32_cube_0101 +%res1 = OpImageGather %f32vec4 %img %f32vec4_0000 %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Sampled Image to be of type OpTypeSampledImage: " + "ImageGather")); +} + +TEST_F(ValidateImage, GatherWrongSampledType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_cube_0101 %uniform_image_f32_cube_0101 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_cube_0101 %img %sampler +%res1 = OpImageGather %u32vec4 %simg %f32vec4_0000 %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Image 'Sampled Type' to be the same as " + "Result Type components: " + "ImageGather")); +} + +TEST_F(ValidateImage, GatherVoidSampledType) { + const std::string body = R"( +%img = OpLoad %type_image_void_2d_0001 %uniform_image_void_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_void_2d_0001 %img %sampler +%res1 = OpImageGather %u32vec4 %simg %f32vec2_00 %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, GatherWrongCoordinateType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_cube_0101 %uniform_image_f32_cube_0101 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_cube_0101 %img %sampler +%res1 = OpImageGather %f32vec4 %simg %u32vec4_0123 %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Coordinate to be float scalar or vector: " + "ImageGather")); +} + +TEST_F(ValidateImage, GatherCoordinateSizeTooSmall) { + const std::string body = R"( +%img = OpLoad %type_image_f32_cube_0101 %uniform_image_f32_cube_0101 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_cube_0101 %img %sampler +%res1 = OpImageGather %f32vec4 %simg %f32_0_5 %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Coordinate to have at least 4 components, " + "but given only 1: " + "ImageGather")); +} + +TEST_F(ValidateImage, GatherWrongComponentType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_cube_0101 %uniform_image_f32_cube_0101 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_cube_0101 %img %sampler +%res1 = OpImageGather %f32vec4 %simg %f32vec4_0000 %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Component to be 32-bit int scalar: " + "ImageGather")); +} + +TEST_F(ValidateImage, GatherComponentNot32Bit) { + const std::string body = R"( +%img = OpLoad %type_image_f32_cube_0101 %uniform_image_f32_cube_0101 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_cube_0101 %img %sampler +%res1 = OpImageGather %f32vec4 %simg %f32vec4_0000 %u64_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Component to be 32-bit int scalar: " + "ImageGather")); +} + +TEST_F(ValidateImage, GatherDimCube) { + const std::string body = R"( +%img = OpLoad %type_image_f32_cube_0101 %uniform_image_f32_cube_0101 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_cube_0101 %img %sampler +%res1 = OpImageGather %f32vec4 %simg %f32vec4_0000 %u32_1 ConstOffsets %const_offsets +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Image Operand ConstOffsets cannot be used with Cube Image 'Dim': " + "ImageGather")); +} + +TEST_F(ValidateImage, GatherConstOffsetsNotArray) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageGather %f32vec4 %simg %f32vec4_0000 %u32_1 ConstOffsets %u32vec4_0123 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Image Operand ConstOffsets to be an array of size 4: " + "ImageGather")); +} + +TEST_F(ValidateImage, GatherConstOffsetsArrayWrongSize) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageGather %f32vec4 %simg %f32vec4_0000 %u32_1 ConstOffsets %const_offsets3x2 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Image Operand ConstOffsets to be an array of size 4: " + "ImageGather")); +} + +TEST_F(ValidateImage, GatherConstOffsetsArrayNotVector) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageGather %f32vec4 %simg %f32vec4_0000 %u32_1 ConstOffsets %const_offsets4xu +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Image Operand ConstOffsets array componenets " + "to be int vectors " + "of size 2: ImageGather")); +} + +TEST_F(ValidateImage, GatherConstOffsetsArrayVectorWrongSize) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageGather %f32vec4 %simg %f32vec4_0000 %u32_1 ConstOffsets %const_offsets4x3 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Image Operand ConstOffsets array componenets " + "to be int vectors " + "of size 2: ImageGather")); +} + +TEST_F(ValidateImage, GatherConstOffsetsArrayNotConst) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%offsets = OpUndef %u32vec2arr4 +%res1 = OpImageGather %f32vec4 %simg %f32vec4_0000 %u32_1 ConstOffsets %offsets +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Image Operand ConstOffsets to be a const object: " + "ImageGather")); +} + +TEST_F(ValidateImage, NotGatherWithConstOffsets) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res2 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec2_hh ConstOffsets %const_offsets +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Image Operand ConstOffsets can only be used with OpImageGather " + "and OpImageDrefGather: ImageSampleImplicitLod")); +} + +TEST_F(ValidateImage, DrefGatherSuccess) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageDrefGather %f32vec4 %simg %f32vec4_0000 %f32_0_5 +%res2 = OpImageDrefGather %f32vec4 %simg %f32vec4_0000 %f32_0_5 ConstOffsets %const_offsets +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, DrefGatherVoidSampledType) { + const std::string body = R"( +%img = OpLoad %type_image_void_2d_0001 %uniform_image_void_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_void_2d_0001 %img %sampler +%res1 = OpImageDrefGather %u32vec4 %simg %f32vec2_00 %f32_0_5 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Image 'Sampled Type' to be the same as " + "Result Type components: " + "ImageDrefGather")); +} + +TEST_F(ValidateImage, DrefGatherWrongDrefType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_cube_0101 %uniform_image_f32_cube_0101 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_cube_0101 %img %sampler +%res1 = OpImageDrefGather %f32vec4 %simg %f32vec4_0000 %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ImageDrefGather: " + "Expected Dref to be of 32-bit float type")); +} + +TEST_F(ValidateImage, ReadSuccess1) { + const std::string body = R"( +%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000 +%res1 = OpImageRead %u32vec4 %img %u32vec2_01 +)"; + + const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, ReadSuccess2) { + const std::string body = R"( +%img = OpLoad %type_image_f32_1d_0002_rgba32f %uniform_image_f32_1d_0002_rgba32f +%res1 = OpImageRead %f32vec4 %img %u32vec2_01 +)"; + + const std::string extra = "\nOpCapability Image1D\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, ReadSuccess3) { + const std::string body = R"( +%img = OpLoad %type_image_f32_cube_0102_rgba32f %uniform_image_f32_cube_0102_rgba32f +%res1 = OpImageRead %f32vec4 %img %u32vec3_012 +)"; + + const std::string extra = "\nOpCapability ImageCubeArray\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, ReadSuccess4) { + const std::string body = R"( +%img = OpLoad %type_image_f32_spd_0002 %uniform_image_f32_spd_0002 +%res1 = OpImageRead %f32vec4 %img %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, ReadNeedCapabilityStorageImageReadWithoutFormat) { + const std::string body = R"( +%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000 +%res1 = OpImageRead %u32vec4 %img %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Capability StorageImageReadWithoutFormat is required " + "to read storage " + "image: ImageRead")); +} + +TEST_F(ValidateImage, ReadNeedCapabilityImage1D) { + const std::string body = R"( +%img = OpLoad %type_image_f32_1d_0002_rgba32f %uniform_image_f32_1d_0002_rgba32f +%res1 = OpImageRead %f32vec4 %img %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Capability Image1D is required to access storage image: ImageRead")); +} + +TEST_F(ValidateImage, ReadNeedCapabilityImageCubeArray) { + const std::string body = R"( +%img = OpLoad %type_image_f32_cube_0102_rgba32f %uniform_image_f32_cube_0102_rgba32f +%res1 = OpImageRead %f32vec4 %img %u32vec3_012 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Capability ImageCubeArray is required to access storage image: " + "ImageRead")); +} + +// TODO(atgoo@github.com) Disabled until the spec is clarified. +TEST_F(ValidateImage, DISABLED_ReadWrongResultType) { + const std::string body = R"( +%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000 +%res1 = OpImageRead %f32 %img %u32vec2_01 +)"; + + const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected Result Type to be int or float vector type: ImageRead")); +} + +// TODO(atgoo@github.com) Disabled until the spec is clarified. +TEST_F(ValidateImage, DISABLED_ReadWrongNumComponentsResultType) { + const std::string body = R"( +%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000 +%res1 = OpImageRead %f32vec3 %img %u32vec2_01 +)"; + + const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Result Type to have 4 components: ImageRead")); +} + +TEST_F(ValidateImage, ReadNotImage) { + const std::string body = R"( +%sampler = OpLoad %type_sampler %uniform_sampler +%res1 = OpImageRead %f32vec4 %sampler %u32vec2_01 +)"; + + const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Image to be of type OpTypeImage: ImageRead")); +} + +TEST_F(ValidateImage, ReadImageSampled) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%res1 = OpImageRead %f32vec4 %img %u32vec2_01 +)"; + + const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Image 'Sampled' parameter to be 0 or 2: ImageRead")); +} + +TEST_F(ValidateImage, ReadWrongSampledType) { + const std::string body = R"( +%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000 +%res1 = OpImageRead %f32vec4 %img %u32vec2_01 +)"; + + const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Image 'Sampled Type' to be the same as " + "Result Type components: " + "ImageRead")); +} + +TEST_F(ValidateImage, ReadVoidSampledType) { + const std::string body = R"( +%img = OpLoad %type_image_void_2d_0002 %uniform_image_void_2d_0002 +%res1 = OpImageRead %f32vec4 %img %u32vec2_01 +%res2 = OpImageRead %u32vec4 %img %u32vec2_01 +%res3 = OpImageRead %s32vec4 %img %u32vec2_01 +)"; + + const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, ReadWrongCoordinateType) { + const std::string body = R"( +%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000 +%res1 = OpImageRead %u32vec4 %img %f32vec2_00 +)"; + + const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Coordinate to be int scalar or vector: ImageRead")); +} + +TEST_F(ValidateImage, ReadCoordinateSizeTooSmall) { + const std::string body = R"( +%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000 +%res1 = OpImageRead %u32vec4 %img %u32_1 +)"; + + const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Coordinate to have at least 2 components, " + "but given only 1: " + "ImageRead")); +} + +TEST_F(ValidateImage, WriteSuccess1) { + const std::string body = R"( +%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000 +%res1 = OpImageWrite %img %u32vec2_01 %u32vec4_0123 +)"; + + const std::string extra = "\nOpCapability StorageImageWriteWithoutFormat\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, WriteSuccess2) { + const std::string body = R"( +%img = OpLoad %type_image_f32_1d_0002_rgba32f %uniform_image_f32_1d_0002_rgba32f +%res1 = OpImageWrite %img %u32_1 %f32vec4_0000 +)"; + + const std::string extra = "\nOpCapability Image1D\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, WriteSuccess3) { + const std::string body = R"( +%img = OpLoad %type_image_f32_cube_0102_rgba32f %uniform_image_f32_cube_0102_rgba32f +%res1 = OpImageWrite %img %u32vec3_012 %f32vec4_0000 +)"; + + const std::string extra = "\nOpCapability ImageCubeArray\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, WriteSuccess4) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0010 %uniform_image_f32_2d_0010 +;TODO(atgoo@github.com) Is it legal to write to MS image without sample index? +%res1 = OpImageWrite %img %u32vec2_01 %f32vec4_0000 +%res2 = OpImageWrite %img %u32vec2_01 %f32vec4_0000 Sample %u32_1 +)"; + + const std::string extra = "\nOpCapability StorageImageWriteWithoutFormat\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, WriteSubpassData) { + const std::string body = R"( +%img = OpLoad %type_image_f32_spd_0002 %uniform_image_f32_spd_0002 +%res1 = OpImageWrite %img %u32vec2_01 %f32vec4_0000 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Image 'Dim' cannot be SubpassData: ImageWrite")); +} + +TEST_F(ValidateImage, WriteNeedCapabilityStorageImageWriteWithoutFormat) { + const std::string body = R"( +%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000 +%res1 = OpImageWrite %img %u32vec2_01 %u32vec4_0123 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Capability StorageImageWriteWithoutFormat is required to write to " + "storage image: ImageWrite")); +} + +TEST_F(ValidateImage, WriteNeedCapabilityImage1D) { + const std::string body = R"( +%img = OpLoad %type_image_f32_1d_0002_rgba32f %uniform_image_f32_1d_0002_rgba32f +%res1 = OpImageWrite %img %u32vec2_01 %f32vec4_0000 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Capability Image1D is required to access storage " + "image: ImageWrite")); +} + +TEST_F(ValidateImage, WriteNeedCapabilityImageCubeArray) { + const std::string body = R"( +%img = OpLoad %type_image_f32_cube_0102_rgba32f %uniform_image_f32_cube_0102_rgba32f +%res1 = OpImageWrite %img %u32vec3_012 %f32vec4_0000 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Capability ImageCubeArray is required to access storage image: " + "ImageWrite")); +} + +TEST_F(ValidateImage, WriteNotImage) { + const std::string body = R"( +%sampler = OpLoad %type_sampler %uniform_sampler +%res1 = OpImageWrite %sampler %u32vec2_01 %f32vec4_0000 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Image to be of type OpTypeImage: ImageWrite")); +} + +TEST_F(ValidateImage, WriteImageSampled) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%res1 = OpImageWrite %img %u32vec2_01 %f32vec4_0000 +)"; + + const std::string extra = "\nOpCapability StorageImageWriteWithoutFormat\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Image 'Sampled' parameter to be 0 or 2: ImageWrite")); +} + +TEST_F(ValidateImage, WriteWrongCoordinateType) { + const std::string body = R"( +%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000 +%res1 = OpImageWrite %img %f32vec2_00 %u32vec4_0123 +)"; + + const std::string extra = "\nOpCapability StorageImageWriteWithoutFormat\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Coordinate to be int scalar or vector: ImageWrite")); +} + +TEST_F(ValidateImage, WriteCoordinateSizeTooSmall) { + const std::string body = R"( +%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000 +%res1 = OpImageWrite %img %u32_1 %u32vec4_0123 +)"; + + const std::string extra = "\nOpCapability StorageImageWriteWithoutFormat\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Coordinate to have at least 2 components, " + "but given only 1: " + "ImageWrite")); +} + +TEST_F(ValidateImage, WriteTexelWrongType) { + const std::string body = R"( +%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000 +%res1 = OpImageWrite %img %u32vec2_01 %img +)"; + + const std::string extra = "\nOpCapability StorageImageWriteWithoutFormat\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected Texel to be int or float vector or scalar: ImageWrite")); +} + +TEST_F(ValidateImage, DISABLED_WriteTexelNotVector4) { + const std::string body = R"( +%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000 +%res1 = OpImageWrite %img %u32vec2_01 %u32vec3_012 +)"; + + const std::string extra = "\nOpCapability StorageImageWriteWithoutFormat\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Texel to have 4 components: ImageWrite")); +} + +TEST_F(ValidateImage, WriteTexelWrongComponentType) { + const std::string body = R"( +%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000 +%res1 = OpImageWrite %img %u32vec2_01 %f32vec4_0000 +)"; + + const std::string extra = "\nOpCapability StorageImageWriteWithoutFormat\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected Image 'Sampled Type' to be the same as Texel components: " + "ImageWrite")); +} + +TEST_F(ValidateImage, WriteSampleNotInteger) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0010 %uniform_image_f32_2d_0010 +%res1 = OpImageWrite %img %u32vec2_01 %f32vec4_0000 Sample %f32_1 +)"; + + const std::string extra = "\nOpCapability StorageImageWriteWithoutFormat\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Image Operand Sample to be int scalar: " + "ImageWrite")); +} + +TEST_F(ValidateImage, SampleNotMultisampled) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0002 %uniform_image_f32_2d_0002 +%res2 = OpImageWrite %img %u32vec2_01 %f32vec4_0000 Sample %u32_1 +)"; + + const std::string extra = "\nOpCapability StorageImageWriteWithoutFormat\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Image Operand Sample requires non-zero 'MS' parameter: ImageWrite")); +} + +TEST_F(ValidateImage, SampleWrongOpcode) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0010 %uniform_image_f32_2d_0010 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0010 %img %sampler +%res1 = OpImageSampleExplicitLod %f32vec4 %simg %f32vec2_00 Sample %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Image Operand Sample can only be used with " + "OpImageFetch, OpImageRead, OpImageWrite, " + "OpImageSparseFetch and OpImageSparseRead: " + "ImageSampleExplicitLod")); +} + +TEST_F(ValidateImage, SampleImageToImageSuccess) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%img2 = OpImage %type_image_f32_2d_0001 %simg +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, SampleImageToImageWrongResultType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%img2 = OpImage %type_sampled_image_f32_2d_0001 %simg +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type to be OpTypeImage: Image")); +} + +TEST_F(ValidateImage, SampleImageToImageNotSampledImage) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%img2 = OpImage %type_image_f32_2d_0001 %img +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected Sample Image to be of type OpTypeSampleImage: Image")); +} + +TEST_F(ValidateImage, SampleImageToImageNotTheSameImageType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%img2 = OpImage %type_image_f32_2d_0002 %simg +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Sample Image image type to be equal to " + "Result Type: Image")); +} + +TEST_F(ValidateImage, QueryFormatSuccess) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%res1 = OpImageQueryFormat %u32 %img +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, QueryFormatWrongResultType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%res1 = OpImageQueryFormat %bool %img +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected Result Type to be int scalar type: ImageQueryFormat")); +} + +TEST_F(ValidateImage, QueryFormatNotImage) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageQueryFormat %u32 %simg +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected operand to be of type OpTypeImage: ImageQueryFormat")); +} + +TEST_F(ValidateImage, QueryOrderSuccess) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%res1 = OpImageQueryOrder %u32 %img +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, QueryOrderWrongResultType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%res1 = OpImageQueryOrder %bool %img +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Result Type to be int scalar type: ImageQueryOrder")); +} + +TEST_F(ValidateImage, QueryOrderNotImage) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageQueryOrder %u32 %simg +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected operand to be of type OpTypeImage: ImageQueryOrder")); +} + +TEST_F(ValidateImage, QuerySizeLodSuccess) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%res1 = OpImageQuerySizeLod %u32vec2 %img %u32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, QuerySizeLodWrongResultType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%res1 = OpImageQuerySizeLod %f32vec2 %img %u32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type to be int scalar or vector type: " + "ImageQuerySizeLod")); +} + +TEST_F(ValidateImage, QuerySizeLodResultTypeWrongSize) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%res1 = OpImageQuerySizeLod %u32 %img %u32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Result Type has 1 components, but 2 expected: ImageQuerySizeLod")); +} + +TEST_F(ValidateImage, QuerySizeLodNotImage) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageQuerySizeLod %u32vec2 %simg %u32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Image to be of type OpTypeImage: ImageQuerySizeLod")); +} + +TEST_F(ValidateImage, QuerySizeLodWrongImageDim) { + const std::string body = R"( +%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001 +%res1 = OpImageQuerySizeLod %u32vec2 %img %u32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Image 'Dim' must be 1D, 2D, 3D or Cube: ImageQuerySizeLod")); +} + +TEST_F(ValidateImage, QuerySizeLodMultisampled) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0010 %uniform_image_f32_2d_0010 +%res1 = OpImageQuerySizeLod %u32vec2 %img %u32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Image 'MS' must be 0: ImageQuerySizeLod")); +} + +TEST_F(ValidateImage, QuerySizeLodWrongLodType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%res1 = OpImageQuerySizeLod %u32vec2 %img %f32_0 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Level of Detail to be int scalar: " + "ImageQuerySizeLod")); +} + +TEST_F(ValidateImage, QuerySizeSuccess) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%res1 = OpImageQuerySize %u32vec2 %img +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, QuerySizeWrongResultType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%res1 = OpImageQuerySize %f32vec2 %img +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type to be int scalar or vector type: " + "ImageQuerySize")); +} + +TEST_F(ValidateImage, QuerySizeNotImage) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageQuerySize %u32vec2 %simg +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Image to be of type OpTypeImage: ImageQuerySize")); +} + +// TODO(atgoo@github.com) Add more tests for OpQuerySize. + +TEST_F(ValidateImage, QueryLodSuccessKernel) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageQueryLod %f32vec2 %simg %f32vec2_hh +%res2 = OpImageQueryLod %f32vec2 %simg %u32vec2_01 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, QueryLodSuccessShader) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageQueryLod %f32vec2 %simg %f32vec2_hh +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, QueryLodWrongResultType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageQueryLod %u32vec2 %simg %f32vec2_hh +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Result Type to be float vector type: ImageQueryLod")); +} + +TEST_F(ValidateImage, QueryLodResultTypeWrongSize) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageQueryLod %f32vec3 %simg %f32vec2_hh +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Result Type to have 2 components: ImageQueryLod")); +} + +TEST_F(ValidateImage, QueryLodNotSampledImage) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%res1 = OpImageQueryLod %f32vec2 %img %f32vec2_hh +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Image operand to be of type OpTypeSampledImage: " + "ImageQueryLod")); +} + +TEST_F(ValidateImage, QueryLodWrongDim) { + const std::string body = R"( +%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_rect_0001 %img %sampler +%res1 = OpImageQueryLod %f32vec2 %simg %f32vec2_hh +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Image 'Dim' must be 1D, 2D, 3D or Cube: ImageQueryLod")); +} + +TEST_F(ValidateImage, QueryLodWrongCoordinateType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageQueryLod %f32vec2 %simg %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected Coordinate to be float scalar or vector: ImageQueryLod")); +} + +TEST_F(ValidateImage, QueryLodCoordinateSizeTooSmall) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageQueryLod %f32vec2 %simg %f32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Coordinate to have at least 2 components, " + "but given only 1: " + "ImageQueryLod")); +} + +TEST_F(ValidateImage, QueryLevelsSuccess) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%res1 = OpImageQueryLevels %u32 %img +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, QueryLevelsWrongResultType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%res1 = OpImageQueryLevels %f32 %img +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected Result Type to be int scalar type: ImageQueryLevels")); +} + +TEST_F(ValidateImage, QueryLevelsNotImage) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageQueryLevels %u32 %simg +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected Image to be of type OpTypeImage: ImageQueryLevels")); +} + +TEST_F(ValidateImage, QueryLevelsWrongDim) { + const std::string body = R"( +%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001 +%res1 = OpImageQueryLevels %u32 %img +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Image 'Dim' must be 1D, 2D, 3D or Cube: ImageQueryLevels")); +} + +TEST_F(ValidateImage, QuerySamplesSuccess) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0010 %uniform_image_f32_2d_0010 +%res1 = OpImageQuerySamples %u32 %img +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, QuerySamplesNot2D) { + const std::string body = R"( +%img = OpLoad %type_image_f32_3d_0010 %uniform_image_f32_3d_0010 +%res1 = OpImageQuerySamples %u32 %img +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Image 'Dim' must be 2D: ImageQuerySamples")); +} + +TEST_F(ValidateImage, QuerySamplesNotMultisampled) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%res1 = OpImageQuerySamples %u32 %img +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Image 'MS' must be 1: ImageQuerySamples")); +} + +TEST_F(ValidateImage, QueryLodWrongExecutionModel) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageQueryLod %f32vec2 %simg %f32vec2_hh +)"; + + CompileSuccessfully(GenerateShaderCode(body, "", "Vertex").c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpImageQueryLod requires Fragment execution model")); +} + +TEST_F(ValidateImage, QueryLodWrongExecutionModelWithFunc) { + const std::string body = R"( +%call_ret = OpFunctionCall %void %my_func +OpReturn +OpFunctionEnd +%my_func = OpFunction %void None %func +%my_func_entry = OpLabel +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageQueryLod %f32vec2 %simg %f32vec2_hh +)"; + + CompileSuccessfully(GenerateShaderCode(body, "", "Vertex").c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpImageQueryLod requires Fragment execution model")); +} + +TEST_F(ValidateImage, ImplicitLodWrongExecutionModel) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSampleImplicitLod %f32vec4 %simg %f32vec2_hh +)"; + + CompileSuccessfully(GenerateShaderCode(body, "", "Vertex").c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("ImplicitLod instructions require Fragment execution model")); +} + +TEST_F(ValidateImage, ReadSubpassDataWrongExecutionModel) { + const std::string body = R"( +%img = OpLoad %type_image_f32_spd_0002 %uniform_image_f32_spd_0002 +%res1 = OpImageRead %f32vec4 %img %u32vec2_01 +)"; + + const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n"; + CompileSuccessfully(GenerateShaderCode(body, extra, "Vertex").c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Dim SubpassData requires Fragment execution model: ImageRead")); +} + +TEST_F(ValidateImage, SparseSampleImplicitLodSuccess) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSparseSampleImplicitLod %struct_u32_f32vec4 %simg %f32vec2_hh +%res2 = OpImageSparseSampleImplicitLod %struct_u32_f32vec4 %simg %f32vec2_hh Bias %f32_0_25 +%res4 = OpImageSparseSampleImplicitLod %struct_u32_f32vec4 %simg %f32vec2_hh ConstOffset %s32vec2_01 +%res5 = OpImageSparseSampleImplicitLod %struct_u32_f32vec4 %simg %f32vec2_hh Offset %s32vec2_01 +%res6 = OpImageSparseSampleImplicitLod %struct_u32_f32vec4 %simg %f32vec2_hh MinLod %f32_0_5 +%res7 = OpImageSparseSampleImplicitLod %struct_u64_f32vec4 %simg %f32vec2_hh Bias|Offset|MinLod %f32_0_25 %s32vec2_01 %f32_0_5 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, SparseSampleImplicitLodResultTypeNotStruct) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSparseSampleImplicitLod %f32 %simg %f32vec2_hh +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ImageSparseSampleImplicitLod: " + "expected Result Type to be OpTypeStruct")); +} + +TEST_F(ValidateImage, SparseSampleImplicitLodResultTypeNotTwoMembers1) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSparseSampleImplicitLod %struct_u32 %simg %f32vec2_hh +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ImageSparseSampleImplicitLod: expected Result Type " + "to be a struct containing an int scalar and a texel")); +} + +TEST_F(ValidateImage, SparseSampleImplicitLodResultTypeNotTwoMembers2) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSparseSampleImplicitLod %struct_u32_f32vec4_u32 %simg %f32vec2_hh +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ImageSparseSampleImplicitLod: expected Result Type " + "to be a struct containing an int scalar and a texel")); +} + +TEST_F(ValidateImage, SparseSampleImplicitLodResultTypeFirstMemberNotInt) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSparseSampleImplicitLod %struct_f32_f32vec4 %simg %f32vec2_hh +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ImageSparseSampleImplicitLod: expected Result Type " + "to be a struct containing an int scalar and a texel")); +} + +TEST_F(ValidateImage, SparseSampleImplicitLodResultTypeTexelNotVector) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSparseSampleImplicitLod %struct_u32_u32 %simg %f32vec2_hh +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type's second member to be int or " + "float vector type: ImageSparseSampleImplicitLod")); +} + +TEST_F(ValidateImage, SparseSampleImplicitLodWrongNumComponentsTexel) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSparseSampleImplicitLod %struct_u32_f32vec3 %simg %f32vec2_hh +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type's second member to have 4 " + "components: ImageSparseSampleImplicitLod")); +} + +TEST_F(ValidateImage, SparseSampleImplicitLodWrongComponentTypeTexel) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSparseSampleImplicitLod %struct_u32_u32vec4 %simg %f32vec2_hh +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Image 'Sampled Type' to be the same as " + "Result Type's second member components: " + "ImageSparseSampleImplicitLod")); +} + +TEST_F(ValidateImage, SparseSampleDrefImplicitLodSuccess) { + const std::string body = R"( +%img = OpLoad %type_image_u32_2d_0001 %uniform_image_u32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_u32_2d_0001 %img %sampler +%res1 = OpImageSparseSampleDrefImplicitLod %struct_u32_u32 %simg %f32vec2_hh %f32_1 +%res2 = OpImageSparseSampleDrefImplicitLod %struct_u32_u32 %simg %f32vec2_hh %f32_1 Bias %f32_0_25 +%res4 = OpImageSparseSampleDrefImplicitLod %struct_u32_u32 %simg %f32vec2_hh %f32_1 ConstOffset %s32vec2_01 +%res5 = OpImageSparseSampleDrefImplicitLod %struct_u32_u32 %simg %f32vec2_hh %f32_1 Offset %s32vec2_01 +%res6 = OpImageSparseSampleDrefImplicitLod %struct_u32_u32 %simg %f32vec2_hh %f32_1 MinLod %f32_0_5 +%res7 = OpImageSparseSampleDrefImplicitLod %struct_u32_u32 %simg %f32vec2_hh %f32_1 Bias|Offset|MinLod %f32_0_25 %s32vec2_01 %f32_0_5 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, SparseSampleDrefImplicitLodResultTypeNotStruct) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSparseSampleDrefImplicitLod %f32 %simg %f32vec2_hh %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ImageSparseSampleDrefImplicitLod: " + "expected Result Type to be OpTypeStruct")); +} + +TEST_F(ValidateImage, SparseSampleDrefImplicitLodResultTypeNotTwoMembers1) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSparseSampleDrefImplicitLod %struct_u32 %simg %f32vec2_hh %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("ImageSparseSampleDrefImplicitLod: expected Result Type " + "to be a struct containing an int scalar and a texel")); +} + +TEST_F(ValidateImage, SparseSampleDrefImplicitLodResultTypeNotTwoMembers2) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSparseSampleDrefImplicitLod %struct_u32_f32_u32 %simg %f32vec2_hh %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("ImageSparseSampleDrefImplicitLod: expected Result Type " + "to be a struct containing an int scalar and a texel")); +} + +TEST_F(ValidateImage, SparseSampleDrefImplicitLodResultTypeFirstMemberNotInt) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSparseSampleDrefImplicitLod %struct_f32_f32 %simg %f32vec2_hh %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("ImageSparseSampleDrefImplicitLod: expected Result Type " + "to be a struct containing an int scalar and a texel")); +} + +TEST_F(ValidateImage, SparseSampleDrefImplicitLodDifferentSampledType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSparseSampleDrefImplicitLod %struct_u32_u32 %simg %f32vec2_hh %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Image 'Sampled Type' to be the same as " + "Result Type's second member: " + "ImageSparseSampleDrefImplicitLod")); +} + +TEST_F(ValidateImage, SparseFetchSuccess) { + const std::string body = R"( +%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001 +%res1 = OpImageSparseFetch %struct_u32_f32vec4 %img %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, SparseFetchResultTypeNotStruct) { + const std::string body = R"( +%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001 +%res1 = OpImageSparseFetch %f32 %img %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ImageSparseFetch: " + "expected Result Type to be OpTypeStruct")); +} + +TEST_F(ValidateImage, SparseFetchResultTypeNotTwoMembers1) { + const std::string body = R"( +%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001 +%res1 = OpImageSparseFetch %struct_u32 %img %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ImageSparseFetch: expected Result Type " + "to be a struct containing an int scalar and a texel")); +} + +TEST_F(ValidateImage, SparseFetchResultTypeNotTwoMembers2) { + const std::string body = R"( +%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001 +%res1 = OpImageSparseFetch %struct_u32_f32vec4_u32 %img %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ImageSparseFetch: expected Result Type " + "to be a struct containing an int scalar and a texel")); +} + +TEST_F(ValidateImage, SparseFetchResultTypeFirstMemberNotInt) { + const std::string body = R"( +%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001 +%res1 = OpImageSparseFetch %struct_f32_f32vec4 %img %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ImageSparseFetch: expected Result Type " + "to be a struct containing an int scalar and a texel")); +} + +TEST_F(ValidateImage, SparseFetchResultTypeTexelNotVector) { + const std::string body = R"( +%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001 +%res1 = OpImageSparseFetch %struct_u32_u32 %img %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type's second member to be int or " + "float vector type: ImageSparseFetch")); +} + +TEST_F(ValidateImage, SparseFetchWrongNumComponentsTexel) { + const std::string body = R"( +%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001 +%res1 = OpImageSparseFetch %struct_u32_f32vec3 %img %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type's second member to have 4 " + "components: ImageSparseFetch")); +} + +TEST_F(ValidateImage, SparseFetchWrongComponentTypeTexel) { + const std::string body = R"( +%img = OpLoad %type_image_f32_rect_0001 %uniform_image_f32_rect_0001 +%res1 = OpImageSparseFetch %struct_u32_u32vec4 %img %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Image 'Sampled Type' to be the same as " + "Result Type's second member components: " + "ImageSparseFetch")); +} + +TEST_F(ValidateImage, SparseReadSuccess) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0002 %uniform_image_f32_2d_0002 +%res1 = OpImageSparseRead %struct_u32_f32vec4 %img %u32vec2_01 +)"; + + const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, SparseReadResultTypeNotStruct) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0002 %uniform_image_f32_2d_0002 +%res1 = OpImageSparseRead %f32 %img %u32vec2_01 +)"; + + const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ImageSparseRead: " + "expected Result Type to be OpTypeStruct")); +} + +TEST_F(ValidateImage, SparseReadResultTypeNotTwoMembers1) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0002 %uniform_image_f32_2d_0002 +%res1 = OpImageSparseRead %struct_u32 %img %u32vec2_01 +)"; + + const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ImageSparseRead: expected Result Type " + "to be a struct containing an int scalar and a texel")); +} + +TEST_F(ValidateImage, SparseReadResultTypeNotTwoMembers2) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0002 %uniform_image_f32_2d_0002 +%res1 = OpImageSparseRead %struct_u32_f32vec4_u32 %img %u32vec2_01 +)"; + + const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ImageSparseRead: expected Result Type " + "to be a struct containing an int scalar and a texel")); +} + +TEST_F(ValidateImage, SparseReadResultTypeFirstMemberNotInt) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0002 %uniform_image_f32_2d_0002 +%res1 = OpImageSparseRead %struct_f32_f32vec4 %img %u32vec2_01 +)"; + + const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ImageSparseRead: expected Result Type " + "to be a struct containing an int scalar and a texel")); +} + +TEST_F(ValidateImage, SparseReadResultTypeTexelWrongType) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0002 %uniform_image_f32_2d_0002 +%res1 = OpImageSparseRead %struct_u32_u32arr4 %img %u32vec2_01 +)"; + + const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type's second member to be int or " + "float scalar or vector type: ImageSparseRead")); +} + +TEST_F(ValidateImage, SparseReadWrongComponentTypeTexel) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0002 %uniform_image_f32_2d_0002 +%res1 = OpImageSparseRead %struct_u32_u32vec4 %img %u32vec2_01 +)"; + + const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n"; + CompileSuccessfully(GenerateShaderCode(body, extra).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Image 'Sampled Type' to be the same as " + "Result Type's second member components: " + "ImageSparseRead")); +} + +TEST_F(ValidateImage, SparseReadSubpassDataNotAllowed) { + const std::string body = R"( +%img = OpLoad %type_image_f32_spd_0002 %uniform_image_f32_spd_0002 +%res1 = OpImageSparseRead %struct_u32_f32vec4 %img %u32vec2_01 +)"; + + const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n"; + CompileSuccessfully(GenerateShaderCode(body, extra, "Fragment").c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Image Dim SubpassData cannot be used with ImageSparseRead")); +} + +TEST_F(ValidateImage, SparseGatherSuccess) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSparseGather %struct_u32_f32vec4 %simg %f32vec4_0000 %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, SparseGatherResultTypeNotStruct) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSparseGather %f32 %simg %f32vec2_hh %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ImageSparseGather: " + "expected Result Type to be OpTypeStruct")); +} + +TEST_F(ValidateImage, SparseGatherResultTypeNotTwoMembers1) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSparseGather %struct_u32 %simg %f32vec2_hh %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ImageSparseGather: expected Result Type " + "to be a struct containing an int scalar and a texel")); +} + +TEST_F(ValidateImage, SparseGatherResultTypeNotTwoMembers2) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSparseGather %struct_u32_f32vec4_u32 %simg %f32vec2_hh %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ImageSparseGather: expected Result Type " + "to be a struct containing an int scalar and a texel")); +} + +TEST_F(ValidateImage, SparseGatherResultTypeFirstMemberNotInt) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSparseGather %struct_f32_f32vec4 %simg %f32vec2_hh %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ImageSparseGather: expected Result Type " + "to be a struct containing an int scalar and a texel")); +} + +TEST_F(ValidateImage, SparseGatherResultTypeTexelNotVector) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSparseGather %struct_u32_u32 %simg %f32vec2_hh %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type's second member to be int or " + "float vector type: ImageSparseGather")); +} + +TEST_F(ValidateImage, SparseGatherWrongNumComponentsTexel) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSparseGather %struct_u32_f32vec3 %simg %f32vec2_hh %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Result Type's second member to have 4 " + "components: ImageSparseGather")); +} + +TEST_F(ValidateImage, SparseGatherWrongComponentTypeTexel) { + const std::string body = R"( +%img = OpLoad %type_image_f32_2d_0001 %uniform_image_f32_2d_0001 +%sampler = OpLoad %type_sampler %uniform_sampler +%simg = OpSampledImage %type_sampled_image_f32_2d_0001 %img %sampler +%res1 = OpImageSparseGather %struct_u32_u32vec4 %simg %f32vec2_hh %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected Image 'Sampled Type' to be the same as " + "Result Type's second member components: " + "ImageSparseGather")); +} + +TEST_F(ValidateImage, SparseTexelsResidentSuccess) { + const std::string body = R"( +%res1 = OpImageSparseTexelsResident %bool %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateImage, SparseTexelsResidentResultTypeNotBool) { + const std::string body = R"( +%res1 = OpImageSparseTexelsResident %u32 %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ImageSparseTexelsResident: " + "expected Result Type to be bool scalar type")); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/val/val_layout_test.cpp b/3rdparty/spirv-tools/test/val/val_layout_test.cpp new file mode 100644 index 000000000..b91454ce5 --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_layout_test.cpp @@ -0,0 +1,613 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Validation tests for Logical Layout + +#include +#include +#include +#include + +#include "gmock/gmock.h" +#include "source/diagnostic.h" +#include "unit_spirv.h" +#include "val_fixtures.h" + +using std::function; +using std::ostream; +using std::ostream_iterator; +using std::pair; +using std::string; +using std::stringstream; +using std::tie; +using std::tuple; +using std::vector; + +using libspirv::spvResultToString; +using ::testing::Eq; +using ::testing::HasSubstr; +using ::testing::StrEq; + +using pred_type = function; +using ValidateLayout = + spvtest::ValidateBase>>; + +namespace { + +// returns true if order is equal to VAL +template +spv_result_t Equals(int order) { + return order == VAL ? SPV_SUCCESS : RET; +} + +// returns true if order is between MIN and MAX(inclusive) +template +struct Range { + explicit Range(bool inverse = false) : inverse_(inverse) {} + spv_result_t operator()(int order) { + return (inverse_ ^ (order >= MIN && order <= MAX)) ? SPV_SUCCESS : RET; + } + + private: + bool inverse_; +}; + +template +spv_result_t InvalidSet(int order) { + for (spv_result_t val : {T(true)(order)...}) + if (val != SPV_SUCCESS) return val; + return SPV_SUCCESS; +} + +// SPIRV source used to test the logical layout +const vector& getInstructions() { + // clang-format off + static const vector instructions = { + "OpCapability Shader", + "OpExtension \"TestExtension\"", + "%inst = OpExtInstImport \"GLSL.std.450\"", + "OpMemoryModel Logical GLSL450", + "OpEntryPoint GLCompute %func \"\"", + "OpExecutionMode %func LocalSize 1 1 1", + "%str = OpString \"Test String\"", + "%str2 = OpString \"blabla\"", + "OpSource GLSL 450 %str \"uniform vec3 var = vec3(4.0);\"", + "OpSourceContinued \"void main(){return;}\"", + "OpSourceExtension \"Test extension\"", + "OpName %func \"MyFunction\"", + "OpMemberName %struct 1 \"my_member\"", + "OpDecorate %dgrp RowMajor", + "OpMemberDecorate %struct 1 RowMajor", + "%dgrp = OpDecorationGroup", + "OpGroupDecorate %dgrp %mat33 %mat44", + "%intt = OpTypeInt 32 1", + "%floatt = OpTypeFloat 32", + "%voidt = OpTypeVoid", + "%boolt = OpTypeBool", + "%vec4 = OpTypeVector %floatt 4", + "%vec3 = OpTypeVector %floatt 3", + "%mat33 = OpTypeMatrix %vec3 3", + "%mat44 = OpTypeMatrix %vec4 4", + "%struct = OpTypeStruct %intt %mat33", + "%vfunct = OpTypeFunction %voidt", + "%viifunct = OpTypeFunction %voidt %intt %intt", + "%one = OpConstant %intt 1", + // TODO(umar): OpConstant fails because the type is not defined + // TODO(umar): OpGroupMemberDecorate + "OpLine %str 3 4", + "OpNoLine", + "%func = OpFunction %voidt None %vfunct", + "%l = OpLabel", + "OpReturn ; %func return", + "OpFunctionEnd ; %func end", + "%func2 = OpFunction %voidt None %viifunct", + "%funcp1 = OpFunctionParameter %intt", + "%funcp2 = OpFunctionParameter %intt", + "%fLabel = OpLabel", + "OpNop", + "OpReturn ; %func2 return", + "OpFunctionEnd" + }; + return instructions; +} + +static const int kRangeEnd = 1000; +pred_type All = Range<0, kRangeEnd>(); + +INSTANTIATE_TEST_CASE_P(InstructionsOrder, + ValidateLayout, + ::testing::Combine(::testing::Range((int)0, (int)getInstructions().size()), + // Note: Because of ID dependencies between instructions, some instructions + // are not free to be placed anywhere without triggering an non-layout + // validation error. Therefore, "Lines to compile" for some instructions + // are not "All" in the below. + // + // | Instruction | Line(s) valid | Lines to compile + ::testing::Values( make_tuple(string("OpCapability") , Equals<0> , Range<0, 2>()) + , make_tuple(string("OpExtension") , Equals<1> , All) + , make_tuple(string("OpExtInstImport") , Equals<2> , All) + , make_tuple(string("OpMemoryModel") , Equals<3> , Range<1, kRangeEnd>()) + , make_tuple(string("OpEntryPoint") , Equals<4> , All) + , make_tuple(string("OpExecutionMode") , Equals<5> , All) + , make_tuple(string("OpSource ") , Range<6, 10>() , Range<7, kRangeEnd>()) + , make_tuple(string("OpSourceContinued ") , Range<6, 10>() , All) + , make_tuple(string("OpSourceExtension ") , Range<6, 10>() , All) + , make_tuple(string("%str2 = OpString ") , Range<6, 10>() , All) + , make_tuple(string("OpName ") , Range<11, 12>() , All) + , make_tuple(string("OpMemberName ") , Range<11, 12>() , All) + , make_tuple(string("OpDecorate ") , Range<13, 16>() , All) + , make_tuple(string("OpMemberDecorate ") , Range<13, 16>() , All) + , make_tuple(string("OpGroupDecorate ") , Range<13, 16>() , Range<16, kRangeEnd>()) + , make_tuple(string("OpDecorationGroup") , Range<13, 16>() , Range<0, 15>()) + , make_tuple(string("OpTypeBool") , Range<17, 30>() , All) + , make_tuple(string("OpTypeVoid") , Range<17, 30>() , Range<0, 25>()) + , make_tuple(string("OpTypeFloat") , Range<17, 30>() , Range<0,20>()) + , make_tuple(string("OpTypeInt") , Range<17, 30>() , Range<0, 20>()) + , make_tuple(string("OpTypeVector %floatt 4") , Range<17, 30>() , Range<19, 23>()) + , make_tuple(string("OpTypeMatrix %vec4 4") , Range<17, 30>() , Range<22, kRangeEnd>()) + , make_tuple(string("OpTypeStruct") , Range<17, 30>() , Range<24, kRangeEnd>()) + , make_tuple(string("%vfunct = OpTypeFunction"), Range<17, 30>() , Range<20, 30>()) + , make_tuple(string("OpConstant") , Range<17, 30>() , Range<20, kRangeEnd>()) + , make_tuple(string("OpLine ") , Range<17, kRangeEnd>() , Range<7, kRangeEnd>()) + , make_tuple(string("OpNoLine") , Range<17, kRangeEnd>() , All) + , make_tuple(string("%fLabel = OpLabel") , Equals<38> , All) + , make_tuple(string("OpNop") , Equals<39> , Range<39,kRangeEnd>()) + , make_tuple(string("OpReturn ; %func2 return") , Equals<40> , All) + )),); +// clang-format on + +// Creates a new vector which removes the string if the substr is found in the +// instructions vector and reinserts it in the location specified by order. +// NOTE: This will not work correctly if there are two instances of substr in +// instructions +vector GenerateCode(string substr, int order) { + vector code(getInstructions().size()); + vector inst(1); + partition_copy(begin(getInstructions()), end(getInstructions()), begin(code), + begin(inst), [=](const string& str) { + return string::npos == str.find(substr); + }); + + code.insert(begin(code) + order, inst.front()); + return code; +} + +// This test will check the logical layout of a binary by removing each +// instruction in the pair of the INSTANTIATE_TEST_CASE_P call and moving it in +// the SPIRV source formed by combining the vector "instructions". +TEST_P(ValidateLayout, Layout) { + int order; + string instruction; + pred_type pred; + pred_type test_pred; // Predicate to determine if the test should be build + tuple testCase; + + tie(order, testCase) = GetParam(); + tie(instruction, pred, test_pred) = testCase; + + // Skip test which break the code generation + if (test_pred(order)) return; + + vector code = GenerateCode(instruction, order); + + stringstream ss; + copy(begin(code), end(code), ostream_iterator(ss, "\n")); + + // printf("code: \n%s\n", ss.str().c_str()); + CompileSuccessfully(ss.str()); + spv_result_t result; + // clang-format off + ASSERT_EQ(pred(order), result = ValidateInstructions()) + << "Actual: " << spvResultToString(result) + << "\nExpected: " << spvResultToString(pred(order)) + << "\nOrder: " << order + << "\nInstruction: " << instruction + << "\nCode: \n" << ss.str(); + // clang-format on +} + +TEST_F(ValidateLayout, MemoryModelMissing) { + string str = R"( + OpCapability Matrix + OpExtension "TestExtension" + %inst = OpExtInstImport "GLSL.std.450" + OpEntryPoint GLCompute %func "" + OpExecutionMode %func LocalSize 1 1 1 + )"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "EntryPoint cannot appear before the memory model instruction")); +} + +TEST_F(ValidateLayout, FunctionDefinitionBeforeDeclarationBad) { + char str[] = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpDecorate %var Restrict +%intt = OpTypeInt 32 1 +%voidt = OpTypeVoid +%vfunct = OpTypeFunction %voidt +%vifunct = OpTypeFunction %voidt %intt +%ptrt = OpTypePointer Function %intt +%func = OpFunction %voidt None %vfunct +%funcl = OpLabel + OpNop + OpReturn + OpFunctionEnd +%func2 = OpFunction %voidt None %vifunct ; must appear before definition +%func2p = OpFunctionParameter %intt + OpFunctionEnd +)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Function declarations must appear before function definitions.")); +} + +// TODO(umar): Passes but gives incorrect error message. Should be fixed after +// type checking +TEST_F(ValidateLayout, LabelBeforeFunctionParameterBad) { + char str[] = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpDecorate %var Restrict +%intt = OpTypeInt 32 1 +%voidt = OpTypeVoid +%vfunct = OpTypeFunction %voidt +%vifunct = OpTypeFunction %voidt %intt +%ptrt = OpTypePointer Function %intt +%func = OpFunction %voidt None %vifunct +%funcl = OpLabel ; Label appears before function parameter +%func2p = OpFunctionParameter %intt + OpNop + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Function parameters must only appear immediately " + "after the function definition")); +} + +TEST_F(ValidateLayout, FuncParameterNotImmediatlyAfterFuncBad) { + char str[] = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpDecorate %var Restrict +%intt = OpTypeInt 32 1 +%voidt = OpTypeVoid +%vfunct = OpTypeFunction %voidt +%vifunct = OpTypeFunction %voidt %intt +%ptrt = OpTypePointer Function %intt +%func = OpFunction %voidt None %vifunct +%funcl = OpLabel + OpNop + OpBranch %next +%func2p = OpFunctionParameter %intt ;FunctionParameter appears in a function but not immediately afterwards +%next = OpLabel + OpNop + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Function parameters must only appear immediately " + "after the function definition")); +} + +TEST_F(ValidateLayout, OpUndefCanAppearInTypeDeclarationSection) { + string str = R"( + OpCapability Kernel + OpCapability Linkage + OpMemoryModel Logical OpenCL +%voidt = OpTypeVoid +%uintt = OpTypeInt 32 0 +%funct = OpTypeFunction %voidt +%udef = OpUndef %uintt +%func = OpFunction %voidt None %funct +%entry = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateLayout, OpUndefCanAppearInBlock) { + string str = R"( + OpCapability Kernel + OpCapability Linkage + OpMemoryModel Logical OpenCL +%voidt = OpTypeVoid +%uintt = OpTypeInt 32 0 +%funct = OpTypeFunction %voidt +%func = OpFunction %voidt None %funct +%entry = OpLabel +%udef = OpUndef %uintt + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateLayout, MissingFunctionEndForFunctionWithBody) { + const auto s = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +%void = OpTypeVoid +%tf = OpTypeFunction %void +%f = OpFunction %void None %tf +%l = OpLabel +OpReturn +)"; + + CompileSuccessfully(s); + ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + StrEq("Missing OpFunctionEnd at end of module.")); +} + +TEST_F(ValidateLayout, MissingFunctionEndForFunctionPrototype) { + const auto s = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +%void = OpTypeVoid +%tf = OpTypeFunction %void +%f = OpFunction %void None %tf +)"; + + CompileSuccessfully(s); + ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + StrEq("Missing OpFunctionEnd at end of module.")); +} + +using ValidateOpFunctionParameter = spvtest::ValidateBase; + +TEST_F(ValidateOpFunctionParameter, OpLineBetweenParameters) { + const auto s = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +%foo_frag = OpString "foo.frag" +%i32 = OpTypeInt 32 1 +%tf = OpTypeFunction %i32 %i32 %i32 +%c = OpConstant %i32 123 +%f = OpFunction %i32 None %tf +OpLine %foo_frag 1 1 +%p1 = OpFunctionParameter %i32 +OpNoLine +%p2 = OpFunctionParameter %i32 +%l = OpLabel +OpReturnValue %c +OpFunctionEnd +)"; + CompileSuccessfully(s); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateOpFunctionParameter, TooManyParameters) { + const auto s = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +%i32 = OpTypeInt 32 1 +%tf = OpTypeFunction %i32 %i32 %i32 +%c = OpConstant %i32 123 +%f = OpFunction %i32 None %tf +%p1 = OpFunctionParameter %i32 +%p2 = OpFunctionParameter %i32 +%xp3 = OpFunctionParameter %i32 +%xp4 = OpFunctionParameter %i32 +%xp5 = OpFunctionParameter %i32 +%xp6 = OpFunctionParameter %i32 +%xp7 = OpFunctionParameter %i32 +%l = OpLabel +OpReturnValue %c +OpFunctionEnd +)"; + CompileSuccessfully(s); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); +} + +using ValidateEntryPoint = spvtest::ValidateBase; + +// Tests that not having OpEntryPoint causes an error. +TEST_F(ValidateEntryPoint, NoEntryPointBad) { + std::string spirv = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450)"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("No OpEntryPoint instruction was found. This is only " + "allowed if the Linkage capability is being used.")); +} + +// Invalid. A function may not be a target of both OpEntryPoint and +// OpFunctionCall. +TEST_F(ValidateEntryPoint, FunctionIsTargetOfEntryPointAndFunctionCallBad) { + std::string spirv = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %foo "foo" +%voidt = OpTypeVoid +%funct = OpTypeFunction %voidt +%foo = OpFunction %voidt None %funct +%entry = OpLabel +%recurse = OpFunctionCall %voidt %foo + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("A function (1) may not be targeted by both an OpEntryPoint " + "instruction and an OpFunctionCall instruction.")); +} + +// Valid. Module with a function but no entry point is valid when Linkage +// Capability is used. +TEST_F(ValidateEntryPoint, NoEntryPointWithLinkageCapGood) { + std::string spirv = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 +%voidt = OpTypeVoid +%funct = OpTypeFunction %voidt +%foo = OpFunction %voidt None %funct +%entry = OpLabel + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateLayout, ModuleProcessedInvalidIn10) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpName %void "void" + OpModuleProcessed "this is ok in 1.1 and later" + OpDecorate %void Volatile ; bogus, but makes the example short +%void = OpTypeVoid +)"; + + CompileSuccessfully(str, SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(SPV_ERROR_WRONG_VERSION, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_0)); + // In a 1.0 environment the version check fails. + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Invalid SPIR-V binary version 1.1 for target " + "environment SPIR-V 1.0.")); +} + +TEST_F(ValidateLayout, ModuleProcessedValidIn11) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpName %void "void" + OpModuleProcessed "this is ok in 1.1 and later" + OpDecorate %void Volatile ; bogus, but makes the example short +%void = OpTypeVoid +)"; + + CompileSuccessfully(str, SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); + EXPECT_THAT(getDiagnosticString(), Eq("")); +} + +TEST_F(ValidateLayout, ModuleProcessedBeforeLastNameIsTooEarly) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpModuleProcessed "this is too early" + OpName %void "void" +%void = OpTypeVoid +)"; + + CompileSuccessfully(str, SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); + // By the mechanics of the validator, we assume ModuleProcessed is in the + // right spot, but then that OpName is in the wrong spot. + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Name cannot appear in a function declaration")); +} + +TEST_F(ValidateLayout, ModuleProcessedInvalidAfterFirstAnnotation) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpDecorate %void Volatile ; this is bogus, but keeps the example short + OpModuleProcessed "this is too late" +%void = OpTypeVoid +)"; + + CompileSuccessfully(str, SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("ModuleProcessed cannot appear in a function declaration")); +} + +TEST_F(ValidateLayout, ModuleProcessedInvalidInFunctionBeforeLabel) { + char str[] = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %main "main" +%void = OpTypeVoid +%voidfn = OpTypeFunction %void +%main = OpFunction %void None %voidfn + OpModuleProcessed "this is too late, in function before label" +%entry = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str, SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("ModuleProcessed cannot appear in a function declaration")); +} + +TEST_F(ValidateLayout, ModuleProcessedInvalidInBasicBlock) { + char str[] = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %main "main" +%void = OpTypeVoid +%voidfn = OpTypeFunction %void +%main = OpFunction %void None %voidfn +%entry = OpLabel + OpModuleProcessed "this is too late, in basic block" + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str, SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("ModuleProcessed cannot appear in a function declaration")); +} + +// TODO(umar): Test optional instructions +} // namespace diff --git a/3rdparty/spirv-tools/test/val/val_limits_test.cpp b/3rdparty/spirv-tools/test/val/val_limits_test.cpp new file mode 100644 index 000000000..791ffa075 --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_limits_test.cpp @@ -0,0 +1,706 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Validation tests for Universal Limits. (Section 2.17 of the SPIR-V Spec) + +#include +#include +#include + +#include "gmock/gmock.h" +#include "unit_spirv.h" +#include "val_fixtures.h" + +namespace { + +using std::string; +using ::testing::HasSubstr; +using ::testing::MatchesRegex; + +using ValidateLimits = spvtest::ValidateBase; + +string header = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 +)"; + +TEST_F(ValidateLimits, IdLargerThanBoundBad) { + string str = header + R"( +; %i32 has ID 1 +%i32 = OpTypeInt 32 1 +%c = OpConstant %i32 100 + +; Fake an instruction with 64 as the result id. +; !64 = OpConstantNull %i32 +!0x3002e !1 !64 +)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Result '64' must be less than the ID bound '3'.")); +} + +TEST_F(ValidateLimits, IdEqualToBoundBad) { + string str = header + R"( +; %i32 has ID 1 +%i32 = OpTypeInt 32 1 +%c = OpConstant %i32 100 + +; Fake an instruction with 64 as the result id. +; !64 = OpConstantNull %i32 +!0x3002e !1 !64 +)"; + + CompileSuccessfully(str); + + // The largest ID used in this program is 64. Let's overwrite the ID bound in + // the header to be 64. This should result in an error because all IDs must + // satisfy: 0 < id < bound. + OverwriteAssembledBinary(3, 64); + + ASSERT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Result '64' must be less than the ID bound '64'.")); +} + +TEST_F(ValidateLimits, StructNumMembersGood) { + std::ostringstream spirv; + spirv << header << R"( +%1 = OpTypeInt 32 0 +%2 = OpTypeStruct)"; + for (int i = 0; i < 16383; ++i) { + spirv << " %1"; + } + CompileSuccessfully(spirv.str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateLimits, StructNumMembersExceededBad) { + std::ostringstream spirv; + spirv << header << R"( +%1 = OpTypeInt 32 0 +%2 = OpTypeStruct)"; + for (int i = 0; i < 16384; ++i) { + spirv << " %1"; + } + CompileSuccessfully(spirv.str()); + ASSERT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Number of OpTypeStruct members (16384) has exceeded " + "the limit (16383).")); +} + +TEST_F(ValidateLimits, CustomizedStructNumMembersGood) { + std::ostringstream spirv; + spirv << header << R"( +%1 = OpTypeInt 32 0 +%2 = OpTypeStruct)"; + for (int i = 0; i < 32000; ++i) { + spirv << " %1"; + } + spvValidatorOptionsSetUniversalLimit( + options_, spv_validator_limit_max_struct_members, 32000u); + CompileSuccessfully(spirv.str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateLimits, CustomizedStructNumMembersBad) { + std::ostringstream spirv; + spirv << header << R"( +%1 = OpTypeInt 32 0 +%2 = OpTypeStruct)"; + for (int i = 0; i < 32001; ++i) { + spirv << " %1"; + } + spvValidatorOptionsSetUniversalLimit( + options_, spv_validator_limit_max_struct_members, 32000u); + CompileSuccessfully(spirv.str()); + ASSERT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Number of OpTypeStruct members (32001) has exceeded " + "the limit (32000).")); +} + +// Valid: Switch statement has 16,383 branches. +TEST_F(ValidateLimits, SwitchNumBranchesGood) { + std::ostringstream spirv; + spirv << header << R"( +%1 = OpTypeVoid +%2 = OpTypeFunction %1 +%3 = OpTypeInt 32 0 +%4 = OpConstant %3 1234 +%5 = OpFunction %1 None %2 +%7 = OpLabel +%8 = OpIAdd %3 %4 %4 +%9 = OpSwitch %4 %10)"; + + // Now add the (literal, label) pairs + for (int i = 0; i < 16383; ++i) { + spirv << " 1 %10"; + } + + spirv << R"( +%10 = OpLabel +OpReturn +OpFunctionEnd + )"; + + CompileSuccessfully(spirv.str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid: Switch statement has 16,384 branches. +TEST_F(ValidateLimits, SwitchNumBranchesBad) { + std::ostringstream spirv; + spirv << header << R"( +%1 = OpTypeVoid +%2 = OpTypeFunction %1 +%3 = OpTypeInt 32 0 +%4 = OpConstant %3 1234 +%5 = OpFunction %1 None %2 +%7 = OpLabel +%8 = OpIAdd %3 %4 %4 +%9 = OpSwitch %4 %10)"; + + // Now add the (literal, label) pairs + for (int i = 0; i < 16384; ++i) { + spirv << " 1 %10"; + } + + spirv << R"( +%10 = OpLabel +OpReturn +OpFunctionEnd + )"; + + CompileSuccessfully(spirv.str()); + ASSERT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Number of (literal, label) pairs in OpSwitch (16384) " + "exceeds the limit (16383).")); +} + +// Valid: Switch statement has 10 branches (limit is 10) +TEST_F(ValidateLimits, CustomizedSwitchNumBranchesGood) { + std::ostringstream spirv; + spirv << header << R"( +%1 = OpTypeVoid +%2 = OpTypeFunction %1 +%3 = OpTypeInt 32 0 +%4 = OpConstant %3 1234 +%5 = OpFunction %1 None %2 +%7 = OpLabel +%8 = OpIAdd %3 %4 %4 +%9 = OpSwitch %4 %10)"; + + // Now add the (literal, label) pairs + for (int i = 0; i < 10; ++i) { + spirv << " 1 %10"; + } + + spirv << R"( +%10 = OpLabel +OpReturn +OpFunctionEnd + )"; + + spvValidatorOptionsSetUniversalLimit( + options_, spv_validator_limit_max_switch_branches, 10u); + CompileSuccessfully(spirv.str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid: Switch statement has 11 branches (limit is 10) +TEST_F(ValidateLimits, CustomizedSwitchNumBranchesBad) { + std::ostringstream spirv; + spirv << header << R"( +%1 = OpTypeVoid +%2 = OpTypeFunction %1 +%3 = OpTypeInt 32 0 +%4 = OpConstant %3 1234 +%5 = OpFunction %1 None %2 +%7 = OpLabel +%8 = OpIAdd %3 %4 %4 +%9 = OpSwitch %4 %10)"; + + // Now add the (literal, label) pairs + for (int i = 0; i < 11; ++i) { + spirv << " 1 %10"; + } + + spirv << R"( +%10 = OpLabel +OpReturn +OpFunctionEnd + )"; + + spvValidatorOptionsSetUniversalLimit( + options_, spv_validator_limit_max_switch_branches, 10u); + CompileSuccessfully(spirv.str()); + ASSERT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Number of (literal, label) pairs in OpSwitch (11) " + "exceeds the limit (10).")); +} + +// Valid: OpTypeFunction with 255 arguments. +TEST_F(ValidateLimits, OpTypeFunctionGood) { + int num_args = 255; + std::ostringstream spirv; + spirv << header << R"( +%1 = OpTypeInt 32 0 +%2 = OpTypeFunction %1)"; + // add parameters + for (int i = 0; i < num_args; ++i) { + spirv << " %1"; + } + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid: OpTypeFunction with 256 arguments. (limit is 255 according to the +// spec Universal Limits (2.17). +TEST_F(ValidateLimits, OpTypeFunctionBad) { + int num_args = 256; + std::ostringstream spirv; + spirv << header << R"( +%1 = OpTypeInt 32 0 +%2 = OpTypeFunction %1)"; + for (int i = 0; i < num_args; ++i) { + spirv << " %1"; + } + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpTypeFunction may not take more than 255 arguments. " + "OpTypeFunction '2' has 256 arguments.")); +} + +// Valid: OpTypeFunction with 100 arguments (Custom limit: 100) +TEST_F(ValidateLimits, CustomizedOpTypeFunctionGood) { + int num_args = 100; + std::ostringstream spirv; + spirv << header << R"( +%1 = OpTypeInt 32 0 +%2 = OpTypeFunction %1)"; + // add parameters + for (int i = 0; i < num_args; ++i) { + spirv << " %1"; + } + spvValidatorOptionsSetUniversalLimit( + options_, spv_validator_limit_max_function_args, 100u); + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid: OpTypeFunction with 101 arguments. (Custom limit: 100) +TEST_F(ValidateLimits, CustomizedOpTypeFunctionBad) { + int num_args = 101; + std::ostringstream spirv; + spirv << header << R"( +%1 = OpTypeInt 32 0 +%2 = OpTypeFunction %1)"; + for (int i = 0; i < num_args; ++i) { + spirv << " %1"; + } + spvValidatorOptionsSetUniversalLimit( + options_, spv_validator_limit_max_function_args, 100u); + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpTypeFunction may not take more than 100 arguments. " + "OpTypeFunction '2' has 101 arguments.")); +} + +// Valid: module has 65,535 global variables. +TEST_F(ValidateLimits, NumGlobalVarsGood) { + int num_globals = 65535; + std::ostringstream spirv; + spirv << header << R"( + %int = OpTypeInt 32 0 +%_ptr_int = OpTypePointer Input %int + )"; + + for (int i = 0; i < num_globals; ++i) { + spirv << "%var_" << i << " = OpVariable %_ptr_int Input\n"; + } + + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid: module has 65,536 global variables (limit is 65,535). +TEST_F(ValidateLimits, NumGlobalVarsBad) { + int num_globals = 65536; + std::ostringstream spirv; + spirv << header << R"( + %int = OpTypeInt 32 0 +%_ptr_int = OpTypePointer Input %int + )"; + + for (int i = 0; i < num_globals; ++i) { + spirv << "%var_" << i << " = OpVariable %_ptr_int Input\n"; + } + + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Number of Global Variables (Storage Class other than " + "'Function') exceeded the valid limit (65535).")); +} + +// Valid: module has 50 global variables (limit is 50) +TEST_F(ValidateLimits, CustomizedNumGlobalVarsGood) { + int num_globals = 50; + std::ostringstream spirv; + spirv << header << R"( + %int = OpTypeInt 32 0 +%_ptr_int = OpTypePointer Input %int + )"; + + for (int i = 0; i < num_globals; ++i) { + spirv << "%var_" << i << " = OpVariable %_ptr_int Input\n"; + } + + spvValidatorOptionsSetUniversalLimit( + options_, spv_validator_limit_max_global_variables, 50u); + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid: module has 51 global variables (limit is 50). +TEST_F(ValidateLimits, CustomizedNumGlobalVarsBad) { + int num_globals = 51; + std::ostringstream spirv; + spirv << header << R"( + %int = OpTypeInt 32 0 +%_ptr_int = OpTypePointer Input %int + )"; + + for (int i = 0; i < num_globals; ++i) { + spirv << "%var_" << i << " = OpVariable %_ptr_int Input\n"; + } + + spvValidatorOptionsSetUniversalLimit( + options_, spv_validator_limit_max_global_variables, 50u); + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Number of Global Variables (Storage Class other than " + "'Function') exceeded the valid limit (50).")); +} + +// Valid: module has 524,287 local variables. +TEST_F(ValidateLimits, NumLocalVarsGood) { + int num_locals = 524287; + std::ostringstream spirv; + spirv << header << R"( + %int = OpTypeInt 32 0 + %_ptr_int = OpTypePointer Function %int + %voidt = OpTypeVoid + %funct = OpTypeFunction %voidt + %main = OpFunction %voidt None %funct + %entry = OpLabel + )"; + + for (int i = 0; i < num_locals; ++i) { + spirv << "%var_" << i << " = OpVariable %_ptr_int Function\n"; + } + + spirv << R"( + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid: module has 524,288 local variables (limit is 524,287). +TEST_F(ValidateLimits, NumLocalVarsBad) { + int num_locals = 524288; + std::ostringstream spirv; + spirv << header << R"( + %int = OpTypeInt 32 0 + %_ptr_int = OpTypePointer Function %int + %voidt = OpTypeVoid + %funct = OpTypeFunction %voidt + %main = OpFunction %voidt None %funct + %entry = OpLabel + )"; + + for (int i = 0; i < num_locals; ++i) { + spirv << "%var_" << i << " = OpVariable %_ptr_int Function\n"; + } + + spirv << R"( + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Number of local variables ('Function' Storage Class) " + "exceeded the valid limit (524287).")); +} + +// Valid: module has 100 local variables (limit is 100). +TEST_F(ValidateLimits, CustomizedNumLocalVarsGood) { + int num_locals = 100; + std::ostringstream spirv; + spirv << header << R"( + %int = OpTypeInt 32 0 + %_ptr_int = OpTypePointer Function %int + %voidt = OpTypeVoid + %funct = OpTypeFunction %voidt + %main = OpFunction %voidt None %funct + %entry = OpLabel + )"; + + for (int i = 0; i < num_locals; ++i) { + spirv << "%var_" << i << " = OpVariable %_ptr_int Function\n"; + } + + spirv << R"( + OpReturn + OpFunctionEnd + )"; + + spvValidatorOptionsSetUniversalLimit( + options_, spv_validator_limit_max_local_variables, 100u); + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid: module has 101 local variables (limit is 100). +TEST_F(ValidateLimits, CustomizedNumLocalVarsBad) { + int num_locals = 101; + std::ostringstream spirv; + spirv << header << R"( + %int = OpTypeInt 32 0 + %_ptr_int = OpTypePointer Function %int + %voidt = OpTypeVoid + %funct = OpTypeFunction %voidt + %main = OpFunction %voidt None %funct + %entry = OpLabel + )"; + + for (int i = 0; i < num_locals; ++i) { + spirv << "%var_" << i << " = OpVariable %_ptr_int Function\n"; + } + + spirv << R"( + OpReturn + OpFunctionEnd + )"; + + spvValidatorOptionsSetUniversalLimit( + options_, spv_validator_limit_max_local_variables, 100u); + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Number of local variables ('Function' Storage Class) " + "exceeded the valid limit (100).")); +} + +// Valid: Structure nesting depth of 255. +TEST_F(ValidateLimits, StructNestingDepthGood) { + std::ostringstream spirv; + spirv << header << R"( + %int = OpTypeInt 32 0 + %s_depth_1 = OpTypeStruct %int + )"; + for (auto i = 2; i <= 255; ++i) { + spirv << "%s_depth_" << i << " = OpTypeStruct %int %s_depth_" << i - 1; + spirv << "\n"; + } + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid: Structure nesting depth of 256. +TEST_F(ValidateLimits, StructNestingDepthBad) { + std::ostringstream spirv; + spirv << header << R"( + %int = OpTypeInt 32 0 + %s_depth_1 = OpTypeStruct %int + )"; + for (auto i = 2; i <= 256; ++i) { + spirv << "%s_depth_" << i << " = OpTypeStruct %int %s_depth_" << i - 1; + spirv << "\n"; + } + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Structure Nesting Depth may not be larger than 255. Found 256.")); +} + +// Valid: Structure nesting depth of 100 (limit is 100). +TEST_F(ValidateLimits, CustomizedStructNestingDepthGood) { + std::ostringstream spirv; + spirv << header << R"( + %int = OpTypeInt 32 0 + %s_depth_1 = OpTypeStruct %int + )"; + for (auto i = 2; i <= 100; ++i) { + spirv << "%s_depth_" << i << " = OpTypeStruct %int %s_depth_" << i - 1; + spirv << "\n"; + } + spvValidatorOptionsSetUniversalLimit( + options_, spv_validator_limit_max_struct_depth, 100u); + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid: Structure nesting depth of 101 (limit is 100). +TEST_F(ValidateLimits, CustomizedStructNestingDepthBad) { + std::ostringstream spirv; + spirv << header << R"( + %int = OpTypeInt 32 0 + %s_depth_1 = OpTypeStruct %int + )"; + for (auto i = 2; i <= 101; ++i) { + spirv << "%s_depth_" << i << " = OpTypeStruct %int %s_depth_" << i - 1; + spirv << "\n"; + } + spvValidatorOptionsSetUniversalLimit( + options_, spv_validator_limit_max_struct_depth, 100u); + CompileSuccessfully(spirv.str()); + EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Structure Nesting Depth may not be larger than 100. Found 101.")); +} + +// clang-format off +// Generates an SPIRV program with the given control flow nesting depth +void GenerateSpirvProgramWithCfgNestingDepth(std::string& str, int depth) { + std::ostringstream spirv; + spirv << header << R"( + %void = OpTypeVoid + %3 = OpTypeFunction %void + %bool = OpTypeBool + %12 = OpConstantTrue %bool + %main = OpFunction %void None %3 + %5 = OpLabel + OpBranch %6 + %6 = OpLabel + OpLoopMerge %8 %9 None + OpBranch %10 + %10 = OpLabel + OpBranchConditional %12 %7 %8 + %7 = OpLabel + )"; + int first_id = 13; + int last_id = 14; + // We already have 1 level of nesting due to the Loop. + int num_if_conditions = depth-1; + int largest_index = first_id + 2*num_if_conditions - 2; + for (int i = first_id; i <= largest_index; i = i + 2) { + spirv << "OpSelectionMerge %" << i+1 << " None" << "\n"; + spirv << "OpBranchConditional %12 " << "%" << i << " %" << i+1 << "\n"; + spirv << "%" << i << " = OpLabel" << "\n"; + } + spirv << "OpBranch %9" << "\n"; + + for (int i = largest_index+1; i > last_id; i = i - 2) { + spirv << "%" << i << " = OpLabel" << "\n"; + spirv << "OpBranch %" << i-2 << "\n"; + } + spirv << "%" << last_id << " = OpLabel" << "\n"; + spirv << "OpBranch %9" << "\n"; + spirv << R"( + %9 = OpLabel + OpBranch %6 + %8 = OpLabel + OpReturn + OpFunctionEnd + )"; + str = spirv.str(); +} +// clang-format on + +// Valid: Control Flow Nesting depth is 1023. +TEST_F(ValidateLimits, ControlFlowDepthGood) { + std::string spirv; + GenerateSpirvProgramWithCfgNestingDepth(spirv, 1023); + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid: Control Flow Nesting depth is 1024. (limit is 1023). +TEST_F(ValidateLimits, ControlFlowDepthBad) { + std::string spirv; + GenerateSpirvProgramWithCfgNestingDepth(spirv, 1024); + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Maximum Control Flow nesting depth exceeded.")); +} + +// Valid: Control Flow Nesting depth is 10 (custom limit: 10). +TEST_F(ValidateLimits, CustomizedControlFlowDepthGood) { + std::string spirv; + GenerateSpirvProgramWithCfgNestingDepth(spirv, 10); + spvValidatorOptionsSetUniversalLimit( + options_, spv_validator_limit_max_control_flow_nesting_depth, 10u); + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// Invalid: Control Flow Nesting depth is 11. (custom limit: 10). +TEST_F(ValidateLimits, CustomizedControlFlowDepthBad) { + std::string spirv; + GenerateSpirvProgramWithCfgNestingDepth(spirv, 11); + spvValidatorOptionsSetUniversalLimit( + options_, spv_validator_limit_max_control_flow_nesting_depth, 10u); + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Maximum Control Flow nesting depth exceeded.")); +} + +// Valid. The purpose here is to test the CFG depth calculation code when a loop +// continue target is the loop iteself. It also exercises the case where a loop +// is unreachable. +TEST_F(ValidateLimits, ControlFlowNoEntryToLoopGood) { + string str = header + R"( + OpName %entry "entry" + OpName %loop "loop" + OpName %exit "exit" +%voidt = OpTypeVoid +%funct = OpTypeFunction %voidt +%main = OpFunction %voidt None %funct +%entry = OpLabel + OpBranch %exit +%loop = OpLabel + OpLoopMerge %loop %loop None + OpBranch %loop +%exit = OpLabel + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(str); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/val/val_literals_test.cpp b/3rdparty/spirv-tools/test/val/val_literals_test.cpp new file mode 100644 index 000000000..8a7981b43 --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_literals_test.cpp @@ -0,0 +1,138 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Validation tests for ilegal literals + +#include +#include + +#include "gmock/gmock.h" +#include "val_fixtures.h" + +using ::testing::HasSubstr; + +using ValidateLiterals = spvtest::ValidateBase; +using ValidateLiteralsShader = spvtest::ValidateBase; +using ValidateLiteralsKernel = spvtest::ValidateBase; + +namespace { + +std::string GenerateShaderCode() { + std::string str = R"( + OpCapability Shader + OpCapability Linkage + OpCapability Int16 + OpCapability Int64 + OpCapability Float16 + OpCapability Float64 + OpMemoryModel Logical GLSL450 +%int16 = OpTypeInt 16 1 +%uint16 = OpTypeInt 16 0 +%int32 = OpTypeInt 32 1 +%uint32 = OpTypeInt 32 0 +%int64 = OpTypeInt 64 1 +%uint64 = OpTypeInt 64 0 +%half = OpTypeFloat 16 +%float = OpTypeFloat 32 +%double = OpTypeFloat 64 +%10 = OpTypeVoid + )"; + return str; +} + +std::string GenerateKernelCode() { + std::string str = R"( + OpCapability Kernel + OpCapability Addresses + OpCapability Linkage + OpCapability Int8 + OpMemoryModel Physical64 OpenCL +%uint8 = OpTypeInt 8 0 + )"; + return str; +} + +TEST_F(ValidateLiterals, LiteralsShaderGood) { + std::string str = GenerateShaderCode() + R"( +%11 = OpConstant %int16 !0x00007FFF +%12 = OpConstant %int16 !0xFFFF8000 +%13 = OpConstant %int16 !0xFFFFABCD +%14 = OpConstant %uint16 !0x0000ABCD +%15 = OpConstant %int16 -32768 +%16 = OpConstant %uint16 65535 +%17 = OpConstant %int32 -2147483648 +%18 = OpConstant %uint32 4294967295 +%19 = OpConstant %int64 -9223372036854775808 +%20 = OpConstant %uint64 18446744073709551615 +%21 = OpConstant %half !0x0000FFFF +%22 = OpConstant %float !0xFFFFFFFF +%23 = OpConstant %double !0xFFFFFFFF !0xFFFFFFFF + )"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateLiteralsShader, LiteralsShaderBad) { + std::string str = GenerateShaderCode() + GetParam(); + std::string inst_id = "11"; + CompileSuccessfully(str); + EXPECT_EQ(SPV_ERROR_INVALID_VALUE, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("The high-order bits of a literal number in instruction " + + inst_id + + " must be 0 for a floating-point type, " + "or 0 for an integer type with Signedness of 0, " + "or sign extended when Signedness is 1")); +} + +INSTANTIATE_TEST_CASE_P( + LiteralsShaderCases, ValidateLiteralsShader, + ::testing::Values("%11 = OpConstant %int16 !0xFFFF0000", // Sign bit is 0 + "%11 = OpConstant %int16 !0x00008000", // Sign bit is 1 + "%11 = OpConstant %int16 !0xABCD8000", // Sign bit is 1 + "%11 = OpConstant %int16 !0xABCD0000", + "%11 = OpConstant %uint16 !0xABCD0000", + "%11 = OpConstant %half !0xABCD0000", + "%11 = OpConstant %half !0x00010000")); + +TEST_F(ValidateLiterals, LiteralsKernelGood) { + std::string str = GenerateKernelCode() + R"( +%4 = OpConstant %uint8 !0x000000AB +%6 = OpConstant %uint8 255 + )"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateLiteralsKernel, LiteralsKernelBad) { + std::string str = GenerateKernelCode() + GetParam(); + std::string inst_id = "2"; + CompileSuccessfully(str); + EXPECT_EQ(SPV_ERROR_INVALID_VALUE, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("The high-order bits of a literal number in instruction " + + inst_id + + " must be 0 for a floating-point type, " + "or 0 for an integer type with Signedness of 0, " + "or sign extended when Signedness is 1")); +} + +INSTANTIATE_TEST_CASE_P( + LiteralsKernelCases, ValidateLiteralsKernel, + ::testing::Values("%2 = OpConstant %uint8 !0xABCDEF00", + "%2 = OpConstant %uint8 !0xABCDEFFF")); + +} // namespace diff --git a/3rdparty/spirv-tools/test/val/val_logicals_test.cpp b/3rdparty/spirv-tools/test/val/val_logicals_test.cpp new file mode 100644 index 000000000..8464a216a --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_logicals_test.cpp @@ -0,0 +1,919 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Tests for unique type declaration rules validator. + +#include + +#include "gmock/gmock.h" +#include "unit_spirv.h" +#include "val_fixtures.h" + +namespace { + +using ::testing::HasSubstr; +using ::testing::Not; + +using ValidateLogicals = spvtest::ValidateBase; + +std::string GenerateShaderCode( + const std::string& body, + const std::string& capabilities_and_extensions = "") { + const std::string capabilities = + R"( +OpCapability Shader +OpCapability Int64 +OpCapability Float64)"; + + const std::string after_extension_before_body = + R"( +%ext_inst = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +%void = OpTypeVoid +%func = OpTypeFunction %void +%bool = OpTypeBool +%f32 = OpTypeFloat 32 +%u32 = OpTypeInt 32 0 +%s32 = OpTypeInt 32 1 +%f64 = OpTypeFloat 64 +%u64 = OpTypeInt 64 0 +%s64 = OpTypeInt 64 1 +%boolvec2 = OpTypeVector %bool 2 +%s32vec2 = OpTypeVector %s32 2 +%u32vec2 = OpTypeVector %u32 2 +%u64vec2 = OpTypeVector %u64 2 +%f32vec2 = OpTypeVector %f32 2 +%f64vec2 = OpTypeVector %f64 2 +%boolvec3 = OpTypeVector %bool 3 +%u32vec3 = OpTypeVector %u32 3 +%u64vec3 = OpTypeVector %u64 3 +%s32vec3 = OpTypeVector %s32 3 +%f32vec3 = OpTypeVector %f32 3 +%f64vec3 = OpTypeVector %f64 3 +%boolvec4 = OpTypeVector %bool 4 +%u32vec4 = OpTypeVector %u32 4 +%u64vec4 = OpTypeVector %u64 4 +%s32vec4 = OpTypeVector %s32 4 +%f32vec4 = OpTypeVector %f32 4 +%f64vec4 = OpTypeVector %f64 4 + +%f32_0 = OpConstant %f32 0 +%f32_1 = OpConstant %f32 1 +%f32_2 = OpConstant %f32 2 +%f32_3 = OpConstant %f32 3 +%f32_4 = OpConstant %f32 4 + +%s32_0 = OpConstant %s32 0 +%s32_1 = OpConstant %s32 1 +%s32_2 = OpConstant %s32 2 +%s32_3 = OpConstant %s32 3 +%s32_4 = OpConstant %s32 4 +%s32_m1 = OpConstant %s32 -1 + +%u32_0 = OpConstant %u32 0 +%u32_1 = OpConstant %u32 1 +%u32_2 = OpConstant %u32 2 +%u32_3 = OpConstant %u32 3 +%u32_4 = OpConstant %u32 4 + +%f64_0 = OpConstant %f64 0 +%f64_1 = OpConstant %f64 1 +%f64_2 = OpConstant %f64 2 +%f64_3 = OpConstant %f64 3 +%f64_4 = OpConstant %f64 4 + +%s64_0 = OpConstant %s64 0 +%s64_1 = OpConstant %s64 1 +%s64_2 = OpConstant %s64 2 +%s64_3 = OpConstant %s64 3 +%s64_4 = OpConstant %s64 4 +%s64_m1 = OpConstant %s64 -1 + +%u64_0 = OpConstant %u64 0 +%u64_1 = OpConstant %u64 1 +%u64_2 = OpConstant %u64 2 +%u64_3 = OpConstant %u64 3 +%u64_4 = OpConstant %u64 4 + +%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1 +%u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2 +%u32vec3_012 = OpConstantComposite %u32vec3 %u32_0 %u32_1 %u32_2 +%u32vec3_123 = OpConstantComposite %u32vec3 %u32_1 %u32_2 %u32_3 +%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3 +%u32vec4_1234 = OpConstantComposite %u32vec4 %u32_1 %u32_2 %u32_3 %u32_4 + +%s32vec2_01 = OpConstantComposite %s32vec2 %s32_0 %s32_1 +%s32vec2_12 = OpConstantComposite %s32vec2 %s32_1 %s32_2 +%s32vec3_012 = OpConstantComposite %s32vec3 %s32_0 %s32_1 %s32_2 +%s32vec3_123 = OpConstantComposite %s32vec3 %s32_1 %s32_2 %s32_3 +%s32vec4_0123 = OpConstantComposite %s32vec4 %s32_0 %s32_1 %s32_2 %s32_3 +%s32vec4_1234 = OpConstantComposite %s32vec4 %s32_1 %s32_2 %s32_3 %s32_4 + +%f32vec2_01 = OpConstantComposite %f32vec2 %f32_0 %f32_1 +%f32vec2_12 = OpConstantComposite %f32vec2 %f32_1 %f32_2 +%f32vec3_012 = OpConstantComposite %f32vec3 %f32_0 %f32_1 %f32_2 +%f32vec3_123 = OpConstantComposite %f32vec3 %f32_1 %f32_2 %f32_3 +%f32vec4_0123 = OpConstantComposite %f32vec4 %f32_0 %f32_1 %f32_2 %f32_3 +%f32vec4_1234 = OpConstantComposite %f32vec4 %f32_1 %f32_2 %f32_3 %f32_4 + +%f64vec2_01 = OpConstantComposite %f64vec2 %f64_0 %f64_1 +%f64vec2_12 = OpConstantComposite %f64vec2 %f64_1 %f64_2 +%f64vec3_012 = OpConstantComposite %f64vec3 %f64_0 %f64_1 %f64_2 +%f64vec3_123 = OpConstantComposite %f64vec3 %f64_1 %f64_2 %f64_3 +%f64vec4_0123 = OpConstantComposite %f64vec4 %f64_0 %f64_1 %f64_2 %f64_3 +%f64vec4_1234 = OpConstantComposite %f64vec4 %f64_1 %f64_2 %f64_3 %f64_4 + +%true = OpConstantTrue %bool +%false = OpConstantFalse %bool +%boolvec2_tf = OpConstantComposite %boolvec2 %true %false +%boolvec3_tft = OpConstantComposite %boolvec3 %true %false %true +%boolvec4_tftf = OpConstantComposite %boolvec4 %true %false %true %false + +%f32vec4ptr = OpTypePointer Function %f32vec4 + +%main = OpFunction %void None %func +%main_entry = OpLabel)"; + + const std::string after_body = + R"( +OpReturn +OpFunctionEnd)"; + + return capabilities + capabilities_and_extensions + + after_extension_before_body + body + after_body; +} + +std::string GenerateKernelCode( + const std::string& body, + const std::string& capabilities_and_extensions = "") { + const std::string capabilities = + R"( +OpCapability Addresses +OpCapability Kernel +OpCapability Linkage +OpCapability Int64 +OpCapability Float64)"; + + const std::string after_extension_before_body = + R"( +OpMemoryModel Physical32 OpenCL +%void = OpTypeVoid +%func = OpTypeFunction %void +%bool = OpTypeBool +%f32 = OpTypeFloat 32 +%u32 = OpTypeInt 32 0 +%f64 = OpTypeFloat 64 +%u64 = OpTypeInt 64 0 +%boolvec2 = OpTypeVector %bool 2 +%u32vec2 = OpTypeVector %u32 2 +%u64vec2 = OpTypeVector %u64 2 +%f32vec2 = OpTypeVector %f32 2 +%f64vec2 = OpTypeVector %f64 2 +%boolvec3 = OpTypeVector %bool 3 +%u32vec3 = OpTypeVector %u32 3 +%u64vec3 = OpTypeVector %u64 3 +%f32vec3 = OpTypeVector %f32 3 +%f64vec3 = OpTypeVector %f64 3 +%boolvec4 = OpTypeVector %bool 4 +%u32vec4 = OpTypeVector %u32 4 +%u64vec4 = OpTypeVector %u64 4 +%f32vec4 = OpTypeVector %f32 4 +%f64vec4 = OpTypeVector %f64 4 + +%f32_0 = OpConstant %f32 0 +%f32_1 = OpConstant %f32 1 +%f32_2 = OpConstant %f32 2 +%f32_3 = OpConstant %f32 3 +%f32_4 = OpConstant %f32 4 + +%u32_0 = OpConstant %u32 0 +%u32_1 = OpConstant %u32 1 +%u32_2 = OpConstant %u32 2 +%u32_3 = OpConstant %u32 3 +%u32_4 = OpConstant %u32 4 + +%f64_0 = OpConstant %f64 0 +%f64_1 = OpConstant %f64 1 +%f64_2 = OpConstant %f64 2 +%f64_3 = OpConstant %f64 3 +%f64_4 = OpConstant %f64 4 + +%u64_0 = OpConstant %u64 0 +%u64_1 = OpConstant %u64 1 +%u64_2 = OpConstant %u64 2 +%u64_3 = OpConstant %u64 3 +%u64_4 = OpConstant %u64 4 + +%u32vec2_01 = OpConstantComposite %u32vec2 %u32_0 %u32_1 +%u32vec2_12 = OpConstantComposite %u32vec2 %u32_1 %u32_2 +%u32vec3_012 = OpConstantComposite %u32vec3 %u32_0 %u32_1 %u32_2 +%u32vec3_123 = OpConstantComposite %u32vec3 %u32_1 %u32_2 %u32_3 +%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3 +%u32vec4_1234 = OpConstantComposite %u32vec4 %u32_1 %u32_2 %u32_3 %u32_4 + +%f32vec2_01 = OpConstantComposite %f32vec2 %f32_0 %f32_1 +%f32vec2_12 = OpConstantComposite %f32vec2 %f32_1 %f32_2 +%f32vec3_012 = OpConstantComposite %f32vec3 %f32_0 %f32_1 %f32_2 +%f32vec3_123 = OpConstantComposite %f32vec3 %f32_1 %f32_2 %f32_3 +%f32vec4_0123 = OpConstantComposite %f32vec4 %f32_0 %f32_1 %f32_2 %f32_3 +%f32vec4_1234 = OpConstantComposite %f32vec4 %f32_1 %f32_2 %f32_3 %f32_4 + +%f64vec2_01 = OpConstantComposite %f64vec2 %f64_0 %f64_1 +%f64vec2_12 = OpConstantComposite %f64vec2 %f64_1 %f64_2 +%f64vec3_012 = OpConstantComposite %f64vec3 %f64_0 %f64_1 %f64_2 +%f64vec3_123 = OpConstantComposite %f64vec3 %f64_1 %f64_2 %f64_3 +%f64vec4_0123 = OpConstantComposite %f64vec4 %f64_0 %f64_1 %f64_2 %f64_3 +%f64vec4_1234 = OpConstantComposite %f64vec4 %f64_1 %f64_2 %f64_3 %f64_4 + +%true = OpConstantTrue %bool +%false = OpConstantFalse %bool +%boolvec2_tf = OpConstantComposite %boolvec2 %true %false +%boolvec3_tft = OpConstantComposite %boolvec3 %true %false %true +%boolvec4_tftf = OpConstantComposite %boolvec4 %true %false %true %false + +%f32vec4ptr = OpTypePointer Function %f32vec4 + +%main = OpFunction %void None %func +%main_entry = OpLabel)"; + + const std::string after_body = + R"( +OpReturn +OpFunctionEnd)"; + + return capabilities + capabilities_and_extensions + + after_extension_before_body + body + after_body; +} + +TEST_F(ValidateLogicals, OpAnySuccess) { + const std::string body = R"( +%val1 = OpAny %bool %boolvec2_tf +%val2 = OpAny %bool %boolvec3_tft +%val3 = OpAny %bool %boolvec4_tftf +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateLogicals, OpAnyWrongTypeId) { + const std::string body = R"( +%val = OpAny %u32 %boolvec2_tf +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected bool scalar type as Result Type: Any")); +} + +TEST_F(ValidateLogicals, OpAnyWrongOperand) { + const std::string body = R"( +%val = OpAny %bool %u32vec3_123 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected operand to be vector bool: Any")); +} + +TEST_F(ValidateLogicals, OpIsNanSuccess) { + const std::string body = R"( +%val1 = OpIsNan %bool %f32_1 +%val2 = OpIsNan %bool %f64_0 +%val3 = OpIsNan %boolvec2 %f32vec2_12 +%val4 = OpIsNan %boolvec3 %f32vec3_123 +%val5 = OpIsNan %boolvec4 %f32vec4_1234 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateLogicals, OpIsNanWrongTypeId) { + const std::string body = R"( +%val1 = OpIsNan %u32 %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected bool scalar or vector type as Result Type: IsNan")); +} + +TEST_F(ValidateLogicals, OpIsNanOperandNotFloat) { + const std::string body = R"( +%val1 = OpIsNan %bool %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected operand to be scalar or vector float: IsNan")); +} + +TEST_F(ValidateLogicals, OpIsNanOperandWrongSize) { + const std::string body = R"( +%val1 = OpIsNan %bool %f32vec2_12 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected vector sizes of Result Type and the operand to be equal: " + "IsNan")); +} + +TEST_F(ValidateLogicals, OpLessOrGreaterSuccess) { + const std::string body = R"( +%val1 = OpLessOrGreater %bool %f32_0 %f32_1 +%val2 = OpLessOrGreater %bool %f64_0 %f64_0 +%val3 = OpLessOrGreater %boolvec2 %f32vec2_12 %f32vec2_12 +%val4 = OpLessOrGreater %boolvec3 %f32vec3_123 %f32vec3_123 +%val5 = OpLessOrGreater %boolvec4 %f32vec4_1234 %f32vec4_1234 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateLogicals, OpLessOrGreaterWrongTypeId) { + const std::string body = R"( +%val1 = OpLessOrGreater %u32 %f32_1 %f32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected bool scalar or vector type as Result Type: LessOrGreater")); +} + +TEST_F(ValidateLogicals, OpLessOrGreaterLeftOperandNotFloat) { + const std::string body = R"( +%val1 = OpLessOrGreater %bool %u32_1 %f32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected operands to be scalar or vector float: LessOrGreater")); +} + +TEST_F(ValidateLogicals, OpLessOrGreaterLeftOperandWrongSize) { + const std::string body = R"( +%val1 = OpLessOrGreater %bool %f32vec2_12 %f32_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected vector sizes of Result Type and the operands to be equal: " + "LessOrGreater")); +} + +TEST_F(ValidateLogicals, OpLessOrGreaterOperandsDifferentType) { + const std::string body = R"( +%val1 = OpLessOrGreater %bool %f32_1 %f64_1 +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected left and right operands to have the same type: " + "LessOrGreater")); +} + +TEST_F(ValidateLogicals, OpFOrdEqualSuccess) { + const std::string body = R"( +%val1 = OpFOrdEqual %bool %f32_0 %f32_1 +%val2 = OpFOrdEqual %bool %f64_0 %f64_0 +%val3 = OpFOrdEqual %boolvec2 %f32vec2_12 %f32vec2_12 +%val4 = OpFOrdEqual %boolvec3 %f32vec3_123 %f32vec3_123 +%val5 = OpFOrdEqual %boolvec4 %f32vec4_1234 %f32vec4_1234 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateLogicals, OpFOrdEqualWrongTypeId) { + const std::string body = R"( +%val1 = OpFOrdEqual %u32 %f32_1 %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected bool scalar or vector type as Result Type: FOrdEqual")); +} + +TEST_F(ValidateLogicals, OpFOrdEqualLeftOperandNotFloat) { + const std::string body = R"( +%val1 = OpFOrdEqual %bool %u32_1 %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected operands to be scalar or vector float: FOrdEqual")); +} + +TEST_F(ValidateLogicals, OpFOrdEqualLeftOperandWrongSize) { + const std::string body = R"( +%val1 = OpFOrdEqual %bool %f32vec2_12 %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected vector sizes of Result Type and the operands to be equal: " + "FOrdEqual")); +} + +TEST_F(ValidateLogicals, OpFOrdEqualOperandsDifferentType) { + const std::string body = R"( +%val1 = OpFOrdEqual %bool %f32_1 %f64_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected left and right operands to have the same type: " + "FOrdEqual")); +} + +TEST_F(ValidateLogicals, OpLogicalEqualSuccess) { + const std::string body = R"( +%val1 = OpLogicalEqual %bool %true %false +%val2 = OpLogicalEqual %boolvec2 %boolvec2_tf %boolvec2_tf +%val3 = OpLogicalEqual %boolvec3 %boolvec3_tft %boolvec3_tft +%val4 = OpLogicalEqual %boolvec4 %boolvec4_tftf %boolvec4_tftf +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateLogicals, OpLogicalEqualWrongTypeId) { + const std::string body = R"( +%val1 = OpLogicalEqual %u32 %true %false +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected bool scalar or vector type as Result Type: LogicalEqual")); +} + +TEST_F(ValidateLogicals, OpLogicalEqualWrongLeftOperand) { + const std::string body = R"( +%val1 = OpLogicalEqual %bool %boolvec2_tf %false +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected both operands to be of Result Type: LogicalEqual")); +} + +TEST_F(ValidateLogicals, OpLogicalEqualWrongRightOperand) { + const std::string body = R"( +%val1 = OpLogicalEqual %boolvec2 %boolvec2_tf %false +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected both operands to be of Result Type: LogicalEqual")); +} + +TEST_F(ValidateLogicals, OpLogicalNotSuccess) { + const std::string body = R"( +%val1 = OpLogicalNot %bool %true +%val2 = OpLogicalNot %boolvec2 %boolvec2_tf +%val3 = OpLogicalNot %boolvec3 %boolvec3_tft +%val4 = OpLogicalNot %boolvec4 %boolvec4_tftf +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateLogicals, OpLogicalNotWrongTypeId) { + const std::string body = R"( +%val1 = OpLogicalNot %u32 %true +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected bool scalar or vector type as Result Type: LogicalNot")); +} + +TEST_F(ValidateLogicals, OpLogicalNotWrongOperand) { + const std::string body = R"( +%val1 = OpLogicalNot %bool %boolvec2_tf +)"; + + CompileSuccessfully(GenerateKernelCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected operand to be of Result Type: LogicalNot")); +} + +TEST_F(ValidateLogicals, OpSelectSuccess) { + const std::string body = R"( +%val1 = OpSelect %u32 %true %u32_0 %u32_1 +%val2 = OpSelect %f32 %true %f32_0 %f32_1 +%val3 = OpSelect %f64 %true %f64_0 %f64_1 +%val4 = OpSelect %f32vec2 %boolvec2_tf %f32vec2_01 %f32vec2_12 +%val5 = OpSelect %f32vec4 %boolvec4_tftf %f32vec4_0123 %f32vec4_1234 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateLogicals, OpSelectWrongTypeId) { + const std::string body = R"( +%val1 = OpSelect %void %true %u32_0 %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected scalar or vector type as Result Type: Select")); +} + +TEST_F(ValidateLogicals, OpSelectPointerNoCapability) { + const std::string body = R"( +%x = OpVariable %f32vec4ptr Function +%y = OpVariable %f32vec4ptr Function +OpStore %x %f32vec4_0123 +OpStore %y %f32vec4_1234 +%val1 = OpSelect %f32vec4ptr %true %x %y +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Using pointers with OpSelect requires capability VariablePointers " + "or VariablePointersStorageBuffer")); +} + +TEST_F(ValidateLogicals, OpSelectPointerWithCapability1) { + const std::string body = R"( +%x = OpVariable %f32vec4ptr Function +%y = OpVariable %f32vec4ptr Function +OpStore %x %f32vec4_0123 +OpStore %y %f32vec4_1234 +%val1 = OpSelect %f32vec4ptr %true %x %y +)"; + + const std::string extra_cap_ext = R"( +OpCapability VariablePointers +OpExtension "SPV_KHR_variable_pointers" +)"; + + CompileSuccessfully(GenerateShaderCode(body, extra_cap_ext).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateLogicals, OpSelectPointerWithCapability2) { + const std::string body = R"( +%x = OpVariable %f32vec4ptr Function +%y = OpVariable %f32vec4ptr Function +OpStore %x %f32vec4_0123 +OpStore %y %f32vec4_1234 +%val1 = OpSelect %f32vec4ptr %true %x %y +)"; + + const std::string extra_cap_ext = R"( +OpCapability VariablePointersStorageBuffer +OpExtension "SPV_KHR_variable_pointers" +)"; + + CompileSuccessfully(GenerateShaderCode(body, extra_cap_ext).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateLogicals, OpSelectWrongCondition) { + const std::string body = R"( +%val1 = OpSelect %u32 %u32_1 %u32_0 %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected bool scalar or vector type as condition: Select")); +} + +TEST_F(ValidateLogicals, OpSelectWrongConditionDimension) { + const std::string body = R"( +%val1 = OpSelect %u32vec2 %true %u32vec2_01 %u32vec2_12 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected vector sizes of Result Type and the condition to be equal: " + "Select")); +} + +TEST_F(ValidateLogicals, OpSelectWrongLeftObject) { + const std::string body = R"( +%val1 = OpSelect %bool %true %u32vec2_01 %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected both objects to be of Result Type: Select")); +} + +TEST_F(ValidateLogicals, OpSelectWrongRightObject) { + const std::string body = R"( +%val1 = OpSelect %bool %true %u32_1 %u32vec2_01 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected both objects to be of Result Type: Select")); +} + +TEST_F(ValidateLogicals, OpIEqualSuccess) { + const std::string body = R"( +%val1 = OpIEqual %bool %u32_0 %s32_1 +%val2 = OpIEqual %bool %s64_0 %u64_0 +%val3 = OpIEqual %boolvec2 %s32vec2_12 %u32vec2_12 +%val4 = OpIEqual %boolvec3 %s32vec3_123 %u32vec3_123 +%val5 = OpIEqual %boolvec4 %s32vec4_1234 %u32vec4_1234 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateLogicals, OpIEqualWrongTypeId) { + const std::string body = R"( +%val1 = OpIEqual %u32 %s32_1 %s32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected bool scalar or vector type as Result Type: IEqual")); +} + +TEST_F(ValidateLogicals, OpIEqualLeftOperandNotInt) { + const std::string body = R"( +%val1 = OpIEqual %bool %f32_1 %s32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected operands to be scalar or vector int: IEqual")); +} + +TEST_F(ValidateLogicals, OpIEqualLeftOperandWrongSize) { + const std::string body = R"( +%val1 = OpIEqual %bool %s32vec2_12 %s32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected vector sizes of Result Type and the operands to be equal: " + "IEqual")); +} + +TEST_F(ValidateLogicals, OpIEqualRightOperandNotInt) { + const std::string body = R"( +%val1 = OpIEqual %bool %u32_1 %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected operands to be scalar or vector int: IEqual")); +} + +TEST_F(ValidateLogicals, OpIEqualDifferentBitWidth) { + const std::string body = R"( +%val1 = OpIEqual %bool %u32_1 %u64_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected both operands to have the same component bit " + "width: IEqual")); +} + +TEST_F(ValidateLogicals, OpUGreaterThanSuccess) { + const std::string body = R"( +%val1 = OpUGreaterThan %bool %u32_0 %u32_1 +%val2 = OpUGreaterThan %bool %s32_0 %u32_1 +%val3 = OpUGreaterThan %bool %u64_0 %u64_0 +%val4 = OpUGreaterThan %bool %u64_0 %s64_0 +%val5 = OpUGreaterThan %boolvec2 %u32vec2_12 %u32vec2_12 +%val6 = OpUGreaterThan %boolvec3 %s32vec3_123 %u32vec3_123 +%val7 = OpUGreaterThan %boolvec4 %u32vec4_1234 %u32vec4_1234 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateLogicals, OpUGreaterThanWrongTypeId) { + const std::string body = R"( +%val1 = OpUGreaterThan %u32 %u32_1 %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected bool scalar or vector type as Result Type: UGreaterThan")); +} + +TEST_F(ValidateLogicals, OpUGreaterThanLeftOperandNotInt) { + const std::string body = R"( +%val1 = OpUGreaterThan %bool %f32_1 %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected operands to be scalar or vector int: UGreaterThan")); +} + +TEST_F(ValidateLogicals, OpUGreaterThanLeftOperandWrongSize) { + const std::string body = R"( +%val1 = OpUGreaterThan %bool %u32vec2_12 %u32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected vector sizes of Result Type and the operands to be equal: " + "UGreaterThan")); +} + +TEST_F(ValidateLogicals, OpUGreaterThanRightOperandNotInt) { + const std::string body = R"( +%val1 = OpUGreaterThan %bool %u32_1 %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected operands to be scalar or vector int: UGreaterThan")); +} + +TEST_F(ValidateLogicals, OpUGreaterThanDifferentBitWidth) { + const std::string body = R"( +%val1 = OpUGreaterThan %bool %u32_1 %u64_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected both operands to have the same component bit width: " + "UGreaterThan")); +} + +TEST_F(ValidateLogicals, OpSGreaterThanSuccess) { + const std::string body = R"( +%val1 = OpSGreaterThan %bool %s32_0 %s32_1 +%val2 = OpSGreaterThan %bool %u32_0 %s32_1 +%val3 = OpSGreaterThan %bool %s64_0 %s64_0 +%val4 = OpSGreaterThan %bool %s64_0 %u64_0 +%val5 = OpSGreaterThan %boolvec2 %s32vec2_12 %s32vec2_12 +%val6 = OpSGreaterThan %boolvec3 %s32vec3_123 %u32vec3_123 +%val7 = OpSGreaterThan %boolvec4 %s32vec4_1234 %s32vec4_1234 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateLogicals, OpSGreaterThanWrongTypeId) { + const std::string body = R"( +%val1 = OpSGreaterThan %s32 %s32_1 %s32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected bool scalar or vector type as Result Type: SGreaterThan")); +} + +TEST_F(ValidateLogicals, OpSGreaterThanLeftOperandNotInt) { + const std::string body = R"( +%val1 = OpSGreaterThan %bool %f32_1 %s32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected operands to be scalar or vector int: SGreaterThan")); +} + +TEST_F(ValidateLogicals, OpSGreaterThanLeftOperandWrongSize) { + const std::string body = R"( +%val1 = OpSGreaterThan %bool %s32vec2_12 %s32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Expected vector sizes of Result Type and the operands to be equal: " + "SGreaterThan")); +} + +TEST_F(ValidateLogicals, OpSGreaterThanRightOperandNotInt) { + const std::string body = R"( +%val1 = OpSGreaterThan %bool %s32_1 %f32_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Expected operands to be scalar or vector int: SGreaterThan")); +} + +TEST_F(ValidateLogicals, OpSGreaterThanDifferentBitWidth) { + const std::string body = R"( +%val1 = OpSGreaterThan %bool %s32_1 %s64_1 +)"; + + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected both operands to have the same component bit " + "width: SGreaterThan")); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/val/val_primitives_test.cpp b/3rdparty/spirv-tools/test/val/val_primitives_test.cpp new file mode 100644 index 000000000..5ef4e63ef --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_primitives_test.cpp @@ -0,0 +1,313 @@ +// Copyright (c) 2017 LunarG Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "gmock/gmock.h" +#include "unit_spirv.h" +#include "val_fixtures.h" + +namespace { + +using ::testing::HasSubstr; +using ::testing::Not; + +using ValidatePrimitives = spvtest::ValidateBase; + +std::string GenerateShaderCode( + const std::string& body, + const std::string& capabilities_and_extensions = + "OpCapability GeometryStreams", + const std::string& execution_model = "Geometry") { + std::ostringstream ss; + ss << capabilities_and_extensions << "\n"; + ss << "OpMemoryModel Logical GLSL450\n"; + ss << "OpEntryPoint " << execution_model << " %main \"main\"\n"; + + ss << R"( +%void = OpTypeVoid +%func = OpTypeFunction %void +%f32 = OpTypeFloat 32 +%u32 = OpTypeInt 32 0 +%u32vec4 = OpTypeVector %u32 4 + +%f32_0 = OpConstant %f32 0 +%u32_0 = OpConstant %u32 0 +%u32_1 = OpConstant %u32 1 +%u32_2 = OpConstant %u32 2 +%u32_3 = OpConstant %u32 3 +%u32vec4_0123 = OpConstantComposite %u32vec4 %u32_0 %u32_1 %u32_2 %u32_3 + +%main = OpFunction %void None %func +%main_entry = OpLabel +)"; + + ss << body; + + ss << R"( +OpReturn +OpFunctionEnd)"; + + return ss.str(); +} + +// Returns SPIR-V assembly fragment representing a function call, +// the end of the callee body, and the preamble and body of the called +// function with the given body, but missing the final return and +// function-end. The result is of the form where it can be used in the +// |body| argument to GenerateShaderCode. +std::string CallAndCallee(const std::string& body) { + std::ostringstream ss; + ss << R"( +%dummy = OpFunctionCall %void %foo +OpReturn +OpFunctionEnd + +%foo = OpFunction %void None %func +%foo_entry = OpLabel +)"; + + ss << body; + + return ss.str(); +} + +// OpEmitVertex doesn't have any parameters, so other validation +// is handled by the binary parser, and generic dominance checks. +TEST_F(ValidatePrimitives, EmitVertexSuccess) { + CompileSuccessfully( + GenerateShaderCode("OpEmitVertex", "OpCapability Geometry")); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidatePrimitives, EmitVertexFailMissingCapability) { + CompileSuccessfully( + GenerateShaderCode("OpEmitVertex", "OpCapability Shader", "Vertex")); + EXPECT_EQ(SPV_ERROR_INVALID_CAPABILITY, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Opcode EmitVertex requires one of these capabilities: Geometry")); +} + +TEST_F(ValidatePrimitives, EmitVertexFailWrongExecutionMode) { + CompileSuccessfully( + GenerateShaderCode("OpEmitVertex", "OpCapability Geometry", "Vertex")); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("EmitVertex instructions require Geometry execution model")); +} + +TEST_F(ValidatePrimitives, EmitVertexFailWrongExecutionModeNestedFunction) { + CompileSuccessfully(GenerateShaderCode(CallAndCallee("OpEmitVertex"), + "OpCapability Geometry", "Vertex")); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("EmitVertex instructions require Geometry execution model")); +} + +// OpEndPrimitive doesn't have any parameters, so other validation +// is handled by the binary parser, and generic dominance checks. +TEST_F(ValidatePrimitives, EndPrimitiveSuccess) { + CompileSuccessfully( + GenerateShaderCode("OpEndPrimitive", "OpCapability Geometry")); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidatePrimitives, EndPrimitiveFailMissingCapability) { + CompileSuccessfully( + GenerateShaderCode("OpEndPrimitive", "OpCapability Shader", "Vertex")); + EXPECT_EQ(SPV_ERROR_INVALID_CAPABILITY, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Opcode EndPrimitive requires one of these capabilities: Geometry")); +} + +TEST_F(ValidatePrimitives, EndPrimitiveFailWrongExecutionMode) { + CompileSuccessfully( + GenerateShaderCode("OpEndPrimitive", "OpCapability Geometry", "Vertex")); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("EndPrimitive instructions require Geometry execution model")); +} + +TEST_F(ValidatePrimitives, EndPrimitiveFailWrongExecutionModeNestedFunction) { + CompileSuccessfully(GenerateShaderCode(CallAndCallee("OpEndPrimitive"), + "OpCapability Geometry", "Vertex")); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("EndPrimitive instructions require Geometry execution model")); +} + +TEST_F(ValidatePrimitives, EmitStreamVertexSuccess) { + const std::string body = R"( +OpEmitStreamVertex %u32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidatePrimitives, EmitStreamVertexFailMissingCapability) { + CompileSuccessfully(GenerateShaderCode("OpEmitStreamVertex %u32_0", + "OpCapability Shader", "Vertex")); + EXPECT_EQ(SPV_ERROR_INVALID_CAPABILITY, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Opcode EmitStreamVertex requires one of these " + "capabilities: GeometryStreams")); +} + +TEST_F(ValidatePrimitives, EmitStreamVertexFailWrongExecutionMode) { + CompileSuccessfully(GenerateShaderCode( + "OpEmitStreamVertex %u32_0", "OpCapability GeometryStreams", "Vertex")); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "EmitStreamVertex instructions require Geometry execution model")); +} + +TEST_F(ValidatePrimitives, + EmitStreamVertexFailWrongExecutionModeNestedFunction) { + CompileSuccessfully( + GenerateShaderCode(CallAndCallee("OpEmitStreamVertex %u32_0"), + "OpCapability GeometryStreams", "Vertex")); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "EmitStreamVertex instructions require Geometry execution model")); +} + +TEST_F(ValidatePrimitives, EmitStreamVertexNonInt) { + const std::string body = R"( +OpEmitStreamVertex %f32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("EmitStreamVertex: " + "expected Stream to be int scalar")); +} + +TEST_F(ValidatePrimitives, EmitStreamVertexNonScalar) { + const std::string body = R"( +OpEmitStreamVertex %u32vec4_0123 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("EmitStreamVertex: " + "expected Stream to be int scalar")); +} + +TEST_F(ValidatePrimitives, EmitStreamVertexNonConstant) { + const std::string body = R"( +%val1 = OpIAdd %u32 %u32_0 %u32_1 +OpEmitStreamVertex %val1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("EmitStreamVertex: " + "expected Stream to be constant instruction")); +} + +TEST_F(ValidatePrimitives, EndStreamPrimitiveSuccess) { + const std::string body = R"( +OpEndStreamPrimitive %u32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidatePrimitives, EndStreamPrimitiveFailMissingCapability) { + CompileSuccessfully(GenerateShaderCode("OpEndStreamPrimitive %u32_0", + "OpCapability Shader", "Vertex")); + EXPECT_EQ(SPV_ERROR_INVALID_CAPABILITY, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Opcode EndStreamPrimitive requires one of these " + "capabilities: GeometryStreams")); +} + +TEST_F(ValidatePrimitives, EndStreamPrimitiveFailWrongExecutionMode) { + CompileSuccessfully(GenerateShaderCode( + "OpEndStreamPrimitive %u32_0", "OpCapability GeometryStreams", "Vertex")); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "EndStreamPrimitive instructions require Geometry execution model")); +} + +TEST_F(ValidatePrimitives, + EndStreamPrimitiveFailWrongExecutionModeNestedFunction) { + CompileSuccessfully( + GenerateShaderCode(CallAndCallee("OpEndStreamPrimitive %u32_0"), + "OpCapability GeometryStreams", "Vertex")); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "EndStreamPrimitive instructions require Geometry execution model")); +} + +TEST_F(ValidatePrimitives, EndStreamPrimitiveNonInt) { + const std::string body = R"( +OpEndStreamPrimitive %f32_0 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("EndStreamPrimitive: " + "expected Stream to be int scalar")); +} + +TEST_F(ValidatePrimitives, EndStreamPrimitiveNonScalar) { + const std::string body = R"( +OpEndStreamPrimitive %u32vec4_0123 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("EndStreamPrimitive: " + "expected Stream to be int scalar")); +} + +TEST_F(ValidatePrimitives, EndStreamPrimitiveNonConstant) { + const std::string body = R"( +%val1 = OpIAdd %u32 %u32_0 %u32_1 +OpEndStreamPrimitive %val1 +)"; + + CompileSuccessfully(GenerateShaderCode(body)); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("EndStreamPrimitive: " + "expected Stream to be constant instruction")); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/val/val_ssa_test.cpp b/3rdparty/spirv-tools/test/val/val_ssa_test.cpp new file mode 100644 index 000000000..f6a712fa2 --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_ssa_test.cpp @@ -0,0 +1,1435 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Validation tests for SSA + +#include +#include +#include + +#include "gmock/gmock.h" +#include "unit_spirv.h" +#include "val_fixtures.h" + +using ::testing::HasSubstr; +using ::testing::MatchesRegex; + +using std::pair; +using std::string; +using std::stringstream; + +namespace { +using ValidateSSA = spvtest::ValidateBase>; + +TEST_F(ValidateSSA, Default) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %3 "" + OpExecutionMode %3 LocalSize 1 1 1 +%1 = OpTypeVoid +%2 = OpTypeFunction %1 +%3 = OpFunction %1 None %2 +%4 = OpLabel + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateSSA, IdUndefinedBad) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpName %missing "missing" +%voidt = OpTypeVoid +%vfunct = OpTypeFunction %voidt +%func = OpFunction %vfunct None %missing +%flabel = OpLabel + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("missing")); +} + +TEST_F(ValidateSSA, IdRedefinedBad) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpName %2 "redefined" +%1 = OpTypeVoid +%2 = OpTypeFunction %1 +%2 = OpFunction %1 None %2 +%4 = OpLabel + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); +} + +TEST_F(ValidateSSA, DominateUsageBad) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpName %1 "not_dominant" +%2 = OpTypeFunction %1 ; uses %1 before it's definition +%1 = OpTypeVoid +)"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("not_dominant")); +} + +TEST_F(ValidateSSA, DominateUsageWithinBlockBad) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpName %bad "bad" +%voidt = OpTypeVoid +%funct = OpTypeFunction %voidt +%uintt = OpTypeInt 32 0 +%one = OpConstant %uintt 1 +%func = OpFunction %voidt None %funct +%entry = OpLabel +%sum = OpIAdd %uintt %one %bad +%bad = OpCopyObject %uintt %sum + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + MatchesRegex("ID .\\[bad\\] has not been defined")); +} + +TEST_F(ValidateSSA, DominateUsageSameInstructionBad) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpName %sum "sum" +%voidt = OpTypeVoid +%funct = OpTypeFunction %voidt +%uintt = OpTypeInt 32 0 +%one = OpConstant %uintt 1 +%func = OpFunction %voidt None %funct +%entry = OpLabel +%sum = OpIAdd %uintt %one %sum + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + MatchesRegex("ID .\\[sum\\] has not been defined")); +} + +TEST_F(ValidateSSA, ForwardNameGood) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpName %3 "main" +%1 = OpTypeVoid +%2 = OpTypeFunction %1 +%3 = OpFunction %1 None %2 +%4 = OpLabel + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateSSA, ForwardNameMissingTargetBad) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpName %5 "main" ; Target never defined +)"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("main")); +} + +TEST_F(ValidateSSA, ForwardMemberNameGood) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpMemberName %struct 0 "value" + OpMemberName %struct 1 "size" +%intt = OpTypeInt 32 1 +%uintt = OpTypeInt 32 0 +%struct = OpTypeStruct %intt %uintt +)"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateSSA, ForwardMemberNameMissingTargetBad) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpMemberName %struct 0 "value" + OpMemberName %bad 1 "size" ; Target is not defined +%intt = OpTypeInt 32 1 +%uintt = OpTypeInt 32 0 +%struct = OpTypeStruct %intt %uintt +)"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("size")); +} + +TEST_F(ValidateSSA, ForwardDecorateGood) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpDecorate %var Restrict +%intt = OpTypeInt 32 1 +%ptrt = OpTypePointer UniformConstant %intt +%var = OpVariable %ptrt UniformConstant +)"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateSSA, ForwardDecorateInvalidIDBad) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpName %missing "missing" + OpDecorate %missing Restrict ;Missing ID +%voidt = OpTypeVoid +%intt = OpTypeInt 32 1 +%ptrt = OpTypePointer UniformConstant %intt +%var = OpVariable %ptrt UniformConstant +%2 = OpTypeFunction %voidt +%3 = OpFunction %voidt None %2 +%4 = OpLabel + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("missing")); +} + +TEST_F(ValidateSSA, ForwardMemberDecorateGood) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpMemberDecorate %struct 1 RowMajor +%intt = OpTypeInt 32 1 +%f32 = OpTypeFloat 32 +%vec3 = OpTypeVector %f32 3 +%mat33 = OpTypeMatrix %vec3 3 +%struct = OpTypeStruct %intt %mat33 +)"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateSSA, ForwardMemberDecorateInvalidIdBad) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpName %missing "missing" + OpMemberDecorate %missing 1 RowMajor ; Target not defined +%intt = OpTypeInt 32 1 +%f32 = OpTypeFloat 32 +%vec3 = OpTypeVector %f32 3 +%mat33 = OpTypeMatrix %vec3 3 +%struct = OpTypeStruct %intt %mat33 +)"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("missing")); +} + +TEST_F(ValidateSSA, ForwardGroupDecorateGood) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpDecorate %dgrp RowMajor +%dgrp = OpDecorationGroup + OpGroupDecorate %dgrp %mat33 %mat44 +%f32 = OpTypeFloat 32 +%vec3 = OpTypeVector %f32 3 +%vec4 = OpTypeVector %f32 4 +%mat33 = OpTypeMatrix %vec3 3 +%mat44 = OpTypeMatrix %vec4 4 +)"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateSSA, ForwardGroupDecorateMissingGroupBad) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpName %missing "missing" + OpDecorate %dgrp RowMajor +%dgrp = OpDecorationGroup + OpGroupDecorate %missing %mat33 %mat44 ; Target not defined +%intt = OpTypeInt 32 1 +%vec3 = OpTypeVector %intt 3 +%vec4 = OpTypeVector %intt 4 +%mat33 = OpTypeMatrix %vec3 3 +%mat44 = OpTypeMatrix %vec4 4 +)"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("missing")); +} + +TEST_F(ValidateSSA, ForwardGroupDecorateMissingTargetBad) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpName %missing "missing" + OpDecorate %dgrp RowMajor +%dgrp = OpDecorationGroup + OpGroupDecorate %dgrp %missing %mat44 ; Target not defined +%f32 = OpTypeFloat 32 +%vec3 = OpTypeVector %f32 3 +%vec4 = OpTypeVector %f32 4 +%mat33 = OpTypeMatrix %vec3 3 +%mat44 = OpTypeMatrix %vec4 4 +)"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("missing")); +} + +TEST_F(ValidateSSA, ForwardGroupDecorateDecorationGroupDominateBad) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpName %dgrp "group" + OpDecorate %dgrp RowMajor + OpGroupDecorate %dgrp %mat33 %mat44 ; Decoration group does not dominate usage +%dgrp = OpDecorationGroup +%intt = OpTypeInt 32 1 +%vec3 = OpTypeVector %intt 3 +%vec4 = OpTypeVector %intt 4 +%mat33 = OpTypeMatrix %vec3 3 +%mat44 = OpTypeMatrix %vec4 4 +)"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("group")); +} + +TEST_F(ValidateSSA, ForwardDecorateInvalidIdBad) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpName %missing "missing" + OpDecorate %missing Restrict ; Missing target +%voidt = OpTypeVoid +%intt = OpTypeInt 32 1 +%ptrt = OpTypePointer UniformConstant %intt +%var = OpVariable %ptrt UniformConstant +%2 = OpTypeFunction %voidt +%3 = OpFunction %voidt None %2 +%4 = OpLabel + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("missing")); +} + +TEST_F(ValidateSSA, FunctionCallGood) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 +%1 = OpTypeVoid +%2 = OpTypeInt 32 1 +%3 = OpTypeInt 32 0 +%4 = OpTypeFunction %1 +%8 = OpTypeFunction %1 %2 %3 +%four = OpConstant %2 4 +%five = OpConstant %3 5 +%9 = OpFunction %1 None %8 +%10 = OpFunctionParameter %2 +%11 = OpFunctionParameter %3 +%12 = OpLabel + OpReturn + OpFunctionEnd +%5 = OpFunction %1 None %4 +%6 = OpLabel +%7 = OpFunctionCall %1 %9 %four %five + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateSSA, ForwardFunctionCallGood) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 +%1 = OpTypeVoid +%2 = OpTypeInt 32 1 +%3 = OpTypeInt 32 0 +%four = OpConstant %2 4 +%five = OpConstant %3 5 +%8 = OpTypeFunction %1 %2 %3 +%4 = OpTypeFunction %1 +%5 = OpFunction %1 None %4 +%6 = OpLabel +%7 = OpFunctionCall %1 %9 %four %five + OpReturn + OpFunctionEnd +%9 = OpFunction %1 None %8 +%10 = OpFunctionParameter %2 +%11 = OpFunctionParameter %3 +%12 = OpLabel + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateSSA, ForwardBranchConditionalGood) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 +%voidt = OpTypeVoid +%boolt = OpTypeBool +%vfunct = OpTypeFunction %voidt +%true = OpConstantTrue %boolt +%main = OpFunction %voidt None %vfunct +%mainl = OpLabel + OpSelectionMerge %endl None + OpBranchConditional %true %truel %falsel +%truel = OpLabel + OpNop + OpBranch %endl +%falsel = OpLabel + OpNop + OpBranch %endl +%endl = OpLabel + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateSSA, ForwardBranchConditionalWithWeightsGood) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 +%voidt = OpTypeVoid +%boolt = OpTypeBool +%vfunct = OpTypeFunction %voidt +%true = OpConstantTrue %boolt +%main = OpFunction %voidt None %vfunct +%mainl = OpLabel + OpSelectionMerge %endl None + OpBranchConditional %true %truel %falsel 1 9 +%truel = OpLabel + OpNop + OpBranch %endl +%falsel = OpLabel + OpNop + OpBranch %endl +%endl = OpLabel + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateSSA, ForwardBranchConditionalNonDominantConditionBad) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpName %tcpy "conditional" +%voidt = OpTypeVoid +%boolt = OpTypeBool +%vfunct = OpTypeFunction %voidt +%true = OpConstantTrue %boolt +%main = OpFunction %voidt None %vfunct +%mainl = OpLabel + OpSelectionMerge %endl None + OpBranchConditional %tcpy %truel %falsel ; +%truel = OpLabel + OpNop + OpBranch %endl +%falsel = OpLabel + OpNop + OpBranch %endl +%endl = OpLabel +%tcpy = OpCopyObject %boolt %true + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("conditional")); +} + +TEST_F(ValidateSSA, ForwardBranchConditionalMissingTargetBad) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + OpName %missing "missing" +%voidt = OpTypeVoid +%boolt = OpTypeBool +%vfunct = OpTypeFunction %voidt +%true = OpConstantTrue %boolt +%main = OpFunction %voidt None %vfunct +%mainl = OpLabel + OpSelectionMerge %endl None + OpBranchConditional %true %missing %falsel +%truel = OpLabel + OpNop + OpBranch %endl +%falsel = OpLabel + OpNop + OpBranch %endl +%endl = OpLabel + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("missing")); +} + +// Since Int8 requires the Kernel capability, the signedness of int types may +// not be "1". +const string kHeader = R"( +OpCapability Int8 +OpCapability DeviceEnqueue +OpCapability Linkage +OpMemoryModel Logical OpenCL +)"; + +const string kBasicTypes = R"( +%voidt = OpTypeVoid +%boolt = OpTypeBool +%int8t = OpTypeInt 8 0 +%uintt = OpTypeInt 32 0 +%vfunct = OpTypeFunction %voidt +%intptrt = OpTypePointer UniformConstant %uintt +%zero = OpConstant %uintt 0 +%one = OpConstant %uintt 1 +%ten = OpConstant %uintt 10 +%false = OpConstantFalse %boolt +)"; + +const string kKernelTypesAndConstants = R"( +%queuet = OpTypeQueue + +%three = OpConstant %uintt 3 +%arr3t = OpTypeArray %uintt %three +%ndt = OpTypeStruct %uintt %arr3t %arr3t %arr3t + +%eventt = OpTypeEvent + +%offset = OpConstant %uintt 0 +%local = OpConstant %uintt 1 +%gl = OpConstant %uintt 1 + +%nevent = OpConstant %uintt 0 +%event = OpConstantNull %eventt + +%firstp = OpConstant %int8t 0 +%psize = OpConstant %uintt 0 +%palign = OpConstant %uintt 32 +%lsize = OpConstant %uintt 1 +%flags = OpConstant %uintt 0 ; NoWait + +%kfunct = OpTypeFunction %voidt %intptrt +)"; + +const string kKernelSetup = R"( +%dqueue = OpGetDefaultQueue %queuet +%ndval = OpBuildNDRange %ndt %gl %local %offset +%revent = OpUndef %eventt + +)"; + +const string kKernelDefinition = R"( +%kfunc = OpFunction %voidt None %kfunct +%iparam = OpFunctionParameter %intptrt +%kfuncl = OpLabel + OpNop + OpReturn + OpFunctionEnd +)"; + +TEST_F(ValidateSSA, EnqueueKernelGood) { + string str = kHeader + kBasicTypes + kKernelTypesAndConstants + + kKernelDefinition + R"( + %main = OpFunction %voidt None %vfunct + %mainl = OpLabel + )" + kKernelSetup + R"( + %err = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent + %event %revent %kfunc %firstp %psize + %palign %lsize + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateSSA, ForwardEnqueueKernelGood) { + string str = kHeader + kBasicTypes + kKernelTypesAndConstants + R"( + %main = OpFunction %voidt None %vfunct + %mainl = OpLabel + )" + + kKernelSetup + R"( + %err = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent + %event %revent %kfunc %firstp %psize + %palign %lsize + OpReturn + OpFunctionEnd + )" + kKernelDefinition; + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateSSA, EnqueueMissingFunctionBad) { + string str = kHeader + "OpName %kfunc \"kfunc\"" + kBasicTypes + + kKernelTypesAndConstants + R"( + %main = OpFunction %voidt None %vfunct + %mainl = OpLabel + )" + kKernelSetup + R"( + %err = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent + %event %revent %kfunc %firstp %psize + %palign %lsize + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("kfunc")); +} + +string forwardKernelNonDominantParameterBaseCode(string name = string()) { + string op_name; + if (name.empty()) { + op_name = ""; + } else { + op_name = "\nOpName %" + name + " \"" + name + "\"\n"; + } + string out = kHeader + op_name + kBasicTypes + kKernelTypesAndConstants + + kKernelDefinition + + R"( + %main = OpFunction %voidt None %vfunct + %mainl = OpLabel + )" + + kKernelSetup; + return out; +} + +TEST_F(ValidateSSA, ForwardEnqueueKernelMissingParameter1Bad) { + string str = forwardKernelNonDominantParameterBaseCode("missing") + R"( + %err = OpEnqueueKernel %missing %dqueue %flags %ndval + %nevent %event %revent %kfunc %firstp + %psize %palign %lsize + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("missing")); +} + +TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter2Bad) { + string str = forwardKernelNonDominantParameterBaseCode("dqueue2") + R"( + %err = OpEnqueueKernel %uintt %dqueue2 %flags %ndval + %nevent %event %revent %kfunc + %firstp %psize %palign %lsize + %dqueue2 = OpGetDefaultQueue %queuet + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("dqueue2")); +} + +TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter3Bad) { + string str = forwardKernelNonDominantParameterBaseCode("ndval2") + R"( + %err = OpEnqueueKernel %uintt %dqueue %flags %ndval2 + %nevent %event %revent %kfunc %firstp + %psize %palign %lsize + %ndval2 = OpBuildNDRange %ndt %gl %local %offset + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("ndval2")); +} + +TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter4Bad) { + string str = forwardKernelNonDominantParameterBaseCode("nevent2") + R"( + %err = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent2 + %event %revent %kfunc %firstp %psize + %palign %lsize + %nevent2 = OpCopyObject %uintt %nevent + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("nevent2")); +} + +TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter5Bad) { + string str = forwardKernelNonDominantParameterBaseCode("event2") + R"( + %err = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent + %event2 %revent %kfunc %firstp %psize + %palign %lsize + %event2 = OpCopyObject %eventt %event + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("event2")); +} + +TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter6Bad) { + string str = forwardKernelNonDominantParameterBaseCode("revent2") + R"( + %err = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent + %event %revent2 %kfunc %firstp %psize + %palign %lsize + %revent2 = OpCopyObject %eventt %revent + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("revent2")); +} + +TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter8Bad) { + string str = forwardKernelNonDominantParameterBaseCode("firstp2") + R"( + %err = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent + %event %revent %kfunc %firstp2 %psize + %palign %lsize + %firstp2 = OpCopyObject %int8t %firstp + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("firstp2")); +} + +TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter9Bad) { + string str = forwardKernelNonDominantParameterBaseCode("psize2") + R"( + %err = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent + %event %revent %kfunc %firstp %psize2 + %palign %lsize + %psize2 = OpCopyObject %uintt %psize + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("psize2")); +} + +TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter10Bad) { + string str = forwardKernelNonDominantParameterBaseCode("palign2") + R"( + %err = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent + %event %revent %kfunc %firstp %psize + %palign2 %lsize + %palign2 = OpCopyObject %uintt %palign + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("palign2")); +} + +TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter11Bad) { + string str = forwardKernelNonDominantParameterBaseCode("lsize2") + R"( + %err = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent + %event %revent %kfunc %firstp %psize + %palign %lsize2 + %lsize2 = OpCopyObject %uintt %lsize + OpReturn + OpFunctionEnd + )"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("lsize2")); +} + +static const bool kWithNDrange = true; +static const bool kNoNDrange = false; +pair cases[] = { + {"OpGetKernelNDrangeSubGroupCount", kWithNDrange}, + {"OpGetKernelNDrangeMaxSubGroupSize", kWithNDrange}, + {"OpGetKernelWorkGroupSize", kNoNDrange}, + {"OpGetKernelPreferredWorkGroupSizeMultiple", kNoNDrange}}; + +INSTANTIATE_TEST_CASE_P(KernelArgs, ValidateSSA, ::testing::ValuesIn(cases), ); + +static const string return_instructions = R"( + OpReturn + OpFunctionEnd +)"; + +TEST_P(ValidateSSA, GetKernelGood) { + string instruction = GetParam().first; + bool with_ndrange = GetParam().second; + string ndrange_param = with_ndrange ? " %ndval " : " "; + + stringstream ss; + // clang-format off + ss << forwardKernelNonDominantParameterBaseCode() + " %numsg = " + << instruction + " %uintt" + ndrange_param + "%kfunc %firstp %psize %palign" + << return_instructions; + // clang-format on + + CompileSuccessfully(ss.str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateSSA, ForwardGetKernelGood) { + string instruction = GetParam().first; + bool with_ndrange = GetParam().second; + string ndrange_param = with_ndrange ? " %ndval " : " "; + + // clang-format off + string str = kHeader + kBasicTypes + kKernelTypesAndConstants + + R"( + %main = OpFunction %voidt None %vfunct + %mainl = OpLabel + )" + + kKernelSetup + " %numsg = " + + instruction + " %uintt" + ndrange_param + "%kfunc %firstp %psize %palign" + + return_instructions + kKernelDefinition; + // clang-format on + + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ValidateSSA, ForwardGetKernelMissingDefinitionBad) { + string instruction = GetParam().first; + bool with_ndrange = GetParam().second; + string ndrange_param = with_ndrange ? " %ndval " : " "; + + stringstream ss; + // clang-format off + ss << forwardKernelNonDominantParameterBaseCode("missing") + " %numsg = " + << instruction + " %uintt" + ndrange_param + "%missing %firstp %psize %palign" + << return_instructions; + // clang-format on + + CompileSuccessfully(ss.str()); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("missing")); +} + +TEST_P(ValidateSSA, ForwardGetKernelNDrangeSubGroupCountMissingParameter1Bad) { + string instruction = GetParam().first; + bool with_ndrange = GetParam().second; + string ndrange_param = with_ndrange ? " %ndval " : " "; + + stringstream ss; + // clang-format off + ss << forwardKernelNonDominantParameterBaseCode("missing") + " %numsg = " + << instruction + " %missing" + ndrange_param + "%kfunc %firstp %psize %palign" + << return_instructions; + // clang-format on + + CompileSuccessfully(ss.str()); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("missing")); +} + +TEST_P(ValidateSSA, + ForwardGetKernelNDrangeSubGroupCountNonDominantParameter2Bad) { + string instruction = GetParam().first; + bool with_ndrange = GetParam().second; + string ndrange_param = with_ndrange ? " %ndval2 " : " "; + + stringstream ss; + // clang-format off + ss << forwardKernelNonDominantParameterBaseCode("ndval2") + " %numsg = " + << instruction + " %uintt" + ndrange_param + "%kfunc %firstp %psize %palign" + << "\n %ndval2 = OpBuildNDRange %ndt %gl %local %offset" + << return_instructions; + // clang-format on + + if (GetParam().second) { + CompileSuccessfully(ss.str()); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("ndval2")); + } +} + +TEST_P(ValidateSSA, + ForwardGetKernelNDrangeSubGroupCountNonDominantParameter4Bad) { + string instruction = GetParam().first; + bool with_ndrange = GetParam().second; + string ndrange_param = with_ndrange ? " %ndval " : " "; + + stringstream ss; + // clang-format off + ss << forwardKernelNonDominantParameterBaseCode("firstp2") + " %numsg = " + << instruction + " %uintt" + ndrange_param + "%kfunc %firstp2 %psize %palign" + << "\n %firstp2 = OpCopyObject %int8t %firstp" + << return_instructions; + // clang-format on + + CompileSuccessfully(ss.str()); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("firstp2")); +} + +TEST_P(ValidateSSA, + ForwardGetKernelNDrangeSubGroupCountNonDominantParameter5Bad) { + string instruction = GetParam().first; + bool with_ndrange = GetParam().second; + string ndrange_param = with_ndrange ? " %ndval " : " "; + + stringstream ss; + // clang-format off + ss << forwardKernelNonDominantParameterBaseCode("psize2") + " %numsg = " + << instruction + " %uintt" + ndrange_param + "%kfunc %firstp %psize2 %palign" + << "\n %psize2 = OpCopyObject %uintt %psize" + << return_instructions; + // clang-format on + + CompileSuccessfully(ss.str()); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("psize2")); +} + +TEST_P(ValidateSSA, + ForwardGetKernelNDrangeSubGroupCountNonDominantParameter6Bad) { + string instruction = GetParam().first; + bool with_ndrange = GetParam().second; + string ndrange_param = with_ndrange ? " %ndval " : " "; + + stringstream ss; + // clang-format off + ss << forwardKernelNonDominantParameterBaseCode("palign2") + " %numsg = " + << instruction + " %uintt" + ndrange_param + "%kfunc %firstp %psize %palign2" + << "\n %palign2 = OpCopyObject %uintt %palign" + << return_instructions; + // clang-format on + + if (GetParam().second) { + CompileSuccessfully(ss.str()); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("palign2")); + } +} + +TEST_F(ValidateSSA, PhiGood) { + string str = kHeader + kBasicTypes + + R"( +%func = OpFunction %voidt None %vfunct +%preheader = OpLabel +%init = OpCopyObject %uintt %zero + OpBranch %loop +%loop = OpLabel +%i = OpPhi %uintt %init %preheader %loopi %loop +%loopi = OpIAdd %uintt %i %one + OpNop +%cond = OpSLessThan %boolt %i %ten + OpLoopMerge %endl %loop None + OpBranchConditional %cond %loop %endl +%endl = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateSSA, PhiMissingTypeBad) { + string str = kHeader + "OpName %missing \"missing\"" + kBasicTypes + + R"( +%func = OpFunction %voidt None %vfunct +%preheader = OpLabel +%init = OpCopyObject %uintt %zero + OpBranch %loop +%loop = OpLabel +%i = OpPhi %missing %init %preheader %loopi %loop +%loopi = OpIAdd %uintt %i %one + OpNop +%cond = OpSLessThan %boolt %i %ten + OpLoopMerge %endl %loop None + OpBranchConditional %cond %loop %endl +%endl = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("missing")); +} + +TEST_F(ValidateSSA, PhiMissingIdBad) { + string str = kHeader + "OpName %missing \"missing\"" + kBasicTypes + + R"( +%func = OpFunction %voidt None %vfunct +%preheader = OpLabel +%init = OpCopyObject %uintt %zero + OpBranch %loop +%loop = OpLabel +%i = OpPhi %uintt %missing %preheader %loopi %loop +%loopi = OpIAdd %uintt %i %one + OpNop +%cond = OpSLessThan %boolt %i %ten + OpLoopMerge %endl %loop None + OpBranchConditional %cond %loop %endl +%endl = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("missing")); +} + +TEST_F(ValidateSSA, PhiMissingLabelBad) { + string str = kHeader + "OpName %missing \"missing\"" + kBasicTypes + + R"( +%func = OpFunction %voidt None %vfunct +%preheader = OpLabel +%init = OpCopyObject %uintt %zero + OpBranch %loop +%loop = OpLabel +%i = OpPhi %uintt %init %missing %loopi %loop +%loopi = OpIAdd %uintt %i %one + OpNop +%cond = OpSLessThan %boolt %i %ten + OpLoopMerge %endl %loop None + OpBranchConditional %cond %loop %endl +%endl = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("missing")); +} + +TEST_F(ValidateSSA, IdDominatesItsUseGood) { + string str = kHeader + kBasicTypes + + R"( +%func = OpFunction %voidt None %vfunct +%entry = OpLabel +%cond = OpSLessThan %boolt %one %ten +%eleven = OpIAdd %uintt %one %ten + OpSelectionMerge %merge None + OpBranchConditional %cond %t %f +%t = OpLabel +%twelve = OpIAdd %uintt %eleven %one + OpBranch %merge +%f = OpLabel +%twentytwo = OpIAdd %uintt %eleven %ten + OpBranch %merge +%merge = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateSSA, IdDoesNotDominateItsUseBad) { + string str = kHeader + + "OpName %eleven \"eleven\"\n" + "OpName %true_block \"true_block\"\n" + "OpName %false_block \"false_block\"" + + kBasicTypes + + R"( +%func = OpFunction %voidt None %vfunct +%entry = OpLabel +%cond = OpSLessThan %boolt %one %ten + OpSelectionMerge %merge None + OpBranchConditional %cond %true_block %false_block +%true_block = OpLabel +%eleven = OpIAdd %uintt %one %ten +%twelve = OpIAdd %uintt %eleven %one + OpBranch %merge +%false_block = OpLabel +%twentytwo = OpIAdd %uintt %eleven %ten + OpBranch %merge +%merge = OpLabel + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + MatchesRegex("ID .\\[eleven\\] defined in block .\\[true_block\\] does " + "not dominate its use in block .\\[false_block\\]")); +} + +TEST_F(ValidateSSA, PhiUseDoesntDominateDefinitionGood) { + string str = kHeader + kBasicTypes + + R"( +%func = OpFunction %voidt None %vfunct +%entry = OpLabel +%var_one = OpVariable %intptrt Function %one +%one_val = OpLoad %uintt %var_one + OpBranch %loop +%loop = OpLabel +%i = OpPhi %uintt %one_val %entry %inew %cont +%cond = OpSLessThan %boolt %one %ten + OpLoopMerge %merge %cont None + OpBranchConditional %cond %body %merge +%body = OpLabel + OpBranch %cont +%cont = OpLabel +%inew = OpIAdd %uintt %i %one + OpBranch %loop +%merge = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateSSA, + PhiUseDoesntDominateUseOfPhiOperandUsedBeforeDefinitionBad) { + string str = kHeader + "OpName %inew \"inew\"" + kBasicTypes + + R"( +%func = OpFunction %voidt None %vfunct +%entry = OpLabel +%var_one = OpVariable %intptrt Function %one +%one_val = OpLoad %uintt %var_one + OpBranch %loop +%loop = OpLabel +%i = OpPhi %uintt %one_val %entry %inew %cont +%bad = OpIAdd %uintt %inew %one +%cond = OpSLessThan %boolt %one %ten + OpLoopMerge %merge %cont None + OpBranchConditional %cond %body %merge +%body = OpLabel + OpBranch %cont +%cont = OpLabel +%inew = OpIAdd %uintt %i %one + OpBranch %loop +%merge = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + MatchesRegex("ID .\\[inew\\] has not been defined")); +} + +TEST_F(ValidateSSA, PhiUseMayComeFromNonDominatingBlockGood) { + string str = kHeader + "OpName %if_true \"if_true\"\n" + + "OpName %exit \"exit\"\n" + "OpName %copy \"copy\"\n" + + kBasicTypes + + R"( +%func = OpFunction %voidt None %vfunct +%entry = OpLabel + OpBranchConditional %false %if_true %exit + +%if_true = OpLabel +%copy = OpCopyObject %boolt %false + OpBranch %exit + +; The use of %copy here is ok, even though it was defined +; in a block that does not dominate %exit. That's the point +; of an OpPhi. +%exit = OpLabel +%value = OpPhi %boolt %false %entry %copy %if_true + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString(); +} + +TEST_F(ValidateSSA, PhiUsesItsOwnDefinitionGood) { + // See https://github.com/KhronosGroup/SPIRV-Tools/issues/415 + // + // Non-phi instructions can't use their own definitions, as + // already checked in test DominateUsageSameInstructionBad. + string str = kHeader + "OpName %loop \"loop\"\n" + + "OpName %value \"value\"\n" + kBasicTypes + + R"( +%func = OpFunction %voidt None %vfunct +%entry = OpLabel + OpBranch %loop + +%loop = OpLabel +%value = OpPhi %boolt %false %entry %value %loop + OpBranch %loop + + OpFunctionEnd +)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString(); +} + +TEST_F(ValidateSSA, PhiVariableDefNotDominatedByParentBlockBad) { + string str = kHeader + "OpName %if_true \"if_true\"\n" + + "OpName %if_false \"if_false\"\n" + "OpName %exit \"exit\"\n" + + "OpName %value \"phi\"\n" + "OpName %true_copy \"true_copy\"\n" + + "OpName %false_copy \"false_copy\"\n" + kBasicTypes + + R"( +%func = OpFunction %voidt None %vfunct +%entry = OpLabel + OpBranchConditional %false %if_true %if_false + +%if_true = OpLabel +%true_copy = OpCopyObject %boolt %false + OpBranch %exit + +%if_false = OpLabel +%false_copy = OpCopyObject %boolt %false + OpBranch %exit + +; The (variable,Id) pairs are swapped. +%exit = OpLabel +%value = OpPhi %boolt %true_copy %if_false %false_copy %if_true + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + MatchesRegex("In OpPhi instruction .\\[phi\\], ID .\\[true_copy\\] " + "definition does not dominate its parent .\\[if_false\\]")); +} + +TEST_F(ValidateSSA, PhiVariableDefDominatesButNotDefinedInParentBlock) { + string str = kHeader + "OpName %if_true \"if_true\"\n" + kBasicTypes + + R"( +%func = OpFunction %voidt None %vfunct +%entry = OpLabel + OpBranchConditional %false %if_true %if_false + +%if_true = OpLabel +%true_copy = OpCopyObject %boolt %false + OpBranch %if_tnext +%if_tnext = OpLabel + OpBranch %exit + +%if_false = OpLabel +%false_copy = OpCopyObject %boolt %false + OpBranch %if_fnext +%if_fnext = OpLabel + OpBranch %exit + +%exit = OpLabel +%value = OpPhi %boolt %true_copy %if_tnext %false_copy %if_fnext + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateSSA, + DominanceCheckIgnoresUsesInUnreachableBlocksDefInBlockGood) { + string str = kHeader + kBasicTypes + + R"( +%func = OpFunction %voidt None %vfunct +%entry = OpLabel +%def = OpCopyObject %boolt %false + OpReturn + +%unreach = OpLabel +%use = OpCopyObject %boolt %def + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString(); +} + +TEST_F(ValidateSSA, PhiVariableUnreachableDefNotInParentBlock) { + string str = kHeader + "OpName %unreachable \"unreachable\"\n" + kBasicTypes + + R"( +%func = OpFunction %voidt None %vfunct +%entry = OpLabel + OpBranch %if_false + +%unreachable = OpLabel +%copy = OpCopyObject %boolt %false + OpBranch %if_tnext +%if_tnext = OpLabel + OpBranch %exit + +%if_false = OpLabel +%false_copy = OpCopyObject %boolt %false + OpBranch %if_fnext +%if_fnext = OpLabel + OpBranch %exit + +%exit = OpLabel +%value = OpPhi %boolt %copy %if_tnext %false_copy %if_fnext + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateSSA, + DominanceCheckIgnoresUsesInUnreachableBlocksDefIsParamGood) { + string str = kHeader + kBasicTypes + + R"( +%void_fn_int = OpTypeFunction %voidt %uintt +%func = OpFunction %voidt None %void_fn_int +%int_param = OpFunctionParameter %uintt +%entry = OpLabel + OpReturn + +%unreach = OpLabel +%use = OpCopyObject %uintt %int_param + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString(); +} + +TEST_F(ValidateSSA, UseFunctionParameterFromOtherFunctionBad) { + string str = kHeader + + "OpName %first \"first\"\n" + "OpName %func \"func\"\n" + + "OpName %func2 \"func2\"\n" + kBasicTypes + + R"( +%viifunct = OpTypeFunction %voidt %uintt %uintt +%func = OpFunction %voidt None %viifunct +%first = OpFunctionParameter %uintt +%second = OpFunctionParameter %uintt + OpFunctionEnd +%func2 = OpFunction %voidt None %viifunct +%first2 = OpFunctionParameter %uintt +%second2 = OpFunctionParameter %uintt +%entry2 = OpLabel +%baduse = OpIAdd %uintt %first %first2 + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + MatchesRegex("ID .\\[first\\] used in function .\\[func2\\] is used " + "outside of it's defining function .\\[func\\]")); +} + +TEST_F(ValidateSSA, TypeForwardPointerForwardReference) { + // See https://github.com/KhronosGroup/SPIRV-Tools/issues/429 + // + // ForwardPointers can references instructions that have not been defined + string str = R"( + OpCapability Kernel + OpCapability Addresses + OpCapability Linkage + OpMemoryModel Logical OpenCL + OpName %intptrt "intptrt" + OpTypeForwardPointer %intptrt UniformConstant + %uint = OpTypeInt 32 0 + %intptrt = OpTypePointer UniformConstant %uint +)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateSSA, TypeStructForwardReference) { + string str = R"( + OpCapability Kernel + OpCapability Addresses + OpCapability Linkage + OpMemoryModel Logical OpenCL + OpName %structptr "structptr" + OpTypeForwardPointer %structptr UniformConstant + %uint = OpTypeInt 32 0 + %structt1 = OpTypeStruct %structptr %uint + %structt2 = OpTypeStruct %uint %structptr + %structt3 = OpTypeStruct %uint %uint %structptr + %structt4 = OpTypeStruct %uint %uint %uint %structptr + %structptr = OpTypePointer UniformConstant %structt1 +)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// TODO(umar): OpGroupMemberDecorate +} // namespace diff --git a/3rdparty/spirv-tools/test/val/val_state_test.cpp b/3rdparty/spirv-tools/test/val/val_state_test.cpp new file mode 100644 index 000000000..c63a0c5c6 --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_state_test.cpp @@ -0,0 +1,136 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Unit tests for ValidationState_t. + +#include + +#include "gtest/gtest.h" +#include "latest_version_spirv_header.h" + +#include "enum_set.h" +#include "extensions.h" +#include "spirv_validator_options.h" +#include "val/construct.h" +#include "val/function.h" +#include "val/validation_state.h" +#include "validate.h" + +namespace { +using libspirv::CapabilitySet; +using libspirv::Extension; +using libspirv::ExtensionSet; +using libspirv::ValidationState_t; +using std::vector; + +// A test with a ValidationState_t member transparently. +class ValidationStateTest : public testing::Test { + public: + ValidationStateTest() + : context_(spvContextCreate(SPV_ENV_UNIVERSAL_1_0)), + options_(spvValidatorOptionsCreate()), + state_(context_, options_) {} + + ~ValidationStateTest() { + spvContextDestroy(context_); + spvValidatorOptionsDestroy(options_); + } + + protected: + spv_context context_; + spv_validator_options options_; + ValidationState_t state_; +}; + +// A test of ValidationState_t::HasAnyOfCapabilities(). +using ValidationState_HasAnyOfCapabilities = ValidationStateTest; + +TEST_F(ValidationState_HasAnyOfCapabilities, EmptyMask) { + EXPECT_TRUE(state_.HasAnyOfCapabilities({})); + state_.RegisterCapability(SpvCapabilityMatrix); + EXPECT_TRUE(state_.HasAnyOfCapabilities({})); + state_.RegisterCapability(SpvCapabilityImageMipmap); + EXPECT_TRUE(state_.HasAnyOfCapabilities({})); + state_.RegisterCapability(SpvCapabilityPipes); + EXPECT_TRUE(state_.HasAnyOfCapabilities({})); + state_.RegisterCapability(SpvCapabilityStorageImageArrayDynamicIndexing); + EXPECT_TRUE(state_.HasAnyOfCapabilities({})); + state_.RegisterCapability(SpvCapabilityClipDistance); + EXPECT_TRUE(state_.HasAnyOfCapabilities({})); + state_.RegisterCapability(SpvCapabilityStorageImageWriteWithoutFormat); + EXPECT_TRUE(state_.HasAnyOfCapabilities({})); +} + +TEST_F(ValidationState_HasAnyOfCapabilities, SingleCapMask) { + EXPECT_FALSE(state_.HasAnyOfCapabilities({SpvCapabilityMatrix})); + EXPECT_FALSE(state_.HasAnyOfCapabilities({SpvCapabilityImageMipmap})); + state_.RegisterCapability(SpvCapabilityMatrix); + EXPECT_TRUE(state_.HasAnyOfCapabilities({SpvCapabilityMatrix})); + EXPECT_FALSE(state_.HasAnyOfCapabilities({SpvCapabilityImageMipmap})); + state_.RegisterCapability(SpvCapabilityImageMipmap); + EXPECT_TRUE(state_.HasAnyOfCapabilities({SpvCapabilityMatrix})); + EXPECT_TRUE(state_.HasAnyOfCapabilities({SpvCapabilityImageMipmap})); +} + +TEST_F(ValidationState_HasAnyOfCapabilities, MultiCapMask) { + const auto set1 = + CapabilitySet{SpvCapabilitySampledRect, SpvCapabilityImageBuffer}; + const auto set2 = CapabilitySet{SpvCapabilityStorageImageWriteWithoutFormat, + SpvCapabilityStorageImageReadWithoutFormat, + SpvCapabilityGeometryStreams}; + EXPECT_FALSE(state_.HasAnyOfCapabilities(set1)); + EXPECT_FALSE(state_.HasAnyOfCapabilities(set2)); + state_.RegisterCapability(SpvCapabilityImageBuffer); + EXPECT_TRUE(state_.HasAnyOfCapabilities(set1)); + EXPECT_FALSE(state_.HasAnyOfCapabilities(set2)); +} + +// A test of ValidationState_t::HasAnyOfExtensions(). +using ValidationState_HasAnyOfExtensions = ValidationStateTest; + +TEST_F(ValidationState_HasAnyOfExtensions, EmptyMask) { + EXPECT_TRUE(state_.HasAnyOfExtensions({})); + state_.RegisterExtension(Extension::kSPV_KHR_shader_ballot); + EXPECT_TRUE(state_.HasAnyOfExtensions({})); + state_.RegisterExtension(Extension::kSPV_KHR_16bit_storage); + EXPECT_TRUE(state_.HasAnyOfExtensions({})); + state_.RegisterExtension(Extension::kSPV_NV_viewport_array2); + EXPECT_TRUE(state_.HasAnyOfExtensions({})); +} + +TEST_F(ValidationState_HasAnyOfExtensions, SingleCapMask) { + EXPECT_FALSE(state_.HasAnyOfExtensions({Extension::kSPV_KHR_shader_ballot})); + EXPECT_FALSE(state_.HasAnyOfExtensions({Extension::kSPV_KHR_16bit_storage})); + state_.RegisterExtension(Extension::kSPV_KHR_shader_ballot); + EXPECT_TRUE(state_.HasAnyOfExtensions({Extension::kSPV_KHR_shader_ballot})); + EXPECT_FALSE(state_.HasAnyOfExtensions({Extension::kSPV_KHR_16bit_storage})); + state_.RegisterExtension(Extension::kSPV_KHR_16bit_storage); + EXPECT_TRUE(state_.HasAnyOfExtensions({Extension::kSPV_KHR_shader_ballot})); + EXPECT_TRUE(state_.HasAnyOfExtensions({Extension::kSPV_KHR_16bit_storage})); +} + +TEST_F(ValidationState_HasAnyOfExtensions, MultiCapMask) { + const auto set1 = ExtensionSet{Extension::kSPV_KHR_multiview, + Extension::kSPV_KHR_16bit_storage}; + const auto set2 = ExtensionSet{Extension::kSPV_KHR_shader_draw_parameters, + Extension::kSPV_NV_stereo_view_rendering, + Extension::kSPV_KHR_shader_ballot}; + EXPECT_FALSE(state_.HasAnyOfExtensions(set1)); + EXPECT_FALSE(state_.HasAnyOfExtensions(set2)); + state_.RegisterExtension(Extension::kSPV_KHR_multiview); + EXPECT_TRUE(state_.HasAnyOfExtensions(set1)); + EXPECT_FALSE(state_.HasAnyOfExtensions(set2)); +} +} // namespace diff --git a/3rdparty/spirv-tools/test/val/val_storage_test.cpp b/3rdparty/spirv-tools/test/val/val_storage_test.cpp new file mode 100644 index 000000000..02ef6f58d --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_storage_test.cpp @@ -0,0 +1,178 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Validation tests for OpVariable storage class + +#include +#include +#include + +#include "gmock/gmock.h" +#include "val_fixtures.h" + +using ::testing::HasSubstr; + +using ValidateStorage = spvtest::ValidateBase; + +namespace { + +TEST_F(ValidateStorage, FunctionStorageInsideFunction) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 +%intt = OpTypeInt 32 1 +%voidt = OpTypeVoid +%vfunct = OpTypeFunction %voidt +%ptrt = OpTypePointer Function %intt +%func = OpFunction %voidt None %vfunct +%funcl = OpLabel +%var = OpVariable %ptrt Function + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateStorage, FunctionStorageOutsideFunction) { + char str[] = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 +%intt = OpTypeInt 32 1 +%voidt = OpTypeVoid +%vfunct = OpTypeFunction %voidt +%ptrt = OpTypePointer Function %intt +%var = OpVariable %ptrt Function +%func = OpFunction %voidt None %vfunct +%funcl = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Variables can not have a function[7] storage class " + "outside of a function")); +} + +TEST_F(ValidateStorage, OtherStorageOutsideFunction) { + char str[] = R"( + OpCapability Shader + OpCapability Kernel + OpCapability AtomicStorage + OpCapability Linkage + OpMemoryModel Logical GLSL450 +%intt = OpTypeInt 32 0 +%voidt = OpTypeVoid +%vfunct = OpTypeFunction %voidt +%ptrt = OpTypePointer Function %intt +%unicon = OpVariable %ptrt UniformConstant +%input = OpVariable %ptrt Input +%unif = OpVariable %ptrt Uniform +%output = OpVariable %ptrt Output +%wgroup = OpVariable %ptrt Workgroup +%xwgrp = OpVariable %ptrt CrossWorkgroup +%priv = OpVariable %ptrt Private +%pushco = OpVariable %ptrt PushConstant +%atomct = OpVariable %ptrt AtomicCounter +%image = OpVariable %ptrt Image +%func = OpFunction %voidt None %vfunct +%funcl = OpLabel + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(str); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +// clang-format off +TEST_P(ValidateStorage, OtherStorageInsideFunction) { + std::stringstream ss; + ss << R"( + OpCapability Shader + OpCapability Kernel + OpCapability AtomicStorage + OpCapability Linkage + OpMemoryModel Logical GLSL450 +%intt = OpTypeInt 32 0 +%voidt = OpTypeVoid +%vfunct = OpTypeFunction %voidt +%ptrt = OpTypePointer Function %intt +%func = OpFunction %voidt None %vfunct +%funcl = OpLabel +%var = OpVariable %ptrt )" << GetParam() << R"( + OpReturn + OpFunctionEnd +)"; + + CompileSuccessfully(ss.str()); + ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr( + "Variables must have a function[7] storage class inside of a function")); +} + +INSTANTIATE_TEST_CASE_P(MatrixOp, ValidateStorage, + ::testing::Values( + "Input", + "Uniform", + "Output", + "Workgroup", + "CrossWorkgroup", + "Private", + "PushConstant", + "AtomicCounter", + "Image"),); +// clang-format on + +TEST_F(ValidateStorage, GenericVariableOutsideFunction) { + const auto str = R"( + OpCapability Kernel + OpCapability Linkage + OpMemoryModel Logical OpenCL +%intt = OpTypeInt 32 0 +%ptrt = OpTypePointer Function %intt +%var = OpVariable %ptrt Generic +)"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpVariable storage class cannot be Generic")); +} + +TEST_F(ValidateStorage, GenericVariableInsideFunction) { + const auto str = R"( + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 +%intt = OpTypeInt 32 1 +%voidt = OpTypeVoid +%vfunct = OpTypeFunction %voidt +%ptrt = OpTypePointer Function %intt +%func = OpFunction %voidt None %vfunct +%funcl = OpLabel +%var = OpVariable %ptrt Generic + OpReturn + OpFunctionEnd +)"; + CompileSuccessfully(str); + ASSERT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpVariable storage class cannot be Generic")); +} +} // namespace diff --git a/3rdparty/spirv-tools/test/val/val_type_unique_test.cpp b/3rdparty/spirv-tools/test/val/val_type_unique_test.cpp new file mode 100644 index 000000000..ad16f246d --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_type_unique_test.cpp @@ -0,0 +1,269 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Tests for unique type declaration rules validator. + +#include + +#include "gmock/gmock.h" +#include "unit_spirv.h" +#include "val_fixtures.h" + +namespace { + +using ::testing::HasSubstr; +using ::testing::Not; + +using std::string; + +using ValidateTypeUnique = spvtest::ValidateBase; + +const spv_result_t kDuplicateTypeError = SPV_ERROR_INVALID_DATA; + +const string& GetHeader() { + static const string header = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +%floatt = OpTypeFloat 32 +%vec2t = OpTypeVector %floatt 2 +%vec3t = OpTypeVector %floatt 3 +%vec4t = OpTypeVector %floatt 4 +%mat22t = OpTypeMatrix %vec2t 2 +%mat33t = OpTypeMatrix %vec3t 3 +%mat44t = OpTypeMatrix %vec4t 4 +%intt = OpTypeInt 32 1 +%uintt = OpTypeInt 32 0 +%num3 = OpConstant %uintt 3 +%const3 = OpConstant %uintt 3 +%val3 = OpConstant %uintt 3 +%array = OpTypeArray %vec3t %num3 +%struct = OpTypeStruct %floatt %floatt %vec3t +%boolt = OpTypeBool +%array2 = OpTypeArray %vec3t %num3 +%voidt = OpTypeVoid +%vfunct = OpTypeFunction %voidt +%struct2 = OpTypeStruct %floatt %floatt %vec3t +%false = OpConstantFalse %boolt +%true = OpConstantTrue %boolt +%runtime_arrayt = OpTypeRuntimeArray %floatt +%runtime_arrayt2 = OpTypeRuntimeArray %floatt +)"; + + return header; +} + +const string& GetBody() { + static const string body = R"( +%main = OpFunction %voidt None %vfunct +%mainl = OpLabel +%a = OpIAdd %uintt %const3 %val3 +%b = OpIAdd %uintt %const3 %val3 +OpSelectionMerge %endl None +OpBranchConditional %true %truel %falsel +%truel = OpLabel +%add1 = OpIAdd %uintt %a %b +%add2 = OpIAdd %uintt %a %b +OpBranch %endl +%falsel = OpLabel +%sub1 = OpISub %uintt %a %b +%sub2 = OpISub %uintt %a %b +OpBranch %endl +%endl = OpLabel +OpReturn +OpFunctionEnd +)"; + + return body; +} + +// Returns expected error string if |opcode| produces a duplicate type +// declaration. +string GetErrorString(SpvOp opcode) { + return "Duplicate non-aggregate type declarations are not allowed. Opcode: " + + std::string(spvOpcodeString(opcode)); +} + +TEST_F(ValidateTypeUnique, success) { + string str = GetHeader() + GetBody(); + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateTypeUnique, duplicate_void) { + string str = GetHeader() + R"( +%boolt2 = OpTypeVoid +)" + GetBody(); + CompileSuccessfully(str.c_str()); + ASSERT_EQ(kDuplicateTypeError, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(GetErrorString(SpvOpTypeVoid))); +} + +TEST_F(ValidateTypeUnique, duplicate_bool) { + string str = GetHeader() + R"( +%boolt2 = OpTypeBool +)" + GetBody(); + CompileSuccessfully(str.c_str()); + ASSERT_EQ(kDuplicateTypeError, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(GetErrorString(SpvOpTypeBool))); +} + +TEST_F(ValidateTypeUnique, duplicate_int) { + string str = GetHeader() + R"( +%uintt2 = OpTypeInt 32 0 +)" + GetBody(); + CompileSuccessfully(str.c_str()); + ASSERT_EQ(kDuplicateTypeError, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(GetErrorString(SpvOpTypeInt))); +} + +TEST_F(ValidateTypeUnique, duplicate_float) { + string str = GetHeader() + R"( +%floatt2 = OpTypeFloat 32 +)" + GetBody(); + CompileSuccessfully(str.c_str()); + ASSERT_EQ(kDuplicateTypeError, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr(GetErrorString(SpvOpTypeFloat))); +} + +TEST_F(ValidateTypeUnique, duplicate_vec3) { + string str = GetHeader() + R"( +%vec3t2 = OpTypeVector %floatt 3 +)" + GetBody(); + CompileSuccessfully(str.c_str()); + ASSERT_EQ(kDuplicateTypeError, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr(GetErrorString(SpvOpTypeVector))); +} + +TEST_F(ValidateTypeUnique, duplicate_mat33) { + string str = GetHeader() + R"( +%mat33t2 = OpTypeMatrix %vec3t 3 +)" + GetBody(); + CompileSuccessfully(str.c_str()); + ASSERT_EQ(kDuplicateTypeError, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr(GetErrorString(SpvOpTypeMatrix))); +} + +TEST_F(ValidateTypeUnique, duplicate_vfunc) { + string str = GetHeader() + R"( +%vfunct2 = OpTypeFunction %voidt +)" + GetBody(); + CompileSuccessfully(str.c_str()); + ASSERT_EQ(kDuplicateTypeError, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr(GetErrorString(SpvOpTypeFunction))); +} + +TEST_F(ValidateTypeUnique, duplicate_pipe_storage) { + string str = R"( +OpCapability Addresses +OpCapability Kernel +OpCapability Linkage +OpCapability Pipes +OpCapability PipeStorage +OpMemoryModel Physical32 OpenCL +%ps = OpTypePipeStorage +%ps2 = OpTypePipeStorage +)"; + CompileSuccessfully(str.c_str(), SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(kDuplicateTypeError, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr(GetErrorString(SpvOpTypePipeStorage))); +} + +TEST_F(ValidateTypeUnique, duplicate_named_barrier) { + string str = R"( +OpCapability Addresses +OpCapability Kernel +OpCapability Linkage +OpCapability NamedBarrier +OpMemoryModel Physical32 OpenCL +%nb = OpTypeNamedBarrier +%nb2 = OpTypeNamedBarrier +)"; + CompileSuccessfully(str.c_str(), SPV_ENV_UNIVERSAL_1_1); + ASSERT_EQ(kDuplicateTypeError, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr(GetErrorString(SpvOpTypeNamedBarrier))); +} + +TEST_F(ValidateTypeUnique, duplicate_forward_pointer) { + string str = R"( +OpCapability Addresses +OpCapability Kernel +OpCapability GenericPointer +OpCapability Linkage +OpMemoryModel Physical32 OpenCL +OpTypeForwardPointer %ptr Generic +OpTypeForwardPointer %ptr2 Generic +%intt = OpTypeInt 32 0 +%floatt = OpTypeFloat 32 +%ptr = OpTypePointer Generic %intt +%ptr2 = OpTypePointer Generic %floatt +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateTypeUnique, duplicate_void_with_extension) { + string str = R"( +OpCapability Addresses +OpCapability Kernel +OpCapability Linkage +OpCapability Pipes +OpExtension "SPV_VALIDATOR_ignore_type_decl_unique" +OpMemoryModel Physical32 OpenCL +%voidt = OpTypeVoid +%voidt2 = OpTypeVoid +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + Not(HasSubstr(GetErrorString(SpvOpTypeVoid)))); +} + +TEST_F(ValidateTypeUnique, DuplicatePointerTypesNoExtension) { + string str = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +%u32 = OpTypeInt 32 0 +%ptr1 = OpTypePointer Input %u32 +%ptr2 = OpTypePointer Input %u32 +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(kDuplicateTypeError, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr(GetErrorString(SpvOpTypePointer))); +} + +TEST_F(ValidateTypeUnique, DuplicatePointerTypesWithExtension) { + string str = R"( +OpCapability Shader +OpCapability Linkage +OpExtension "SPV_KHR_variable_pointers" +OpMemoryModel Logical GLSL450 +%u32 = OpTypeInt 32 0 +%ptr1 = OpTypePointer Input %u32 +%ptr2 = OpTypePointer Input %u32 +)"; + CompileSuccessfully(str.c_str()); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + Not(HasSubstr(GetErrorString(SpvOpTypePointer)))); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/val/val_validation_state_test.cpp b/3rdparty/spirv-tools/test/val/val_validation_state_test.cpp new file mode 100644 index 000000000..7af6a4609 --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_validation_state_test.cpp @@ -0,0 +1,155 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Basic tests for the ValidationState_t datastructure. + +#include + +#include "gmock/gmock.h" +#include "spirv_validator_options.h" +#include "unit_spirv.h" +#include "val_fixtures.h" + +namespace { + +using std::string; +using ::testing::HasSubstr; + +using ValidationStateTest = spvtest::ValidateBase; + +const char header[] = + " OpCapability Shader" + " OpCapability Linkage" + " OpMemoryModel Logical GLSL450 "; + +const char kVoidFVoid[] = + " %void = OpTypeVoid" + " %void_f = OpTypeFunction %void" + " %func = OpFunction %void None %void_f" + " %label = OpLabel" + " OpReturn" + " OpFunctionEnd "; + +// Tests that the instruction count in ValidationState is correct. +TEST_F(ValidationStateTest, CheckNumInstructions) { + string spirv = string(header) + "%int = OpTypeInt 32 0"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState()); + EXPECT_EQ(size_t(4), vstate_->ordered_instructions().size()); +} + +// Tests that the number of global variables in ValidationState is correct. +TEST_F(ValidationStateTest, CheckNumGlobalVars) { + string spirv = string(header) + R"( + %int = OpTypeInt 32 0 +%_ptr_int = OpTypePointer Input %int + %var_1 = OpVariable %_ptr_int Input + %var_2 = OpVariable %_ptr_int Input + )"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState()); + EXPECT_EQ(unsigned(2), vstate_->num_global_vars()); +} + +// Tests that the number of local variables in ValidationState is correct. +TEST_F(ValidationStateTest, CheckNumLocalVars) { + string spirv = string(header) + R"( + %int = OpTypeInt 32 0 + %_ptr_int = OpTypePointer Function %int + %voidt = OpTypeVoid + %funct = OpTypeFunction %voidt + %main = OpFunction %voidt None %funct + %entry = OpLabel + %var_1 = OpVariable %_ptr_int Function + %var_2 = OpVariable %_ptr_int Function + %var_3 = OpVariable %_ptr_int Function + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState()); + EXPECT_EQ(unsigned(3), vstate_->num_local_vars()); +} + +// Tests that the "id bound" in ValidationState is correct. +TEST_F(ValidationStateTest, CheckIdBound) { + string spirv = string(header) + R"( + %int = OpTypeInt 32 0 + %voidt = OpTypeVoid + )"; + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState()); + EXPECT_EQ(unsigned(3), vstate_->getIdBound()); +} + +// Tests that the entry_points in ValidationState is correct. +TEST_F(ValidationStateTest, CheckEntryPoints) { + string spirv = string(header) + " OpEntryPoint Vertex %func \"shader\"" + + string(kVoidFVoid); + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState()); + EXPECT_EQ(size_t(1), vstate_->entry_points().size()); + EXPECT_EQ(SpvOpFunction, + vstate_->FindDef(vstate_->entry_points()[0])->opcode()); +} + +TEST_F(ValidationStateTest, CheckStructMemberLimitOption) { + spvValidatorOptionsSetUniversalLimit( + options_, spv_validator_limit_max_struct_members, 32000u); + EXPECT_EQ(32000u, options_->universal_limits_.max_struct_members); +} + +TEST_F(ValidationStateTest, CheckNumGlobalVarsLimitOption) { + spvValidatorOptionsSetUniversalLimit( + options_, spv_validator_limit_max_global_variables, 100u); + EXPECT_EQ(100u, options_->universal_limits_.max_global_variables); +} + +TEST_F(ValidationStateTest, CheckNumLocalVarsLimitOption) { + spvValidatorOptionsSetUniversalLimit( + options_, spv_validator_limit_max_local_variables, 100u); + EXPECT_EQ(100u, options_->universal_limits_.max_local_variables); +} + +TEST_F(ValidationStateTest, CheckStructDepthLimitOption) { + spvValidatorOptionsSetUniversalLimit( + options_, spv_validator_limit_max_struct_depth, 100u); + EXPECT_EQ(100u, options_->universal_limits_.max_struct_depth); +} + +TEST_F(ValidationStateTest, CheckSwitchBranchesLimitOption) { + spvValidatorOptionsSetUniversalLimit( + options_, spv_validator_limit_max_switch_branches, 100u); + EXPECT_EQ(100u, options_->universal_limits_.max_switch_branches); +} + +TEST_F(ValidationStateTest, CheckFunctionArgsLimitOption) { + spvValidatorOptionsSetUniversalLimit( + options_, spv_validator_limit_max_function_args, 100u); + EXPECT_EQ(100u, options_->universal_limits_.max_function_args); +} + +TEST_F(ValidationStateTest, CheckCFGDepthLimitOption) { + spvValidatorOptionsSetUniversalLimit( + options_, spv_validator_limit_max_control_flow_nesting_depth, 100u); + EXPECT_EQ(100u, options_->universal_limits_.max_control_flow_nesting_depth); +} + +TEST_F(ValidationStateTest, CheckAccessChainIndexesLimitOption) { + spvValidatorOptionsSetUniversalLimit( + options_, spv_validator_limit_max_access_chain_indexes, 100u); + EXPECT_EQ(100u, options_->universal_limits_.max_access_chain_indexes); +} + +} // anonymous namespace diff --git a/3rdparty/spirv-tools/test/val/val_version_test.cpp b/3rdparty/spirv-tools/test/val/val_version_test.cpp new file mode 100644 index 000000000..8c0ec4e1a --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_version_test.cpp @@ -0,0 +1,264 @@ +// Copyright (c) 2018 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "gmock/gmock.h" +#include "val_fixtures.h" + +using namespace spvtest; +using ::testing::HasSubstr; +using std::make_tuple; + +using ValidateVersion = + ValidateBase>; + +const std::string vulkan_spirv = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %func "func" +%void = OpTypeVoid +%functy = OpTypeFunction %void +%func = OpFunction %void None %functy +%1 = OpLabel +OpReturn +OpFunctionEnd +)"; + +const std::string opencl_spirv = R"( +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical OpenCL +)"; + +std::string version(spv_target_env env) { + switch (env) { + case SPV_ENV_UNIVERSAL_1_0: + case SPV_ENV_VULKAN_1_0: + case SPV_ENV_OPENGL_4_0: + case SPV_ENV_OPENGL_4_1: + case SPV_ENV_OPENGL_4_2: + case SPV_ENV_OPENGL_4_3: + case SPV_ENV_OPENGL_4_5: + case SPV_ENV_OPENCL_1_2: + case SPV_ENV_OPENCL_2_0: + case SPV_ENV_OPENCL_EMBEDDED_2_0: + return "1.0"; + case SPV_ENV_UNIVERSAL_1_1: + case SPV_ENV_OPENCL_2_1: + case SPV_ENV_OPENCL_EMBEDDED_2_1: + return "1.1"; + case SPV_ENV_UNIVERSAL_1_2: + case SPV_ENV_OPENCL_2_2: + case SPV_ENV_OPENCL_EMBEDDED_2_2: + return "1.2"; + case SPV_ENV_UNIVERSAL_1_3: + case SPV_ENV_VULKAN_1_1: + return "1.3"; + default: + return "0"; + } +} + +TEST_P(ValidateVersion, version) { + CompileSuccessfully(std::get<2>(GetParam()), std::get<0>(GetParam())); + spv_result_t res = ValidateInstructions(std::get<1>(GetParam())); + if (std::get<3>(GetParam())) { + ASSERT_EQ(SPV_SUCCESS, res); + } else { + ASSERT_EQ(SPV_ERROR_WRONG_VERSION, res); + + std::string msg = "Invalid SPIR-V binary version "; + msg += version(std::get<0>(GetParam())); + msg += " for target environment "; + msg += spvTargetEnvDescription(std::get<1>(GetParam())); + EXPECT_THAT(getDiagnosticString(), HasSubstr(msg)); + } +} + +// clang-format off +INSTANTIATE_TEST_CASE_P(Universal, ValidateVersion, + ::testing::Values( + // Binary version, Target environment + make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_0, vulkan_spirv, true), + make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1, vulkan_spirv, true), + make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_2, vulkan_spirv, true), + make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_3, vulkan_spirv, true), + make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_VULKAN_1_0, vulkan_spirv, true), + make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_VULKAN_1_1, vulkan_spirv, true), + make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_OPENGL_4_0, vulkan_spirv, true), + make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_OPENGL_4_1, vulkan_spirv, true), + make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_OPENGL_4_2, vulkan_spirv, true), + make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_OPENGL_4_3, vulkan_spirv, true), + make_tuple(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_OPENGL_4_5, vulkan_spirv, true), + + make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_UNIVERSAL_1_0, vulkan_spirv, false), + make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_UNIVERSAL_1_1, vulkan_spirv, true), + make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_UNIVERSAL_1_2, vulkan_spirv, true), + make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_UNIVERSAL_1_3, vulkan_spirv, true), + make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_VULKAN_1_0, vulkan_spirv, false), + make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_VULKAN_1_1, vulkan_spirv, true), + make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_OPENGL_4_0, vulkan_spirv, false), + make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_OPENGL_4_1, vulkan_spirv, false), + make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_OPENGL_4_2, vulkan_spirv, false), + make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_OPENGL_4_3, vulkan_spirv, false), + make_tuple(SPV_ENV_UNIVERSAL_1_1, SPV_ENV_OPENGL_4_5, vulkan_spirv, false), + + make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_0, vulkan_spirv, false), + make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_1, vulkan_spirv, false), + make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_2, vulkan_spirv, true), + make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_UNIVERSAL_1_3, vulkan_spirv, true), + make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_VULKAN_1_0, vulkan_spirv, false), + make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_VULKAN_1_1, vulkan_spirv, true), + make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_OPENGL_4_0, vulkan_spirv, false), + make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_OPENGL_4_1, vulkan_spirv, false), + make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_OPENGL_4_2, vulkan_spirv, false), + make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_OPENGL_4_3, vulkan_spirv, false), + make_tuple(SPV_ENV_UNIVERSAL_1_2, SPV_ENV_OPENGL_4_5, vulkan_spirv, false), + + make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_UNIVERSAL_1_0, vulkan_spirv, false), + make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_UNIVERSAL_1_1, vulkan_spirv, false), + make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_UNIVERSAL_1_2, vulkan_spirv, false), + make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_UNIVERSAL_1_3, vulkan_spirv, true), + make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_VULKAN_1_0, vulkan_spirv, false), + make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_VULKAN_1_1, vulkan_spirv, true), + make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_OPENGL_4_0, vulkan_spirv, false), + make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_OPENGL_4_1, vulkan_spirv, false), + make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_OPENGL_4_2, vulkan_spirv, false), + make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_OPENGL_4_3, vulkan_spirv, false), + make_tuple(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_OPENGL_4_5, vulkan_spirv, false) + ) +); + +INSTANTIATE_TEST_CASE_P(Vulkan, ValidateVersion, + ::testing::Values( + // Binary version, Target environment + make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_UNIVERSAL_1_0, vulkan_spirv, true), + make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_UNIVERSAL_1_1, vulkan_spirv, true), + make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_UNIVERSAL_1_2, vulkan_spirv, true), + make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_UNIVERSAL_1_3, vulkan_spirv, true), + make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_VULKAN_1_0, vulkan_spirv, true), + make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_VULKAN_1_1, vulkan_spirv, true), + make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_OPENGL_4_0, vulkan_spirv, true), + make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_OPENGL_4_1, vulkan_spirv, true), + make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_OPENGL_4_2, vulkan_spirv, true), + make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_OPENGL_4_3, vulkan_spirv, true), + make_tuple(SPV_ENV_VULKAN_1_0, SPV_ENV_OPENGL_4_5, vulkan_spirv, true), + + make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_UNIVERSAL_1_0, vulkan_spirv, false), + make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_UNIVERSAL_1_1, vulkan_spirv, false), + make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_UNIVERSAL_1_2, vulkan_spirv, false), + make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_UNIVERSAL_1_3, vulkan_spirv, true), + make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_VULKAN_1_0, vulkan_spirv, false), + make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_VULKAN_1_1, vulkan_spirv, true), + make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_OPENGL_4_0, vulkan_spirv, false), + make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_OPENGL_4_1, vulkan_spirv, false), + make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_OPENGL_4_2, vulkan_spirv, false), + make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_OPENGL_4_3, vulkan_spirv, false), + make_tuple(SPV_ENV_VULKAN_1_1, SPV_ENV_OPENGL_4_5, vulkan_spirv, false) + ) +); + +INSTANTIATE_TEST_CASE_P(OpenCL, ValidateVersion, + ::testing::Values( + // Binary version, Target environment + make_tuple(SPV_ENV_OPENCL_2_0, SPV_ENV_UNIVERSAL_1_0, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_2_0, SPV_ENV_UNIVERSAL_1_1, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_2_0, SPV_ENV_UNIVERSAL_1_2, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_2_0, SPV_ENV_UNIVERSAL_1_3, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_2_0, SPV_ENV_OPENCL_2_0, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_2_0, SPV_ENV_OPENCL_2_1, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_2_0, SPV_ENV_OPENCL_2_2, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_2_0, SPV_ENV_OPENCL_EMBEDDED_2_0, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_2_0, SPV_ENV_OPENCL_EMBEDDED_2_1, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_2_0, SPV_ENV_OPENCL_EMBEDDED_2_2, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_2_0, SPV_ENV_OPENCL_1_2, opencl_spirv, true), + + make_tuple(SPV_ENV_OPENCL_2_1, SPV_ENV_UNIVERSAL_1_0, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_2_1, SPV_ENV_UNIVERSAL_1_1, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_2_1, SPV_ENV_UNIVERSAL_1_2, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_2_1, SPV_ENV_UNIVERSAL_1_3, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_2_1, SPV_ENV_OPENCL_2_0, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_2_1, SPV_ENV_OPENCL_2_1, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_2_1, SPV_ENV_OPENCL_2_2, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_2_1, SPV_ENV_OPENCL_EMBEDDED_2_0, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_2_1, SPV_ENV_OPENCL_EMBEDDED_2_1, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_2_1, SPV_ENV_OPENCL_EMBEDDED_2_2, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_2_1, SPV_ENV_OPENCL_1_2, opencl_spirv, true), + + make_tuple(SPV_ENV_OPENCL_2_2, SPV_ENV_UNIVERSAL_1_0, opencl_spirv, false), + make_tuple(SPV_ENV_OPENCL_2_2, SPV_ENV_UNIVERSAL_1_1, opencl_spirv, false), + make_tuple(SPV_ENV_OPENCL_2_2, SPV_ENV_UNIVERSAL_1_2, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_2_2, SPV_ENV_UNIVERSAL_1_3, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_2_2, SPV_ENV_OPENCL_2_0, opencl_spirv, false), + make_tuple(SPV_ENV_OPENCL_2_2, SPV_ENV_OPENCL_2_1, opencl_spirv, false), + make_tuple(SPV_ENV_OPENCL_2_2, SPV_ENV_OPENCL_2_2, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_2_2, SPV_ENV_OPENCL_EMBEDDED_2_0, opencl_spirv, false), + make_tuple(SPV_ENV_OPENCL_2_2, SPV_ENV_OPENCL_EMBEDDED_2_1, opencl_spirv, false), + make_tuple(SPV_ENV_OPENCL_2_2, SPV_ENV_OPENCL_EMBEDDED_2_2, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_2_2, SPV_ENV_OPENCL_1_2, opencl_spirv, false), + + make_tuple(SPV_ENV_OPENCL_1_2, SPV_ENV_UNIVERSAL_1_0, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_1_2, SPV_ENV_UNIVERSAL_1_1, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_1_2, SPV_ENV_UNIVERSAL_1_2, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_1_2, SPV_ENV_UNIVERSAL_1_3, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_1_2, SPV_ENV_OPENCL_2_0, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_1_2, SPV_ENV_OPENCL_2_1, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_1_2, SPV_ENV_OPENCL_2_2, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_1_2, SPV_ENV_OPENCL_EMBEDDED_2_0, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_1_2, SPV_ENV_OPENCL_EMBEDDED_2_1, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_1_2, SPV_ENV_OPENCL_EMBEDDED_2_2, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_1_2, SPV_ENV_OPENCL_1_2, opencl_spirv, true) + ) +); + +INSTANTIATE_TEST_CASE_P(OpenCLEmbedded, ValidateVersion, + ::testing::Values( + // Binary version, Target environment + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_0, SPV_ENV_UNIVERSAL_1_0, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_0, SPV_ENV_UNIVERSAL_1_1, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_0, SPV_ENV_UNIVERSAL_1_2, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_0, SPV_ENV_UNIVERSAL_1_3, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_0, SPV_ENV_OPENCL_2_0, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_0, SPV_ENV_OPENCL_2_1, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_0, SPV_ENV_OPENCL_2_2, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_0, SPV_ENV_OPENCL_EMBEDDED_2_0, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_0, SPV_ENV_OPENCL_EMBEDDED_2_1, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_0, SPV_ENV_OPENCL_EMBEDDED_2_2, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_0, SPV_ENV_OPENCL_1_2, opencl_spirv, true), + + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_1, SPV_ENV_UNIVERSAL_1_0, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_1, SPV_ENV_UNIVERSAL_1_1, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_1, SPV_ENV_UNIVERSAL_1_2, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_1, SPV_ENV_UNIVERSAL_1_3, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_1, SPV_ENV_OPENCL_2_0, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_1, SPV_ENV_OPENCL_2_1, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_1, SPV_ENV_OPENCL_2_2, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_1, SPV_ENV_OPENCL_EMBEDDED_2_0, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_1, SPV_ENV_OPENCL_EMBEDDED_2_1, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_1, SPV_ENV_OPENCL_EMBEDDED_2_2, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_1, SPV_ENV_OPENCL_1_2, opencl_spirv, true), + + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_2, SPV_ENV_UNIVERSAL_1_0, opencl_spirv, false), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_2, SPV_ENV_UNIVERSAL_1_1, opencl_spirv, false), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_2, SPV_ENV_UNIVERSAL_1_2, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_2, SPV_ENV_UNIVERSAL_1_3, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_2, SPV_ENV_OPENCL_2_0, opencl_spirv, false), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_2, SPV_ENV_OPENCL_2_1, opencl_spirv, false), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_2, SPV_ENV_OPENCL_2_2, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_2, SPV_ENV_OPENCL_EMBEDDED_2_0, opencl_spirv, false), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_2, SPV_ENV_OPENCL_EMBEDDED_2_1, opencl_spirv, false), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_2, SPV_ENV_OPENCL_EMBEDDED_2_2, opencl_spirv, true), + make_tuple(SPV_ENV_OPENCL_EMBEDDED_2_2, SPV_ENV_OPENCL_1_2, opencl_spirv, false) + ) +); +// clang-format on diff --git a/3rdparty/spirv-tools/tools/CMakeLists.txt b/3rdparty/spirv-tools/tools/CMakeLists.txt new file mode 100644 index 000000000..5dacca38a --- /dev/null +++ b/3rdparty/spirv-tools/tools/CMakeLists.txt @@ -0,0 +1,80 @@ +# Copyright (c) 2015-2016 The Khronos Group Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +add_subdirectory(lesspipe) +add_subdirectory(emacs) + +# Add a SPIR-V Tools command line tool. Signature: +# add_spvtools_tool( +# TARGET target_name +# SRCS src_file1.cpp src_file2.cpp +# LIBS lib_target1 lib_target2 +# ) +function(add_spvtools_tool) + set(one_value_args TARGET) + set(multi_value_args SRCS LIBS) + cmake_parse_arguments( + ARG "" "${one_value_args}" "${multi_value_args}" ${ARGN}) + + add_executable(${ARG_TARGET} ${ARG_SRCS}) + spvtools_default_compile_options(${ARG_TARGET}) + target_link_libraries(${ARG_TARGET} PRIVATE ${ARG_LIBS}) + target_include_directories(${ARG_TARGET} PRIVATE + ${spirv-tools_SOURCE_DIR} + ${spirv-tools_BINARY_DIR} + ) + set_property(TARGET ${ARG_TARGET} PROPERTY FOLDER "SPIRV-Tools executables") +endfunction() + +if (NOT ${SPIRV_SKIP_EXECUTABLES}) + add_spvtools_tool(TARGET spirv-as SRCS as/as.cpp LIBS ${SPIRV_TOOLS}) + add_spvtools_tool(TARGET spirv-dis SRCS dis/dis.cpp LIBS ${SPIRV_TOOLS}) + add_spvtools_tool(TARGET spirv-val SRCS val/val.cpp LIBS ${SPIRV_TOOLS}) + add_spvtools_tool(TARGET spirv-opt SRCS opt/opt.cpp LIBS SPIRV-Tools-opt ${SPIRV_TOOLS}) + add_spvtools_tool(TARGET spirv-link SRCS link/linker.cpp LIBS SPIRV-Tools-link ${SPIRV_TOOLS}) + add_spvtools_tool(TARGET spirv-stats + SRCS stats/stats.cpp + stats/stats_analyzer.cpp + LIBS ${SPIRV_TOOLS}) + add_spvtools_tool(TARGET spirv-cfg + SRCS cfg/cfg.cpp + cfg/bin_to_dot.h + cfg/bin_to_dot.cpp + LIBS ${SPIRV_TOOLS}) + target_include_directories(spirv-cfg PRIVATE ${spirv-tools_SOURCE_DIR} + ${SPIRV_HEADER_INCLUDE_DIR}) + target_include_directories(spirv-stats PRIVATE ${spirv-tools_SOURCE_DIR} + ${SPIRV_HEADER_INCLUDE_DIR}) + + set(SPIRV_INSTALL_TARGETS spirv-as spirv-dis spirv-val spirv-opt spirv-stats + spirv-cfg spirv-link) + + if(SPIRV_BUILD_COMPRESSION) + add_spvtools_tool(TARGET spirv-markv + SRCS comp/markv.cpp + comp/markv_model_factory.cpp + comp/markv_model_shader.cpp + LIBS SPIRV-Tools-comp SPIRV-Tools-opt ${SPIRV_TOOLS}) + target_include_directories(spirv-markv PRIVATE ${spirv-tools_SOURCE_DIR} + ${SPIRV_HEADER_INCLUDE_DIR}) + set(SPIRV_INSTALL_TARGETS ${SPIRV_INSTALL_TARGETS} spirv-markv) + endif(SPIRV_BUILD_COMPRESSION) + + if(ENABLE_SPIRV_TOOLS_INSTALL) + install(TARGETS ${SPIRV_INSTALL_TARGETS} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + endif(ENABLE_SPIRV_TOOLS_INSTALL) +endif() diff --git a/3rdparty/spirv-tools/tools/as/as.cpp b/3rdparty/spirv-tools/tools/as/as.cpp new file mode 100644 index 000000000..0d9363f77 --- /dev/null +++ b/3rdparty/spirv-tools/tools/as/as.cpp @@ -0,0 +1,154 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "source/spirv_target_env.h" +#include "spirv-tools/libspirv.h" +#include "tools/io.h" + +void print_usage(char* argv0) { + printf( + R"(%s - Create a SPIR-V binary module from SPIR-V assembly text + +Usage: %s [options] [] + +The SPIR-V assembly text is read from . If no file is specified, +or if the filename is "-", then the binary is read from standard input. +The SPIR-V binary module is written to file "out.spv", unless the -o option +is used. + +Options: + + -h, --help Print this help. + + -o Set the output filename. Use '-' to mean stdout. + --version Display assembler version information. + --preserve-numeric-ids + Numeric IDs in the binary will have the same values as in the + source. Non-numeric IDs are allocated by filling in the gaps, + starting with 1 and going up. + --target-env {vulkan1.0|vulkan1.1|spv1.0|spv1.1|spv1.2|spv1.3} + Use Vulkan 1.0, Vulkan 1.1, SPIR-V 1.0, SPIR-V 1.1, + SPIR-V 1.2, or SPIR-V 1.3 +)", + argv0, argv0); +} + +static const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_3; + +int main(int argc, char** argv) { + const char* inFile = nullptr; + const char* outFile = nullptr; + uint32_t options = 0; + spv_target_env target_env = kDefaultEnvironment; + for (int argi = 1; argi < argc; ++argi) { + if ('-' == argv[argi][0]) { + switch (argv[argi][1]) { + case 'h': { + print_usage(argv[0]); + return 0; + } + case 'o': { + if (!outFile && argi + 1 < argc) { + outFile = argv[++argi]; + } else { + print_usage(argv[0]); + return 1; + } + } break; + case 0: { + // Setting a filename of "-" to indicate stdin. + if (!inFile) { + inFile = argv[argi]; + } else { + fprintf(stderr, "error: More than one input file specified\n"); + return 1; + } + } break; + case '-': { + // Long options + if (0 == strcmp(argv[argi], "--version")) { + printf("%s\n", spvSoftwareVersionDetailsString()); + printf("Target: %s\n", + spvTargetEnvDescription(kDefaultEnvironment)); + return 0; + } else if (0 == strcmp(argv[argi], "--help")) { + print_usage(argv[0]); + return 0; + } else if (0 == strcmp(argv[argi], "--preserve-numeric-ids")) { + options |= SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS; + } else if (0 == strcmp(argv[argi], "--target-env")) { + if (argi + 1 < argc) { + const auto env_str = argv[++argi]; + if (!spvParseTargetEnv(env_str, &target_env)) { + fprintf(stderr, "error: Unrecognized target env: %s\n", + env_str); + return 1; + } + } else { + fprintf(stderr, "error: Missing argument to --target-env\n"); + return 1; + } + } else { + fprintf(stderr, "error: Unrecognized option: %s\n\n", argv[argi]); + print_usage(argv[0]); + return 1; + } + } break; + default: + fprintf(stderr, "error: Unrecognized option: %s\n\n", argv[argi]); + print_usage(argv[0]); + return 1; + } + } else { + if (!inFile) { + inFile = argv[argi]; + } else { + fprintf(stderr, "error: More than one input file specified\n"); + return 1; + } + } + } + + if (!outFile) { + outFile = "out.spv"; + } + + std::vector contents; + if (!ReadFile(inFile, "r", &contents)) return 1; + + spv_binary binary; + spv_diagnostic diagnostic = nullptr; + spv_context context = spvContextCreate(target_env); + spv_result_t error = spvTextToBinaryWithOptions( + context, contents.data(), contents.size(), options, &binary, &diagnostic); + spvContextDestroy(context); + if (error) { + spvDiagnosticPrint(diagnostic); + spvDiagnosticDestroy(diagnostic); + return error; + } + + if (!WriteFile(outFile, "wb", binary->code, binary->wordCount)) { + spvBinaryDestroy(binary); + return 1; + } + + spvBinaryDestroy(binary); + + return 0; +} diff --git a/3rdparty/spirv-tools/tools/cfg/bin_to_dot.cpp b/3rdparty/spirv-tools/tools/cfg/bin_to_dot.cpp new file mode 100644 index 000000000..a7a2a2034 --- /dev/null +++ b/3rdparty/spirv-tools/tools/cfg/bin_to_dot.cpp @@ -0,0 +1,187 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "bin_to_dot.h" + +#include +#include +#include +#include + +#include "assembly_grammar.h" +#include "name_mapper.h" + +namespace { + +const char* kMergeStyle = "style=dashed"; +const char* kContinueStyle = "style=dotted"; + +// A DotConverter can be used to dump the GraphViz "dot" graph for +// a SPIR-V module. +class DotConverter { + public: + DotConverter(libspirv::NameMapper name_mapper, std::iostream* out) + : name_mapper_(std::move(name_mapper)), out_(*out) {} + + // Emits the graph preamble. + void Begin() const { + out_ << "digraph {\n"; + // Emit a simple legend + out_ << "legend_merge_src [shape=plaintext, label=\"\"];\n" + << "legend_merge_dest [shape=plaintext, label=\"\"];\n" + << "legend_merge_src -> legend_merge_dest [label=\" merge\"," + << kMergeStyle << "];\n" + << "legend_continue_src [shape=plaintext, label=\"\"];\n" + << "legend_continue_dest [shape=plaintext, label=\"\"];\n" + << "legend_continue_src -> legend_continue_dest [label=\" continue\"," + << kContinueStyle << "];\n"; + } + // Emits the graph postamble. + void End() const { out_ << "}\n"; } + + // Emits the Dot commands for the given instruction. + spv_result_t HandleInstruction(const spv_parsed_instruction_t& inst); + + private: + // Ends processing for the current block, emitting its dot code. + void FlushBlock(const std::vector& successors); + + // The ID of the current functio, or 0 if outside of a function. + uint32_t current_function_id_ = 0; + + // The ID of the current basic block, or 0 if outside of a block. + uint32_t current_block_id_ = 0; + + // Have we completed processing for the entry block to this fuction? + bool seen_function_entry_block_ = false; + + // The Id of the merge block for this block if it exists, or 0 otherwise. + uint32_t merge_ = 0; + // The Id of the continue target block for this block if it exists, or 0 + // otherwise. + uint32_t continue_target_ = 0; + + // An object for mapping Ids to names. + libspirv::NameMapper name_mapper_; + + // The output stream. + std::ostream& out_; +}; + +spv_result_t DotConverter::HandleInstruction( + const spv_parsed_instruction_t& inst) { + switch (inst.opcode) { + case SpvOpFunction: + current_function_id_ = inst.result_id; + seen_function_entry_block_ = false; + break; + case SpvOpFunctionEnd: + current_function_id_ = 0; + break; + + case SpvOpLabel: + current_block_id_ = inst.result_id; + break; + + case SpvOpBranch: + FlushBlock({inst.words[1]}); + break; + case SpvOpBranchConditional: + FlushBlock({inst.words[2], inst.words[3]}); + break; + case SpvOpSwitch: { + std::vector successors{inst.words[2]}; + for (size_t i = 3; i < inst.num_operands; i += 2) { + successors.push_back(inst.words[inst.operands[i].offset]); + } + FlushBlock(successors); + } break; + + case SpvOpKill: + case SpvOpReturn: + case SpvOpUnreachable: + case SpvOpReturnValue: + FlushBlock({}); + break; + + case SpvOpLoopMerge: + merge_ = inst.words[1]; + continue_target_ = inst.words[2]; + break; + case SpvOpSelectionMerge: + merge_ = inst.words[1]; + break; + default: + break; + } + return SPV_SUCCESS; +} + +void DotConverter::FlushBlock(const std::vector& successors) { + out_ << current_block_id_; + if (!seen_function_entry_block_) { + out_ << " [label=\"" << name_mapper_(current_block_id_) << "\nFn " + << name_mapper_(current_function_id_) << " entry\", shape=box];\n"; + } else { + out_ << " [label=\"" << name_mapper_(current_block_id_) << "\"];\n"; + } + + for (auto successor : successors) { + out_ << current_block_id_ << " -> " << successor << ";\n"; + } + + if (merge_) { + out_ << current_block_id_ << " -> " << merge_ << " [" << kMergeStyle + << "];\n"; + } + if (continue_target_) { + out_ << current_block_id_ << " -> " << continue_target_ << " [" + << kContinueStyle << "];\n"; + } + + // Reset the book-keeping for a block. + seen_function_entry_block_ = true; + merge_ = 0; + continue_target_ = 0; +} + +spv_result_t HandleInstruction( + void* user_data, const spv_parsed_instruction_t* parsed_instruction) { + assert(user_data); + auto converter = static_cast(user_data); + return converter->HandleInstruction(*parsed_instruction); +} + +} // anonymous namespace + +spv_result_t BinaryToDot(const spv_const_context context, const uint32_t* words, + size_t num_words, std::iostream* out, + spv_diagnostic* diagnostic) { + // Invalid arguments return error codes, but don't necessarily generate + // diagnostics. These are programmer errors, not user errors. + if (!diagnostic) return SPV_ERROR_INVALID_DIAGNOSTIC; + const libspirv::AssemblyGrammar grammar(context); + if (!grammar.isValid()) return SPV_ERROR_INVALID_TABLE; + + libspirv::FriendlyNameMapper friendly_mapper(context, words, num_words); + DotConverter converter(friendly_mapper.GetNameMapper(), out); + converter.Begin(); + if (auto error = spvBinaryParse(context, &converter, words, num_words, + nullptr, HandleInstruction, diagnostic)) { + return error; + } + converter.End(); + + return SPV_SUCCESS; +} diff --git a/3rdparty/spirv-tools/tools/cfg/bin_to_dot.h b/3rdparty/spirv-tools/tools/cfg/bin_to_dot.h new file mode 100644 index 000000000..1181b2521 --- /dev/null +++ b/3rdparty/spirv-tools/tools/cfg/bin_to_dot.h @@ -0,0 +1,27 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef BIN_TO_DOT_H_ +#define BIN_TO_DOT_H_ + +#include +#include "spirv-tools/libspirv.h" + +// Dumps the control flow graph for the given module to the output stream. +// Returns SPV_SUCCESS on succes. +spv_result_t BinaryToDot(const spv_const_context context, const uint32_t* words, + size_t num_words, std::iostream* out, + spv_diagnostic* diagnostic); + +#endif // BIN_TO_DOT_H_ diff --git a/3rdparty/spirv-tools/tools/cfg/cfg.cpp b/3rdparty/spirv-tools/tools/cfg/cfg.cpp new file mode 100644 index 000000000..2d0bcfae3 --- /dev/null +++ b/3rdparty/spirv-tools/tools/cfg/cfg.cpp @@ -0,0 +1,128 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include + +#include "spirv-tools/libspirv.h" +#include "tools/io.h" + +#include "bin_to_dot.h" + +// Prints a program usage message to stdout. +static void print_usage(const char* argv0) { + printf( + R"(%s - Show the control flow graph in GraphiViz "dot" form. EXPERIMENTAL + +Usage: %s [options] [] + +The SPIR-V binary is read from . If no file is specified, +or if the filename is "-", then the binary is read from standard input. + +Options: + + -h, --help Print this help. + --version Display version information. + + -o Set the output filename. + Output goes to standard output if this option is + not specified, or if the filename is "-". +)", + argv0, argv0); +} + +static const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_2; + +int main(int argc, char** argv) { + const char* inFile = nullptr; + const char* outFile = nullptr; // Stays nullptr if printing to stdout. + + for (int argi = 1; argi < argc; ++argi) { + if ('-' == argv[argi][0]) { + switch (argv[argi][1]) { + case 'h': + print_usage(argv[0]); + return 0; + case 'o': { + if (!outFile && argi + 1 < argc) { + outFile = argv[++argi]; + } else { + print_usage(argv[0]); + return 1; + } + } break; + case '-': { + // Long options + if (0 == strcmp(argv[argi], "--help")) { + print_usage(argv[0]); + return 0; + } else if (0 == strcmp(argv[argi], "--version")) { + printf("%s EXPERIMENTAL\n", spvSoftwareVersionDetailsString()); + printf("Target: %s\n", + spvTargetEnvDescription(kDefaultEnvironment)); + return 0; + } else { + print_usage(argv[0]); + return 1; + } + } break; + case 0: { + // Setting a filename of "-" to indicate stdin. + if (!inFile) { + inFile = argv[argi]; + } else { + fprintf(stderr, "error: More than one input file specified\n"); + return 1; + } + } break; + default: + print_usage(argv[0]); + return 1; + } + } else { + if (!inFile) { + inFile = argv[argi]; + } else { + fprintf(stderr, "error: More than one input file specified\n"); + return 1; + } + } + } + + // Read the input binary. + std::vector contents; + if (!ReadFile(inFile, "rb", &contents)) return 1; + spv_context context = spvContextCreate(kDefaultEnvironment); + spv_diagnostic diagnostic = nullptr; + + std::stringstream ss; + auto error = + BinaryToDot(context, contents.data(), contents.size(), &ss, &diagnostic); + if (error) { + spvDiagnosticPrint(diagnostic); + spvDiagnosticDestroy(diagnostic); + spvContextDestroy(context); + return error; + } + std::string str = ss.str(); + WriteFile(outFile, "w", str.data(), str.size()); + + spvDiagnosticDestroy(diagnostic); + spvContextDestroy(context); + + return 0; +} diff --git a/3rdparty/spirv-tools/tools/comp/markv.cpp b/3rdparty/spirv-tools/tools/comp/markv.cpp new file mode 100644 index 000000000..216b83a4a --- /dev/null +++ b/3rdparty/spirv-tools/tools/comp/markv.cpp @@ -0,0 +1,382 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "markv_model_factory.h" +#include "source/comp/markv.h" +#include "source/spirv_target_env.h" +#include "source/table.h" +#include "spirv-tools/optimizer.hpp" +#include "tools/io.h" + +namespace { + +const auto kSpvEnv = SPV_ENV_UNIVERSAL_1_2; + +enum Task { + kNoTask = 0, + kEncode, + kDecode, + kTest, +}; + +struct ScopedContext { + ScopedContext(spv_target_env env) : context(spvContextCreate(env)) {} + ~ScopedContext() { spvContextDestroy(context); } + spv_context context; +}; + +void print_usage(char* argv0) { + printf( + R"(%s - Encodes or decodes a SPIR-V binary to or from a MARK-V binary. + +USAGE: %s [e|d|t] [options] [] + +The input binary is read from . If no file is specified, +or if the filename is "-", then the binary is read from standard input. + +If no output is specified then the output is printed to stdout in a human +readable format. + +WIP: MARK-V codec is in early stages of development. At the moment it only +can encode and decode some SPIR-V files and only if exacly the same build of +software is used (is doesn't write or handle version numbers yet). + +Tasks: + e Encode SPIR-V to MARK-V. + d Decode MARK-V to SPIR-V. + t Test the codec by first encoding the given SPIR-V file to + MARK-V, then decoding it back to SPIR-V and comparing results. + +Options: + -h, --help Print this help. + --comments Write codec comments to stderr. + --version Display MARK-V codec version. + --validate Validate SPIR-V while encoding or decoding. + --model= + Compression model, possible values: + shader_lite - fast, poor compression ratio + shader_mid - balanced + shader_max - best compression ratio + Default: shader_lite + + -o Set the output filename. + Output goes to standard output if this option is + not specified, or if the filename is "-". + Not needed for 't' task (testing). +)", + argv0, argv0); +} + +void DiagnosticsMessageHandler(spv_message_level_t level, const char*, + const spv_position_t& position, + const char* message) { + switch (level) { + case SPV_MSG_FATAL: + case SPV_MSG_INTERNAL_ERROR: + case SPV_MSG_ERROR: + std::cerr << "error: " << position.index << ": " << message << std::endl; + break; + case SPV_MSG_WARNING: + std::cerr << "warning: " << position.index << ": " << message + << std::endl; + break; + case SPV_MSG_INFO: + std::cerr << "info: " << position.index << ": " << message << std::endl; + break; + default: + break; + } +} + +} // namespace + +int main(int argc, char** argv) { + const char* input_filename = nullptr; + const char* output_filename = nullptr; + + Task task = kNoTask; + + if (argc < 3) { + print_usage(argv[0]); + return 0; + } + + const char* task_char = argv[1]; + if (0 == strcmp("e", task_char)) { + task = kEncode; + } else if (0 == strcmp("d", task_char)) { + task = kDecode; + } else if (0 == strcmp("t", task_char)) { + task = kTest; + } + + if (task == kNoTask) { + print_usage(argv[0]); + return 1; + } + + bool want_comments = false; + bool validate_spirv_binary = false; + + spvtools::MarkvModelType model_type = spvtools::kMarkvModelUnknown; + + for (int argi = 2; argi < argc; ++argi) { + if ('-' == argv[argi][0]) { + switch (argv[argi][1]) { + case 'h': + print_usage(argv[0]); + return 0; + case 'o': { + if (!output_filename && argi + 1 < argc && + (task == kEncode || task == kDecode)) { + output_filename = argv[++argi]; + } else { + print_usage(argv[0]); + return 1; + } + } break; + case '-': { + if (0 == strcmp(argv[argi], "--help")) { + print_usage(argv[0]); + return 0; + } else if (0 == strcmp(argv[argi], "--comments")) { + want_comments = true; + } else if (0 == strcmp(argv[argi], "--version")) { + fprintf(stderr, "error: Not implemented\n"); + return 1; + } else if (0 == strcmp(argv[argi], "--validate")) { + validate_spirv_binary = true; + } else if (0 == strcmp(argv[argi], "--model=shader_lite")) { + if (model_type != spvtools::kMarkvModelUnknown) + fprintf(stderr, "error: More than one model specified\n"); + model_type = spvtools::kMarkvModelShaderLite; + } else if (0 == strcmp(argv[argi], "--model=shader_mid")) { + if (model_type != spvtools::kMarkvModelUnknown) + fprintf(stderr, "error: More than one model specified\n"); + model_type = spvtools::kMarkvModelShaderMid; + } else if (0 == strcmp(argv[argi], "--model=shader_max")) { + if (model_type != spvtools::kMarkvModelUnknown) + fprintf(stderr, "error: More than one model specified\n"); + model_type = spvtools::kMarkvModelShaderMax; + } else { + print_usage(argv[0]); + return 1; + } + } break; + case '\0': { + // Setting a filename of "-" to indicate stdin. + if (!input_filename) { + input_filename = argv[argi]; + } else { + fprintf(stderr, "error: More than one input file specified\n"); + return 1; + } + } break; + default: + print_usage(argv[0]); + return 1; + } + } else { + if (!input_filename) { + input_filename = argv[argi]; + } else { + fprintf(stderr, "error: More than one input file specified\n"); + return 1; + } + } + } + + if (model_type == spvtools::kMarkvModelUnknown) + model_type = spvtools::kMarkvModelShaderLite; + + const auto no_comments = spvtools::MarkvLogConsumer(); + const auto output_to_stderr = [](const std::string& str) { + std::cerr << str; + }; + + ScopedContext ctx(kSpvEnv); + + std::unique_ptr model = + spvtools::CreateMarkvModel(model_type); + + std::vector spirv; + std::vector markv; + + spvtools::MarkvCodecOptions options; + options.validate_spirv_binary = validate_spirv_binary; + + if (task == kEncode) { + if (!ReadFile(input_filename, "rb", &spirv)) return 1; + assert(!spirv.empty()); + + if (SPV_SUCCESS != + spvtools::SpirvToMarkv(ctx.context, spirv, options, *model, + DiagnosticsMessageHandler, + want_comments ? output_to_stderr : no_comments, + spvtools::MarkvDebugConsumer(), &markv)) { + std::cerr << "error: Failed to encode " << input_filename << " to MARK-V " + << std::endl; + return 1; + } + + if (!WriteFile(output_filename, "wb", markv.data(), markv.size())) + return 1; + } else if (task == kDecode) { + if (!ReadFile(input_filename, "rb", &markv)) return 1; + assert(!markv.empty()); + + if (SPV_SUCCESS != + spvtools::MarkvToSpirv(ctx.context, markv, options, *model, + DiagnosticsMessageHandler, + want_comments ? output_to_stderr : no_comments, + spvtools::MarkvDebugConsumer(), &spirv)) { + std::cerr << "error: Failed to decode " << input_filename << " to SPIR-V " + << std::endl; + return 1; + } + + if (!WriteFile(output_filename, "wb", spirv.data(), spirv.size())) + return 1; + } else if (task == kTest) { + if (!ReadFile(input_filename, "rb", &spirv)) return 1; + assert(!spirv.empty()); + + std::vector spirv_before; + spvtools::Optimizer optimizer(kSpvEnv); + optimizer.RegisterPass(spvtools::CreateCompactIdsPass()); + if (!optimizer.Run(spirv.data(), spirv.size(), &spirv_before)) { + std::cerr << "error: Optimizer failure on: " << input_filename + << std::endl; + } + + std::vector encoder_instruction_bits; + std::vector encoder_instruction_comments; + std::vector> encoder_instruction_words; + std::vector decoder_instruction_bits; + std::vector decoder_instruction_comments; + std::vector> decoder_instruction_words; + + const auto encoder_debug_consumer = [&](const std::vector& words, + const std::string& bits, + const std::string& comment) { + encoder_instruction_words.push_back(words); + encoder_instruction_bits.push_back(bits); + encoder_instruction_comments.push_back(comment); + return true; + }; + + if (SPV_SUCCESS != + spvtools::SpirvToMarkv(ctx.context, spirv_before, options, *model, + DiagnosticsMessageHandler, + want_comments ? output_to_stderr : no_comments, + encoder_debug_consumer, &markv)) { + std::cerr << "error: Failed to encode " << input_filename << " to MARK-V " + << std::endl; + return 1; + } + + const auto write_bug_report = [&]() { + for (size_t inst_index = 0; inst_index < decoder_instruction_words.size(); + ++inst_index) { + std::cerr << "\nInstruction #" << inst_index << std::endl; + std::cerr << "\nEncoder words: "; + for (uint32_t word : encoder_instruction_words[inst_index]) + std::cerr << word << " "; + std::cerr << "\nDecoder words: "; + for (uint32_t word : decoder_instruction_words[inst_index]) + std::cerr << word << " "; + std::cerr << std::endl; + + std::cerr << "\nEncoder bits: " << encoder_instruction_bits[inst_index]; + std::cerr << "\nDecoder bits: " << decoder_instruction_bits[inst_index]; + std::cerr << std::endl; + + std::cerr << "\nEncoder comments:\n" + << encoder_instruction_comments[inst_index]; + std::cerr << "Decoder comments:\n" + << decoder_instruction_comments[inst_index]; + std::cerr << std::endl; + } + }; + + const auto decoder_debug_consumer = [&](const std::vector& words, + const std::string& bits, + const std::string& comment) { + const size_t inst_index = decoder_instruction_words.size(); + if (inst_index >= encoder_instruction_words.size()) { + write_bug_report(); + std::cerr << "error: Decoder has more instructions than encoder: " + << input_filename << std::endl; + return false; + } + + decoder_instruction_words.push_back(words); + decoder_instruction_bits.push_back(bits); + decoder_instruction_comments.push_back(comment); + + if (encoder_instruction_words[inst_index] != + decoder_instruction_words[inst_index]) { + write_bug_report(); + std::cerr << "error: Words of the last decoded instruction differ from " + "reference: " + << input_filename << std::endl; + return false; + } + + if (encoder_instruction_bits[inst_index] != + decoder_instruction_bits[inst_index]) { + write_bug_report(); + std::cerr << "error: Bits of the last decoded instruction differ from " + "reference: " + << input_filename << std::endl; + return false; + } + return true; + }; + + std::vector spirv_after; + const spv_result_t decoding_result = spvtools::MarkvToSpirv( + ctx.context, markv, options, *model, DiagnosticsMessageHandler, + want_comments ? output_to_stderr : no_comments, decoder_debug_consumer, + &spirv_after); + + if (decoding_result == SPV_REQUESTED_TERMINATION) { + std::cerr << "error: Decoding interrupted by the debugger: " + << input_filename << std::endl; + return 1; + } + + if (decoding_result != SPV_SUCCESS) { + std::cerr << "error: Failed to decode encoded " << input_filename + << " back to SPIR-V " << std::endl; + return 1; + } + + assert(spirv_before.size() == spirv_after.size()); + assert(std::mismatch(std::next(spirv_before.begin(), 5), spirv_before.end(), + std::next(spirv_after.begin(), 5)) == + std::make_pair(spirv_before.end(), spirv_after.end())); + } + + return 0; +} diff --git a/3rdparty/spirv-tools/tools/comp/markv_model_factory.cpp b/3rdparty/spirv-tools/tools/comp/markv_model_factory.cpp new file mode 100644 index 000000000..ce190417c --- /dev/null +++ b/3rdparty/spirv-tools/tools/comp/markv_model_factory.cpp @@ -0,0 +1,47 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "markv_model_factory.h" + +#include "markv_model_shader.h" + +namespace spvtools { + +std::unique_ptr CreateMarkvModel(MarkvModelType type) { + std::unique_ptr model; + switch (type) { + case kMarkvModelShaderLite: { + model.reset(new MarkvModelShaderLite()); + break; + } + case kMarkvModelShaderMid: { + model.reset(new MarkvModelShaderMid()); + break; + } + case kMarkvModelShaderMax: { + model.reset(new MarkvModelShaderMax()); + break; + } + case kMarkvModelUnknown: { + assert(0 && "kMarkvModelUnknown supplied to CreateMarkvModel"); + return model; + } + } + + model->SetModelType(static_cast(type)); + + return model; +} + +} // namespace spvtools diff --git a/3rdparty/spirv-tools/tools/comp/markv_model_factory.h b/3rdparty/spirv-tools/tools/comp/markv_model_factory.h new file mode 100644 index 000000000..b0bf5e7a4 --- /dev/null +++ b/3rdparty/spirv-tools/tools/comp/markv_model_factory.h @@ -0,0 +1,35 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SPIRV_TOOLS_COMP_MARKV_MODEL_FACTORY_H_ +#define SPIRV_TOOLS_COMP_MARKV_MODEL_FACTORY_H_ + +#include + +#include "source/comp/markv_model.h" + +namespace spvtools { + +enum MarkvModelType { + kMarkvModelUnknown = 0, + kMarkvModelShaderLite, + kMarkvModelShaderMid, + kMarkvModelShaderMax, +}; + +std::unique_ptr CreateMarkvModel(MarkvModelType type); + +} // namespace spvtools + +#endif // SPIRV_TOOLS_COMP_MARKV_MODEL_FACTORY_H_ diff --git a/3rdparty/spirv-tools/tools/comp/markv_model_shader.cpp b/3rdparty/spirv-tools/tools/comp/markv_model_shader.cpp new file mode 100644 index 000000000..f96c277d0 --- /dev/null +++ b/3rdparty/spirv-tools/tools/comp/markv_model_shader.cpp @@ -0,0 +1,100 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "markv_model_shader.h" + +#include +#include +#include +#include +#include +#include + +using spvutils::HuffmanCodec; + +namespace spvtools { + +namespace { + +// Signals that the value is not in the coding scheme and a fallback method +// needs to be used. +const uint64_t kMarkvNoneOfTheAbove = MarkvModel::GetMarkvNoneOfTheAbove(); + +inline uint32_t CombineOpcodeAndNumOperands(uint32_t opcode, + uint32_t num_operands) { + return opcode | (num_operands << 16); +} + +// The following file contains autogenerated statistical coding rules. +// Can be generated by running spirv-stats on representative corpus of shaders +// with flags: +// --codegen_opcode_and_num_operands_hist +// --codegen_opcode_and_num_operands_markov_huffman_codecs +// --codegen_literal_string_huffman_codecs +// --codegen_non_id_word_huffman_codecs +// --codegen_id_descriptor_huffman_codecs +// +// Example: +// find -type f -print0 | xargs -0 -s 2000000 +// ~/SPIRV-Tools/build/tools/spirv-stats -v +// --codegen_opcode_and_num_operands_hist +// --codegen_opcode_and_num_operands_markov_huffman_codecs +// --codegen_literal_string_huffman_codecs --codegen_non_id_word_huffman_codecs +// --codegen_id_descriptor_huffman_codecs -o +// ~/SPIRV-Tools/source/comp/markv_autogen.inc +#include "markv_model_shader_default_autogen.inc" + +} // namespace + +MarkvModelShaderLite::MarkvModelShaderLite() { + const uint16_t kVersionNumber = 1; + SetModelVersion(kVersionNumber); + + opcode_and_num_operands_huffman_codec_.reset( + new HuffmanCodec(GetOpcodeAndNumOperandsHist())); + + id_fallback_strategy_ = IdFallbackStrategy::kShortDescriptor; +} + +MarkvModelShaderMid::MarkvModelShaderMid() { + const uint16_t kVersionNumber = 1; + SetModelVersion(kVersionNumber); + + opcode_and_num_operands_huffman_codec_.reset( + new HuffmanCodec(GetOpcodeAndNumOperandsHist())); + non_id_word_huffman_codecs_ = GetNonIdWordHuffmanCodecs(); + id_descriptor_huffman_codecs_ = GetIdDescriptorHuffmanCodecs(); + descriptors_with_coding_scheme_ = GetDescriptorsWithCodingScheme(); + literal_string_huffman_codecs_ = GetLiteralStringHuffmanCodecs(); + + id_fallback_strategy_ = IdFallbackStrategy::kShortDescriptor; +} + +MarkvModelShaderMax::MarkvModelShaderMax() { + const uint16_t kVersionNumber = 1; + SetModelVersion(kVersionNumber); + + opcode_and_num_operands_huffman_codec_.reset( + new HuffmanCodec(GetOpcodeAndNumOperandsHist())); + opcode_and_num_operands_markov_huffman_codecs_ = + GetOpcodeAndNumOperandsMarkovHuffmanCodecs(); + non_id_word_huffman_codecs_ = GetNonIdWordHuffmanCodecs(); + id_descriptor_huffman_codecs_ = GetIdDescriptorHuffmanCodecs(); + descriptors_with_coding_scheme_ = GetDescriptorsWithCodingScheme(); + literal_string_huffman_codecs_ = GetLiteralStringHuffmanCodecs(); + + id_fallback_strategy_ = IdFallbackStrategy::kRuleBased; +} + +} // namespace spvtools diff --git a/3rdparty/spirv-tools/tools/comp/markv_model_shader.h b/3rdparty/spirv-tools/tools/comp/markv_model_shader.h new file mode 100644 index 000000000..f45a86949 --- /dev/null +++ b/3rdparty/spirv-tools/tools/comp/markv_model_shader.h @@ -0,0 +1,45 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SPIRV_TOOLS_MARKV_MODEL_SHADER_H_ +#define SPIRV_TOOLS_MARKV_MODEL_SHADER_H_ + +#include "source/comp/markv_model.h" + +namespace spvtools { + +// MARK-V shader compression model, which only uses fast and lightweight +// algorithms, which do not require training and are not heavily dependent on +// SPIR-V grammar. Compression ratio is worse than by other models. +class MarkvModelShaderLite : public MarkvModel { + public: + MarkvModelShaderLite(); +}; + +// MARK-V shader compression model with balanced compression ratio and runtime +// performance. +class MarkvModelShaderMid : public MarkvModel { + public: + MarkvModelShaderMid(); +}; + +// MARK-V shader compression model designed for maximum compression. +class MarkvModelShaderMax : public MarkvModel { + public: + MarkvModelShaderMax(); +}; + +} // namespace spvtools + +#endif // SPIRV_TOOLS_MARKV_MODEL_SHADER_H_ diff --git a/3rdparty/spirv-tools/tools/comp/markv_model_shader_default_autogen.inc b/3rdparty/spirv-tools/tools/comp/markv_model_shader_default_autogen.inc new file mode 100644 index 000000000..0093cf1c0 --- /dev/null +++ b/3rdparty/spirv-tools/tools/comp/markv_model_shader_default_autogen.inc @@ -0,0 +1,14519 @@ + +std::map GetOpcodeAndNumOperandsHist() { + return std::map({ + { CombineOpcodeAndNumOperands(SpvOpExtInst, 7), 158282 }, + { CombineOpcodeAndNumOperands(SpvOpDot, 4), 151035 }, + { CombineOpcodeAndNumOperands(SpvOpVectorShuffle, 6), 183292 }, + { CombineOpcodeAndNumOperands(SpvOpImageSampleImplicitLod, 4), 126492 }, + { CombineOpcodeAndNumOperands(SpvOpExecutionMode, 2), 13311 }, + { CombineOpcodeAndNumOperands(SpvOpFNegate, 3), 29952 }, + { CombineOpcodeAndNumOperands(SpvOpExtInst, 5), 106847 }, + { CombineOpcodeAndNumOperands(SpvOpImageSampleExplicitLod, 7), 26350 }, + { CombineOpcodeAndNumOperands(SpvOpImageSampleExplicitLod, 6), 28186 }, + { CombineOpcodeAndNumOperands(SpvOpFDiv, 4), 41635 }, + { CombineOpcodeAndNumOperands(SpvOpFMul, 4), 412786 }, + { CombineOpcodeAndNumOperands(SpvOpFunction, 4), 62905 }, + { CombineOpcodeAndNumOperands(SpvOpVectorShuffle, 8), 118614 }, + { CombineOpcodeAndNumOperands(SpvOpDecorate, 2), 100735 }, + { CombineOpcodeAndNumOperands(SpvOpReturnValue, 1), 40852 }, + { CombineOpcodeAndNumOperands(SpvOpVectorTimesScalar, 4), 157091 }, + { CombineOpcodeAndNumOperands(SpvOpExtInst, 6), 122100 }, + { CombineOpcodeAndNumOperands(SpvOpAccessChain, 5), 82930 }, + { CombineOpcodeAndNumOperands(SpvOpFSub, 4), 161019 }, + { CombineOpcodeAndNumOperands(SpvOpConstant, 3), 466014 }, + { CombineOpcodeAndNumOperands(SpvOpCompositeExtract, 5), 107126 }, + { CombineOpcodeAndNumOperands(SpvOpTypeImage, 8), 34775 }, + { CombineOpcodeAndNumOperands(SpvOpImageSampleDrefExplicitLod, 7), 26146 }, + { CombineOpcodeAndNumOperands(SpvOpMemoryModel, 2), 18879 }, + { CombineOpcodeAndNumOperands(SpvOpDecorate, 3), 485251 }, + { CombineOpcodeAndNumOperands(SpvOpCompositeConstruct, 4), 78011 }, + { CombineOpcodeAndNumOperands(SpvOpTypeFloat, 2), 18879 }, + { CombineOpcodeAndNumOperands(SpvOpVectorTimesMatrix, 4), 15848 }, + { CombineOpcodeAndNumOperands(SpvOpTypeVector, 3), 69404 }, + { CombineOpcodeAndNumOperands(SpvOpTypeFunction, 3), 19998 }, + { CombineOpcodeAndNumOperands(SpvOpConstantComposite, 6), 40228 }, + { CombineOpcodeAndNumOperands(SpvOpCapability, 1), 22510 }, + { CombineOpcodeAndNumOperands(SpvOpTypeArray, 3), 37585 }, + { CombineOpcodeAndNumOperands(SpvOpTypeInt, 3), 30454 }, + { CombineOpcodeAndNumOperands(SpvOpFunctionCall, 4), 29021 }, + { CombineOpcodeAndNumOperands(SpvOpFAdd, 4), 342237 }, + { CombineOpcodeAndNumOperands(SpvOpTypeMatrix, 3), 24449 }, + { CombineOpcodeAndNumOperands(SpvOpLabel, 1), 129408 }, + { CombineOpcodeAndNumOperands(SpvOpTypePointer, 3), 246535 }, + { CombineOpcodeAndNumOperands(SpvOpAccessChain, 4), 503456 }, + { CombineOpcodeAndNumOperands(SpvOpTypeFunction, 2), 19779 }, + { CombineOpcodeAndNumOperands(SpvOpBranchConditional, 3), 24139 }, + { CombineOpcodeAndNumOperands(SpvOpVariable, 3), 697946 }, + { CombineOpcodeAndNumOperands(SpvOpConstantComposite, 5), 55769 }, + { CombineOpcodeAndNumOperands(SpvOpTypeVoid, 1), 18879 }, + { CombineOpcodeAndNumOperands(SpvOpCompositeConstruct, 6), 145508 }, + { CombineOpcodeAndNumOperands(SpvOpFunctionParameter, 2), 85583 }, + { CombineOpcodeAndNumOperands(SpvOpTypeSampledImage, 2), 34775 }, + { CombineOpcodeAndNumOperands(SpvOpConstantComposite, 4), 66362 }, + { CombineOpcodeAndNumOperands(SpvOpLoad, 3), 1272902 }, + { CombineOpcodeAndNumOperands(SpvOpReturn, 0), 22122 }, + { CombineOpcodeAndNumOperands(SpvOpCompositeExtract, 4), 861008 }, + { CombineOpcodeAndNumOperands(SpvOpFunctionEnd, 0), 62905 }, + { CombineOpcodeAndNumOperands(SpvOpExtInstImport, 2), 18879 }, + { CombineOpcodeAndNumOperands(SpvOpSelectionMerge, 2), 22009 }, + { CombineOpcodeAndNumOperands(SpvOpBranch, 1), 38275 }, + { CombineOpcodeAndNumOperands(SpvOpTypeBool, 1), 12208 }, + { CombineOpcodeAndNumOperands(SpvOpSampledImage, 4), 95518 }, + { CombineOpcodeAndNumOperands(SpvOpMemberDecorate, 3), 94887 }, + { CombineOpcodeAndNumOperands(SpvOpMemberDecorate, 4), 1942215 }, + { CombineOpcodeAndNumOperands(SpvOpCompositeConstruct, 5), 205266 }, + { CombineOpcodeAndNumOperands(SpvOpUndef, 2), 22157 }, + { CombineOpcodeAndNumOperands(SpvOpCompositeInsert, 5), 142749 }, + { CombineOpcodeAndNumOperands(SpvOpCompositeInsert, 6), 24420 }, + { CombineOpcodeAndNumOperands(SpvOpCompositeExtract, 6), 16896 }, + { CombineOpcodeAndNumOperands(SpvOpStore, 2), 604982 }, + { CombineOpcodeAndNumOperands(SpvOpIAdd, 4), 14471 }, + { CombineOpcodeAndNumOperands(SpvOpVectorShuffle, 7), 269658 }, + { kMarkvNoneOfTheAbove, 399895 }, + }); +} + +std::map>> +GetOpcodeAndNumOperandsMarkovHuffmanCodecs() { + std::map>> codecs; + { + std::unique_ptr> codec(new HuffmanCodec(35, { + {0, 0, 0}, + {65790, 0, 0}, + {131134, 0, 0}, + {196669, 0, 0}, + {262209, 0, 0}, + {262221, 0, 0}, + {262225, 0, 0}, + {262230, 0, 0}, + {262273, 0, 0}, + {262277, 0, 0}, + {262286, 0, 0}, + {327745, 0, 0}, + {327761, 0, 0}, + {327762, 0, 0}, + {393295, 0, 0}, + {393304, 0, 0}, + {458831, 0, 0}, + {458840, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 11, 8}, + {0, 12, 19}, + {0, 18, 20}, + {0, 5, 21}, + {0, 15, 7}, + {0, 10, 1}, + {0, 23, 22}, + {0, 14, 24}, + {0, 6, 4}, + {0, 2, 17}, + {0, 13, 25}, + {0, 9, 26}, + {0, 28, 27}, + {0, 3, 29}, + {0, 30, 16}, + {0, 32, 31}, + {0, 34, 33}, + })); + + codecs.emplace(SpvOpImageSampleExplicitLod, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(55, { + {0, 0, 0}, + {65785, 0, 0}, + {65790, 0, 0}, + {131134, 0, 0}, + {196669, 0, 0}, + {196735, 0, 0}, + {262201, 0, 0}, + {262209, 0, 0}, + {262224, 0, 0}, + {262225, 0, 0}, + {262231, 0, 0}, + {262273, 0, 0}, + {262275, 0, 0}, + {262277, 0, 0}, + {262280, 0, 0}, + {262286, 0, 0}, + {327692, 0, 0}, + {327745, 0, 0}, + {327760, 0, 0}, + {327762, 0, 0}, + {393228, 0, 0}, + {393295, 0, 0}, + {393296, 0, 0}, + {393303, 0, 0}, + {393304, 0, 0}, + {458764, 0, 0}, + {458831, 0, 0}, + {524367, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 14, 5}, + {0, 29, 17}, + {0, 1, 30}, + {0, 10, 20}, + {0, 32, 31}, + {0, 33, 2}, + {0, 34, 23}, + {0, 8, 35}, + {0, 6, 36}, + {0, 19, 22}, + {0, 28, 25}, + {0, 38, 37}, + {0, 13, 39}, + {0, 40, 24}, + {0, 27, 21}, + {0, 26, 41}, + {0, 42, 12}, + {0, 15, 43}, + {0, 44, 18}, + {0, 45, 3}, + {0, 11, 7}, + {0, 16, 46}, + {0, 47, 9}, + {0, 4, 48}, + {0, 50, 49}, + {0, 52, 51}, + {0, 54, 53}, + })); + + codecs.emplace(SpvOpFDiv, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(19, { + {0, 0, 0}, + {196669, 0, 0}, + {262209, 0, 0}, + {262224, 0, 0}, + {262225, 0, 0}, + {262231, 0, 0}, + {262286, 0, 0}, + {393295, 0, 0}, + {393304, 0, 0}, + {458840, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 8, 10}, + {0, 11, 3}, + {0, 2, 9}, + {0, 4, 1}, + {0, 5, 6}, + {0, 13, 12}, + {0, 15, 14}, + {0, 16, 7}, + {0, 18, 17}, + })); + + codecs.emplace(SpvOpSampledImage, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(67, { + {0, 0, 0}, + {65785, 0, 0}, + {65790, 0, 0}, + {131134, 0, 0}, + {131319, 0, 0}, + {196669, 0, 0}, + {196735, 0, 0}, + {262209, 0, 0}, + {262224, 0, 0}, + {262225, 0, 0}, + {262231, 0, 0}, + {262272, 0, 0}, + {262273, 0, 0}, + {262275, 0, 0}, + {262277, 0, 0}, + {262280, 0, 0}, + {262285, 0, 0}, + {262286, 0, 0}, + {262292, 0, 0}, + {327692, 0, 0}, + {327745, 0, 0}, + {327760, 0, 0}, + {327761, 0, 0}, + {327762, 0, 0}, + {393228, 0, 0}, + {393281, 0, 0}, + {393295, 0, 0}, + {393296, 0, 0}, + {393297, 0, 0}, + {393298, 0, 0}, + {393304, 0, 0}, + {458764, 0, 0}, + {458831, 0, 0}, + {524367, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 4, 10}, + {0, 30, 35}, + {0, 1, 36}, + {0, 11, 37}, + {0, 38, 6}, + {0, 16, 39}, + {0, 15, 40}, + {0, 25, 2}, + {0, 41, 20}, + {0, 26, 19}, + {0, 42, 29}, + {0, 28, 22}, + {0, 23, 34}, + {0, 44, 43}, + {0, 17, 45}, + {0, 24, 27}, + {0, 18, 33}, + {0, 47, 46}, + {0, 8, 48}, + {0, 50, 49}, + {0, 32, 51}, + {0, 31, 52}, + {0, 53, 21}, + {0, 54, 13}, + {0, 3, 55}, + {0, 7, 14}, + {0, 57, 56}, + {0, 58, 5}, + {0, 59, 9}, + {0, 61, 60}, + {0, 63, 62}, + {0, 64, 12}, + {0, 66, 65}, + })); + + codecs.emplace(SpvOpFMul, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(79, { + {0, 0, 0}, + {65785, 0, 0}, + {65790, 0, 0}, + {131134, 0, 0}, + {196669, 0, 0}, + {196735, 0, 0}, + {262201, 0, 0}, + {262209, 0, 0}, + {262224, 0, 0}, + {262225, 0, 0}, + {262230, 0, 0}, + {262231, 0, 0}, + {262272, 0, 0}, + {262273, 0, 0}, + {262275, 0, 0}, + {262277, 0, 0}, + {262280, 0, 0}, + {262286, 0, 0}, + {262288, 0, 0}, + {262292, 0, 0}, + {262328, 0, 0}, + {262334, 0, 0}, + {327692, 0, 0}, + {327737, 0, 0}, + {327745, 0, 0}, + {327760, 0, 0}, + {327761, 0, 0}, + {327762, 0, 0}, + {393228, 0, 0}, + {393281, 0, 0}, + {393295, 0, 0}, + {393296, 0, 0}, + {393297, 0, 0}, + {393303, 0, 0}, + {393304, 0, 0}, + {458764, 0, 0}, + {458831, 0, 0}, + {458840, 0, 0}, + {524345, 0, 0}, + {524367, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 38, 33}, + {0, 18, 41}, + {0, 42, 23}, + {0, 43, 6}, + {0, 34, 44}, + {0, 1, 45}, + {0, 31, 14}, + {0, 47, 46}, + {0, 48, 2}, + {0, 12, 21}, + {0, 49, 30}, + {0, 37, 50}, + {0, 51, 20}, + {0, 5, 24}, + {0, 40, 16}, + {0, 29, 13}, + {0, 26, 52}, + {0, 53, 17}, + {0, 36, 54}, + {0, 55, 28}, + {0, 57, 56}, + {0, 19, 25}, + {0, 39, 8}, + {0, 32, 58}, + {0, 59, 27}, + {0, 22, 10}, + {0, 35, 60}, + {0, 62, 61}, + {0, 63, 7}, + {0, 65, 64}, + {0, 4, 66}, + {0, 68, 67}, + {0, 11, 3}, + {0, 15, 69}, + {0, 9, 70}, + {0, 72, 71}, + {0, 74, 73}, + {0, 76, 75}, + {0, 78, 77}, + })); + + codecs.emplace(SpvOpFAdd, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(55, { + {0, 0, 0}, + {65556, 0, 0}, + {65562, 0, 0}, + {131073, 0, 0}, + {131094, 0, 0}, + {131105, 0, 0}, + {196629, 0, 0}, + {196631, 0, 0}, + {196632, 0, 0}, + {196636, 0, 0}, + {196640, 0, 0}, + {196641, 0, 0}, + {196651, 0, 0}, + {196667, 0, 0}, + {262177, 0, 0}, + {262188, 0, 0}, + {262198, 0, 0}, + {327713, 0, 0}, + {327724, 0, 0}, + {393249, 0, 0}, + {393260, 0, 0}, + {458785, 0, 0}, + {524313, 0, 0}, + {524321, 0, 0}, + {589857, 0, 0}, + {655393, 0, 0}, + {720929, 0, 0}, + {852001, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 26, 24}, + {0, 29, 27}, + {0, 4, 30}, + {0, 21, 9}, + {0, 31, 20}, + {0, 33, 32}, + {0, 34, 3}, + {0, 8, 35}, + {0, 36, 5}, + {0, 23, 16}, + {0, 38, 37}, + {0, 25, 2}, + {0, 39, 1}, + {0, 17, 40}, + {0, 41, 15}, + {0, 18, 42}, + {0, 43, 6}, + {0, 44, 14}, + {0, 28, 19}, + {0, 7, 45}, + {0, 46, 22}, + {0, 48, 47}, + {0, 49, 11}, + {0, 51, 50}, + {0, 12, 10}, + {0, 53, 52}, + {0, 13, 54}, + })); + + codecs.emplace(SpvOpTypePointer, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(57, { + {0, 0, 0}, + {65785, 0, 0}, + {65790, 0, 0}, + {131134, 0, 0}, + {196669, 0, 0}, + {196735, 0, 0}, + {262209, 0, 0}, + {262224, 0, 0}, + {262225, 0, 0}, + {262272, 0, 0}, + {262273, 0, 0}, + {262275, 0, 0}, + {262277, 0, 0}, + {262280, 0, 0}, + {262286, 0, 0}, + {262292, 0, 0}, + {262328, 0, 0}, + {327692, 0, 0}, + {327745, 0, 0}, + {327760, 0, 0}, + {327761, 0, 0}, + {327762, 0, 0}, + {393228, 0, 0}, + {393273, 0, 0}, + {393295, 0, 0}, + {393296, 0, 0}, + {458764, 0, 0}, + {458831, 0, 0}, + {524367, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 9, 23}, + {0, 1, 30}, + {0, 5, 31}, + {0, 32, 28}, + {0, 33, 25}, + {0, 34, 29}, + {0, 18, 24}, + {0, 27, 16}, + {0, 7, 13}, + {0, 14, 35}, + {0, 20, 10}, + {0, 36, 21}, + {0, 2, 37}, + {0, 38, 3}, + {0, 39, 22}, + {0, 40, 19}, + {0, 41, 11}, + {0, 6, 4}, + {0, 12, 42}, + {0, 43, 8}, + {0, 15, 26}, + {0, 45, 44}, + {0, 47, 46}, + {0, 48, 17}, + {0, 50, 49}, + {0, 52, 51}, + {0, 54, 53}, + {0, 56, 55}, + })); + + codecs.emplace(SpvOpFSub, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(13, { + {0, 0, 0}, + {65785, 0, 0}, + {131134, 0, 0}, + {196719, 0, 0}, + {262209, 0, 0}, + {262276, 0, 0}, + {327745, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 7, 4}, + {0, 2, 8}, + {0, 1, 9}, + {0, 5, 10}, + {0, 3, 6}, + {0, 12, 11}, + })); + + codecs.emplace(SpvOpIAdd, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(83, { + {0, 0, 0}, + {65785, 0, 0}, + {65790, 0, 0}, + {131134, 0, 0}, + {131319, 0, 0}, + {196669, 0, 0}, + {196732, 0, 0}, + {196735, 0, 0}, + {262209, 0, 0}, + {262221, 0, 0}, + {262224, 0, 0}, + {262225, 0, 0}, + {262230, 0, 0}, + {262231, 0, 0}, + {262273, 0, 0}, + {262275, 0, 0}, + {262277, 0, 0}, + {262280, 0, 0}, + {262286, 0, 0}, + {262288, 0, 0}, + {262292, 0, 0}, + {262328, 0, 0}, + {262334, 0, 0}, + {262340, 0, 0}, + {327692, 0, 0}, + {327737, 0, 0}, + {327745, 0, 0}, + {327760, 0, 0}, + {327761, 0, 0}, + {327762, 0, 0}, + {393228, 0, 0}, + {393273, 0, 0}, + {393295, 0, 0}, + {393296, 0, 0}, + {393297, 0, 0}, + {393298, 0, 0}, + {393304, 0, 0}, + {458764, 0, 0}, + {458831, 0, 0}, + {458840, 0, 0}, + {458842, 0, 0}, + {524367, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 25, 2}, + {0, 31, 43}, + {0, 4, 44}, + {0, 26, 45}, + {0, 39, 46}, + {0, 34, 36}, + {0, 19, 47}, + {0, 6, 48}, + {0, 35, 9}, + {0, 12, 29}, + {0, 21, 49}, + {0, 22, 13}, + {0, 17, 50}, + {0, 23, 51}, + {0, 52, 7}, + {0, 37, 1}, + {0, 53, 3}, + {0, 54, 24}, + {0, 56, 55}, + {0, 32, 57}, + {0, 59, 58}, + {0, 42, 10}, + {0, 60, 8}, + {0, 5, 41}, + {0, 61, 20}, + {0, 62, 38}, + {0, 64, 63}, + {0, 40, 65}, + {0, 66, 18}, + {0, 15, 28}, + {0, 14, 67}, + {0, 68, 30}, + {0, 70, 69}, + {0, 72, 71}, + {0, 73, 27}, + {0, 16, 74}, + {0, 75, 33}, + {0, 77, 76}, + {0, 79, 78}, + {0, 81, 80}, + {0, 82, 11}, + })); + + codecs.emplace(SpvOpCompositeExtract, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(29, { + {0, 0, 0}, + {65790, 0, 0}, + {131134, 0, 0}, + {196669, 0, 0}, + {262209, 0, 0}, + {262225, 0, 0}, + {262273, 0, 0}, + {262288, 0, 0}, + {262292, 0, 0}, + {327692, 0, 0}, + {327761, 0, 0}, + {327762, 0, 0}, + {393295, 0, 0}, + {458831, 0, 0}, + {524367, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 10, 6}, + {0, 16, 13}, + {0, 7, 17}, + {0, 15, 18}, + {0, 19, 12}, + {0, 20, 14}, + {0, 1, 4}, + {0, 22, 21}, + {0, 11, 8}, + {0, 2, 5}, + {0, 9, 23}, + {0, 3, 24}, + {0, 26, 25}, + {0, 28, 27}, + })); + + codecs.emplace(SpvOpVectorTimesMatrix, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {65784, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(SpvOpBranch, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {262198, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(SpvOpFunctionEnd, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {65784, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(SpvOpBranchConditional, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(53, { + {0, 0, 0}, + {65785, 0, 0}, + {65790, 0, 0}, + {131134, 0, 0}, + {131319, 0, 0}, + {196665, 0, 0}, + {196669, 0, 0}, + {196735, 0, 0}, + {262201, 0, 0}, + {262209, 0, 0}, + {262224, 0, 0}, + {262225, 0, 0}, + {262231, 0, 0}, + {262273, 0, 0}, + {262275, 0, 0}, + {262277, 0, 0}, + {262280, 0, 0}, + {262286, 0, 0}, + {262288, 0, 0}, + {262292, 0, 0}, + {327692, 0, 0}, + {327745, 0, 0}, + {327760, 0, 0}, + {393228, 0, 0}, + {393295, 0, 0}, + {458764, 0, 0}, + {458831, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 25, 16}, + {0, 21, 28}, + {0, 18, 23}, + {0, 4, 29}, + {0, 10, 5}, + {0, 1, 30}, + {0, 32, 31}, + {0, 22, 33}, + {0, 34, 8}, + {0, 35, 15}, + {0, 13, 36}, + {0, 26, 17}, + {0, 38, 37}, + {0, 39, 11}, + {0, 40, 14}, + {0, 12, 27}, + {0, 19, 41}, + {0, 24, 42}, + {0, 44, 43}, + {0, 45, 7}, + {0, 20, 46}, + {0, 9, 47}, + {0, 48, 2}, + {0, 50, 49}, + {0, 6, 3}, + {0, 52, 51}, + })); + + codecs.emplace(SpvOpFunctionCall, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(71, { + {0, 0, 0}, + {65556, 0, 0}, + {65562, 0, 0}, + {131073, 0, 0}, + {131094, 0, 0}, + {131099, 0, 0}, + {131134, 0, 0}, + {196629, 0, 0}, + {196631, 0, 0}, + {196632, 0, 0}, + {196636, 0, 0}, + {196640, 0, 0}, + {196651, 0, 0}, + {196665, 0, 0}, + {196667, 0, 0}, + {196669, 0, 0}, + {262188, 0, 0}, + {262198, 0, 0}, + {262201, 0, 0}, + {262209, 0, 0}, + {262225, 0, 0}, + {262275, 0, 0}, + {262280, 0, 0}, + {262292, 0, 0}, + {327692, 0, 0}, + {327724, 0, 0}, + {327737, 0, 0}, + {327745, 0, 0}, + {393228, 0, 0}, + {393260, 0, 0}, + {393273, 0, 0}, + {393295, 0, 0}, + {393296, 0, 0}, + {458831, 0, 0}, + {524313, 0, 0}, + {524367, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 22, 4}, + {0, 32, 23}, + {0, 37, 30}, + {0, 21, 38}, + {0, 39, 31}, + {0, 41, 40}, + {0, 13, 42}, + {0, 43, 26}, + {0, 10, 44}, + {0, 28, 45}, + {0, 35, 18}, + {0, 20, 46}, + {0, 33, 47}, + {0, 24, 48}, + {0, 6, 49}, + {0, 3, 50}, + {0, 16, 51}, + {0, 27, 52}, + {0, 53, 1}, + {0, 9, 17}, + {0, 29, 54}, + {0, 19, 2}, + {0, 8, 36}, + {0, 55, 34}, + {0, 25, 56}, + {0, 7, 57}, + {0, 5, 58}, + {0, 60, 59}, + {0, 61, 15}, + {0, 63, 62}, + {0, 65, 64}, + {0, 66, 11}, + {0, 12, 67}, + {0, 69, 68}, + {0, 14, 70}, + })); + + codecs.emplace(SpvOpVariable, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(5, { + {0, 0, 0}, + {131134, 0, 0}, + {196669, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 3}, + {0, 2, 4}, + })); + + codecs.emplace(SpvOpAccessChain, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(73, { + {0, 0, 0}, + {252, 0, 0}, + {253, 0, 0}, + {65785, 0, 0}, + {65790, 0, 0}, + {131073, 0, 0}, + {131134, 0, 0}, + {131319, 0, 0}, + {196665, 0, 0}, + {196667, 0, 0}, + {196669, 0, 0}, + {196735, 0, 0}, + {196854, 0, 0}, + {262201, 0, 0}, + {262209, 0, 0}, + {262221, 0, 0}, + {262225, 0, 0}, + {262272, 0, 0}, + {262273, 0, 0}, + {262275, 0, 0}, + {262276, 0, 0}, + {262277, 0, 0}, + {262280, 0, 0}, + {262286, 0, 0}, + {262292, 0, 0}, + {262321, 0, 0}, + {327692, 0, 0}, + {327745, 0, 0}, + {327761, 0, 0}, + {327762, 0, 0}, + {393228, 0, 0}, + {393295, 0, 0}, + {393296, 0, 0}, + {393298, 0, 0}, + {393461, 0, 0}, + {458831, 0, 0}, + {524367, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 28, 5}, + {0, 30, 8}, + {0, 13, 38}, + {0, 40, 39}, + {0, 41, 26}, + {0, 42, 19}, + {0, 43, 29}, + {0, 23, 44}, + {0, 36, 32}, + {0, 45, 22}, + {0, 2, 46}, + {0, 21, 20}, + {0, 48, 47}, + {0, 33, 49}, + {0, 4, 50}, + {0, 51, 24}, + {0, 18, 11}, + {0, 52, 12}, + {0, 25, 15}, + {0, 53, 17}, + {0, 37, 54}, + {0, 55, 35}, + {0, 7, 27}, + {0, 57, 56}, + {0, 58, 31}, + {0, 6, 59}, + {0, 1, 60}, + {0, 62, 61}, + {0, 63, 14}, + {0, 3, 16}, + {0, 34, 64}, + {0, 66, 65}, + {0, 68, 67}, + {0, 70, 69}, + {0, 10, 9}, + {0, 72, 71}, + })); + + codecs.emplace(SpvOpLabel, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(5, { + {0, 0, 0}, + {56, 0, 0}, + {65784, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 2}, + {0, 1, 4}, + })); + + codecs.emplace(SpvOpReturn, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(5, { + {0, 0, 0}, + {65784, 0, 0}, + {131127, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 3}, + {0, 2, 4}, + })); + + codecs.emplace(SpvOpFunction, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(31, { + {0, 0, 0}, + {65556, 0, 0}, + {196629, 0, 0}, + {196631, 0, 0}, + {196632, 0, 0}, + {196636, 0, 0}, + {196640, 0, 0}, + {196641, 0, 0}, + {196651, 0, 0}, + {196667, 0, 0}, + {262177, 0, 0}, + {262188, 0, 0}, + {262198, 0, 0}, + {327713, 0, 0}, + {393260, 0, 0}, + {524313, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 12, 1}, + {0, 13, 5}, + {0, 18, 17}, + {0, 7, 19}, + {0, 9, 20}, + {0, 16, 21}, + {0, 15, 10}, + {0, 22, 4}, + {0, 24, 23}, + {0, 25, 14}, + {0, 8, 11}, + {0, 2, 26}, + {0, 28, 27}, + {0, 3, 6}, + {0, 30, 29}, + })); + + codecs.emplace(SpvOpTypeVector, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(5, { + {0, 0, 0}, + {65784, 0, 0}, + {131127, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 3}, + {0, 4, 1}, + })); + + codecs.emplace(SpvOpFunctionParameter, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(5, { + {0, 0, 0}, + {56, 0, 0}, + {65784, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 2}, + {0, 1, 4}, + })); + + codecs.emplace(SpvOpReturnValue, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {131105, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(SpvOpTypeVoid, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(89, { + {0, 0, 0}, + {253, 0, 0}, + {65785, 0, 0}, + {65790, 0, 0}, + {131134, 0, 0}, + {131319, 0, 0}, + {196665, 0, 0}, + {196669, 0, 0}, + {196735, 0, 0}, + {262201, 0, 0}, + {262209, 0, 0}, + {262224, 0, 0}, + {262225, 0, 0}, + {262272, 0, 0}, + {262273, 0, 0}, + {262275, 0, 0}, + {262277, 0, 0}, + {262280, 0, 0}, + {262286, 0, 0}, + {262288, 0, 0}, + {262292, 0, 0}, + {327692, 0, 0}, + {327737, 0, 0}, + {327745, 0, 0}, + {327760, 0, 0}, + {327761, 0, 0}, + {327762, 0, 0}, + {393228, 0, 0}, + {393273, 0, 0}, + {393281, 0, 0}, + {393295, 0, 0}, + {393296, 0, 0}, + {458764, 0, 0}, + {458809, 0, 0}, + {458831, 0, 0}, + {524345, 0, 0}, + {524367, 0, 0}, + {589881, 0, 0}, + {655417, 0, 0}, + {720953, 0, 0}, + {786489, 0, 0}, + {852025, 0, 0}, + {917561, 0, 0}, + {983097, 0, 0}, + {1114169, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 40, 32}, + {0, 46, 29}, + {0, 38, 27}, + {0, 20, 47}, + {0, 49, 48}, + {0, 50, 44}, + {0, 51, 43}, + {0, 14, 5}, + {0, 42, 52}, + {0, 13, 19}, + {0, 3, 26}, + {0, 54, 53}, + {0, 56, 55}, + {0, 57, 6}, + {0, 39, 37}, + {0, 15, 58}, + {0, 18, 31}, + {0, 59, 21}, + {0, 60, 17}, + {0, 61, 41}, + {0, 62, 24}, + {0, 34, 63}, + {0, 35, 64}, + {0, 65, 8}, + {0, 66, 36}, + {0, 67, 30}, + {0, 16, 11}, + {0, 69, 68}, + {0, 70, 28}, + {0, 22, 71}, + {0, 33, 72}, + {0, 45, 73}, + {0, 75, 74}, + {0, 77, 76}, + {0, 78, 12}, + {0, 1, 2}, + {0, 9, 79}, + {0, 25, 80}, + {0, 23, 81}, + {0, 4, 82}, + {0, 84, 83}, + {0, 86, 85}, + {0, 7, 10}, + {0, 88, 87}, + })); + + codecs.emplace(SpvOpStore, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(13, { + {0, 0, 0}, + {131075, 0, 0}, + {131088, 0, 0}, + {131143, 0, 0}, + {196624, 0, 0}, + {196679, 0, 0}, + {262216, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 4}, + {0, 1, 8}, + {0, 7, 9}, + {0, 6, 10}, + {0, 5, 11}, + {0, 2, 12}, + })); + + codecs.emplace(SpvOpEntryPoint, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(97, { + {0, 0, 0}, + {65785, 0, 0}, + {65790, 0, 0}, + {131134, 0, 0}, + {131319, 0, 0}, + {196665, 0, 0}, + {196669, 0, 0}, + {196732, 0, 0}, + {196735, 0, 0}, + {262201, 0, 0}, + {262209, 0, 0}, + {262224, 0, 0}, + {262225, 0, 0}, + {262230, 0, 0}, + {262231, 0, 0}, + {262272, 0, 0}, + {262273, 0, 0}, + {262275, 0, 0}, + {262276, 0, 0}, + {262277, 0, 0}, + {262280, 0, 0}, + {262286, 0, 0}, + {262288, 0, 0}, + {262292, 0, 0}, + {262326, 0, 0}, + {262328, 0, 0}, + {262330, 0, 0}, + {327692, 0, 0}, + {327737, 0, 0}, + {327745, 0, 0}, + {327760, 0, 0}, + {327761, 0, 0}, + {327762, 0, 0}, + {393228, 0, 0}, + {393273, 0, 0}, + {393281, 0, 0}, + {393295, 0, 0}, + {393296, 0, 0}, + {393297, 0, 0}, + {393304, 0, 0}, + {458764, 0, 0}, + {458809, 0, 0}, + {458817, 0, 0}, + {458831, 0, 0}, + {458840, 0, 0}, + {524345, 0, 0}, + {524367, 0, 0}, + {589881, 0, 0}, + {720953, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 42, 47}, + {0, 48, 50}, + {0, 45, 51}, + {0, 34, 52}, + {0, 53, 41}, + {0, 1, 54}, + {0, 55, 5}, + {0, 15, 4}, + {0, 56, 35}, + {0, 26, 24}, + {0, 18, 28}, + {0, 57, 38}, + {0, 59, 58}, + {0, 60, 25}, + {0, 20, 9}, + {0, 7, 61}, + {0, 62, 22}, + {0, 11, 31}, + {0, 63, 8}, + {0, 64, 40}, + {0, 66, 65}, + {0, 27, 44}, + {0, 29, 67}, + {0, 68, 39}, + {0, 69, 2}, + {0, 37, 49}, + {0, 71, 70}, + {0, 30, 72}, + {0, 73, 17}, + {0, 33, 74}, + {0, 23, 14}, + {0, 32, 75}, + {0, 21, 76}, + {0, 77, 16}, + {0, 46, 78}, + {0, 13, 79}, + {0, 80, 12}, + {0, 19, 81}, + {0, 43, 36}, + {0, 83, 82}, + {0, 10, 84}, + {0, 85, 3}, + {0, 6, 86}, + {0, 88, 87}, + {0, 90, 89}, + {0, 92, 91}, + {0, 94, 93}, + {0, 96, 95}, + })); + + codecs.emplace(SpvOpLoad, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(47, { + {0, 0, 0}, + {262159, 0, 0}, + {327695, 0, 0}, + {393231, 0, 0}, + {458767, 0, 0}, + {524303, 0, 0}, + {589839, 0, 0}, + {655375, 0, 0}, + {720911, 0, 0}, + {786447, 0, 0}, + {851983, 0, 0}, + {917519, 0, 0}, + {983055, 0, 0}, + {1048591, 0, 0}, + {1114127, 0, 0}, + {1179663, 0, 0}, + {1245199, 0, 0}, + {1310735, 0, 0}, + {1376271, 0, 0}, + {1441807, 0, 0}, + {1507343, 0, 0}, + {1572879, 0, 0}, + {1638415, 0, 0}, + {1703951, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 23}, + {0, 22, 25}, + {0, 21, 26}, + {0, 6, 20}, + {0, 19, 27}, + {0, 29, 28}, + {0, 24, 18}, + {0, 30, 13}, + {0, 31, 14}, + {0, 32, 7}, + {0, 17, 15}, + {0, 33, 2}, + {0, 34, 8}, + {0, 16, 12}, + {0, 35, 3}, + {0, 36, 5}, + {0, 9, 37}, + {0, 39, 38}, + {0, 11, 40}, + {0, 4, 10}, + {0, 42, 41}, + {0, 44, 43}, + {0, 46, 45}, + })); + + codecs.emplace(SpvOpMemoryModel, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(7, { + {0, 0, 0}, + {196631, 0, 0}, + {196640, 0, 0}, + {196641, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 3}, + {0, 4, 5}, + {0, 1, 6}, + })); + + codecs.emplace(SpvOpTypeFloat, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(69, { + {0, 0, 0}, + {65790, 0, 0}, + {131134, 0, 0}, + {196669, 0, 0}, + {196735, 0, 0}, + {262201, 0, 0}, + {262209, 0, 0}, + {262224, 0, 0}, + {262225, 0, 0}, + {262231, 0, 0}, + {262272, 0, 0}, + {262273, 0, 0}, + {262275, 0, 0}, + {262277, 0, 0}, + {262280, 0, 0}, + {262286, 0, 0}, + {262288, 0, 0}, + {262289, 0, 0}, + {262292, 0, 0}, + {327692, 0, 0}, + {327745, 0, 0}, + {327760, 0, 0}, + {327761, 0, 0}, + {327762, 0, 0}, + {327849, 0, 0}, + {393228, 0, 0}, + {393281, 0, 0}, + {393295, 0, 0}, + {393296, 0, 0}, + {393304, 0, 0}, + {458764, 0, 0}, + {458809, 0, 0}, + {458831, 0, 0}, + {524345, 0, 0}, + {524367, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 33, 10}, + {0, 31, 36}, + {0, 26, 37}, + {0, 5, 38}, + {0, 20, 39}, + {0, 22, 40}, + {0, 24, 25}, + {0, 15, 41}, + {0, 9, 17}, + {0, 1, 42}, + {0, 4, 43}, + {0, 35, 44}, + {0, 34, 45}, + {0, 19, 46}, + {0, 7, 29}, + {0, 16, 47}, + {0, 48, 32}, + {0, 49, 27}, + {0, 11, 14}, + {0, 18, 28}, + {0, 23, 50}, + {0, 51, 12}, + {0, 52, 21}, + {0, 6, 53}, + {0, 55, 54}, + {0, 57, 56}, + {0, 3, 58}, + {0, 13, 59}, + {0, 60, 8}, + {0, 30, 61}, + {0, 62, 2}, + {0, 64, 63}, + {0, 66, 65}, + {0, 68, 67}, + })); + + codecs.emplace(SpvOpCompositeConstruct, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(39, { + {0, 0, 0}, + {65556, 0, 0}, + {131094, 0, 0}, + {131105, 0, 0}, + {196629, 0, 0}, + {196631, 0, 0}, + {196632, 0, 0}, + {196640, 0, 0}, + {196641, 0, 0}, + {262177, 0, 0}, + {327713, 0, 0}, + {393249, 0, 0}, + {458785, 0, 0}, + {524313, 0, 0}, + {524321, 0, 0}, + {589857, 0, 0}, + {655393, 0, 0}, + {786465, 0, 0}, + {917537, 0, 0}, + {1048609, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 19, 18}, + {0, 21, 15}, + {0, 1, 22}, + {0, 16, 23}, + {0, 14, 24}, + {0, 20, 25}, + {0, 13, 17}, + {0, 3, 26}, + {0, 6, 11}, + {0, 27, 12}, + {0, 4, 28}, + {0, 29, 10}, + {0, 9, 30}, + {0, 7, 31}, + {0, 33, 32}, + {0, 34, 5}, + {0, 8, 35}, + {0, 2, 36}, + {0, 38, 37}, + })); + + codecs.emplace(SpvOpTypeFunction, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {131086, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(SpvOpExtInstImport, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(5, { + {0, 0, 0}, + {131099, 0, 0}, + {196640, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 3}, + {0, 1, 4}, + })); + + codecs.emplace(SpvOpTypeImage, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(9, { + {0, 0, 0}, + {131143, 0, 0}, + {196679, 0, 0}, + {196680, 0, 0}, + {262216, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 5, 2}, + {0, 3, 6}, + {0, 7, 1}, + {0, 4, 8}, + })); + + codecs.emplace(SpvOpMemberDecorate, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(5, { + {0, 0, 0}, + {65553, 0, 0}, + {131083, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 3}, + {0, 2, 4}, + })); + + codecs.emplace(SpvOpCapability, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(17, { + {0, 0, 0}, + {196629, 0, 0}, + {196631, 0, 0}, + {196632, 0, 0}, + {196640, 0, 0}, + {196651, 0, 0}, + {196667, 0, 0}, + {327713, 0, 0}, + {458785, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 7, 8}, + {0, 1, 10}, + {0, 6, 11}, + {0, 9, 12}, + {0, 4, 13}, + {0, 3, 14}, + {0, 15, 2}, + {0, 5, 16}, + })); + + codecs.emplace(SpvOpTypeInt, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(29, { + {0, 0, 0}, + {65556, 0, 0}, + {131073, 0, 0}, + {196629, 0, 0}, + {196631, 0, 0}, + {196632, 0, 0}, + {196636, 0, 0}, + {196640, 0, 0}, + {196651, 0, 0}, + {196667, 0, 0}, + {262188, 0, 0}, + {262198, 0, 0}, + {327724, 0, 0}, + {393260, 0, 0}, + {524313, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 6}, + {0, 16, 3}, + {0, 11, 17}, + {0, 5, 18}, + {0, 15, 19}, + {0, 13, 20}, + {0, 1, 4}, + {0, 12, 21}, + {0, 7, 22}, + {0, 14, 23}, + {0, 24, 10}, + {0, 25, 9}, + {0, 27, 26}, + {0, 8, 28}, + })); + + codecs.emplace(SpvOpConstantComposite, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(15, { + {0, 0, 0}, + {65556, 0, 0}, + {196631, 0, 0}, + {196640, 0, 0}, + {196651, 0, 0}, + {196667, 0, 0}, + {327724, 0, 0}, + {393260, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 6, 7}, + {0, 1, 9}, + {0, 10, 8}, + {0, 2, 11}, + {0, 5, 12}, + {0, 13, 4}, + {0, 3, 14}, + })); + + codecs.emplace(SpvOpTypeSampledImage, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(21, { + {0, 0, 0}, + {131073, 0, 0}, + {196629, 0, 0}, + {196631, 0, 0}, + {196632, 0, 0}, + {196636, 0, 0}, + {196640, 0, 0}, + {196641, 0, 0}, + {196651, 0, 0}, + {196667, 0, 0}, + {262198, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 5}, + {0, 11, 12}, + {0, 8, 13}, + {0, 7, 14}, + {0, 4, 10}, + {0, 9, 2}, + {0, 16, 15}, + {0, 1, 17}, + {0, 19, 18}, + {0, 6, 20}, + })); + + codecs.emplace(SpvOpTypeStruct, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(49, { + {0, 0, 0}, + {65785, 0, 0}, + {65790, 0, 0}, + {131134, 0, 0}, + {196669, 0, 0}, + {196735, 0, 0}, + {262201, 0, 0}, + {262209, 0, 0}, + {262224, 0, 0}, + {262225, 0, 0}, + {262272, 0, 0}, + {262273, 0, 0}, + {262275, 0, 0}, + {262277, 0, 0}, + {262286, 0, 0}, + {262292, 0, 0}, + {327692, 0, 0}, + {327745, 0, 0}, + {327760, 0, 0}, + {327762, 0, 0}, + {393228, 0, 0}, + {393295, 0, 0}, + {393296, 0, 0}, + {458764, 0, 0}, + {458831, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 20, 12}, + {0, 26, 24}, + {0, 21, 27}, + {0, 28, 16}, + {0, 10, 8}, + {0, 30, 29}, + {0, 31, 17}, + {0, 32, 13}, + {0, 25, 6}, + {0, 1, 33}, + {0, 14, 11}, + {0, 3, 34}, + {0, 18, 35}, + {0, 37, 36}, + {0, 23, 5}, + {0, 38, 2}, + {0, 39, 7}, + {0, 4, 9}, + {0, 40, 19}, + {0, 42, 41}, + {0, 43, 22}, + {0, 45, 44}, + {0, 46, 15}, + {0, 48, 47}, + })); + + codecs.emplace(SpvOpFNegate, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(11, { + {0, 0, 0}, + {65555, 0, 0}, + {131143, 0, 0}, + {196679, 0, 0}, + {196680, 0, 0}, + {262216, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 4, 6}, + {0, 1, 2}, + {0, 8, 7}, + {0, 5, 9}, + {0, 3, 10}, + })); + + codecs.emplace(SpvOpDecorate, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(25, { + {0, 0, 0}, + {65562, 0, 0}, + {196629, 0, 0}, + {196631, 0, 0}, + {196632, 0, 0}, + {196636, 0, 0}, + {196640, 0, 0}, + {196641, 0, 0}, + {196651, 0, 0}, + {196667, 0, 0}, + {262177, 0, 0}, + {262198, 0, 0}, + {327713, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 12, 11}, + {0, 9, 14}, + {0, 10, 15}, + {0, 13, 16}, + {0, 4, 17}, + {0, 2, 1}, + {0, 18, 7}, + {0, 20, 19}, + {0, 21, 3}, + {0, 22, 6}, + {0, 5, 8}, + {0, 24, 23}, + })); + + codecs.emplace(SpvOpTypeMatrix, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(31, { + {0, 0, 0}, + {65556, 0, 0}, + {131073, 0, 0}, + {131094, 0, 0}, + {196629, 0, 0}, + {196631, 0, 0}, + {196632, 0, 0}, + {196636, 0, 0}, + {196640, 0, 0}, + {196651, 0, 0}, + {196667, 0, 0}, + {262188, 0, 0}, + {262198, 0, 0}, + {327724, 0, 0}, + {393260, 0, 0}, + {524313, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 12, 2}, + {0, 17, 3}, + {0, 5, 18}, + {0, 1, 19}, + {0, 16, 4}, + {0, 21, 20}, + {0, 6, 15}, + {0, 7, 22}, + {0, 24, 23}, + {0, 13, 14}, + {0, 25, 8}, + {0, 26, 11}, + {0, 27, 10}, + {0, 29, 28}, + {0, 30, 9}, + })); + + codecs.emplace(SpvOpConstant, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(33, { + {0, 0, 0}, + {131113, 0, 0}, + {196629, 0, 0}, + {196631, 0, 0}, + {196632, 0, 0}, + {196640, 0, 0}, + {196641, 0, 0}, + {196651, 0, 0}, + {196667, 0, 0}, + {262188, 0, 0}, + {262198, 0, 0}, + {327713, 0, 0}, + {327724, 0, 0}, + {393249, 0, 0}, + {393260, 0, 0}, + {524313, 0, 0}, + {524321, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 6, 4}, + {0, 13, 11}, + {0, 16, 15}, + {0, 18, 10}, + {0, 20, 19}, + {0, 21, 2}, + {0, 23, 22}, + {0, 8, 24}, + {0, 9, 25}, + {0, 17, 26}, + {0, 14, 27}, + {0, 12, 28}, + {0, 1, 3}, + {0, 5, 29}, + {0, 30, 7}, + {0, 32, 31}, + })); + + codecs.emplace(SpvOpTypeBool, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(11, { + {0, 0, 0}, + {196636, 0, 0}, + {196640, 0, 0}, + {196651, 0, 0}, + {196667, 0, 0}, + {524313, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 4, 5}, + {0, 3, 7}, + {0, 2, 8}, + {0, 6, 9}, + {0, 1, 10}, + })); + + codecs.emplace(SpvOpTypeArray, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(67, { + {0, 0, 0}, + {65785, 0, 0}, + {65790, 0, 0}, + {131134, 0, 0}, + {131319, 0, 0}, + {196669, 0, 0}, + {196735, 0, 0}, + {262201, 0, 0}, + {262209, 0, 0}, + {262224, 0, 0}, + {262225, 0, 0}, + {262231, 0, 0}, + {262272, 0, 0}, + {262273, 0, 0}, + {262275, 0, 0}, + {262277, 0, 0}, + {262280, 0, 0}, + {262286, 0, 0}, + {262292, 0, 0}, + {262334, 0, 0}, + {327692, 0, 0}, + {327737, 0, 0}, + {327745, 0, 0}, + {327760, 0, 0}, + {327761, 0, 0}, + {327762, 0, 0}, + {393228, 0, 0}, + {393273, 0, 0}, + {393281, 0, 0}, + {393295, 0, 0}, + {393296, 0, 0}, + {458764, 0, 0}, + {458831, 0, 0}, + {524367, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 7, 27}, + {0, 11, 28}, + {0, 35, 21}, + {0, 36, 1}, + {0, 4, 37}, + {0, 39, 38}, + {0, 40, 30}, + {0, 41, 12}, + {0, 19, 42}, + {0, 13, 43}, + {0, 16, 44}, + {0, 45, 22}, + {0, 34, 18}, + {0, 29, 24}, + {0, 46, 25}, + {0, 6, 2}, + {0, 9, 31}, + {0, 17, 47}, + {0, 49, 48}, + {0, 50, 33}, + {0, 51, 26}, + {0, 20, 52}, + {0, 32, 53}, + {0, 3, 54}, + {0, 15, 14}, + {0, 23, 55}, + {0, 8, 56}, + {0, 58, 57}, + {0, 10, 59}, + {0, 5, 60}, + {0, 62, 61}, + {0, 64, 63}, + {0, 66, 65}, + })); + + codecs.emplace(SpvOpExtInst, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(57, { + {0, 0, 0}, + {65790, 0, 0}, + {131134, 0, 0}, + {196665, 0, 0}, + {196669, 0, 0}, + {196718, 0, 0}, + {262201, 0, 0}, + {262209, 0, 0}, + {262224, 0, 0}, + {262225, 0, 0}, + {262231, 0, 0}, + {262273, 0, 0}, + {262275, 0, 0}, + {262277, 0, 0}, + {262280, 0, 0}, + {262286, 0, 0}, + {262292, 0, 0}, + {327692, 0, 0}, + {327745, 0, 0}, + {327760, 0, 0}, + {327762, 0, 0}, + {393228, 0, 0}, + {393273, 0, 0}, + {393295, 0, 0}, + {393296, 0, 0}, + {393303, 0, 0}, + {458764, 0, 0}, + {458831, 0, 0}, + {524367, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 18, 6}, + {0, 30, 22}, + {0, 31, 25}, + {0, 10, 32}, + {0, 21, 33}, + {0, 3, 34}, + {0, 35, 5}, + {0, 23, 36}, + {0, 14, 17}, + {0, 37, 26}, + {0, 1, 38}, + {0, 29, 39}, + {0, 13, 40}, + {0, 41, 19}, + {0, 28, 20}, + {0, 16, 42}, + {0, 27, 43}, + {0, 8, 24}, + {0, 7, 44}, + {0, 9, 45}, + {0, 15, 46}, + {0, 12, 47}, + {0, 48, 2}, + {0, 4, 49}, + {0, 51, 50}, + {0, 11, 52}, + {0, 54, 53}, + {0, 56, 55}, + })); + + codecs.emplace(SpvOpVectorTimesScalar, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(67, { + {0, 0, 0}, + {65785, 0, 0}, + {65790, 0, 0}, + {131134, 0, 0}, + {196669, 0, 0}, + {196735, 0, 0}, + {262201, 0, 0}, + {262209, 0, 0}, + {262224, 0, 0}, + {262225, 0, 0}, + {262230, 0, 0}, + {262231, 0, 0}, + {262272, 0, 0}, + {262273, 0, 0}, + {262275, 0, 0}, + {262277, 0, 0}, + {262280, 0, 0}, + {262286, 0, 0}, + {262292, 0, 0}, + {327692, 0, 0}, + {327737, 0, 0}, + {327745, 0, 0}, + {327760, 0, 0}, + {327761, 0, 0}, + {327762, 0, 0}, + {393228, 0, 0}, + {393273, 0, 0}, + {393295, 0, 0}, + {393296, 0, 0}, + {393303, 0, 0}, + {393304, 0, 0}, + {458764, 0, 0}, + {458831, 0, 0}, + {524367, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 26, 29}, + {0, 20, 35}, + {0, 12, 36}, + {0, 6, 37}, + {0, 38, 28}, + {0, 30, 5}, + {0, 8, 39}, + {0, 2, 40}, + {0, 41, 21}, + {0, 1, 10}, + {0, 43, 42}, + {0, 23, 16}, + {0, 44, 33}, + {0, 34, 31}, + {0, 14, 45}, + {0, 19, 46}, + {0, 25, 47}, + {0, 49, 48}, + {0, 27, 22}, + {0, 7, 50}, + {0, 17, 32}, + {0, 18, 51}, + {0, 24, 52}, + {0, 54, 53}, + {0, 55, 9}, + {0, 56, 11}, + {0, 57, 4}, + {0, 15, 58}, + {0, 59, 13}, + {0, 60, 3}, + {0, 62, 61}, + {0, 64, 63}, + {0, 66, 65}, + })); + + codecs.emplace(SpvOpVectorShuffle, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(33, { + {0, 0, 0}, + {65790, 0, 0}, + {131134, 0, 0}, + {196669, 0, 0}, + {262201, 0, 0}, + {262209, 0, 0}, + {262225, 0, 0}, + {262231, 0, 0}, + {262273, 0, 0}, + {262277, 0, 0}, + {262286, 0, 0}, + {262292, 0, 0}, + {327745, 0, 0}, + {393281, 0, 0}, + {393295, 0, 0}, + {393296, 0, 0}, + {458831, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 13, 12}, + {0, 1, 18}, + {0, 19, 11}, + {0, 9, 20}, + {0, 10, 21}, + {0, 22, 15}, + {0, 23, 8}, + {0, 4, 24}, + {0, 25, 7}, + {0, 17, 26}, + {0, 5, 27}, + {0, 14, 3}, + {0, 29, 28}, + {0, 30, 2}, + {0, 6, 31}, + {0, 32, 16}, + })); + + codecs.emplace(SpvOpImageSampleImplicitLod, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(55, { + {0, 0, 0}, + {65785, 0, 0}, + {65790, 0, 0}, + {131134, 0, 0}, + {196669, 0, 0}, + {196735, 0, 0}, + {196817, 0, 0}, + {262209, 0, 0}, + {262224, 0, 0}, + {262225, 0, 0}, + {262273, 0, 0}, + {262275, 0, 0}, + {262277, 0, 0}, + {262280, 0, 0}, + {262286, 0, 0}, + {262292, 0, 0}, + {327692, 0, 0}, + {327745, 0, 0}, + {327760, 0, 0}, + {327761, 0, 0}, + {327762, 0, 0}, + {393228, 0, 0}, + {393281, 0, 0}, + {393295, 0, 0}, + {393296, 0, 0}, + {393298, 0, 0}, + {458764, 0, 0}, + {458831, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 5, 2}, + {0, 22, 29}, + {0, 30, 1}, + {0, 6, 31}, + {0, 9, 32}, + {0, 28, 3}, + {0, 27, 33}, + {0, 20, 16}, + {0, 34, 8}, + {0, 10, 35}, + {0, 4, 36}, + {0, 24, 23}, + {0, 21, 13}, + {0, 7, 37}, + {0, 38, 14}, + {0, 25, 39}, + {0, 17, 11}, + {0, 12, 19}, + {0, 41, 40}, + {0, 42, 18}, + {0, 15, 43}, + {0, 45, 44}, + {0, 47, 46}, + {0, 26, 48}, + {0, 50, 49}, + {0, 52, 51}, + {0, 54, 53}, + })); + + codecs.emplace(SpvOpDot, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(11, { + {0, 0, 0}, + {131075, 0, 0}, + {131088, 0, 0}, + {196624, 0, 0}, + {196679, 0, 0}, + {262216, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 5, 3}, + {0, 2, 7}, + {0, 1, 8}, + {0, 6, 9}, + {0, 4, 10}, + })); + + codecs.emplace(SpvOpExecutionMode, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {196858, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(SpvOpSelectionMerge, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(23, { + {0, 0, 0}, + {131134, 0, 0}, + {196669, 0, 0}, + {262209, 0, 0}, + {262224, 0, 0}, + {262225, 0, 0}, + {262277, 0, 0}, + {327745, 0, 0}, + {327761, 0, 0}, + {327762, 0, 0}, + {393295, 0, 0}, + {393296, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 12}, + {0, 7, 13}, + {0, 5, 1}, + {0, 4, 10}, + {0, 14, 6}, + {0, 16, 15}, + {0, 17, 11}, + {0, 3, 8}, + {0, 19, 18}, + {0, 9, 20}, + {0, 22, 21}, + })); + + codecs.emplace(SpvOpImageSampleDrefExplicitLod, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(7, { + {0, 0, 0}, + {65790, 0, 0}, + {131073, 0, 0}, + {262198, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 4, 1}, + {0, 3, 5}, + {0, 2, 6}, + })); + + codecs.emplace(SpvOpUndef, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(59, { + {0, 0, 0}, + {65785, 0, 0}, + {131134, 0, 0}, + {131319, 0, 0}, + {196669, 0, 0}, + {196735, 0, 0}, + {262209, 0, 0}, + {262221, 0, 0}, + {262224, 0, 0}, + {262225, 0, 0}, + {262230, 0, 0}, + {262273, 0, 0}, + {262275, 0, 0}, + {262277, 0, 0}, + {262280, 0, 0}, + {262286, 0, 0}, + {262288, 0, 0}, + {262292, 0, 0}, + {262334, 0, 0}, + {327692, 0, 0}, + {327760, 0, 0}, + {327761, 0, 0}, + {327762, 0, 0}, + {393228, 0, 0}, + {393295, 0, 0}, + {393296, 0, 0}, + {393298, 0, 0}, + {458764, 0, 0}, + {458831, 0, 0}, + {524367, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 17, 3}, + {0, 5, 31}, + {0, 11, 32}, + {0, 33, 12}, + {0, 34, 20}, + {0, 16, 27}, + {0, 35, 23}, + {0, 37, 36}, + {0, 14, 18}, + {0, 39, 38}, + {0, 7, 30}, + {0, 8, 25}, + {0, 40, 15}, + {0, 13, 2}, + {0, 1, 29}, + {0, 19, 41}, + {0, 43, 42}, + {0, 28, 44}, + {0, 46, 45}, + {0, 22, 21}, + {0, 47, 24}, + {0, 48, 26}, + {0, 10, 6}, + {0, 50, 49}, + {0, 52, 51}, + {0, 54, 53}, + {0, 4, 9}, + {0, 56, 55}, + {0, 58, 57}, + })); + + codecs.emplace(SpvOpCompositeInsert, std::move(codec)); + } + + return codecs; +} + +std::map>> +GetLiteralStringHuffmanCodecs() { + std::map>> codecs; + { + std::unique_ptr> codec(new HuffmanCodec(7, { + {"", 0, 0}, + {"MainPs", 0, 0}, + {"MainVs", 0, 0}, + {"kMarkvNoneOfTheAbove", 0, 0}, + {"main", 0, 0}, + {"", 2, 3}, + {"", 1, 5}, + {"", 4, 6}, + })); + + codecs.emplace(SpvOpEntryPoint, std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {"", 0, 0}, + {"GLSL.std.450", 0, 0}, + {"kMarkvNoneOfTheAbove", 0, 0}, + {"", 1, 2}, + })); + + codecs.emplace(SpvOpExtInstImport, std::move(codec)); + } + + return codecs; +} + +std::map, std::unique_ptr>> +GetNonIdWordHuffmanCodecs() { + std::map, std::unique_ptr>> codecs; + { + std::unique_ptr> codec(new HuffmanCodec(33, { + {0, 0, 0}, + {4, 0, 0}, + {8, 0, 0}, + {10, 0, 0}, + {26, 0, 0}, + {29, 0, 0}, + {31, 0, 0}, + {37, 0, 0}, + {40, 0, 0}, + {43, 0, 0}, + {46, 0, 0}, + {49, 0, 0}, + {66, 0, 0}, + {67, 0, 0}, + {68, 0, 0}, + {69, 0, 0}, + {71, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 12, 5}, + {0, 18, 13}, + {0, 3, 7}, + {0, 19, 11}, + {0, 20, 16}, + {0, 14, 17}, + {0, 21, 1}, + {0, 2, 6}, + {0, 23, 22}, + {0, 4, 24}, + {0, 26, 25}, + {0, 28, 27}, + {0, 10, 15}, + {0, 8, 9}, + {0, 30, 29}, + {0, 32, 31}, + })); + + codecs.emplace(std::pair(SpvOpExtInst, 3), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {0, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpMemoryModel, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {1, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpMemoryModel, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(5, { + {0, 0, 0}, + {0, 0, 0}, + {4, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 3}, + {0, 2, 4}, + })); + + codecs.emplace(std::pair(SpvOpEntryPoint, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(5, { + {0, 0, 0}, + {7, 0, 0}, + {8, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 2}, + {0, 1, 4}, + })); + + codecs.emplace(std::pair(SpvOpExecutionMode, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(11, { + {0, 0, 0}, + {1, 0, 0}, + {2, 0, 0}, + {3, 0, 0}, + {4, 0, 0}, + {18, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 4, 2}, + {0, 6, 5}, + {0, 7, 1}, + {0, 3, 8}, + {0, 10, 9}, + })); + + codecs.emplace(std::pair(SpvOpExecutionMode, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(5, { + {0, 0, 0}, + {1, 0, 0}, + {32, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 3}, + {0, 1, 4}, + })); + + codecs.emplace(std::pair(SpvOpCapability, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {32, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpTypeInt, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(5, { + {0, 0, 0}, + {0, 0, 0}, + {1, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 3}, + {0, 1, 4}, + })); + + codecs.emplace(std::pair(SpvOpTypeInt, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {32, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpTypeFloat, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(7, { + {0, 0, 0}, + {2, 0, 0}, + {3, 0, 0}, + {4, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 4}, + {0, 1, 5}, + {0, 6, 3}, + })); + + codecs.emplace(std::pair(SpvOpTypeVector, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(7, { + {0, 0, 0}, + {2, 0, 0}, + {3, 0, 0}, + {4, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 4}, + {0, 2, 5}, + {0, 3, 6}, + })); + + codecs.emplace(std::pair(SpvOpTypeMatrix, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(7, { + {0, 0, 0}, + {1, 0, 0}, + {2, 0, 0}, + {3, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 4}, + {0, 2, 5}, + {0, 1, 6}, + })); + + codecs.emplace(std::pair(SpvOpTypeImage, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(5, { + {0, 0, 0}, + {0, 0, 0}, + {1, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 3}, + {0, 1, 4}, + })); + + codecs.emplace(std::pair(SpvOpTypeImage, 3), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {0, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpTypeImage, 4), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {0, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpTypeImage, 5), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {1, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpTypeImage, 6), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {0, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpTypeImage, 7), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(13, { + {0, 0, 0}, + {0, 0, 0}, + {1, 0, 0}, + {2, 0, 0}, + {3, 0, 0}, + {6, 0, 0}, + {7, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 5, 7}, + {0, 6, 8}, + {0, 1, 4}, + {0, 2, 9}, + {0, 10, 3}, + {0, 12, 11}, + })); + + codecs.emplace(std::pair(SpvOpTypePointer, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(173, { + {0, 0, 0}, + {0, 0, 0}, + {1, 0, 0}, + {2, 0, 0}, + {3, 0, 0}, + {4, 0, 0}, + {5, 0, 0}, + {6, 0, 0}, + {7, 0, 0}, + {8, 0, 0}, + {9, 0, 0}, + {10, 0, 0}, + {11, 0, 0}, + {12, 0, 0}, + {13, 0, 0}, + {14, 0, 0}, + {15, 0, 0}, + {16, 0, 0}, + {17, 0, 0}, + {18, 0, 0}, + {19, 0, 0}, + {20, 0, 0}, + {21, 0, 0}, + {22, 0, 0}, + {23, 0, 0}, + {24, 0, 0}, + {26, 0, 0}, + {27, 0, 0}, + {28, 0, 0}, + {29, 0, 0}, + {30, 0, 0}, + {31, 0, 0}, + {32, 0, 0}, + {256, 0, 0}, + {507307272, 0, 0}, + {864026611, 0, 0}, + {981668463, 0, 0}, + {997553156, 0, 0}, + {1014330372, 0, 0}, + {1020708227, 0, 0}, + {1028443341, 0, 0}, + {1032953056, 0, 0}, + {1033463938, 0, 0}, + {1033463943, 0, 0}, + {1039998884, 0, 0}, + {1039998950, 0, 0}, + {1040187392, 0, 0}, + {1042401985, 0, 0}, + {1044220635, 0, 0}, + {1045622707, 0, 0}, + {1045622740, 0, 0}, + {1048576000, 0, 0}, + {1053609165, 0, 0}, + {1053790359, 0, 0}, + {1054448026, 0, 0}, + {1055437881, 0, 0}, + {1056300230, 0, 0}, + {1056964608, 0, 0}, + {1058056805, 0, 0}, + {1059286575, 0, 0}, + {1061158912, 0, 0}, + {1061997773, 0, 0}, + {1064514355, 0, 0}, + {1064854933, 0, 0}, + {1065353216, 0, 0}, + {1069547520, 0, 0}, + {1073741824, 0, 0}, + {1077936128, 0, 0}, + {1082130432, 0, 0}, + {1091567616, 0, 0}, + {1115422720, 0, 0}, + {1124073472, 0, 0}, + {1132396544, 0, 0}, + {1140850688, 0, 0}, + {1199562752, 0, 0}, + {3179067684, 0, 0}, + {3180973575, 0, 0}, + {3182651297, 0, 0}, + {3196448879, 0, 0}, + {3204448256, 0, 0}, + {3204993516, 0, 0}, + {3205248529, 0, 0}, + {3207137644, 0, 0}, + {3208642560, 0, 0}, + {3211081967, 0, 0}, + {3212836864, 0, 0}, + {3332128768, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 38, 37}, + {0, 42, 39}, + {0, 49, 44}, + {0, 45, 43}, + {0, 26, 50}, + {0, 46, 73}, + {0, 35, 28}, + {0, 32, 65}, + {0, 83, 40}, + {0, 60, 62}, + {0, 27, 54}, + {0, 79, 67}, + {0, 31, 74}, + {0, 51, 12}, + {0, 70, 30}, + {0, 15, 16}, + {0, 88, 25}, + {0, 90, 89}, + {0, 34, 71}, + {0, 72, 29}, + {0, 92, 91}, + {0, 14, 33}, + {0, 94, 93}, + {0, 22, 23}, + {0, 21, 95}, + {0, 19, 24}, + {0, 96, 13}, + {0, 47, 41}, + {0, 53, 48}, + {0, 58, 56}, + {0, 63, 59}, + {0, 76, 75}, + {0, 78, 77}, + {0, 81, 80}, + {0, 84, 82}, + {0, 52, 20}, + {0, 97, 69}, + {0, 99, 98}, + {0, 18, 10}, + {0, 68, 61}, + {0, 17, 100}, + {0, 102, 101}, + {0, 11, 36}, + {0, 104, 103}, + {0, 86, 105}, + {0, 107, 106}, + {0, 109, 108}, + {0, 110, 9}, + {0, 8, 111}, + {0, 113, 112}, + {0, 115, 114}, + {0, 117, 116}, + {0, 119, 118}, + {0, 121, 120}, + {0, 123, 122}, + {0, 125, 124}, + {0, 126, 7}, + {0, 127, 85}, + {0, 6, 128}, + {0, 129, 55}, + {0, 130, 5}, + {0, 132, 131}, + {0, 134, 133}, + {0, 136, 135}, + {0, 137, 66}, + {0, 139, 138}, + {0, 141, 140}, + {0, 143, 142}, + {0, 145, 144}, + {0, 146, 57}, + {0, 147, 64}, + {0, 148, 4}, + {0, 149, 2}, + {0, 151, 150}, + {0, 152, 3}, + {0, 154, 153}, + {0, 156, 155}, + {0, 158, 157}, + {0, 159, 1}, + {0, 160, 87}, + {0, 162, 161}, + {0, 164, 163}, + {0, 166, 165}, + {0, 168, 167}, + {0, 170, 169}, + {0, 172, 171}, + })); + + codecs.emplace(std::pair(SpvOpConstant, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {0, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpFunction, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(13, { + {0, 0, 0}, + {0, 0, 0}, + {1, 0, 0}, + {2, 0, 0}, + {3, 0, 0}, + {6, 0, 0}, + {7, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 7}, + {0, 4, 8}, + {0, 9, 2}, + {0, 1, 5}, + {0, 10, 6}, + {0, 12, 11}, + })); + + codecs.emplace(std::pair(SpvOpVariable, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(15, { + {0, 0, 0}, + {0, 0, 0}, + {2, 0, 0}, + {6, 0, 0}, + {11, 0, 0}, + {30, 0, 0}, + {33, 0, 0}, + {34, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 4, 8}, + {0, 9, 1}, + {0, 3, 10}, + {0, 6, 11}, + {0, 12, 2}, + {0, 7, 5}, + {0, 14, 13}, + })); + + codecs.emplace(std::pair(SpvOpDecorate, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(37, { + {0, 0, 0}, + {0, 0, 0}, + {1, 0, 0}, + {2, 0, 0}, + {3, 0, 0}, + {4, 0, 0}, + {5, 0, 0}, + {6, 0, 0}, + {7, 0, 0}, + {8, 0, 0}, + {9, 0, 0}, + {10, 0, 0}, + {12, 0, 0}, + {13, 0, 0}, + {14, 0, 0}, + {15, 0, 0}, + {16, 0, 0}, + {18, 0, 0}, + {64, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 17, 11}, + {0, 10, 13}, + {0, 12, 14}, + {0, 21, 20}, + {0, 9, 22}, + {0, 19, 15}, + {0, 8, 23}, + {0, 18, 24}, + {0, 25, 7}, + {0, 5, 6}, + {0, 26, 16}, + {0, 27, 4}, + {0, 28, 3}, + {0, 30, 29}, + {0, 31, 2}, + {0, 33, 32}, + {0, 35, 34}, + {0, 1, 36}, + })); + + codecs.emplace(std::pair(SpvOpDecorate, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(79, { + {0, 0, 0}, + {0, 0, 0}, + {1, 0, 0}, + {2, 0, 0}, + {3, 0, 0}, + {4, 0, 0}, + {5, 0, 0}, + {6, 0, 0}, + {7, 0, 0}, + {8, 0, 0}, + {9, 0, 0}, + {10, 0, 0}, + {11, 0, 0}, + {12, 0, 0}, + {13, 0, 0}, + {14, 0, 0}, + {15, 0, 0}, + {16, 0, 0}, + {17, 0, 0}, + {18, 0, 0}, + {19, 0, 0}, + {20, 0, 0}, + {21, 0, 0}, + {22, 0, 0}, + {23, 0, 0}, + {24, 0, 0}, + {25, 0, 0}, + {26, 0, 0}, + {27, 0, 0}, + {28, 0, 0}, + {29, 0, 0}, + {30, 0, 0}, + {31, 0, 0}, + {32, 0, 0}, + {33, 0, 0}, + {34, 0, 0}, + {35, 0, 0}, + {36, 0, 0}, + {37, 0, 0}, + {38, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 39, 37}, + {0, 40, 36}, + {0, 34, 35}, + {0, 32, 33}, + {0, 30, 31}, + {0, 27, 29}, + {0, 26, 28}, + {0, 42, 41}, + {0, 23, 25}, + {0, 38, 22}, + {0, 44, 43}, + {0, 46, 45}, + {0, 21, 47}, + {0, 19, 20}, + {0, 17, 18}, + {0, 14, 15}, + {0, 12, 10}, + {0, 16, 13}, + {0, 9, 11}, + {0, 7, 8}, + {0, 6, 5}, + {0, 24, 48}, + {0, 50, 49}, + {0, 3, 4}, + {0, 51, 2}, + {0, 1, 52}, + {0, 54, 53}, + {0, 56, 55}, + {0, 58, 57}, + {0, 60, 59}, + {0, 62, 61}, + {0, 64, 63}, + {0, 66, 65}, + {0, 68, 67}, + {0, 70, 69}, + {0, 72, 71}, + {0, 74, 73}, + {0, 76, 75}, + {0, 78, 77}, + })); + + codecs.emplace(std::pair(SpvOpMemberDecorate, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(7, { + {0, 0, 0}, + {4, 0, 0}, + {7, 0, 0}, + {35, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 4}, + {0, 5, 2}, + {0, 3, 6}, + })); + + codecs.emplace(std::pair(SpvOpMemberDecorate, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(149, { + {0, 0, 0}, + {0, 0, 0}, + {16, 0, 0}, + {28, 0, 0}, + {32, 0, 0}, + {36, 0, 0}, + {40, 0, 0}, + {44, 0, 0}, + {48, 0, 0}, + {60, 0, 0}, + {64, 0, 0}, + {76, 0, 0}, + {80, 0, 0}, + {84, 0, 0}, + {88, 0, 0}, + {92, 0, 0}, + {96, 0, 0}, + {100, 0, 0}, + {108, 0, 0}, + {112, 0, 0}, + {120, 0, 0}, + {124, 0, 0}, + {128, 0, 0}, + {132, 0, 0}, + {136, 0, 0}, + {140, 0, 0}, + {144, 0, 0}, + {148, 0, 0}, + {152, 0, 0}, + {156, 0, 0}, + {160, 0, 0}, + {172, 0, 0}, + {176, 0, 0}, + {192, 0, 0}, + {204, 0, 0}, + {208, 0, 0}, + {224, 0, 0}, + {236, 0, 0}, + {240, 0, 0}, + {248, 0, 0}, + {256, 0, 0}, + {272, 0, 0}, + {288, 0, 0}, + {292, 0, 0}, + {296, 0, 0}, + {300, 0, 0}, + {304, 0, 0}, + {316, 0, 0}, + {320, 0, 0}, + {332, 0, 0}, + {336, 0, 0}, + {348, 0, 0}, + {352, 0, 0}, + {364, 0, 0}, + {368, 0, 0}, + {372, 0, 0}, + {376, 0, 0}, + {384, 0, 0}, + {392, 0, 0}, + {400, 0, 0}, + {416, 0, 0}, + {424, 0, 0}, + {432, 0, 0}, + {448, 0, 0}, + {460, 0, 0}, + {464, 0, 0}, + {468, 0, 0}, + {472, 0, 0}, + {476, 0, 0}, + {480, 0, 0}, + {488, 0, 0}, + {492, 0, 0}, + {496, 0, 0}, + {512, 0, 0}, + {640, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 14, 17}, + {0, 37, 31}, + {0, 21, 39}, + {0, 24, 23}, + {0, 5, 13}, + {0, 38, 76}, + {0, 51, 77}, + {0, 55, 53}, + {0, 58, 56}, + {0, 64, 61}, + {0, 67, 66}, + {0, 70, 68}, + {0, 54, 71}, + {0, 62, 60}, + {0, 65, 63}, + {0, 73, 72}, + {0, 59, 57}, + {0, 52, 74}, + {0, 50, 69}, + {0, 49, 47}, + {0, 48, 46}, + {0, 45, 43}, + {0, 42, 44}, + {0, 78, 41}, + {0, 20, 18}, + {0, 80, 79}, + {0, 15, 27}, + {0, 7, 34}, + {0, 81, 6}, + {0, 28, 3}, + {0, 35, 82}, + {0, 9, 36}, + {0, 84, 83}, + {0, 86, 85}, + {0, 88, 87}, + {0, 90, 89}, + {0, 92, 91}, + {0, 94, 93}, + {0, 96, 95}, + {0, 98, 97}, + {0, 11, 29}, + {0, 99, 25}, + {0, 100, 40}, + {0, 102, 101}, + {0, 26, 32}, + {0, 19, 30}, + {0, 16, 12}, + {0, 4, 8}, + {0, 104, 103}, + {0, 106, 105}, + {0, 33, 107}, + {0, 109, 108}, + {0, 111, 110}, + {0, 22, 112}, + {0, 113, 10}, + {0, 115, 114}, + {0, 75, 116}, + {0, 118, 117}, + {0, 119, 1}, + {0, 121, 120}, + {0, 123, 122}, + {0, 125, 124}, + {0, 127, 126}, + {0, 129, 128}, + {0, 131, 130}, + {0, 132, 2}, + {0, 134, 133}, + {0, 136, 135}, + {0, 138, 137}, + {0, 140, 139}, + {0, 142, 141}, + {0, 144, 143}, + {0, 146, 145}, + {0, 148, 147}, + })); + + codecs.emplace(std::pair(SpvOpMemberDecorate, 3), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(11, { + {0, 0, 0}, + {0, 0, 0}, + {1, 0, 0}, + {2, 0, 0}, + {3, 0, 0}, + {4, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 6}, + {0, 4, 7}, + {0, 8, 3}, + {0, 9, 5}, + {0, 1, 10}, + })); + + codecs.emplace(std::pair(SpvOpVectorShuffle, 4), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(13, { + {0, 0, 0}, + {0, 0, 0}, + {1, 0, 0}, + {2, 0, 0}, + {3, 0, 0}, + {4, 0, 0}, + {5, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 7}, + {0, 8, 5}, + {0, 9, 1}, + {0, 4, 10}, + {0, 11, 6}, + {0, 2, 12}, + })); + + codecs.emplace(std::pair(SpvOpVectorShuffle, 5), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(15, { + {0, 0, 0}, + {0, 0, 0}, + {1, 0, 0}, + {2, 0, 0}, + {3, 0, 0}, + {4, 0, 0}, + {5, 0, 0}, + {6, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 6, 8}, + {0, 5, 2}, + {0, 10, 9}, + {0, 1, 4}, + {0, 12, 11}, + {0, 7, 13}, + {0, 3, 14}, + })); + + codecs.emplace(std::pair(SpvOpVectorShuffle, 6), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(15, { + {0, 0, 0}, + {0, 0, 0}, + {1, 0, 0}, + {2, 0, 0}, + {3, 0, 0}, + {4, 0, 0}, + {5, 0, 0}, + {6, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 8, 5}, + {0, 9, 7}, + {0, 10, 3}, + {0, 11, 2}, + {0, 6, 1}, + {0, 13, 12}, + {0, 4, 14}, + })); + + codecs.emplace(std::pair(SpvOpVectorShuffle, 7), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(61, { + {0, 0, 0}, + {0, 0, 0}, + {1, 0, 0}, + {2, 0, 0}, + {3, 0, 0}, + {4, 0, 0}, + {5, 0, 0}, + {6, 0, 0}, + {7, 0, 0}, + {8, 0, 0}, + {9, 0, 0}, + {10, 0, 0}, + {11, 0, 0}, + {12, 0, 0}, + {13, 0, 0}, + {14, 0, 0}, + {15, 0, 0}, + {16, 0, 0}, + {17, 0, 0}, + {18, 0, 0}, + {19, 0, 0}, + {20, 0, 0}, + {21, 0, 0}, + {22, 0, 0}, + {23, 0, 0}, + {24, 0, 0}, + {27, 0, 0}, + {28, 0, 0}, + {29, 0, 0}, + {30, 0, 0}, + {31, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 30, 16}, + {0, 26, 27}, + {0, 29, 28}, + {0, 18, 22}, + {0, 12, 19}, + {0, 15, 20}, + {0, 14, 23}, + {0, 32, 7}, + {0, 8, 21}, + {0, 11, 33}, + {0, 17, 34}, + {0, 25, 13}, + {0, 36, 35}, + {0, 9, 10}, + {0, 38, 37}, + {0, 39, 31}, + {0, 5, 40}, + {0, 42, 41}, + {0, 44, 43}, + {0, 6, 45}, + {0, 46, 24}, + {0, 48, 47}, + {0, 50, 49}, + {0, 52, 51}, + {0, 54, 53}, + {0, 55, 4}, + {0, 56, 3}, + {0, 57, 2}, + {0, 58, 1}, + {0, 60, 59}, + })); + + codecs.emplace(std::pair(SpvOpCompositeExtract, 3), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(63, { + {0, 0, 0}, + {0, 0, 0}, + {1, 0, 0}, + {2, 0, 0}, + {3, 0, 0}, + {4, 0, 0}, + {5, 0, 0}, + {6, 0, 0}, + {7, 0, 0}, + {8, 0, 0}, + {9, 0, 0}, + {10, 0, 0}, + {11, 0, 0}, + {12, 0, 0}, + {13, 0, 0}, + {29, 0, 0}, + {30, 0, 0}, + {31, 0, 0}, + {32, 0, 0}, + {33, 0, 0}, + {34, 0, 0}, + {35, 0, 0}, + {36, 0, 0}, + {37, 0, 0}, + {38, 0, 0}, + {39, 0, 0}, + {40, 0, 0}, + {41, 0, 0}, + {42, 0, 0}, + {43, 0, 0}, + {44, 0, 0}, + {45, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 13, 14}, + {0, 12, 9}, + {0, 11, 25}, + {0, 27, 26}, + {0, 29, 28}, + {0, 31, 30}, + {0, 23, 22}, + {0, 10, 24}, + {0, 8, 21}, + {0, 17, 7}, + {0, 19, 18}, + {0, 15, 20}, + {0, 6, 16}, + {0, 5, 33}, + {0, 35, 34}, + {0, 37, 36}, + {0, 39, 38}, + {0, 41, 40}, + {0, 43, 42}, + {0, 45, 44}, + {0, 47, 46}, + {0, 49, 48}, + {0, 51, 50}, + {0, 32, 52}, + {0, 54, 53}, + {0, 56, 55}, + {0, 58, 57}, + {0, 3, 2}, + {0, 59, 4}, + {0, 60, 1}, + {0, 62, 61}, + })); + + codecs.emplace(std::pair(SpvOpCompositeExtract, 4), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(9, { + {0, 0, 0}, + {0, 0, 0}, + {1, 0, 0}, + {2, 0, 0}, + {3, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 5}, + {0, 3, 2}, + {0, 6, 4}, + {0, 8, 7}, + })); + + codecs.emplace(std::pair(SpvOpCompositeExtract, 5), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(23, { + {0, 0, 0}, + {0, 0, 0}, + {1, 0, 0}, + {2, 0, 0}, + {3, 0, 0}, + {4, 0, 0}, + {5, 0, 0}, + {6, 0, 0}, + {7, 0, 0}, + {8, 0, 0}, + {9, 0, 0}, + {10, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 12, 11}, + {0, 10, 13}, + {0, 9, 14}, + {0, 7, 5}, + {0, 8, 6}, + {0, 4, 15}, + {0, 17, 16}, + {0, 18, 3}, + {0, 19, 2}, + {0, 20, 1}, + {0, 22, 21}, + })); + + codecs.emplace(std::pair(SpvOpCompositeInsert, 4), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(9, { + {0, 0, 0}, + {0, 0, 0}, + {1, 0, 0}, + {2, 0, 0}, + {3, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 5}, + {0, 2, 6}, + {0, 7, 1}, + {0, 4, 8}, + })); + + codecs.emplace(std::pair(SpvOpCompositeInsert, 5), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {1, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpImageSampleImplicitLod, 4), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(5, { + {0, 0, 0}, + {2, 0, 0}, + {10, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 3}, + {0, 1, 4}, + })); + + codecs.emplace(std::pair(SpvOpImageSampleExplicitLod, 4), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {2, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpImageSampleDrefExplicitLod, 5), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {0, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpSelectionMerge, 1), std::move(codec)); + } + + return codecs; +} + +std::map, std::unique_ptr>> +GetIdDescriptorHuffmanCodecs() { + std::map, std::unique_ptr>> codecs; + { + std::unique_ptr> codec(new HuffmanCodec(9, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 5}, + {0, 4, 6}, + {0, 1, 7}, + {0, 2, 8}, + })); + + codecs.emplace(std::pair(SpvOpExtInst, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(63, { + {0, 0, 0}, + {34183582, 0, 0}, + {223800276, 0, 0}, + {295018543, 0, 0}, + {439764402, 0, 0}, + {443558693, 0, 0}, + {583624926, 0, 0}, + {599185303, 0, 0}, + {779021139, 0, 0}, + {1015552308, 0, 0}, + {1027242654, 0, 0}, + {1077859090, 0, 0}, + {1104362365, 0, 0}, + {1132589448, 0, 0}, + {1236389532, 0, 0}, + {1739837626, 0, 0}, + {1955104493, 0, 0}, + {2161102232, 0, 0}, + {2197874825, 0, 0}, + {2217833278, 0, 0}, + {2244470522, 0, 0}, + {2532518896, 0, 0}, + {2789375411, 0, 0}, + {3061690214, 0, 0}, + {3287039847, 0, 0}, + {3357301402, 0, 0}, + {3365041621, 0, 0}, + {3510257966, 0, 0}, + {3534235309, 0, 0}, + {4018237905, 0, 0}, + {4145966869, 0, 0}, + {4272200782, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 10, 19}, + {0, 6, 1}, + {0, 26, 13}, + {0, 2, 11}, + {0, 15, 22}, + {0, 23, 18}, + {0, 4, 27}, + {0, 28, 12}, + {0, 3, 30}, + {0, 9, 7}, + {0, 20, 14}, + {0, 29, 16}, + {0, 21, 8}, + {0, 34, 33}, + {0, 36, 35}, + {0, 31, 25}, + {0, 37, 24}, + {0, 39, 38}, + {0, 41, 40}, + {0, 43, 42}, + {0, 45, 44}, + {0, 17, 5}, + {0, 47, 46}, + {0, 49, 48}, + {0, 51, 50}, + {0, 53, 52}, + {0, 55, 54}, + {0, 57, 56}, + {0, 59, 58}, + {0, 61, 60}, + {0, 32, 62}, + })); + + codecs.emplace(std::pair(SpvOpExtInst, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {4228502127, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpExtInst, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(113, { + {0, 0, 0}, + {50998433, 0, 0}, + {139011596, 0, 0}, + {181902171, 0, 0}, + {296981500, 0, 0}, + {321630747, 0, 0}, + {416853049, 0, 0}, + {464259778, 0, 0}, + {615982737, 0, 0}, + {669982125, 0, 0}, + {759277550, 0, 0}, + {810488476, 0, 0}, + {870594305, 0, 0}, + {922996215, 0, 0}, + {969500141, 0, 0}, + {1015552308, 0, 0}, + {1139547465, 0, 0}, + {1203545131, 0, 0}, + {1220643281, 0, 0}, + {1220749418, 0, 0}, + {1367301635, 0, 0}, + {1395923345, 0, 0}, + {1554194368, 0, 0}, + {1742737136, 0, 0}, + {1755648697, 0, 0}, + {1962162282, 0, 0}, + {1964254745, 0, 0}, + {2055836767, 0, 0}, + {2096388952, 0, 0}, + {2124837447, 0, 0}, + {2161102232, 0, 0}, + {2321729979, 0, 0}, + {2346547796, 0, 0}, + {2399809085, 0, 0}, + {2432827426, 0, 0}, + {2455417440, 0, 0}, + {2572638469, 0, 0}, + {2614879967, 0, 0}, + {2855506940, 0, 0}, + {2919796598, 0, 0}, + {2970183398, 0, 0}, + {2976066508, 0, 0}, + {3044188332, 0, 0}, + {3061690214, 0, 0}, + {3091876332, 0, 0}, + {3104643263, 0, 0}, + {3107165180, 0, 0}, + {3187066832, 0, 0}, + {3413713311, 0, 0}, + {3487022798, 0, 0}, + {3602693817, 0, 0}, + {3678875745, 0, 0}, + {3701632935, 0, 0}, + {3829325073, 0, 0}, + {4040340620, 0, 0}, + {4174489262, 0, 0}, + {4272200782, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 33, 7}, + {0, 13, 34}, + {0, 21, 18}, + {0, 53, 22}, + {0, 39, 1}, + {0, 14, 9}, + {0, 43, 26}, + {0, 51, 35}, + {0, 19, 6}, + {0, 15, 25}, + {0, 55, 29}, + {0, 32, 3}, + {0, 27, 44}, + {0, 10, 46}, + {0, 45, 24}, + {0, 36, 40}, + {0, 47, 8}, + {0, 48, 54}, + {0, 58, 5}, + {0, 60, 59}, + {0, 30, 61}, + {0, 62, 56}, + {0, 64, 63}, + {0, 41, 50}, + {0, 66, 65}, + {0, 68, 67}, + {0, 70, 69}, + {0, 37, 31}, + {0, 4, 17}, + {0, 16, 20}, + {0, 72, 71}, + {0, 73, 52}, + {0, 49, 12}, + {0, 75, 74}, + {0, 76, 11}, + {0, 23, 42}, + {0, 78, 77}, + {0, 80, 79}, + {0, 82, 81}, + {0, 84, 83}, + {0, 85, 28}, + {0, 87, 86}, + {0, 89, 88}, + {0, 91, 90}, + {0, 93, 92}, + {0, 94, 2}, + {0, 96, 95}, + {0, 98, 97}, + {0, 100, 99}, + {0, 102, 101}, + {0, 38, 103}, + {0, 105, 104}, + {0, 107, 106}, + {0, 109, 108}, + {0, 111, 110}, + {0, 57, 112}, + })); + + codecs.emplace(std::pair(SpvOpExtInst, 4), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(127, { + {0, 0, 0}, + {72782198, 0, 0}, + {139011596, 0, 0}, + {296981500, 0, 0}, + {300939750, 0, 0}, + {401211099, 0, 0}, + {429277936, 0, 0}, + {505940164, 0, 0}, + {538168945, 0, 0}, + {603915804, 0, 0}, + {688216667, 0, 0}, + {706016261, 0, 0}, + {790502615, 0, 0}, + {810488476, 0, 0}, + {993150979, 0, 0}, + {1203545131, 0, 0}, + {1206726575, 0, 0}, + {1265796414, 0, 0}, + {1314843976, 0, 0}, + {1367301635, 0, 0}, + {1378082995, 0, 0}, + {1410311776, 0, 0}, + {1443829854, 0, 0}, + {1448448666, 0, 0}, + {1468919488, 0, 0}, + {1496351055, 0, 0}, + {1619778288, 0, 0}, + {1684282922, 0, 0}, + {1848784182, 0, 0}, + {1901166356, 0, 0}, + {2095546797, 0, 0}, + {2096388952, 0, 0}, + {2162986400, 0, 0}, + {2197874825, 0, 0}, + {2246405597, 0, 0}, + {2250225826, 0, 0}, + {2282454607, 0, 0}, + {2328748202, 0, 0}, + {2348201466, 0, 0}, + {2597020383, 0, 0}, + {2633682514, 0, 0}, + {2817335337, 0, 0}, + {2855506940, 0, 0}, + {2936040203, 0, 0}, + {2955375511, 0, 0}, + {3122368657, 0, 0}, + {3154597438, 0, 0}, + {3184381405, 0, 0}, + {3187066832, 0, 0}, + {3233393284, 0, 0}, + {3251128023, 0, 0}, + {3260309823, 0, 0}, + {3441531391, 0, 0}, + {3496407048, 0, 0}, + {3582002820, 0, 0}, + {3647586740, 0, 0}, + {3653838348, 0, 0}, + {3730093054, 0, 0}, + {3759072440, 0, 0}, + {3928764629, 0, 0}, + {3969279737, 0, 0}, + {3994511488, 0, 0}, + {4026740269, 0, 0}, + {4274214049, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 43, 23}, + {0, 5, 24}, + {0, 9, 8}, + {0, 36, 21}, + {0, 13, 46}, + {0, 7, 12}, + {0, 35, 20}, + {0, 61, 59}, + {0, 22, 29}, + {0, 38, 62}, + {0, 56, 45}, + {0, 6, 48}, + {0, 33, 30}, + {0, 14, 58}, + {0, 34, 28}, + {0, 51, 40}, + {0, 63, 55}, + {0, 25, 16}, + {0, 17, 11}, + {0, 53, 52}, + {0, 65, 27}, + {0, 39, 41}, + {0, 67, 66}, + {0, 69, 68}, + {0, 10, 4}, + {0, 37, 18}, + {0, 60, 47}, + {0, 1, 32}, + {0, 71, 70}, + {0, 73, 72}, + {0, 57, 26}, + {0, 74, 31}, + {0, 76, 75}, + {0, 77, 44}, + {0, 78, 15}, + {0, 79, 54}, + {0, 81, 80}, + {0, 82, 49}, + {0, 84, 83}, + {0, 86, 85}, + {0, 88, 87}, + {0, 89, 19}, + {0, 91, 90}, + {0, 93, 92}, + {0, 95, 94}, + {0, 2, 96}, + {0, 98, 97}, + {0, 100, 99}, + {0, 102, 101}, + {0, 104, 103}, + {0, 106, 105}, + {0, 3, 107}, + {0, 109, 108}, + {0, 111, 110}, + {0, 113, 112}, + {0, 114, 50}, + {0, 116, 115}, + {0, 118, 117}, + {0, 120, 119}, + {0, 122, 121}, + {0, 124, 123}, + {0, 64, 42}, + {0, 126, 125}, + })); + + codecs.emplace(std::pair(SpvOpExtInst, 5), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(93, { + {0, 0, 0}, + {99347751, 0, 0}, + {102542696, 0, 0}, + {107497541, 0, 0}, + {112452386, 0, 0}, + {139011596, 0, 0}, + {296981500, 0, 0}, + {429277936, 0, 0}, + {451957774, 0, 0}, + {508217552, 0, 0}, + {573901046, 0, 0}, + {774727851, 0, 0}, + {801484894, 0, 0}, + {920604853, 0, 0}, + {925559698, 0, 0}, + {1022915255, 0, 0}, + {1209418480, 0, 0}, + {1287937401, 0, 0}, + {1319785741, 0, 0}, + {1392080469, 0, 0}, + {1538342947, 0, 0}, + {1541020250, 0, 0}, + {1587209598, 0, 0}, + {1594733696, 0, 0}, + {1631434666, 0, 0}, + {1636389511, 0, 0}, + {1684282922, 0, 0}, + {1859128680, 0, 0}, + {1901166356, 0, 0}, + {2004567202, 0, 0}, + {2119793999, 0, 0}, + {2280400314, 0, 0}, + {2538917932, 0, 0}, + {2677264274, 0, 0}, + {2683080096, 0, 0}, + {2854085372, 0, 0}, + {2879917501, 0, 0}, + {3059119137, 0, 0}, + {3174324790, 0, 0}, + {3194725903, 0, 0}, + {3358097187, 0, 0}, + {3547456240, 0, 0}, + {3614752756, 0, 0}, + {3753486980, 0, 0}, + {3811268385, 0, 0}, + {3953733490, 0, 0}, + {3990925720, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 23, 22}, + {0, 36, 31}, + {0, 17, 40}, + {0, 27, 19}, + {0, 35, 33}, + {0, 30, 38}, + {0, 42, 39}, + {0, 46, 32}, + {0, 13, 12}, + {0, 44, 14}, + {0, 29, 11}, + {0, 10, 18}, + {0, 15, 37}, + {0, 1, 4}, + {0, 45, 2}, + {0, 21, 28}, + {0, 8, 5}, + {0, 49, 48}, + {0, 51, 50}, + {0, 53, 52}, + {0, 54, 16}, + {0, 55, 25}, + {0, 56, 3}, + {0, 58, 57}, + {0, 59, 26}, + {0, 20, 7}, + {0, 61, 60}, + {0, 62, 24}, + {0, 41, 63}, + {0, 65, 64}, + {0, 9, 34}, + {0, 67, 66}, + {0, 69, 68}, + {0, 71, 70}, + {0, 73, 72}, + {0, 75, 74}, + {0, 76, 43}, + {0, 78, 77}, + {0, 80, 79}, + {0, 82, 81}, + {0, 84, 83}, + {0, 86, 85}, + {0, 88, 87}, + {0, 90, 89}, + {0, 47, 91}, + {0, 92, 6}, + })); + + codecs.emplace(std::pair(SpvOpExtInst, 6), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(15, { + {0, 0, 0}, + {166253838, 0, 0}, + {679771963, 0, 0}, + {1247793383, 0, 0}, + {2261697609, 0, 0}, + {2263349224, 0, 0}, + {2320303498, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 4, 8}, + {0, 9, 1}, + {0, 3, 5}, + {0, 11, 10}, + {0, 2, 12}, + {0, 7, 6}, + {0, 14, 13}, + })); + + codecs.emplace(std::pair(SpvOpTypeVector, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(9, { + {0, 0, 0}, + {789872778, 0, 0}, + {1415510495, 0, 0}, + {1951208733, 0, 0}, + {2430404313, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 5}, + {0, 4, 6}, + {0, 7, 1}, + {0, 3, 8}, + })); + + codecs.emplace(std::pair(SpvOpTypeVector, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(15, { + {0, 0, 0}, + {1389644742, 0, 0}, + {3232633974, 0, 0}, + {3278176820, 0, 0}, + {3648138580, 0, 0}, + {3687777340, 0, 0}, + {3694383800, 0, 0}, + {3697687030, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 5, 4}, + {0, 9, 6}, + {0, 10, 8}, + {0, 2, 11}, + {0, 12, 3}, + {0, 1, 13}, + {0, 14, 7}, + })); + + codecs.emplace(std::pair(SpvOpTypeArray, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(7, { + {0, 0, 0}, + {1951208733, 0, 0}, + {2160380860, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 4}, + {0, 2, 5}, + {0, 3, 6}, + })); + + codecs.emplace(std::pair(SpvOpTypeArray, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(13, { + {0, 0, 0}, + {144116905, 0, 0}, + {827246872, 0, 0}, + {1545298048, 0, 0}, + {2715370488, 0, 0}, + {2798552666, 0, 0}, + {3812456892, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 3}, + {0, 8, 6}, + {0, 9, 7}, + {0, 1, 10}, + {0, 11, 4}, + {0, 5, 12}, + })); + + codecs.emplace(std::pair(SpvOpTypeArray, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(67, { + {0, 0, 0}, + {40653745, 0, 0}, + {119981689, 0, 0}, + {153085016, 0, 0}, + {451382997, 0, 0}, + {545678922, 0, 0}, + {899570100, 0, 0}, + {929101967, 0, 0}, + {1070791291, 0, 0}, + {1100599986, 0, 0}, + {1103903216, 0, 0}, + {1154919607, 0, 0}, + {1199157863, 0, 0}, + {1258105452, 0, 0}, + {1369578001, 0, 0}, + {1372881231, 0, 0}, + {1674803691, 0, 0}, + {1677700667, 0, 0}, + {1989520052, 0, 0}, + {2593884753, 0, 0}, + {2664825925, 0, 0}, + {2924146124, 0, 0}, + {2926633629, 0, 0}, + {3249265647, 0, 0}, + {3345288309, 0, 0}, + {3410158390, 0, 0}, + {3489360962, 0, 0}, + {3495967422, 0, 0}, + {3504981554, 0, 0}, + {3705139860, 0, 0}, + {3822983876, 0, 0}, + {4141567741, 0, 0}, + {4234287173, 0, 0}, + {4240893633, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 15, 23}, + {0, 20, 17}, + {0, 32, 22}, + {0, 19, 12}, + {0, 13, 3}, + {0, 30, 27}, + {0, 4, 35}, + {0, 24, 36}, + {0, 31, 37}, + {0, 33, 38}, + {0, 39, 7}, + {0, 6, 40}, + {0, 41, 29}, + {0, 14, 42}, + {0, 43, 28}, + {0, 10, 44}, + {0, 45, 18}, + {0, 26, 46}, + {0, 5, 47}, + {0, 48, 2}, + {0, 49, 9}, + {0, 50, 16}, + {0, 34, 25}, + {0, 52, 51}, + {0, 54, 53}, + {0, 56, 55}, + {0, 58, 57}, + {0, 60, 59}, + {0, 8, 21}, + {0, 1, 11}, + {0, 62, 61}, + {0, 64, 63}, + {0, 66, 65}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(11, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2160380860, 0, 0}, + {3278176820, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 4, 6}, + {0, 2, 7}, + {0, 3, 8}, + {0, 9, 1}, + {0, 5, 10}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(13, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2160380860, 0, 0}, + {2320303498, 0, 0}, + {3232633974, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 5, 7}, + {0, 2, 8}, + {0, 4, 9}, + {0, 10, 3}, + {0, 1, 6}, + {0, 12, 11}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(11, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2160380860, 0, 0}, + {2320303498, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 5, 6}, + {0, 1, 7}, + {0, 3, 4}, + {0, 8, 2}, + {0, 10, 9}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 3), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(11, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2160380860, 0, 0}, + {2320303498, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 6}, + {0, 3, 7}, + {0, 5, 4}, + {0, 8, 1}, + {0, 10, 9}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 4), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(9, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2263349224, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 5}, + {0, 1, 6}, + {0, 2, 7}, + {0, 8, 4}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 5), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(9, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 5}, + {0, 1, 6}, + {0, 2, 7}, + {0, 8, 4}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 6), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(9, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 5}, + {0, 4, 6}, + {0, 7, 1}, + {0, 2, 8}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 7), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(9, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 5}, + {0, 1, 6}, + {0, 7, 4}, + {0, 2, 8}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 8), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(7, { + {0, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 4}, + {0, 3, 5}, + {0, 1, 6}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 9), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(9, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 5, 3}, + {0, 1, 6}, + {0, 4, 7}, + {0, 8, 2}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 10), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(9, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 5}, + {0, 1, 6}, + {0, 7, 4}, + {0, 8, 2}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 11), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(5, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 3}, + {0, 4, 1}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 12), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(7, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 4}, + {0, 1, 5}, + {0, 2, 6}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 13), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(9, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 4, 5}, + {0, 3, 6}, + {0, 7, 1}, + {0, 8, 2}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 14), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(7, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 4}, + {0, 5, 3}, + {0, 6, 2}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 15), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(7, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 4}, + {0, 2, 5}, + {0, 1, 6}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 16), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(9, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 5}, + {0, 4, 6}, + {0, 7, 1}, + {0, 8, 2}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 17), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(7, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 4}, + {0, 1, 5}, + {0, 2, 6}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 18), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(7, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 4}, + {0, 3, 5}, + {0, 2, 6}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 19), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(7, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 4}, + {0, 1, 5}, + {0, 2, 6}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 20), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(7, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 4}, + {0, 2, 5}, + {0, 3, 6}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 21), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(9, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 5, 1}, + {0, 2, 6}, + {0, 3, 7}, + {0, 8, 4}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 22), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(9, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 5}, + {0, 2, 6}, + {0, 4, 7}, + {0, 8, 3}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 23), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(11, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2160380860, 0, 0}, + {2320303498, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 6, 4}, + {0, 1, 7}, + {0, 2, 8}, + {0, 3, 9}, + {0, 10, 5}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 24), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(9, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 5}, + {0, 2, 6}, + {0, 4, 7}, + {0, 8, 3}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 25), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(7, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 4}, + {0, 2, 5}, + {0, 3, 6}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 26), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(7, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 4}, + {0, 2, 5}, + {0, 3, 6}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 27), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(5, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 3}, + {0, 1, 4}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 28), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(5, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 3}, + {0, 2, 4}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 29), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(5, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 3}, + {0, 2, 4}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 30), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(7, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 4, 3}, + {0, 1, 5}, + {0, 2, 6}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 31), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(7, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 4}, + {0, 1, 5}, + {0, 2, 6}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 32), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(5, { + {0, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 3}, + {0, 1, 4}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 33), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(5, { + {0, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 3}, + {0, 2, 4}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 34), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(5, { + {0, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 3}, + {0, 1, 4}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 35), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(5, { + {0, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 3}, + {0, 1, 4}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 36), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {679771963, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 37), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {1389644742, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 38), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {3697687030, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 39), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {2320303498, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 40), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {2320303498, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 41), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {679771963, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 42), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {679771963, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 43), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {679771963, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 44), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {679771963, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 45), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {679771963, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 46), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {679771963, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 47), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {679771963, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 48), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {679771963, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 49), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {679771963, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 50), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {679771963, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpTypeStruct, 51), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(101, { + {0, 0, 0}, + {85880059, 0, 0}, + {135486769, 0, 0}, + {304448521, 0, 0}, + {436416061, 0, 0}, + {440421571, 0, 0}, + {450406196, 0, 0}, + {503094540, 0, 0}, + {543621065, 0, 0}, + {626892406, 0, 0}, + {628544021, 0, 0}, + {827698488, 0, 0}, + {869050696, 0, 0}, + {907126242, 0, 0}, + {908777857, 0, 0}, + {910429472, 0, 0}, + {1113409935, 0, 0}, + {1294403159, 0, 0}, + {1296054774, 0, 0}, + {1297706389, 0, 0}, + {1322549027, 0, 0}, + {1784441183, 0, 0}, + {2080953106, 0, 0}, + {2194691858, 0, 0}, + {2448331885, 0, 0}, + {2466255445, 0, 0}, + {2468230023, 0, 0}, + {2547657777, 0, 0}, + {2549309392, 0, 0}, + {2550961007, 0, 0}, + {2894051250, 0, 0}, + {2929019254, 0, 0}, + {2934934694, 0, 0}, + {2936586309, 0, 0}, + {2938237924, 0, 0}, + {3077271274, 0, 0}, + {3092528578, 0, 0}, + {3094180193, 0, 0}, + {3094857332, 0, 0}, + {3095831808, 0, 0}, + {3183924418, 0, 0}, + {3207966516, 0, 0}, + {3282979782, 0, 0}, + {3433956341, 0, 0}, + {3561562003, 0, 0}, + {3563213618, 0, 0}, + {3564865233, 0, 0}, + {3585511591, 0, 0}, + {4028622909, 0, 0}, + {4039938779, 0, 0}, + {4050155669, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 16, 25}, + {0, 50, 1}, + {0, 42, 35}, + {0, 31, 41}, + {0, 4, 43}, + {0, 9, 10}, + {0, 3, 30}, + {0, 52, 47}, + {0, 12, 53}, + {0, 55, 54}, + {0, 36, 56}, + {0, 49, 57}, + {0, 6, 58}, + {0, 34, 33}, + {0, 59, 26}, + {0, 21, 32}, + {0, 60, 15}, + {0, 24, 61}, + {0, 62, 38}, + {0, 22, 2}, + {0, 37, 7}, + {0, 63, 46}, + {0, 14, 13}, + {0, 64, 5}, + {0, 65, 45}, + {0, 66, 19}, + {0, 18, 67}, + {0, 17, 20}, + {0, 68, 11}, + {0, 8, 69}, + {0, 70, 39}, + {0, 72, 71}, + {0, 74, 73}, + {0, 40, 75}, + {0, 76, 23}, + {0, 78, 77}, + {0, 29, 79}, + {0, 28, 80}, + {0, 27, 48}, + {0, 82, 81}, + {0, 51, 83}, + {0, 84, 44}, + {0, 86, 85}, + {0, 88, 87}, + {0, 90, 89}, + {0, 92, 91}, + {0, 94, 93}, + {0, 96, 95}, + {0, 98, 97}, + {0, 100, 99}, + })); + + codecs.emplace(std::pair(SpvOpTypePointer, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(65, { + {0, 0, 0}, + {119981689, 0, 0}, + {162255877, 0, 0}, + {451382997, 0, 0}, + {545678922, 0, 0}, + {679771963, 0, 0}, + {789872778, 0, 0}, + {1100599986, 0, 0}, + {1103903216, 0, 0}, + {1154919607, 0, 0}, + {1343794461, 0, 0}, + {1415510495, 0, 0}, + {1674803691, 0, 0}, + {1951208733, 0, 0}, + {1989520052, 0, 0}, + {2160380860, 0, 0}, + {2263349224, 0, 0}, + {2320303498, 0, 0}, + {2924146124, 0, 0}, + {2984325996, 0, 0}, + {3334207724, 0, 0}, + {3345288309, 0, 0}, + {3410158390, 0, 0}, + {3489360962, 0, 0}, + {3495967422, 0, 0}, + {3504981554, 0, 0}, + {3800912395, 0, 0}, + {3802564010, 0, 0}, + {3866587616, 0, 0}, + {3868239231, 0, 0}, + {3869890846, 0, 0}, + {3998230222, 0, 0}, + {4240893633, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 4, 3}, + {0, 6, 24}, + {0, 11, 7}, + {0, 32, 21}, + {0, 27, 34}, + {0, 35, 25}, + {0, 36, 8}, + {0, 26, 31}, + {0, 14, 15}, + {0, 28, 37}, + {0, 1, 23}, + {0, 39, 38}, + {0, 12, 40}, + {0, 22, 41}, + {0, 10, 16}, + {0, 43, 42}, + {0, 29, 44}, + {0, 2, 45}, + {0, 46, 19}, + {0, 48, 47}, + {0, 18, 49}, + {0, 50, 30}, + {0, 9, 33}, + {0, 52, 51}, + {0, 54, 53}, + {0, 13, 55}, + {0, 17, 56}, + {0, 5, 57}, + {0, 59, 58}, + {0, 60, 20}, + {0, 62, 61}, + {0, 64, 63}, + })); + + codecs.emplace(std::pair(SpvOpTypePointer, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(99, { + {0, 0, 0}, + {75986790, 0, 0}, + {95470391, 0, 0}, + {170378107, 0, 0}, + {172029722, 0, 0}, + {204234270, 0, 0}, + {205885885, 0, 0}, + {244668133, 0, 0}, + {265778447, 0, 0}, + {616435646, 0, 0}, + {618087261, 0, 0}, + {753954113, 0, 0}, + {1000070091, 0, 0}, + {1308462133, 0, 0}, + {1671139745, 0, 0}, + {1774874546, 0, 0}, + {1776526161, 0, 0}, + {1887808856, 0, 0}, + {1889460471, 0, 0}, + {1917966999, 0, 0}, + {2044728014, 0, 0}, + {2192810893, 0, 0}, + {2293247016, 0, 0}, + {2503194620, 0, 0}, + {2605012269, 0, 0}, + {2608484640, 0, 0}, + {2615111110, 0, 0}, + {2668769415, 0, 0}, + {2759951687, 0, 0}, + {2761603302, 0, 0}, + {2856623532, 0, 0}, + {2945369269, 0, 0}, + {2956189845, 0, 0}, + {3085119011, 0, 0}, + {3367313400, 0, 0}, + {3447882276, 0, 0}, + {3633746133, 0, 0}, + {3635397748, 0, 0}, + {3710645347, 0, 0}, + {3712296962, 0, 0}, + {3715846592, 0, 0}, + {3727494858, 0, 0}, + {3747079365, 0, 0}, + {3748965853, 0, 0}, + {3750617468, 0, 0}, + {4018820793, 0, 0}, + {4022124023, 0, 0}, + {4024173916, 0, 0}, + {4215670524, 0, 0}, + {4217322139, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 10, 9}, + {0, 31, 24}, + {0, 40, 13}, + {0, 45, 33}, + {0, 34, 46}, + {0, 43, 38}, + {0, 44, 15}, + {0, 11, 30}, + {0, 21, 6}, + {0, 47, 3}, + {0, 51, 16}, + {0, 14, 52}, + {0, 8, 53}, + {0, 35, 5}, + {0, 55, 54}, + {0, 56, 26}, + {0, 20, 57}, + {0, 39, 19}, + {0, 59, 58}, + {0, 61, 60}, + {0, 4, 62}, + {0, 2, 63}, + {0, 25, 7}, + {0, 64, 27}, + {0, 12, 22}, + {0, 65, 48}, + {0, 41, 42}, + {0, 17, 23}, + {0, 49, 66}, + {0, 68, 67}, + {0, 70, 69}, + {0, 72, 71}, + {0, 74, 73}, + {0, 18, 75}, + {0, 37, 32}, + {0, 76, 36}, + {0, 78, 77}, + {0, 79, 28}, + {0, 81, 80}, + {0, 82, 29}, + {0, 84, 83}, + {0, 86, 85}, + {0, 88, 87}, + {0, 90, 89}, + {0, 91, 50}, + {0, 93, 92}, + {0, 95, 94}, + {0, 1, 96}, + {0, 98, 97}, + })); + + codecs.emplace(std::pair(SpvOpTypeFunction, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(27, { + {0, 0, 0}, + {545678922, 0, 0}, + {679771963, 0, 0}, + {899570100, 0, 0}, + {929101967, 0, 0}, + {1100599986, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {3056042030, 0, 0}, + {3334207724, 0, 0}, + {3357250579, 0, 0}, + {3705139860, 0, 0}, + {3800912395, 0, 0}, + {3802564010, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 5, 3}, + {0, 10, 13}, + {0, 4, 15}, + {0, 16, 11}, + {0, 17, 1}, + {0, 14, 12}, + {0, 19, 18}, + {0, 21, 20}, + {0, 7, 6}, + {0, 9, 22}, + {0, 24, 23}, + {0, 25, 2}, + {0, 26, 8}, + })); + + codecs.emplace(std::pair(SpvOpTypeFunction, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(57, { + {0, 0, 0}, + {283209196, 0, 0}, + {436416061, 0, 0}, + {679771963, 0, 0}, + {789872778, 0, 0}, + {815757910, 0, 0}, + {827698488, 0, 0}, + {1164221089, 0, 0}, + {1294403159, 0, 0}, + {1296054774, 0, 0}, + {1297706389, 0, 0}, + {1525861001, 0, 0}, + {1579585816, 0, 0}, + {1675764636, 0, 0}, + {1824016656, 0, 0}, + {1951208733, 0, 0}, + {1991787192, 0, 0}, + {2180701723, 0, 0}, + {2194691858, 0, 0}, + {2320303498, 0, 0}, + {2881886868, 0, 0}, + {2926633629, 0, 0}, + {3249265647, 0, 0}, + {3334207724, 0, 0}, + {3472123498, 0, 0}, + {3674863070, 0, 0}, + {4050155669, 0, 0}, + {4141567741, 0, 0}, + {4155122613, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 24, 7}, + {0, 17, 1}, + {0, 4, 15}, + {0, 11, 16}, + {0, 28, 30}, + {0, 25, 20}, + {0, 14, 31}, + {0, 32, 26}, + {0, 12, 5}, + {0, 2, 22}, + {0, 33, 13}, + {0, 35, 34}, + {0, 37, 36}, + {0, 39, 38}, + {0, 40, 21}, + {0, 29, 18}, + {0, 27, 41}, + {0, 43, 42}, + {0, 19, 44}, + {0, 45, 23}, + {0, 6, 3}, + {0, 47, 46}, + {0, 49, 48}, + {0, 51, 50}, + {0, 10, 8}, + {0, 53, 52}, + {0, 9, 54}, + {0, 56, 55}, + })); + + codecs.emplace(std::pair(SpvOpTypeFunction, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(17, { + {0, 0, 0}, + {679771963, 0, 0}, + {827698488, 0, 0}, + {1294403159, 0, 0}, + {1296054774, 0, 0}, + {1297706389, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 8, 9}, + {0, 10, 6}, + {0, 1, 5}, + {0, 11, 3}, + {0, 12, 7}, + {0, 13, 2}, + {0, 15, 14}, + {0, 16, 4}, + })); + + codecs.emplace(std::pair(SpvOpTypeFunction, 3), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(17, { + {0, 0, 0}, + {679771963, 0, 0}, + {827698488, 0, 0}, + {1294403159, 0, 0}, + {1296054774, 0, 0}, + {1951208733, 0, 0}, + {2194691858, 0, 0}, + {2320303498, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 8, 5}, + {0, 10, 9}, + {0, 11, 6}, + {0, 7, 12}, + {0, 1, 3}, + {0, 2, 13}, + {0, 15, 14}, + {0, 4, 16}, + })); + + codecs.emplace(std::pair(SpvOpTypeFunction, 4), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(11, { + {0, 0, 0}, + {827698488, 0, 0}, + {1294403159, 0, 0}, + {1296054774, 0, 0}, + {1297706389, 0, 0}, + {1951208733, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 4, 6}, + {0, 5, 7}, + {0, 2, 8}, + {0, 1, 9}, + {0, 10, 3}, + })); + + codecs.emplace(std::pair(SpvOpTypeFunction, 5), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(11, { + {0, 0, 0}, + {827698488, 0, 0}, + {1294403159, 0, 0}, + {1296054774, 0, 0}, + {1951208733, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 6}, + {0, 4, 7}, + {0, 8, 5}, + {0, 3, 9}, + {0, 1, 10}, + })); + + codecs.emplace(std::pair(SpvOpTypeFunction, 6), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(9, { + {0, 0, 0}, + {789872778, 0, 0}, + {827698488, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 5, 1}, + {0, 4, 6}, + {0, 3, 7}, + {0, 2, 8}, + })); + + codecs.emplace(std::pair(SpvOpTypeFunction, 7), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(7, { + {0, 0, 0}, + {543621065, 0, 0}, + {827698488, 0, 0}, + {1951208733, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 4}, + {0, 1, 5}, + {0, 2, 6}, + })); + + codecs.emplace(std::pair(SpvOpTypeFunction, 8), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(7, { + {0, 0, 0}, + {827698488, 0, 0}, + {1951208733, 0, 0}, + {3095831808, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 4}, + {0, 3, 5}, + {0, 1, 6}, + })); + + codecs.emplace(std::pair(SpvOpTypeFunction, 9), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(5, { + {0, 0, 0}, + {1296054774, 0, 0}, + {1951208733, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 2}, + {0, 1, 4}, + })); + + codecs.emplace(std::pair(SpvOpTypeFunction, 10), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(5, { + {0, 0, 0}, + {1296054774, 0, 0}, + {2320303498, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 2}, + {0, 1, 4}, + })); + + codecs.emplace(std::pair(SpvOpTypeFunction, 11), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(5, { + {0, 0, 0}, + {789872778, 0, 0}, + {1951208733, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 2}, + {0, 4, 1}, + })); + + codecs.emplace(std::pair(SpvOpTypeFunction, 12), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(5, { + {0, 0, 0}, + {789872778, 0, 0}, + {1951208733, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 1}, + {0, 4, 3}, + })); + + codecs.emplace(std::pair(SpvOpTypeFunction, 13), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {1951208733, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 1}, + })); + + codecs.emplace(std::pair(SpvOpTypeFunction, 14), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {1951208733, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 1}, + })); + + codecs.emplace(std::pair(SpvOpTypeFunction, 15), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(7, { + {0, 0, 0}, + {789872778, 0, 0}, + {1951208733, 0, 0}, + {2430404313, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 4}, + {0, 1, 5}, + {0, 2, 6}, + })); + + codecs.emplace(std::pair(SpvOpConstant, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(183, { + {0, 0, 0}, + {51041423, 0, 0}, + {52882140, 0, 0}, + {72782198, 0, 0}, + {142465290, 0, 0}, + {144116905, 0, 0}, + {158160339, 0, 0}, + {169135842, 0, 0}, + {210116709, 0, 0}, + {290391815, 0, 0}, + {296981500, 0, 0}, + {385229009, 0, 0}, + {438318340, 0, 0}, + {529742207, 0, 0}, + {628331516, 0, 0}, + {677668732, 0, 0}, + {778500192, 0, 0}, + {825595257, 0, 0}, + {910398460, 0, 0}, + {917019124, 0, 0}, + {959681532, 0, 0}, + {1031290113, 0, 0}, + {1039111164, 0, 0}, + {1064945649, 0, 0}, + {1087394637, 0, 0}, + {1092948665, 0, 0}, + {1156369516, 0, 0}, + {1158021131, 0, 0}, + {1172110445, 0, 0}, + {1304296041, 0, 0}, + {1400019344, 0, 0}, + {1450415100, 0, 0}, + {1452222566, 0, 0}, + {1543646433, 0, 0}, + {1543672828, 0, 0}, + {1612361408, 0, 0}, + {1622381564, 0, 0}, + {1691572958, 0, 0}, + {1755648697, 0, 0}, + {1782996825, 0, 0}, + {1784648440, 0, 0}, + {1930923350, 0, 0}, + {1939359710, 0, 0}, + {1971252067, 0, 0}, + {1979847999, 0, 0}, + {2078849875, 0, 0}, + {2113115132, 0, 0}, + {2135340676, 0, 0}, + {2170273742, 0, 0}, + {2268204687, 0, 0}, + {2285081596, 0, 0}, + {2318200267, 0, 0}, + {2321729979, 0, 0}, + {2326636627, 0, 0}, + {2444465148, 0, 0}, + {2466126792, 0, 0}, + {2490492987, 0, 0}, + {2524697596, 0, 0}, + {2557550659, 0, 0}, + {2678954464, 0, 0}, + {2705477184, 0, 0}, + {2715370488, 0, 0}, + {2732195517, 0, 0}, + {2775815164, 0, 0}, + {2796901051, 0, 0}, + {2798552666, 0, 0}, + {2855506940, 0, 0}, + {2860348412, 0, 0}, + {2922615804, 0, 0}, + {2937761472, 0, 0}, + {2944827576, 0, 0}, + {3092754101, 0, 0}, + {3107165180, 0, 0}, + {3168953855, 0, 0}, + {3184177968, 0, 0}, + {3202349435, 0, 0}, + {3266548732, 0, 0}, + {3332104493, 0, 0}, + {3362723943, 0, 0}, + {3571454885, 0, 0}, + {3712763835, 0, 0}, + {3743748793, 0, 0}, + {3810805277, 0, 0}, + {3912967080, 0, 0}, + {3929248764, 0, 0}, + {3958731802, 0, 0}, + {3997952447, 0, 0}, + {4016096296, 0, 0}, + {4106658327, 0, 0}, + {4172568578, 0, 0}, + {4198082194, 0, 0}, + {4248015868, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 35, 16}, + {0, 49, 42}, + {0, 86, 69}, + {0, 53, 30}, + {0, 45, 89}, + {0, 50, 68}, + {0, 73, 71}, + {0, 17, 46}, + {0, 14, 81}, + {0, 63, 44}, + {0, 12, 3}, + {0, 72, 31}, + {0, 55, 67}, + {0, 36, 19}, + {0, 22, 88}, + {0, 9, 70}, + {0, 93, 23}, + {0, 95, 94}, + {0, 47, 91}, + {0, 34, 32}, + {0, 97, 96}, + {0, 41, 61}, + {0, 99, 98}, + {0, 37, 1}, + {0, 77, 100}, + {0, 51, 60}, + {0, 101, 79}, + {0, 6, 2}, + {0, 11, 7}, + {0, 24, 21}, + {0, 43, 28}, + {0, 59, 56}, + {0, 75, 62}, + {0, 80, 78}, + {0, 87, 83}, + {0, 18, 15}, + {0, 102, 38}, + {0, 104, 103}, + {0, 85, 90}, + {0, 76, 25}, + {0, 29, 105}, + {0, 107, 106}, + {0, 58, 52}, + {0, 109, 108}, + {0, 57, 110}, + {0, 112, 111}, + {0, 114, 113}, + {0, 115, 33}, + {0, 74, 116}, + {0, 118, 117}, + {0, 120, 119}, + {0, 122, 121}, + {0, 124, 123}, + {0, 126, 125}, + {0, 128, 127}, + {0, 130, 129}, + {0, 131, 13}, + {0, 54, 27}, + {0, 133, 132}, + {0, 48, 40}, + {0, 5, 8}, + {0, 82, 134}, + {0, 26, 135}, + {0, 39, 4}, + {0, 136, 64}, + {0, 138, 137}, + {0, 140, 139}, + {0, 84, 141}, + {0, 143, 142}, + {0, 145, 144}, + {0, 147, 146}, + {0, 149, 148}, + {0, 20, 150}, + {0, 65, 151}, + {0, 66, 152}, + {0, 153, 10}, + {0, 155, 154}, + {0, 157, 156}, + {0, 159, 158}, + {0, 161, 160}, + {0, 163, 162}, + {0, 165, 164}, + {0, 167, 166}, + {0, 169, 168}, + {0, 170, 92}, + {0, 172, 171}, + {0, 174, 173}, + {0, 176, 175}, + {0, 178, 177}, + {0, 180, 179}, + {0, 182, 181}, + })); + + codecs.emplace(std::pair(SpvOpConstant, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(9, { + {0, 0, 0}, + {679771963, 0, 0}, + {1247793383, 0, 0}, + {2320303498, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 5}, + {0, 4, 6}, + {0, 1, 3}, + {0, 8, 7}, + })); + + codecs.emplace(std::pair(SpvOpConstantComposite, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(83, { + {0, 0, 0}, + {15502752, 0, 0}, + {46736908, 0, 0}, + {139011596, 0, 0}, + {149720480, 0, 0}, + {249378857, 0, 0}, + {251209228, 0, 0}, + {503145996, 0, 0}, + {836581417, 0, 0}, + {882718761, 0, 0}, + {1289566249, 0, 0}, + {1325348861, 0, 0}, + {1558001705, 0, 0}, + {1646147798, 0, 0}, + {1679946323, 0, 0}, + {1766401548, 0, 0}, + {1992893964, 0, 0}, + {2123388694, 0, 0}, + {2162986400, 0, 0}, + {2580096524, 0, 0}, + {2598189097, 0, 0}, + {2683080096, 0, 0}, + {2698156268, 0, 0}, + {2763960513, 0, 0}, + {3015046341, 0, 0}, + {3133016299, 0, 0}, + {3251128023, 0, 0}, + {3504158761, 0, 0}, + {3535289452, 0, 0}, + {3536941067, 0, 0}, + {3538592682, 0, 0}, + {3540244297, 0, 0}, + {3541895912, 0, 0}, + {3570219049, 0, 0}, + {3653838348, 0, 0}, + {3764205609, 0, 0}, + {3882634684, 0, 0}, + {3913885196, 0, 0}, + {3982047273, 0, 0}, + {4024252457, 0, 0}, + {4243119782, 0, 0}, + {4255182614, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 8, 4}, + {0, 39, 2}, + {0, 38, 10}, + {0, 29, 41}, + {0, 23, 28}, + {0, 9, 24}, + {0, 44, 43}, + {0, 45, 6}, + {0, 20, 12}, + {0, 18, 33}, + {0, 19, 16}, + {0, 7, 46}, + {0, 48, 47}, + {0, 5, 49}, + {0, 13, 11}, + {0, 17, 14}, + {0, 25, 22}, + {0, 40, 36}, + {0, 1, 50}, + {0, 31, 30}, + {0, 51, 32}, + {0, 42, 52}, + {0, 54, 53}, + {0, 55, 15}, + {0, 37, 56}, + {0, 57, 34}, + {0, 59, 58}, + {0, 61, 60}, + {0, 35, 21}, + {0, 62, 26}, + {0, 64, 63}, + {0, 65, 27}, + {0, 3, 66}, + {0, 68, 67}, + {0, 70, 69}, + {0, 72, 71}, + {0, 74, 73}, + {0, 76, 75}, + {0, 78, 77}, + {0, 80, 79}, + {0, 82, 81}, + })); + + codecs.emplace(std::pair(SpvOpConstantComposite, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(65, { + {0, 0, 0}, + {142465290, 0, 0}, + {158160339, 0, 0}, + {169135842, 0, 0}, + {210116709, 0, 0}, + {296981500, 0, 0}, + {615748604, 0, 0}, + {910398460, 0, 0}, + {959681532, 0, 0}, + {1039111164, 0, 0}, + {1087394637, 0, 0}, + {1156369516, 0, 0}, + {1450415100, 0, 0}, + {1543672828, 0, 0}, + {2100532220, 0, 0}, + {2170273742, 0, 0}, + {2285081596, 0, 0}, + {2326636627, 0, 0}, + {2444465148, 0, 0}, + {2732195517, 0, 0}, + {2763232252, 0, 0}, + {2796901051, 0, 0}, + {2855506940, 0, 0}, + {2922615804, 0, 0}, + {2937761472, 0, 0}, + {3202349435, 0, 0}, + {3362723943, 0, 0}, + {3712763835, 0, 0}, + {3810805277, 0, 0}, + {3929248764, 0, 0}, + {4016096296, 0, 0}, + {4172568578, 0, 0}, + {4248015868, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 12, 23}, + {0, 13, 6}, + {0, 20, 14}, + {0, 15, 24}, + {0, 17, 28}, + {0, 16, 31}, + {0, 7, 34}, + {0, 9, 32}, + {0, 36, 35}, + {0, 38, 37}, + {0, 40, 39}, + {0, 2, 8}, + {0, 10, 3}, + {0, 25, 19}, + {0, 27, 26}, + {0, 33, 30}, + {0, 11, 41}, + {0, 1, 21}, + {0, 18, 42}, + {0, 44, 43}, + {0, 46, 45}, + {0, 48, 47}, + {0, 29, 49}, + {0, 4, 50}, + {0, 52, 51}, + {0, 54, 53}, + {0, 56, 55}, + {0, 58, 57}, + {0, 59, 5}, + {0, 61, 60}, + {0, 62, 22}, + {0, 64, 63}, + })); + + codecs.emplace(std::pair(SpvOpConstantComposite, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(57, { + {0, 0, 0}, + {52882140, 0, 0}, + {210116709, 0, 0}, + {296981500, 0, 0}, + {385229009, 0, 0}, + {615748604, 0, 0}, + {910398460, 0, 0}, + {959681532, 0, 0}, + {1031290113, 0, 0}, + {1039111164, 0, 0}, + {1172110445, 0, 0}, + {1450415100, 0, 0}, + {1543672828, 0, 0}, + {1622381564, 0, 0}, + {1782996825, 0, 0}, + {1971252067, 0, 0}, + {2100532220, 0, 0}, + {2268204687, 0, 0}, + {2326636627, 0, 0}, + {2444465148, 0, 0}, + {2490492987, 0, 0}, + {2678954464, 0, 0}, + {2763232252, 0, 0}, + {2855506940, 0, 0}, + {2922615804, 0, 0}, + {3912967080, 0, 0}, + {3929248764, 0, 0}, + {4172568578, 0, 0}, + {4248015868, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 11, 24}, + {0, 12, 5}, + {0, 22, 16}, + {0, 18, 17}, + {0, 30, 27}, + {0, 6, 13}, + {0, 9, 28}, + {0, 32, 31}, + {0, 34, 33}, + {0, 7, 35}, + {0, 4, 1}, + {0, 10, 8}, + {0, 20, 15}, + {0, 25, 21}, + {0, 36, 29}, + {0, 19, 37}, + {0, 39, 38}, + {0, 41, 40}, + {0, 43, 42}, + {0, 26, 44}, + {0, 45, 2}, + {0, 47, 46}, + {0, 49, 48}, + {0, 50, 14}, + {0, 51, 3}, + {0, 53, 52}, + {0, 54, 23}, + {0, 56, 55}, + })); + + codecs.emplace(std::pair(SpvOpConstantComposite, 3), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(39, { + {0, 0, 0}, + {210116709, 0, 0}, + {296981500, 0, 0}, + {615748604, 0, 0}, + {910398460, 0, 0}, + {959681532, 0, 0}, + {1039111164, 0, 0}, + {1092948665, 0, 0}, + {1450415100, 0, 0}, + {1543672828, 0, 0}, + {1612361408, 0, 0}, + {2100532220, 0, 0}, + {2326636627, 0, 0}, + {2444465148, 0, 0}, + {2524697596, 0, 0}, + {2763232252, 0, 0}, + {2855506940, 0, 0}, + {3929248764, 0, 0}, + {4172568578, 0, 0}, + {4248015868, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 8, 7}, + {0, 9, 3}, + {0, 15, 11}, + {0, 10, 21}, + {0, 18, 12}, + {0, 4, 20}, + {0, 22, 19}, + {0, 23, 6}, + {0, 14, 24}, + {0, 5, 25}, + {0, 27, 26}, + {0, 28, 17}, + {0, 30, 29}, + {0, 31, 13}, + {0, 1, 32}, + {0, 34, 33}, + {0, 16, 35}, + {0, 2, 36}, + {0, 38, 37}, + })); + + codecs.emplace(std::pair(SpvOpConstantComposite, 4), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(35, { + {0, 0, 0}, + {296981500, 0, 0}, + {615748604, 0, 0}, + {673708384, 0, 0}, + {959681532, 0, 0}, + {1039111164, 0, 0}, + {1450415100, 0, 0}, + {1543672828, 0, 0}, + {1939359710, 0, 0}, + {2100532220, 0, 0}, + {2113115132, 0, 0}, + {2326636627, 0, 0}, + {2444465148, 0, 0}, + {2763232252, 0, 0}, + {2855506940, 0, 0}, + {3929248764, 0, 0}, + {4172568578, 0, 0}, + {4248015868, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 18, 3}, + {0, 6, 19}, + {0, 12, 4}, + {0, 17, 2}, + {0, 9, 7}, + {0, 20, 13}, + {0, 11, 8}, + {0, 10, 16}, + {0, 21, 15}, + {0, 5, 22}, + {0, 24, 23}, + {0, 26, 25}, + {0, 28, 27}, + {0, 29, 1}, + {0, 31, 30}, + {0, 33, 32}, + {0, 34, 14}, + })); + + codecs.emplace(std::pair(SpvOpConstantComposite, 5), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(23, { + {0, 0, 0}, + {545678922, 0, 0}, + {679771963, 0, 0}, + {929101967, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {3056042030, 0, 0}, + {3334207724, 0, 0}, + {3357250579, 0, 0}, + {3705139860, 0, 0}, + {3800912395, 0, 0}, + {3802564010, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 8, 11}, + {0, 9, 3}, + {0, 1, 13}, + {0, 14, 10}, + {0, 12, 15}, + {0, 17, 16}, + {0, 18, 4}, + {0, 7, 5}, + {0, 20, 19}, + {0, 2, 21}, + {0, 22, 6}, + })); + + codecs.emplace(std::pair(SpvOpFunction, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(89, { + {0, 0, 0}, + {35240468, 0, 0}, + {123060826, 0, 0}, + {184634770, 0, 0}, + {359054425, 0, 0}, + {459968607, 0, 0}, + {619875033, 0, 0}, + {904486530, 0, 0}, + {945128292, 0, 0}, + {950731750, 0, 0}, + {1058429216, 0, 0}, + {1182296898, 0, 0}, + {1238120570, 0, 0}, + {1429389803, 0, 0}, + {1652168174, 0, 0}, + {1717510093, 0, 0}, + {1766422419, 0, 0}, + {1775308984, 0, 0}, + {1776629361, 0, 0}, + {1824526196, 0, 0}, + {1957265068, 0, 0}, + {1998433745, 0, 0}, + {2055664760, 0, 0}, + {2303184249, 0, 0}, + {2451531615, 0, 0}, + {2507457870, 0, 0}, + {2550501832, 0, 0}, + {2590402790, 0, 0}, + {2649103430, 0, 0}, + {2780190687, 0, 0}, + {2831059514, 0, 0}, + {3167253437, 0, 0}, + {3269075805, 0, 0}, + {3323202731, 0, 0}, + {3361419439, 0, 0}, + {3464197236, 0, 0}, + {3472029049, 0, 0}, + {3518630848, 0, 0}, + {3604842236, 0, 0}, + {3653985133, 0, 0}, + {4091916710, 0, 0}, + {4121643374, 0, 0}, + {4185590212, 0, 0}, + {4233562270, 0, 0}, + {4235213885, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 6, 40}, + {0, 14, 31}, + {0, 7, 9}, + {0, 29, 27}, + {0, 18, 44}, + {0, 8, 5}, + {0, 10, 3}, + {0, 41, 37}, + {0, 42, 35}, + {0, 2, 1}, + {0, 47, 46}, + {0, 48, 4}, + {0, 11, 49}, + {0, 50, 36}, + {0, 19, 51}, + {0, 53, 52}, + {0, 55, 54}, + {0, 15, 12}, + {0, 26, 16}, + {0, 56, 21}, + {0, 25, 33}, + {0, 43, 24}, + {0, 57, 39}, + {0, 59, 58}, + {0, 61, 60}, + {0, 62, 34}, + {0, 64, 63}, + {0, 17, 30}, + {0, 66, 65}, + {0, 20, 67}, + {0, 13, 68}, + {0, 28, 69}, + {0, 70, 32}, + {0, 72, 71}, + {0, 73, 22}, + {0, 75, 74}, + {0, 77, 76}, + {0, 79, 78}, + {0, 80, 23}, + {0, 45, 81}, + {0, 83, 82}, + {0, 85, 84}, + {0, 38, 86}, + {0, 88, 87}, + })); + + codecs.emplace(std::pair(SpvOpFunction, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(87, { + {0, 0, 0}, + {75986790, 0, 0}, + {95470391, 0, 0}, + {170378107, 0, 0}, + {172029722, 0, 0}, + {204234270, 0, 0}, + {205885885, 0, 0}, + {244668133, 0, 0}, + {265778447, 0, 0}, + {753954113, 0, 0}, + {1000070091, 0, 0}, + {1671139745, 0, 0}, + {1774874546, 0, 0}, + {1776526161, 0, 0}, + {1887808856, 0, 0}, + {1889460471, 0, 0}, + {1917966999, 0, 0}, + {2044728014, 0, 0}, + {2192810893, 0, 0}, + {2293247016, 0, 0}, + {2503194620, 0, 0}, + {2608484640, 0, 0}, + {2615111110, 0, 0}, + {2668769415, 0, 0}, + {2759951687, 0, 0}, + {2761603302, 0, 0}, + {2856623532, 0, 0}, + {2956189845, 0, 0}, + {3085119011, 0, 0}, + {3367313400, 0, 0}, + {3447882276, 0, 0}, + {3633746133, 0, 0}, + {3635397748, 0, 0}, + {3710645347, 0, 0}, + {3712296962, 0, 0}, + {3727494858, 0, 0}, + {3747079365, 0, 0}, + {3748965853, 0, 0}, + {3750617468, 0, 0}, + {4018820793, 0, 0}, + {4022124023, 0, 0}, + {4024173916, 0, 0}, + {4215670524, 0, 0}, + {4217322139, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 39, 28}, + {0, 29, 40}, + {0, 37, 33}, + {0, 38, 12}, + {0, 9, 26}, + {0, 18, 6}, + {0, 41, 3}, + {0, 11, 13}, + {0, 5, 8}, + {0, 45, 30}, + {0, 22, 46}, + {0, 48, 47}, + {0, 16, 17}, + {0, 34, 49}, + {0, 51, 50}, + {0, 53, 52}, + {0, 7, 2}, + {0, 23, 21}, + {0, 54, 10}, + {0, 20, 36}, + {0, 55, 35}, + {0, 56, 4}, + {0, 43, 57}, + {0, 59, 58}, + {0, 60, 42}, + {0, 62, 61}, + {0, 63, 15}, + {0, 64, 31}, + {0, 14, 65}, + {0, 66, 24}, + {0, 67, 32}, + {0, 68, 19}, + {0, 70, 69}, + {0, 71, 27}, + {0, 73, 72}, + {0, 75, 74}, + {0, 77, 76}, + {0, 78, 25}, + {0, 44, 79}, + {0, 81, 80}, + {0, 83, 82}, + {0, 1, 84}, + {0, 86, 85}, + })); + + codecs.emplace(std::pair(SpvOpFunction, 3), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(41, { + {0, 0, 0}, + {436416061, 0, 0}, + {543621065, 0, 0}, + {679771963, 0, 0}, + {815757910, 0, 0}, + {827698488, 0, 0}, + {1294403159, 0, 0}, + {1296054774, 0, 0}, + {1297706389, 0, 0}, + {1579585816, 0, 0}, + {1675764636, 0, 0}, + {1824016656, 0, 0}, + {1951208733, 0, 0}, + {2194691858, 0, 0}, + {2320303498, 0, 0}, + {2926633629, 0, 0}, + {3095831808, 0, 0}, + {3249265647, 0, 0}, + {3334207724, 0, 0}, + {4050155669, 0, 0}, + {4141567741, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 11}, + {0, 19, 16}, + {0, 9, 4}, + {0, 1, 17}, + {0, 22, 10}, + {0, 24, 23}, + {0, 15, 25}, + {0, 13, 26}, + {0, 27, 20}, + {0, 12, 28}, + {0, 30, 29}, + {0, 31, 18}, + {0, 3, 21}, + {0, 32, 14}, + {0, 34, 33}, + {0, 35, 8}, + {0, 5, 6}, + {0, 37, 36}, + {0, 39, 38}, + {0, 40, 7}, + })); + + codecs.emplace(std::pair(SpvOpFunctionParameter, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(41, { + {0, 0, 0}, + {522971108, 0, 0}, + {615341051, 0, 0}, + {718301639, 0, 0}, + {985750227, 0, 0}, + {1395113939, 0, 0}, + {1510333659, 0, 0}, + {1642805350, 0, 0}, + {1846856260, 0, 0}, + {1957218950, 0, 0}, + {1977038330, 0, 0}, + {1978689945, 0, 0}, + {1980341560, 0, 0}, + {2262220987, 0, 0}, + {2674422363, 0, 0}, + {3197739982, 0, 0}, + {3465954368, 0, 0}, + {3941049054, 0, 0}, + {3945795573, 0, 0}, + {4080527786, 0, 0}, + {4154758669, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 17}, + {0, 4, 15}, + {0, 8, 7}, + {0, 2, 20}, + {0, 22, 19}, + {0, 24, 23}, + {0, 14, 25}, + {0, 16, 26}, + {0, 27, 13}, + {0, 6, 28}, + {0, 30, 29}, + {0, 31, 10}, + {0, 11, 21}, + {0, 32, 12}, + {0, 34, 33}, + {0, 35, 5}, + {0, 9, 18}, + {0, 37, 36}, + {0, 39, 38}, + {0, 40, 1}, + })); + + codecs.emplace(std::pair(SpvOpFunctionParameter, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(27, { + {0, 0, 0}, + {545678922, 0, 0}, + {679771963, 0, 0}, + {899570100, 0, 0}, + {929101967, 0, 0}, + {1100599986, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {3056042030, 0, 0}, + {3334207724, 0, 0}, + {3357250579, 0, 0}, + {3705139860, 0, 0}, + {3800912395, 0, 0}, + {3802564010, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 5, 3}, + {0, 10, 13}, + {0, 4, 15}, + {0, 16, 11}, + {0, 17, 1}, + {0, 14, 12}, + {0, 19, 18}, + {0, 21, 20}, + {0, 22, 8}, + {0, 7, 6}, + {0, 23, 9}, + {0, 25, 24}, + {0, 26, 2}, + })); + + codecs.emplace(std::pair(SpvOpFunctionCall, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(115, { + {0, 0, 0}, + {57149555, 0, 0}, + {86116519, 0, 0}, + {168339452, 0, 0}, + {181902171, 0, 0}, + {284226441, 0, 0}, + {314809953, 0, 0}, + {330249537, 0, 0}, + {527665290, 0, 0}, + {545363837, 0, 0}, + {707478563, 0, 0}, + {740921498, 0, 0}, + {807276090, 0, 0}, + {824323032, 0, 0}, + {835458563, 0, 0}, + {1162127370, 0, 0}, + {1245448751, 0, 0}, + {1277245109, 0, 0}, + {1375043498, 0, 0}, + {1380991098, 0, 0}, + {1603937321, 0, 0}, + {1708264968, 0, 0}, + {1717555224, 0, 0}, + {1765126703, 0, 0}, + {1838993983, 0, 0}, + {1949856502, 0, 0}, + {2108571893, 0, 0}, + {2110223508, 0, 0}, + {2293637521, 0, 0}, + {2377112119, 0, 0}, + {2378763734, 0, 0}, + {2512398201, 0, 0}, + {2516325050, 0, 0}, + {2645135839, 0, 0}, + {2708915136, 0, 0}, + {2894979602, 0, 0}, + {2903897222, 0, 0}, + {2976581453, 0, 0}, + {3054834317, 0, 0}, + {3075866530, 0, 0}, + {3085157904, 0, 0}, + {3242843022, 0, 0}, + {3266028549, 0, 0}, + {3296691317, 0, 0}, + {3299488628, 0, 0}, + {3322500634, 0, 0}, + {3345707173, 0, 0}, + {3536390697, 0, 0}, + {3584683259, 0, 0}, + {3647606635, 0, 0}, + {3760372982, 0, 0}, + {3823959661, 0, 0}, + {3839389658, 0, 0}, + {4124281183, 0, 0}, + {4130950286, 0, 0}, + {4169878842, 0, 0}, + {4174489262, 0, 0}, + {4237497041, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 17, 23}, + {0, 37, 8}, + {0, 45, 39}, + {0, 41, 14}, + {0, 48, 43}, + {0, 40, 31}, + {0, 19, 29}, + {0, 53, 26}, + {0, 10, 5}, + {0, 50, 24}, + {0, 27, 3}, + {0, 59, 32}, + {0, 51, 18}, + {0, 52, 55}, + {0, 60, 57}, + {0, 62, 61}, + {0, 36, 33}, + {0, 64, 63}, + {0, 65, 22}, + {0, 66, 46}, + {0, 6, 67}, + {0, 68, 13}, + {0, 21, 44}, + {0, 1, 69}, + {0, 30, 11}, + {0, 71, 70}, + {0, 12, 72}, + {0, 74, 73}, + {0, 76, 75}, + {0, 16, 2}, + {0, 49, 35}, + {0, 77, 9}, + {0, 42, 28}, + {0, 15, 78}, + {0, 80, 79}, + {0, 82, 81}, + {0, 47, 83}, + {0, 85, 84}, + {0, 87, 86}, + {0, 89, 88}, + {0, 20, 38}, + {0, 54, 90}, + {0, 34, 91}, + {0, 93, 92}, + {0, 25, 94}, + {0, 95, 7}, + {0, 97, 96}, + {0, 56, 98}, + {0, 100, 99}, + {0, 102, 101}, + {0, 104, 103}, + {0, 4, 105}, + {0, 107, 106}, + {0, 58, 108}, + {0, 110, 109}, + {0, 112, 111}, + {0, 114, 113}, + })); + + codecs.emplace(std::pair(SpvOpFunctionCall, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(81, { + {0, 0, 0}, + {35240468, 0, 0}, + {36096192, 0, 0}, + {123060826, 0, 0}, + {184634770, 0, 0}, + {459968607, 0, 0}, + {619875033, 0, 0}, + {950731750, 0, 0}, + {1058429216, 0, 0}, + {1182296898, 0, 0}, + {1238120570, 0, 0}, + {1271484400, 0, 0}, + {1429389803, 0, 0}, + {1717510093, 0, 0}, + {1766422419, 0, 0}, + {1775308984, 0, 0}, + {1817271123, 0, 0}, + {1917336504, 0, 0}, + {1957265068, 0, 0}, + {1998433745, 0, 0}, + {2055664760, 0, 0}, + {2303184249, 0, 0}, + {2308565678, 0, 0}, + {2451531615, 0, 0}, + {2496297824, 0, 0}, + {2507457870, 0, 0}, + {2550501832, 0, 0}, + {2590402790, 0, 0}, + {2649103430, 0, 0}, + {2831059514, 0, 0}, + {2836440943, 0, 0}, + {3269075805, 0, 0}, + {3361419439, 0, 0}, + {3457269042, 0, 0}, + {3464197236, 0, 0}, + {3472029049, 0, 0}, + {3518630848, 0, 0}, + {3587381650, 0, 0}, + {3653985133, 0, 0}, + {4185590212, 0, 0}, + {4233562270, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 40, 37}, + {0, 22, 30}, + {0, 2, 7}, + {0, 24, 11}, + {0, 16, 33}, + {0, 6, 34}, + {0, 42, 27}, + {0, 5, 43}, + {0, 4, 44}, + {0, 36, 8}, + {0, 39, 45}, + {0, 46, 1}, + {0, 3, 47}, + {0, 48, 23}, + {0, 49, 9}, + {0, 50, 35}, + {0, 52, 51}, + {0, 32, 53}, + {0, 13, 10}, + {0, 26, 14}, + {0, 19, 54}, + {0, 55, 25}, + {0, 56, 38}, + {0, 17, 57}, + {0, 59, 58}, + {0, 61, 60}, + {0, 62, 29}, + {0, 12, 15}, + {0, 18, 63}, + {0, 28, 64}, + {0, 65, 31}, + {0, 67, 66}, + {0, 20, 41}, + {0, 69, 68}, + {0, 71, 70}, + {0, 21, 72}, + {0, 74, 73}, + {0, 76, 75}, + {0, 78, 77}, + {0, 80, 79}, + })); + + codecs.emplace(std::pair(SpvOpFunctionCall, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(61, { + {0, 0, 0}, + {37459569, 0, 0}, + {162167595, 0, 0}, + {535067202, 0, 0}, + {701281393, 0, 0}, + {837715723, 0, 0}, + {1320550031, 0, 0}, + {1630583316, 0, 0}, + {1913735398, 0, 0}, + {1918481917, 0, 0}, + {1955871800, 0, 0}, + {1977038330, 0, 0}, + {2053214130, 0, 0}, + {2443959748, 0, 0}, + {2564745684, 0, 0}, + {2622612602, 0, 0}, + {2677252364, 0, 0}, + {2736026107, 0, 0}, + {2790624748, 0, 0}, + {2882994691, 0, 0}, + {2888125966, 0, 0}, + {2970183398, 0, 0}, + {3253403867, 0, 0}, + {3427283542, 0, 0}, + {3570411982, 0, 0}, + {3619787319, 0, 0}, + {3662767579, 0, 0}, + {3884846406, 0, 0}, + {3910458990, 0, 0}, + {3927915220, 0, 0}, + {4224872590, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 5, 20}, + {0, 6, 25}, + {0, 23, 3}, + {0, 2, 4}, + {0, 14, 17}, + {0, 11, 8}, + {0, 27, 10}, + {0, 19, 28}, + {0, 12, 16}, + {0, 33, 32}, + {0, 35, 34}, + {0, 37, 36}, + {0, 39, 38}, + {0, 40, 15}, + {0, 41, 7}, + {0, 1, 21}, + {0, 24, 13}, + {0, 29, 42}, + {0, 44, 43}, + {0, 22, 45}, + {0, 47, 46}, + {0, 49, 48}, + {0, 50, 30}, + {0, 31, 51}, + {0, 53, 52}, + {0, 55, 54}, + {0, 56, 9}, + {0, 57, 26}, + {0, 59, 58}, + {0, 60, 18}, + })); + + codecs.emplace(std::pair(SpvOpFunctionCall, 3), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(39, { + {0, 0, 0}, + {744062262, 0, 0}, + {810488476, 0, 0}, + {1040775722, 0, 0}, + {1280126114, 0, 0}, + {1367301635, 0, 0}, + {1684282922, 0, 0}, + {1918481917, 0, 0}, + {1978689945, 0, 0}, + {1980341560, 0, 0}, + {2443959748, 0, 0}, + {2629265310, 0, 0}, + {2790624748, 0, 0}, + {2970183398, 0, 0}, + {3044188332, 0, 0}, + {3496407048, 0, 0}, + {3662767579, 0, 0}, + {3887377256, 0, 0}, + {3971481069, 0, 0}, + {4224872590, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 2}, + {0, 18, 15}, + {0, 21, 6}, + {0, 13, 11}, + {0, 4, 22}, + {0, 14, 1}, + {0, 24, 23}, + {0, 25, 8}, + {0, 27, 26}, + {0, 20, 17}, + {0, 5, 28}, + {0, 29, 9}, + {0, 16, 10}, + {0, 31, 30}, + {0, 32, 7}, + {0, 19, 33}, + {0, 35, 34}, + {0, 37, 36}, + {0, 38, 12}, + })); + + codecs.emplace(std::pair(SpvOpFunctionCall, 4), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(27, { + {0, 0, 0}, + {37459569, 0, 0}, + {837715723, 0, 0}, + {1352628475, 0, 0}, + {1918481917, 0, 0}, + {1978689945, 0, 0}, + {1980341560, 0, 0}, + {2096388952, 0, 0}, + {2622612602, 0, 0}, + {2790624748, 0, 0}, + {2970183398, 0, 0}, + {3510682541, 0, 0}, + {3783543823, 0, 0}, + {4224872590, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 7, 11}, + {0, 2, 8}, + {0, 15, 12}, + {0, 1, 3}, + {0, 16, 6}, + {0, 18, 17}, + {0, 19, 14}, + {0, 20, 5}, + {0, 10, 21}, + {0, 22, 4}, + {0, 23, 13}, + {0, 25, 24}, + {0, 9, 26}, + })); + + codecs.emplace(std::pair(SpvOpFunctionCall, 5), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(13, { + {0, 0, 0}, + {1510333659, 0, 0}, + {1684282922, 0, 0}, + {1918481917, 0, 0}, + {2790624748, 0, 0}, + {3662767579, 0, 0}, + {4224872590, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 5, 1}, + {0, 8, 2}, + {0, 9, 7}, + {0, 3, 10}, + {0, 6, 11}, + {0, 4, 12}, + })); + + codecs.emplace(std::pair(SpvOpFunctionCall, 6), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(27, { + {0, 0, 0}, + {161668409, 0, 0}, + {188347929, 0, 0}, + {653708953, 0, 0}, + {976111724, 0, 0}, + {1510333659, 0, 0}, + {1918481917, 0, 0}, + {2790624748, 0, 0}, + {3033873113, 0, 0}, + {3499234137, 0, 0}, + {3525913657, 0, 0}, + {3552593177, 0, 0}, + {3570411982, 0, 0}, + {4224872590, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 8, 3}, + {0, 2, 9}, + {0, 10, 11}, + {0, 15, 1}, + {0, 17, 16}, + {0, 19, 18}, + {0, 5, 4}, + {0, 20, 6}, + {0, 12, 21}, + {0, 14, 22}, + {0, 24, 23}, + {0, 7, 25}, + {0, 13, 26}, + })); + + codecs.emplace(std::pair(SpvOpFunctionCall, 7), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(31, { + {0, 0, 0}, + {226836633, 0, 0}, + {296981500, 0, 0}, + {718877177, 0, 0}, + {745556697, 0, 0}, + {798915737, 0, 0}, + {1510333659, 0, 0}, + {1684282922, 0, 0}, + {2444465148, 0, 0}, + {2713718873, 0, 0}, + {3495546641, 0, 0}, + {3564402361, 0, 0}, + {4056442905, 0, 0}, + {4083122425, 0, 0}, + {4123141705, 0, 0}, + {4224872590, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 14, 4}, + {0, 5, 3}, + {0, 9, 8}, + {0, 13, 12}, + {0, 1, 11}, + {0, 18, 17}, + {0, 2, 19}, + {0, 21, 20}, + {0, 23, 22}, + {0, 25, 24}, + {0, 26, 7}, + {0, 27, 16}, + {0, 10, 6}, + {0, 29, 28}, + {0, 15, 30}, + })); + + codecs.emplace(std::pair(SpvOpFunctionCall, 8), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(35, { + {0, 0, 0}, + {161668409, 0, 0}, + {188347929, 0, 0}, + {215027449, 0, 0}, + {296981500, 0, 0}, + {653708953, 0, 0}, + {680388473, 0, 0}, + {1119069977, 0, 0}, + {1510333659, 0, 0}, + {1584774136, 0, 0}, + {2049792025, 0, 0}, + {2444465148, 0, 0}, + {2568512089, 0, 0}, + {3033873113, 0, 0}, + {3499234137, 0, 0}, + {3525913657, 0, 0}, + {3552593177, 0, 0}, + {4224872590, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 7, 6}, + {0, 10, 12}, + {0, 4, 3}, + {0, 16, 11}, + {0, 19, 14}, + {0, 5, 2}, + {0, 20, 13}, + {0, 21, 15}, + {0, 1, 22}, + {0, 24, 23}, + {0, 26, 25}, + {0, 28, 27}, + {0, 18, 29}, + {0, 8, 30}, + {0, 32, 31}, + {0, 9, 33}, + {0, 17, 34}, + })); + + codecs.emplace(std::pair(SpvOpFunctionCall, 9), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(25, { + {0, 0, 0}, + {825595257, 0, 0}, + {1064945649, 0, 0}, + {1290956281, 0, 0}, + {1510333659, 0, 0}, + {2096388952, 0, 0}, + {2248357849, 0, 0}, + {2713718873, 0, 0}, + {3187066832, 0, 0}, + {3205759417, 0, 0}, + {4064212479, 0, 0}, + {4163160985, 0, 0}, + {4224872590, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 8, 3}, + {0, 2, 9}, + {0, 7, 6}, + {0, 5, 14}, + {0, 16, 15}, + {0, 17, 11}, + {0, 19, 18}, + {0, 20, 1}, + {0, 4, 13}, + {0, 22, 21}, + {0, 10, 23}, + {0, 12, 24}, + })); + + codecs.emplace(std::pair(SpvOpFunctionCall, 10), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(27, { + {0, 0, 0}, + {123108003, 0, 0}, + {296981500, 0, 0}, + {595410904, 0, 0}, + {1466938734, 0, 0}, + {1503477720, 0, 0}, + {1816558243, 0, 0}, + {1990431740, 0, 0}, + {2724625059, 0, 0}, + {2790624748, 0, 0}, + {2812498065, 0, 0}, + {3160388974, 0, 0}, + {3745223676, 0, 0}, + {3982311384, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 5, 13}, + {0, 8, 1}, + {0, 12, 11}, + {0, 15, 3}, + {0, 6, 4}, + {0, 16, 7}, + {0, 17, 14}, + {0, 18, 2}, + {0, 19, 10}, + {0, 21, 20}, + {0, 23, 22}, + {0, 25, 24}, + {0, 9, 26}, + })); + + codecs.emplace(std::pair(SpvOpFunctionCall, 11), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(25, { + {0, 0, 0}, + {94145952, 0, 0}, + {1054641568, 0, 0}, + {1269075360, 0, 0}, + {1675922848, 0, 0}, + {2038205856, 0, 0}, + {2433519008, 0, 0}, + {2636942752, 0, 0}, + {2790624748, 0, 0}, + {2840366496, 0, 0}, + {2851900832, 0, 0}, + {2964622752, 0, 0}, + {3654061472, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 7, 1}, + {0, 12, 6}, + {0, 14, 10}, + {0, 13, 4}, + {0, 11, 15}, + {0, 3, 16}, + {0, 2, 17}, + {0, 18, 5}, + {0, 9, 19}, + {0, 21, 20}, + {0, 23, 22}, + {0, 8, 24}, + })); + + codecs.emplace(std::pair(SpvOpFunctionCall, 12), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(45, { + {0, 0, 0}, + {107544081, 0, 0}, + {125015036, 0, 0}, + {586244865, 0, 0}, + {1033081852, 0, 0}, + {1064945649, 0, 0}, + {1155765244, 0, 0}, + {1304296041, 0, 0}, + {1543646433, 0, 0}, + {1782996825, 0, 0}, + {1941148668, 0, 0}, + {2002490364, 0, 0}, + {2022347217, 0, 0}, + {2063832060, 0, 0}, + {2487708241, 0, 0}, + {2726532092, 0, 0}, + {2849215484, 0, 0}, + {2966409025, 0, 0}, + {3445109809, 0, 0}, + {3458449569, 0, 0}, + {3634598908, 0, 0}, + {3695940604, 0, 0}, + {3923810593, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 7, 2}, + {0, 14, 13}, + {0, 1, 23}, + {0, 6, 5}, + {0, 16, 15}, + {0, 24, 17}, + {0, 12, 25}, + {0, 22, 18}, + {0, 10, 26}, + {0, 28, 27}, + {0, 21, 29}, + {0, 31, 30}, + {0, 9, 8}, + {0, 11, 32}, + {0, 33, 19}, + {0, 3, 34}, + {0, 36, 35}, + {0, 38, 37}, + {0, 20, 39}, + {0, 41, 40}, + {0, 42, 4}, + {0, 44, 43}, + })); + + codecs.emplace(std::pair(SpvOpFunctionCall, 13), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(23, { + {0, 0, 0}, + {247698428, 0, 0}, + {309040124, 0, 0}, + {333554713, 0, 0}, + {572905105, 0, 0}, + {1033081852, 0, 0}, + {2002490364, 0, 0}, + {2009007457, 0, 0}, + {2487708241, 0, 0}, + {3634598908, 0, 0}, + {3695940604, 0, 0}, + {3923810593, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 6, 1}, + {0, 9, 7}, + {0, 5, 12}, + {0, 14, 13}, + {0, 15, 8}, + {0, 3, 16}, + {0, 17, 11}, + {0, 10, 4}, + {0, 2, 18}, + {0, 20, 19}, + {0, 22, 21}, + })); + + codecs.emplace(std::pair(SpvOpFunctionCall, 14), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(11, { + {0, 0, 0}, + {247698428, 0, 0}, + {1033081852, 0, 0}, + {2002490364, 0, 0}, + {2910557180, 0, 0}, + {3757282300, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 6, 4}, + {0, 7, 3}, + {0, 2, 8}, + {0, 1, 5}, + {0, 10, 9}, + })); + + codecs.emplace(std::pair(SpvOpFunctionCall, 15), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(9, { + {0, 0, 0}, + {1033081852, 0, 0}, + {1094423548, 0, 0}, + {2002490364, 0, 0}, + {3757282300, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 5}, + {0, 6, 2}, + {0, 4, 7}, + {0, 8, 1}, + })); + + codecs.emplace(std::pair(SpvOpFunctionCall, 16), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(57, { + {0, 0, 0}, + {135486769, 0, 0}, + {450406196, 0, 0}, + {503094540, 0, 0}, + {543621065, 0, 0}, + {827698488, 0, 0}, + {1294403159, 0, 0}, + {1296054774, 0, 0}, + {1297706389, 0, 0}, + {1322549027, 0, 0}, + {1784441183, 0, 0}, + {2194691858, 0, 0}, + {2448331885, 0, 0}, + {2468230023, 0, 0}, + {2547657777, 0, 0}, + {2549309392, 0, 0}, + {2550961007, 0, 0}, + {2934934694, 0, 0}, + {2936586309, 0, 0}, + {2938237924, 0, 0}, + {3094180193, 0, 0}, + {3095831808, 0, 0}, + {3183924418, 0, 0}, + {3561562003, 0, 0}, + {3563213618, 0, 0}, + {3564865233, 0, 0}, + {4028622909, 0, 0}, + {4039938779, 0, 0}, + {4050155669, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 27, 28}, + {0, 10, 2}, + {0, 25, 24}, + {0, 1, 12}, + {0, 30, 3}, + {0, 20, 31}, + {0, 9, 32}, + {0, 34, 33}, + {0, 35, 22}, + {0, 26, 15}, + {0, 19, 36}, + {0, 18, 37}, + {0, 38, 16}, + {0, 39, 8}, + {0, 5, 40}, + {0, 6, 41}, + {0, 21, 42}, + {0, 11, 29}, + {0, 4, 43}, + {0, 13, 23}, + {0, 14, 17}, + {0, 7, 44}, + {0, 46, 45}, + {0, 48, 47}, + {0, 50, 49}, + {0, 52, 51}, + {0, 54, 53}, + {0, 56, 55}, + })); + + codecs.emplace(std::pair(SpvOpVariable, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(57, { + {0, 0, 0}, + {37459569, 0, 0}, + {112745085, 0, 0}, + {137840602, 0, 0}, + {565334834, 0, 0}, + {625975427, 0, 0}, + {630964591, 0, 0}, + {680016782, 0, 0}, + {769422756, 0, 0}, + {1009983433, 0, 0}, + {1093210099, 0, 0}, + {1572088444, 0, 0}, + {1584774136, 0, 0}, + {1641565587, 0, 0}, + {1918481917, 0, 0}, + {2190437442, 0, 0}, + {2790624748, 0, 0}, + {3085467405, 0, 0}, + {3181646225, 0, 0}, + {3192069648, 0, 0}, + {3253403867, 0, 0}, + {3390051757, 0, 0}, + {3560665067, 0, 0}, + {3662767579, 0, 0}, + {4053789056, 0, 0}, + {4064212479, 0, 0}, + {4192247221, 0, 0}, + {4224872590, 0, 0}, + {4290024976, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 20}, + {0, 28, 10}, + {0, 13, 8}, + {0, 15, 17}, + {0, 30, 21}, + {0, 19, 31}, + {0, 4, 32}, + {0, 34, 33}, + {0, 35, 5}, + {0, 7, 24}, + {0, 9, 36}, + {0, 3, 37}, + {0, 38, 6}, + {0, 39, 23}, + {0, 27, 40}, + {0, 14, 41}, + {0, 25, 42}, + {0, 1, 29}, + {0, 12, 43}, + {0, 11, 26}, + {0, 18, 22}, + {0, 16, 44}, + {0, 46, 45}, + {0, 48, 47}, + {0, 50, 49}, + {0, 52, 51}, + {0, 54, 53}, + {0, 56, 55}, + })); + + codecs.emplace(std::pair(SpvOpVariable, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(27, { + {0, 0, 0}, + {162255877, 0, 0}, + {679771963, 0, 0}, + {789872778, 0, 0}, + {1154919607, 0, 0}, + {1343794461, 0, 0}, + {1951208733, 0, 0}, + {2263349224, 0, 0}, + {2320303498, 0, 0}, + {2924146124, 0, 0}, + {2984325996, 0, 0}, + {3334207724, 0, 0}, + {3868239231, 0, 0}, + {3869890846, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 5, 3}, + {0, 9, 7}, + {0, 12, 4}, + {0, 16, 15}, + {0, 18, 17}, + {0, 14, 19}, + {0, 13, 10}, + {0, 20, 1}, + {0, 21, 8}, + {0, 2, 22}, + {0, 11, 23}, + {0, 6, 24}, + {0, 26, 25}, + })); + + codecs.emplace(std::pair(SpvOpLoad, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(83, { + {0, 0, 0}, + {169674806, 0, 0}, + {269823086, 0, 0}, + {408465899, 0, 0}, + {451264926, 0, 0}, + {543558236, 0, 0}, + {810488476, 0, 0}, + {850497536, 0, 0}, + {870594305, 0, 0}, + {883854656, 0, 0}, + {1033363654, 0, 0}, + {1069781886, 0, 0}, + {1141965917, 0, 0}, + {1323407757, 0, 0}, + {1570165302, 0, 0}, + {1684282922, 0, 0}, + {1742737136, 0, 0}, + {1901166356, 0, 0}, + {1949759310, 0, 0}, + {2043873558, 0, 0}, + {2087004702, 0, 0}, + {2096388952, 0, 0}, + {2157103435, 0, 0}, + {2219733501, 0, 0}, + {2356768706, 0, 0}, + {2443959748, 0, 0}, + {2517964682, 0, 0}, + {2614879967, 0, 0}, + {2622612602, 0, 0}, + {2660843182, 0, 0}, + {2959147533, 0, 0}, + {2970183398, 0, 0}, + {3044188332, 0, 0}, + {3091876332, 0, 0}, + {3187066832, 0, 0}, + {3244209297, 0, 0}, + {3487022798, 0, 0}, + {3496407048, 0, 0}, + {3570411982, 0, 0}, + {3692647551, 0, 0}, + {3713290482, 0, 0}, + {3831290364, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 4, 1}, + {0, 35, 13}, + {0, 25, 11}, + {0, 7, 10}, + {0, 19, 36}, + {0, 43, 27}, + {0, 16, 29}, + {0, 22, 3}, + {0, 41, 30}, + {0, 44, 12}, + {0, 2, 24}, + {0, 40, 32}, + {0, 23, 45}, + {0, 46, 39}, + {0, 17, 33}, + {0, 48, 47}, + {0, 8, 49}, + {0, 51, 50}, + {0, 52, 20}, + {0, 53, 14}, + {0, 31, 54}, + {0, 15, 55}, + {0, 57, 56}, + {0, 59, 58}, + {0, 6, 26}, + {0, 61, 60}, + {0, 34, 62}, + {0, 64, 63}, + {0, 5, 37}, + {0, 9, 65}, + {0, 18, 28}, + {0, 66, 38}, + {0, 68, 67}, + {0, 69, 21}, + {0, 71, 70}, + {0, 73, 72}, + {0, 75, 74}, + {0, 77, 76}, + {0, 79, 78}, + {0, 80, 42}, + {0, 82, 81}, + })); + + codecs.emplace(std::pair(SpvOpLoad, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(83, { + {0, 0, 0}, + {28782128, 0, 0}, + {30433743, 0, 0}, + {37459569, 0, 0}, + {137840602, 0, 0}, + {522971108, 0, 0}, + {565334834, 0, 0}, + {625975427, 0, 0}, + {630964591, 0, 0}, + {680016782, 0, 0}, + {1009983433, 0, 0}, + {1079999262, 0, 0}, + {1395113939, 0, 0}, + {1572088444, 0, 0}, + {1584774136, 0, 0}, + {1649426421, 0, 0}, + {1918481917, 0, 0}, + {1957218950, 0, 0}, + {2311941439, 0, 0}, + {2313593054, 0, 0}, + {2790624748, 0, 0}, + {2838165089, 0, 0}, + {2839816704, 0, 0}, + {2841468319, 0, 0}, + {3085467405, 0, 0}, + {3181646225, 0, 0}, + {3192069648, 0, 0}, + {3253403867, 0, 0}, + {3364388739, 0, 0}, + {3366040354, 0, 0}, + {3367691969, 0, 0}, + {3369343584, 0, 0}, + {3560665067, 0, 0}, + {3662767579, 0, 0}, + {3945795573, 0, 0}, + {4053789056, 0, 0}, + {4064212479, 0, 0}, + {4224872590, 0, 0}, + {4239834800, 0, 0}, + {4241486415, 0, 0}, + {4243138030, 0, 0}, + {4244789645, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 27}, + {0, 15, 2}, + {0, 10, 26}, + {0, 7, 24}, + {0, 9, 31}, + {0, 43, 30}, + {0, 29, 12}, + {0, 11, 41}, + {0, 40, 39}, + {0, 44, 23}, + {0, 22, 6}, + {0, 34, 35}, + {0, 18, 45}, + {0, 46, 21}, + {0, 17, 19}, + {0, 48, 47}, + {0, 28, 49}, + {0, 51, 50}, + {0, 52, 38}, + {0, 53, 33}, + {0, 4, 54}, + {0, 13, 55}, + {0, 57, 56}, + {0, 59, 58}, + {0, 37, 8}, + {0, 61, 60}, + {0, 5, 62}, + {0, 64, 63}, + {0, 36, 32}, + {0, 3, 65}, + {0, 14, 16}, + {0, 66, 25}, + {0, 68, 67}, + {0, 69, 20}, + {0, 71, 70}, + {0, 73, 72}, + {0, 75, 74}, + {0, 77, 76}, + {0, 79, 78}, + {0, 80, 42}, + {0, 82, 81}, + })); + + codecs.emplace(std::pair(SpvOpLoad, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(49, { + {0, 0, 0}, + {137840602, 0, 0}, + {522971108, 0, 0}, + {769422756, 0, 0}, + {1009983433, 0, 0}, + {1079999262, 0, 0}, + {1558345254, 0, 0}, + {1572088444, 0, 0}, + {1641565587, 0, 0}, + {1918481917, 0, 0}, + {2311941439, 0, 0}, + {2313593054, 0, 0}, + {2790624748, 0, 0}, + {2838165089, 0, 0}, + {2994529201, 0, 0}, + {2996180816, 0, 0}, + {2997832431, 0, 0}, + {3027538652, 0, 0}, + {3253403867, 0, 0}, + {3364388739, 0, 0}, + {3560665067, 0, 0}, + {3662767579, 0, 0}, + {3945795573, 0, 0}, + {4192247221, 0, 0}, + {4224872590, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 14, 17}, + {0, 16, 15}, + {0, 13, 11}, + {0, 10, 3}, + {0, 22, 18}, + {0, 6, 8}, + {0, 19, 2}, + {0, 27, 26}, + {0, 28, 5}, + {0, 30, 29}, + {0, 32, 31}, + {0, 34, 33}, + {0, 4, 35}, + {0, 37, 36}, + {0, 21, 1}, + {0, 39, 38}, + {0, 40, 24}, + {0, 7, 23}, + {0, 20, 9}, + {0, 42, 41}, + {0, 43, 25}, + {0, 44, 12}, + {0, 46, 45}, + {0, 48, 47}, + })); + + codecs.emplace(std::pair(SpvOpStore, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(59, { + {0, 0, 0}, + {139011596, 0, 0}, + {177111659, 0, 0}, + {296981500, 0, 0}, + {408465899, 0, 0}, + {495107308, 0, 0}, + {810488476, 0, 0}, + {870594305, 0, 0}, + {1367301635, 0, 0}, + {1901166356, 0, 0}, + {2055836767, 0, 0}, + {2087004702, 0, 0}, + {2096388952, 0, 0}, + {2204920111, 0, 0}, + {2517964682, 0, 0}, + {2622612602, 0, 0}, + {2660843182, 0, 0}, + {2842919847, 0, 0}, + {2855506940, 0, 0}, + {2959147533, 0, 0}, + {3044188332, 0, 0}, + {3187066832, 0, 0}, + {3504158761, 0, 0}, + {3570411982, 0, 0}, + {3619787319, 0, 0}, + {3653838348, 0, 0}, + {3692647551, 0, 0}, + {3764205609, 0, 0}, + {3831290364, 0, 0}, + {3913885196, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 20, 29}, + {0, 25, 8}, + {0, 5, 1}, + {0, 24, 26}, + {0, 14, 9}, + {0, 27, 16}, + {0, 31, 7}, + {0, 33, 32}, + {0, 17, 34}, + {0, 35, 13}, + {0, 22, 6}, + {0, 3, 2}, + {0, 23, 36}, + {0, 28, 37}, + {0, 19, 4}, + {0, 38, 10}, + {0, 39, 15}, + {0, 40, 18}, + {0, 42, 41}, + {0, 43, 12}, + {0, 44, 21}, + {0, 45, 11}, + {0, 47, 46}, + {0, 49, 48}, + {0, 51, 50}, + {0, 53, 52}, + {0, 55, 54}, + {0, 57, 56}, + {0, 30, 58}, + })); + + codecs.emplace(std::pair(SpvOpStore, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(35, { + {0, 0, 0}, + {440421571, 0, 0}, + {827698488, 0, 0}, + {907126242, 0, 0}, + {908777857, 0, 0}, + {910429472, 0, 0}, + {1294403159, 0, 0}, + {1296054774, 0, 0}, + {1297706389, 0, 0}, + {2080953106, 0, 0}, + {2468230023, 0, 0}, + {2547657777, 0, 0}, + {2549309392, 0, 0}, + {2550961007, 0, 0}, + {3094857332, 0, 0}, + {3561562003, 0, 0}, + {3563213618, 0, 0}, + {3564865233, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 16, 12}, + {0, 17, 13}, + {0, 14, 19}, + {0, 18, 20}, + {0, 5, 21}, + {0, 11, 7}, + {0, 15, 22}, + {0, 9, 8}, + {0, 24, 23}, + {0, 25, 4}, + {0, 27, 26}, + {0, 28, 3}, + {0, 29, 10}, + {0, 6, 1}, + {0, 31, 30}, + {0, 32, 2}, + {0, 34, 33}, + })); + + codecs.emplace(std::pair(SpvOpAccessChain, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(99, { + {0, 0, 0}, + {27130513, 0, 0}, + {28782128, 0, 0}, + {30433743, 0, 0}, + {32085358, 0, 0}, + {155458798, 0, 0}, + {157110413, 0, 0}, + {163402553, 0, 0}, + {165054168, 0, 0}, + {213642219, 0, 0}, + {215293834, 0, 0}, + {216945449, 0, 0}, + {221900294, 0, 0}, + {545986953, 0, 0}, + {979993429, 0, 0}, + {1079999262, 0, 0}, + {1302400505, 0, 0}, + {1313182965, 0, 0}, + {1314834580, 0, 0}, + {1315613425, 0, 0}, + {1317265040, 0, 0}, + {1558345254, 0, 0}, + {1649426421, 0, 0}, + {2311941439, 0, 0}, + {2313593054, 0, 0}, + {2602027658, 0, 0}, + {2838165089, 0, 0}, + {2839816704, 0, 0}, + {2841468319, 0, 0}, + {2863084840, 0, 0}, + {2994529201, 0, 0}, + {2996180816, 0, 0}, + {2997832431, 0, 0}, + {3027538652, 0, 0}, + {3187387500, 0, 0}, + {3189039115, 0, 0}, + {3364388739, 0, 0}, + {3366040354, 0, 0}, + {3367691969, 0, 0}, + {3369343584, 0, 0}, + {3716914380, 0, 0}, + {3928842969, 0, 0}, + {3930494584, 0, 0}, + {3932146199, 0, 0}, + {3945482286, 0, 0}, + {4105051793, 0, 0}, + {4239834800, 0, 0}, + {4241486415, 0, 0}, + {4243138030, 0, 0}, + {4244789645, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 29, 10}, + {0, 17, 18}, + {0, 13, 14}, + {0, 44, 25}, + {0, 8, 7}, + {0, 20, 11}, + {0, 33, 19}, + {0, 6, 45}, + {0, 42, 43}, + {0, 40, 5}, + {0, 9, 16}, + {0, 1, 4}, + {0, 35, 34}, + {0, 12, 21}, + {0, 52, 51}, + {0, 31, 30}, + {0, 41, 32}, + {0, 54, 53}, + {0, 55, 2}, + {0, 3, 56}, + {0, 58, 57}, + {0, 60, 59}, + {0, 61, 22}, + {0, 63, 62}, + {0, 65, 64}, + {0, 67, 66}, + {0, 39, 68}, + {0, 38, 69}, + {0, 47, 70}, + {0, 49, 71}, + {0, 28, 48}, + {0, 37, 15}, + {0, 73, 72}, + {0, 74, 27}, + {0, 23, 75}, + {0, 76, 26}, + {0, 24, 77}, + {0, 79, 78}, + {0, 81, 80}, + {0, 82, 46}, + {0, 36, 83}, + {0, 85, 84}, + {0, 87, 86}, + {0, 89, 88}, + {0, 91, 90}, + {0, 93, 92}, + {0, 95, 94}, + {0, 97, 96}, + {0, 50, 98}, + })); + + codecs.emplace(std::pair(SpvOpAccessChain, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(101, { + {0, 0, 0}, + {112745085, 0, 0}, + {116376005, 0, 0}, + {137840602, 0, 0}, + {400248103, 0, 0}, + {406044930, 0, 0}, + {468372467, 0, 0}, + {522971108, 0, 0}, + {615341051, 0, 0}, + {625975427, 0, 0}, + {630964591, 0, 0}, + {680016782, 0, 0}, + {763027711, 0, 0}, + {977312655, 0, 0}, + {1009983433, 0, 0}, + {1062250709, 0, 0}, + {1395113939, 0, 0}, + {1410849099, 0, 0}, + {1642805350, 0, 0}, + {1692932387, 0, 0}, + {1698730948, 0, 0}, + {1827244161, 0, 0}, + {1918481917, 0, 0}, + {2096472894, 0, 0}, + {2190437442, 0, 0}, + {2299842241, 0, 0}, + {2433358586, 0, 0}, + {2593325766, 0, 0}, + {2785441472, 0, 0}, + {2790624748, 0, 0}, + {2879917723, 0, 0}, + {2882994691, 0, 0}, + {2902069960, 0, 0}, + {3090408469, 0, 0}, + {3181646225, 0, 0}, + {3255947500, 0, 0}, + {3263901372, 0, 0}, + {3268751013, 0, 0}, + {3347863687, 0, 0}, + {3390051757, 0, 0}, + {3560665067, 0, 0}, + {3617689692, 0, 0}, + {3662767579, 0, 0}, + {3717523241, 0, 0}, + {3854557817, 0, 0}, + {3910458990, 0, 0}, + {3941049054, 0, 0}, + {3945795573, 0, 0}, + {4080527786, 0, 0}, + {4101009465, 0, 0}, + {4290024976, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 32, 44}, + {0, 41, 26}, + {0, 16, 10}, + {0, 27, 45}, + {0, 25, 38}, + {0, 12, 18}, + {0, 6, 35}, + {0, 46, 23}, + {0, 20, 37}, + {0, 52, 19}, + {0, 53, 21}, + {0, 54, 48}, + {0, 33, 55}, + {0, 3, 8}, + {0, 28, 56}, + {0, 13, 57}, + {0, 59, 58}, + {0, 1, 49}, + {0, 47, 60}, + {0, 61, 14}, + {0, 63, 62}, + {0, 64, 43}, + {0, 7, 4}, + {0, 65, 15}, + {0, 67, 66}, + {0, 68, 17}, + {0, 36, 2}, + {0, 30, 69}, + {0, 71, 70}, + {0, 34, 5}, + {0, 73, 72}, + {0, 75, 74}, + {0, 77, 76}, + {0, 24, 78}, + {0, 39, 31}, + {0, 80, 79}, + {0, 9, 11}, + {0, 42, 81}, + {0, 83, 82}, + {0, 29, 50}, + {0, 84, 51}, + {0, 86, 85}, + {0, 22, 40}, + {0, 88, 87}, + {0, 90, 89}, + {0, 92, 91}, + {0, 94, 93}, + {0, 96, 95}, + {0, 98, 97}, + {0, 100, 99}, + })); + + codecs.emplace(std::pair(SpvOpAccessChain, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(69, { + {0, 0, 0}, + {51041423, 0, 0}, + {142465290, 0, 0}, + {144116905, 0, 0}, + {290391815, 0, 0}, + {438318340, 0, 0}, + {529742207, 0, 0}, + {677668732, 0, 0}, + {917019124, 0, 0}, + {1064945649, 0, 0}, + {1156369516, 0, 0}, + {1158021131, 0, 0}, + {1304296041, 0, 0}, + {1452222566, 0, 0}, + {1543646433, 0, 0}, + {1691572958, 0, 0}, + {1782996825, 0, 0}, + {1784648440, 0, 0}, + {1930923350, 0, 0}, + {2170273742, 0, 0}, + {2318200267, 0, 0}, + {2466126792, 0, 0}, + {2557550659, 0, 0}, + {2705477184, 0, 0}, + {2796901051, 0, 0}, + {2798552666, 0, 0}, + {2944827576, 0, 0}, + {3092754101, 0, 0}, + {3184177968, 0, 0}, + {3332104493, 0, 0}, + {3571454885, 0, 0}, + {3810805277, 0, 0}, + {3958731802, 0, 0}, + {4106658327, 0, 0}, + {4198082194, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 27, 33}, + {0, 21, 5}, + {0, 26, 13}, + {0, 20, 8}, + {0, 15, 7}, + {0, 37, 36}, + {0, 32, 29}, + {0, 38, 4}, + {0, 30, 1}, + {0, 9, 12}, + {0, 39, 18}, + {0, 22, 40}, + {0, 42, 41}, + {0, 44, 43}, + {0, 45, 35}, + {0, 46, 34}, + {0, 6, 14}, + {0, 28, 23}, + {0, 48, 47}, + {0, 49, 31}, + {0, 51, 50}, + {0, 19, 24}, + {0, 52, 10}, + {0, 2, 53}, + {0, 55, 54}, + {0, 25, 56}, + {0, 11, 57}, + {0, 59, 58}, + {0, 3, 17}, + {0, 61, 60}, + {0, 16, 62}, + {0, 64, 63}, + {0, 66, 65}, + {0, 68, 67}, + })); + + codecs.emplace(std::pair(SpvOpAccessChain, 3), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(85, { + {0, 0, 0}, + {142465290, 0, 0}, + {144116905, 0, 0}, + {198967948, 0, 0}, + {290391815, 0, 0}, + {529742207, 0, 0}, + {586244865, 0, 0}, + {677668732, 0, 0}, + {825595257, 0, 0}, + {917019124, 0, 0}, + {973521782, 0, 0}, + {1064945649, 0, 0}, + {1156369516, 0, 0}, + {1158021131, 0, 0}, + {1212872174, 0, 0}, + {1304296041, 0, 0}, + {1452222566, 0, 0}, + {1543646433, 0, 0}, + {1600149091, 0, 0}, + {1782996825, 0, 0}, + {1784648440, 0, 0}, + {1839499483, 0, 0}, + {1930923350, 0, 0}, + {2170273742, 0, 0}, + {2226776400, 0, 0}, + {2318200267, 0, 0}, + {2466126792, 0, 0}, + {2557550659, 0, 0}, + {2614053317, 0, 0}, + {2796901051, 0, 0}, + {2798552666, 0, 0}, + {2853403709, 0, 0}, + {2944827576, 0, 0}, + {3184177968, 0, 0}, + {3240680626, 0, 0}, + {3480031018, 0, 0}, + {3571454885, 0, 0}, + {3810805277, 0, 0}, + {3867307935, 0, 0}, + {3958731802, 0, 0}, + {4106658327, 0, 0}, + {4198082194, 0, 0}, + {4254584852, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 7, 11}, + {0, 15, 4}, + {0, 32, 25}, + {0, 44, 39}, + {0, 36, 22}, + {0, 45, 17}, + {0, 24, 46}, + {0, 10, 9}, + {0, 6, 27}, + {0, 28, 18}, + {0, 42, 34}, + {0, 31, 14}, + {0, 41, 38}, + {0, 26, 3}, + {0, 47, 33}, + {0, 21, 8}, + {0, 5, 35}, + {0, 40, 16}, + {0, 37, 23}, + {0, 49, 48}, + {0, 51, 50}, + {0, 53, 52}, + {0, 55, 54}, + {0, 57, 56}, + {0, 59, 58}, + {0, 61, 60}, + {0, 63, 62}, + {0, 65, 64}, + {0, 67, 66}, + {0, 68, 12}, + {0, 29, 69}, + {0, 70, 1}, + {0, 30, 2}, + {0, 43, 71}, + {0, 73, 72}, + {0, 74, 20}, + {0, 75, 19}, + {0, 77, 76}, + {0, 13, 78}, + {0, 80, 79}, + {0, 82, 81}, + {0, 84, 83}, + })); + + codecs.emplace(std::pair(SpvOpAccessChain, 4), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(9, { + {0, 0, 0}, + {144116905, 0, 0}, + {1158021131, 0, 0}, + {1784648440, 0, 0}, + {2798552666, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 5}, + {0, 4, 2}, + {0, 6, 3}, + {0, 8, 7}, + })); + + codecs.emplace(std::pair(SpvOpAccessChain, 5), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(5, { + {0, 0, 0}, + {142465290, 0, 0}, + {1782996825, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 1}, + {0, 4, 3}, + })); + + codecs.emplace(std::pair(SpvOpAccessChain, 6), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(7, { + {0, 0, 0}, + {679771963, 0, 0}, + {2320303498, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 4}, + {0, 2, 5}, + {0, 6, 1}, + })); + + codecs.emplace(std::pair(SpvOpVectorShuffle, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(59, { + {0, 0, 0}, + {177111659, 0, 0}, + {413918748, 0, 0}, + {529383565, 0, 0}, + {646282397, 0, 0}, + {837715723, 0, 0}, + {1019457583, 0, 0}, + {1022544883, 0, 0}, + {1054461787, 0, 0}, + {1097775533, 0, 0}, + {1136775085, 0, 0}, + {1191015885, 0, 0}, + {1196280518, 0, 0}, + {1203545131, 0, 0}, + {1352628475, 0, 0}, + {1367301635, 0, 0}, + {1918742169, 0, 0}, + {1922045399, 0, 0}, + {2055836767, 0, 0}, + {2183547611, 0, 0}, + {2204920111, 0, 0}, + {2358141757, 0, 0}, + {2572638469, 0, 0}, + {2597020383, 0, 0}, + {2842919847, 0, 0}, + {3619787319, 0, 0}, + {3701632935, 0, 0}, + {3783543823, 0, 0}, + {4245257809, 0, 0}, + {4265894873, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 11, 23}, + {0, 12, 2}, + {0, 9, 7}, + {0, 21, 19}, + {0, 4, 29}, + {0, 10, 28}, + {0, 17, 16}, + {0, 27, 3}, + {0, 32, 31}, + {0, 33, 22}, + {0, 6, 34}, + {0, 35, 8}, + {0, 36, 24}, + {0, 38, 37}, + {0, 1, 14}, + {0, 39, 20}, + {0, 5, 40}, + {0, 42, 41}, + {0, 43, 26}, + {0, 45, 44}, + {0, 47, 46}, + {0, 48, 18}, + {0, 15, 49}, + {0, 50, 25}, + {0, 51, 13}, + {0, 53, 52}, + {0, 55, 54}, + {0, 57, 56}, + {0, 30, 58}, + })); + + codecs.emplace(std::pair(SpvOpVectorShuffle, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(59, { + {0, 0, 0}, + {236660303, 0, 0}, + {342159236, 0, 0}, + {371428004, 0, 0}, + {373079619, 0, 0}, + {488500848, 0, 0}, + {495107308, 0, 0}, + {864295921, 0, 0}, + {1071164424, 0, 0}, + {1136911283, 0, 0}, + {1178317551, 0, 0}, + {1510422521, 0, 0}, + {1570165302, 0, 0}, + {1822823090, 0, 0}, + {1858116930, 0, 0}, + {1977038330, 0, 0}, + {2096388952, 0, 0}, + {2157103435, 0, 0}, + {2231688008, 0, 0}, + {2604576561, 0, 0}, + {2622612602, 0, 0}, + {2771938750, 0, 0}, + {2777172031, 0, 0}, + {2996594997, 0, 0}, + {3187066832, 0, 0}, + {3496407048, 0, 0}, + {3570411982, 0, 0}, + {3609540589, 0, 0}, + {3713290482, 0, 0}, + {3797761273, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 18, 8}, + {0, 27, 9}, + {0, 21, 10}, + {0, 14, 24}, + {0, 12, 19}, + {0, 11, 15}, + {0, 23, 2}, + {0, 7, 13}, + {0, 31, 22}, + {0, 32, 4}, + {0, 33, 29}, + {0, 34, 1}, + {0, 35, 3}, + {0, 37, 36}, + {0, 38, 28}, + {0, 39, 5}, + {0, 41, 40}, + {0, 42, 17}, + {0, 16, 43}, + {0, 45, 44}, + {0, 46, 6}, + {0, 48, 47}, + {0, 50, 49}, + {0, 52, 51}, + {0, 25, 53}, + {0, 54, 20}, + {0, 55, 26}, + {0, 57, 56}, + {0, 30, 58}, + })); + + codecs.emplace(std::pair(SpvOpVectorShuffle, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(47, { + {0, 0, 0}, + {236660303, 0, 0}, + {342159236, 0, 0}, + {488500848, 0, 0}, + {495107308, 0, 0}, + {864295921, 0, 0}, + {1178317551, 0, 0}, + {1510422521, 0, 0}, + {1570165302, 0, 0}, + {1858116930, 0, 0}, + {1977038330, 0, 0}, + {2096388952, 0, 0}, + {2157103435, 0, 0}, + {2231688008, 0, 0}, + {2604576561, 0, 0}, + {2622612602, 0, 0}, + {2771938750, 0, 0}, + {2777172031, 0, 0}, + {2996594997, 0, 0}, + {3496407048, 0, 0}, + {3570411982, 0, 0}, + {3609540589, 0, 0}, + {3713290482, 0, 0}, + {3797761273, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 21, 13}, + {0, 16, 6}, + {0, 14, 9}, + {0, 7, 10}, + {0, 18, 2}, + {0, 17, 5}, + {0, 25, 8}, + {0, 22, 12}, + {0, 26, 23}, + {0, 27, 1}, + {0, 28, 3}, + {0, 30, 29}, + {0, 32, 31}, + {0, 34, 33}, + {0, 35, 11}, + {0, 36, 4}, + {0, 38, 37}, + {0, 40, 39}, + {0, 41, 15}, + {0, 42, 19}, + {0, 20, 43}, + {0, 45, 44}, + {0, 24, 46}, + })); + + codecs.emplace(std::pair(SpvOpVectorShuffle, 3), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(15, { + {0, 0, 0}, + {679771963, 0, 0}, + {1146476634, 0, 0}, + {2160380860, 0, 0}, + {2320303498, 0, 0}, + {3334207724, 0, 0}, + {3800912395, 0, 0}, + {3802564010, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 3}, + {0, 9, 6}, + {0, 8, 7}, + {0, 11, 10}, + {0, 4, 12}, + {0, 5, 13}, + {0, 14, 1}, + })); + + codecs.emplace(std::pair(SpvOpCompositeConstruct, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(79, { + {0, 0, 0}, + {107497541, 0, 0}, + {289648234, 0, 0}, + {348584153, 0, 0}, + {369686787, 0, 0}, + {429277936, 0, 0}, + {449954059, 0, 0}, + {508217552, 0, 0}, + {742917749, 0, 0}, + {1032593647, 0, 0}, + {1158929937, 0, 0}, + {1209418480, 0, 0}, + {1319785741, 0, 0}, + {1321616112, 0, 0}, + {1417363940, 0, 0}, + {1541020250, 0, 0}, + {1564342316, 0, 0}, + {1578775276, 0, 0}, + {1631434666, 0, 0}, + {1636389511, 0, 0}, + {2012838864, 0, 0}, + {2262137600, 0, 0}, + {2281956980, 0, 0}, + {2359973133, 0, 0}, + {2464905186, 0, 0}, + {2613179511, 0, 0}, + {2621255555, 0, 0}, + {2817335337, 0, 0}, + {2881302403, 0, 0}, + {3063300848, 0, 0}, + {3151638847, 0, 0}, + {3233393284, 0, 0}, + {3323682385, 0, 0}, + {3337532056, 0, 0}, + {3456899824, 0, 0}, + {3547456240, 0, 0}, + {3675926744, 0, 0}, + {3753486980, 0, 0}, + {3931641900, 0, 0}, + {3970432934, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 25, 1}, + {0, 6, 4}, + {0, 8, 19}, + {0, 39, 24}, + {0, 3, 2}, + {0, 34, 14}, + {0, 10, 9}, + {0, 18, 38}, + {0, 32, 15}, + {0, 27, 16}, + {0, 28, 35}, + {0, 13, 26}, + {0, 20, 23}, + {0, 21, 11}, + {0, 36, 33}, + {0, 5, 22}, + {0, 42, 41}, + {0, 43, 29}, + {0, 45, 44}, + {0, 7, 46}, + {0, 48, 47}, + {0, 30, 31}, + {0, 50, 49}, + {0, 52, 51}, + {0, 54, 53}, + {0, 55, 17}, + {0, 57, 56}, + {0, 59, 58}, + {0, 61, 60}, + {0, 62, 12}, + {0, 64, 63}, + {0, 66, 65}, + {0, 67, 37}, + {0, 69, 68}, + {0, 71, 70}, + {0, 73, 72}, + {0, 75, 74}, + {0, 77, 76}, + {0, 40, 78}, + })); + + codecs.emplace(std::pair(SpvOpCompositeConstruct, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(87, { + {0, 0, 0}, + {153013225, 0, 0}, + {296836635, 0, 0}, + {296981500, 0, 0}, + {778500192, 0, 0}, + {810488476, 0, 0}, + {848380423, 0, 0}, + {900522183, 0, 0}, + {910398460, 0, 0}, + {959681532, 0, 0}, + {1141965917, 0, 0}, + {1287304304, 0, 0}, + {1323407757, 0, 0}, + {1417363940, 0, 0}, + {1471851763, 0, 0}, + {1526654696, 0, 0}, + {1654776395, 0, 0}, + {1684282922, 0, 0}, + {1739837626, 0, 0}, + {1791352211, 0, 0}, + {2195550588, 0, 0}, + {2319227476, 0, 0}, + {2491124112, 0, 0}, + {2789375411, 0, 0}, + {2807448986, 0, 0}, + {2817579280, 0, 0}, + {2835131395, 0, 0}, + {2847102741, 0, 0}, + {2855506940, 0, 0}, + {2860348412, 0, 0}, + {3079287749, 0, 0}, + {3091876332, 0, 0}, + {3168953855, 0, 0}, + {3374978006, 0, 0}, + {3399062057, 0, 0}, + {3510257966, 0, 0}, + {3554463148, 0, 0}, + {3579593979, 0, 0}, + {3757851979, 0, 0}, + {3759503594, 0, 0}, + {3761155209, 0, 0}, + {3762806824, 0, 0}, + {3902853271, 0, 0}, + {4140081844, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 38, 42}, + {0, 14, 23}, + {0, 26, 18}, + {0, 39, 35}, + {0, 6, 40}, + {0, 16, 13}, + {0, 33, 34}, + {0, 12, 4}, + {0, 27, 41}, + {0, 25, 21}, + {0, 24, 1}, + {0, 37, 19}, + {0, 32, 22}, + {0, 2, 8}, + {0, 20, 17}, + {0, 43, 36}, + {0, 29, 15}, + {0, 46, 45}, + {0, 48, 47}, + {0, 50, 49}, + {0, 52, 51}, + {0, 54, 53}, + {0, 7, 55}, + {0, 56, 30}, + {0, 57, 5}, + {0, 59, 58}, + {0, 60, 11}, + {0, 9, 61}, + {0, 63, 62}, + {0, 65, 64}, + {0, 66, 31}, + {0, 68, 67}, + {0, 10, 69}, + {0, 71, 70}, + {0, 28, 72}, + {0, 74, 73}, + {0, 76, 75}, + {0, 78, 77}, + {0, 79, 3}, + {0, 81, 80}, + {0, 83, 82}, + {0, 85, 84}, + {0, 44, 86}, + })); + + codecs.emplace(std::pair(SpvOpCompositeConstruct, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(81, { + {0, 0, 0}, + {14244860, 0, 0}, + {150820676, 0, 0}, + {153013225, 0, 0}, + {269823086, 0, 0}, + {289648234, 0, 0}, + {296981500, 0, 0}, + {678695941, 0, 0}, + {810488476, 0, 0}, + {850592577, 0, 0}, + {870594305, 0, 0}, + {910398460, 0, 0}, + {959681532, 0, 0}, + {1206571206, 0, 0}, + {1287304304, 0, 0}, + {1323407757, 0, 0}, + {1471851763, 0, 0}, + {1526654696, 0, 0}, + {1684282922, 0, 0}, + {1734446471, 0, 0}, + {1758530522, 0, 0}, + {2117320444, 0, 0}, + {2118972059, 0, 0}, + {2120623674, 0, 0}, + {2122275289, 0, 0}, + {2219733501, 0, 0}, + {2262321736, 0, 0}, + {2807448986, 0, 0}, + {2817579280, 0, 0}, + {2835131395, 0, 0}, + {2855506940, 0, 0}, + {2860348412, 0, 0}, + {2951272396, 0, 0}, + {3079287749, 0, 0}, + {3168953855, 0, 0}, + {3502816184, 0, 0}, + {3510257966, 0, 0}, + {3554463148, 0, 0}, + {3997952447, 0, 0}, + {4140081844, 0, 0}, + {4182141402, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 21, 26}, + {0, 29, 16}, + {0, 22, 36}, + {0, 1, 23}, + {0, 20, 5}, + {0, 19, 35}, + {0, 10, 38}, + {0, 13, 24}, + {0, 28, 7}, + {0, 27, 3}, + {0, 40, 2}, + {0, 34, 9}, + {0, 32, 11}, + {0, 33, 18}, + {0, 39, 37}, + {0, 31, 17}, + {0, 43, 42}, + {0, 45, 44}, + {0, 47, 46}, + {0, 49, 48}, + {0, 51, 50}, + {0, 8, 52}, + {0, 15, 53}, + {0, 55, 54}, + {0, 56, 14}, + {0, 58, 57}, + {0, 60, 59}, + {0, 61, 25}, + {0, 63, 62}, + {0, 4, 64}, + {0, 66, 65}, + {0, 68, 67}, + {0, 70, 69}, + {0, 71, 12}, + {0, 6, 72}, + {0, 30, 73}, + {0, 75, 74}, + {0, 77, 76}, + {0, 79, 78}, + {0, 41, 80}, + })); + + codecs.emplace(std::pair(SpvOpCompositeConstruct, 3), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(111, { + {0, 0, 0}, + {34183582, 0, 0}, + {93914936, 0, 0}, + {94303122, 0, 0}, + {117998987, 0, 0}, + {153013225, 0, 0}, + {296981500, 0, 0}, + {451264926, 0, 0}, + {473485679, 0, 0}, + {476788909, 0, 0}, + {478440524, 0, 0}, + {480092139, 0, 0}, + {481743754, 0, 0}, + {810488476, 0, 0}, + {871966503, 0, 0}, + {910398460, 0, 0}, + {918189168, 0, 0}, + {933769938, 0, 0}, + {959681532, 0, 0}, + {1149665466, 0, 0}, + {1166917451, 0, 0}, + {1227221002, 0, 0}, + {1310740861, 0, 0}, + {1323407757, 0, 0}, + {1341516288, 0, 0}, + {1373166395, 0, 0}, + {1445161581, 0, 0}, + {1461645203, 0, 0}, + {1471851763, 0, 0}, + {1526654696, 0, 0}, + {1561718045, 0, 0}, + {1593584949, 0, 0}, + {1684282922, 0, 0}, + {1800404122, 0, 0}, + {1862284649, 0, 0}, + {2213411495, 0, 0}, + {2668680621, 0, 0}, + {2805256437, 0, 0}, + {2807448986, 0, 0}, + {2835131395, 0, 0}, + {2855506940, 0, 0}, + {2860348412, 0, 0}, + {3000904950, 0, 0}, + {3107413701, 0, 0}, + {3168953855, 0, 0}, + {3333131702, 0, 0}, + {3365041621, 0, 0}, + {3456899824, 0, 0}, + {3505028338, 0, 0}, + {3510257966, 0, 0}, + {3554463148, 0, 0}, + {3606320646, 0, 0}, + {3692647551, 0, 0}, + {3861006967, 0, 0}, + {4126287524, 0, 0}, + {4140081844, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 14, 33}, + {0, 35, 25}, + {0, 27, 17}, + {0, 8, 20}, + {0, 3, 54}, + {0, 1, 19}, + {0, 10, 46}, + {0, 11, 9}, + {0, 39, 28}, + {0, 53, 49}, + {0, 12, 2}, + {0, 34, 4}, + {0, 47, 36}, + {0, 23, 45}, + {0, 5, 37}, + {0, 24, 38}, + {0, 43, 26}, + {0, 48, 51}, + {0, 44, 32}, + {0, 15, 16}, + {0, 57, 22}, + {0, 55, 50}, + {0, 29, 58}, + {0, 60, 59}, + {0, 41, 61}, + {0, 63, 62}, + {0, 65, 64}, + {0, 67, 66}, + {0, 69, 68}, + {0, 13, 70}, + {0, 71, 7}, + {0, 42, 31}, + {0, 73, 72}, + {0, 75, 74}, + {0, 21, 30}, + {0, 77, 76}, + {0, 79, 78}, + {0, 81, 80}, + {0, 82, 18}, + {0, 84, 83}, + {0, 86, 85}, + {0, 88, 87}, + {0, 90, 89}, + {0, 52, 91}, + {0, 6, 92}, + {0, 94, 93}, + {0, 96, 95}, + {0, 98, 97}, + {0, 99, 40}, + {0, 101, 100}, + {0, 103, 102}, + {0, 105, 104}, + {0, 107, 106}, + {0, 109, 108}, + {0, 56, 110}, + })); + + codecs.emplace(std::pair(SpvOpCompositeConstruct, 4), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(155, { + {0, 0, 0}, + {18776483, 0, 0}, + {37009196, 0, 0}, + {277023757, 0, 0}, + {296981500, 0, 0}, + {348988933, 0, 0}, + {451264926, 0, 0}, + {564884461, 0, 0}, + {804899022, 0, 0}, + {810488476, 0, 0}, + {870594305, 0, 0}, + {876864198, 0, 0}, + {900522183, 0, 0}, + {928261291, 0, 0}, + {959681532, 0, 0}, + {1164724902, 0, 0}, + {1323407757, 0, 0}, + {1332774287, 0, 0}, + {1404739463, 0, 0}, + {1447712361, 0, 0}, + {1450415100, 0, 0}, + {1513770932, 0, 0}, + {1620634991, 0, 0}, + {1692600167, 0, 0}, + {1860649552, 0, 0}, + {1932614728, 0, 0}, + {2087004702, 0, 0}, + {2148510256, 0, 0}, + {2220475432, 0, 0}, + {2388524817, 0, 0}, + {2460489993, 0, 0}, + {2676385521, 0, 0}, + {2748350697, 0, 0}, + {2855506940, 0, 0}, + {2860348412, 0, 0}, + {2916400082, 0, 0}, + {2988365258, 0, 0}, + {3061856840, 0, 0}, + {3063508455, 0, 0}, + {3065160070, 0, 0}, + {3066811685, 0, 0}, + {3068463300, 0, 0}, + {3070114915, 0, 0}, + {3071766530, 0, 0}, + {3073418145, 0, 0}, + {3075069760, 0, 0}, + {3076721375, 0, 0}, + {3078372990, 0, 0}, + {3080024605, 0, 0}, + {3081676220, 0, 0}, + {3083327835, 0, 0}, + {3084979450, 0, 0}, + {3086631065, 0, 0}, + {3088282680, 0, 0}, + {3114708520, 0, 0}, + {3116360135, 0, 0}, + {3118011750, 0, 0}, + {3119663365, 0, 0}, + {3121314980, 0, 0}, + {3124618210, 0, 0}, + {3126269825, 0, 0}, + {3127921440, 0, 0}, + {3129573055, 0, 0}, + {3131224670, 0, 0}, + {3132876285, 0, 0}, + {3134527900, 0, 0}, + {3136179515, 0, 0}, + {3204260786, 0, 0}, + {3264086791, 0, 0}, + {3276225962, 0, 0}, + {3444275347, 0, 0}, + {3516240523, 0, 0}, + {3588205699, 0, 0}, + {3732136051, 0, 0}, + {3804101227, 0, 0}, + {3874089391, 0, 0}, + {4044115788, 0, 0}, + {4116080964, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 45, 43}, + {0, 3, 46}, + {0, 71, 36}, + {0, 44, 34}, + {0, 76, 54}, + {0, 73, 55}, + {0, 57, 67}, + {0, 51, 56}, + {0, 31, 27}, + {0, 38, 37}, + {0, 40, 39}, + {0, 42, 41}, + {0, 49, 47}, + {0, 35, 50}, + {0, 21, 70}, + {0, 19, 5}, + {0, 8, 58}, + {0, 17, 11}, + {0, 24, 18}, + {0, 30, 29}, + {0, 52, 9}, + {0, 77, 22}, + {0, 62, 48}, + {0, 25, 53}, + {0, 20, 59}, + {0, 26, 60}, + {0, 72, 6}, + {0, 79, 69}, + {0, 80, 7}, + {0, 81, 2}, + {0, 12, 13}, + {0, 82, 68}, + {0, 65, 61}, + {0, 74, 63}, + {0, 23, 83}, + {0, 64, 10}, + {0, 84, 32}, + {0, 66, 28}, + {0, 15, 85}, + {0, 86, 16}, + {0, 88, 87}, + {0, 90, 89}, + {0, 92, 91}, + {0, 1, 93}, + {0, 95, 94}, + {0, 97, 96}, + {0, 99, 98}, + {0, 100, 75}, + {0, 102, 101}, + {0, 104, 103}, + {0, 106, 105}, + {0, 107, 14}, + {0, 109, 108}, + {0, 111, 110}, + {0, 113, 112}, + {0, 115, 114}, + {0, 117, 116}, + {0, 119, 118}, + {0, 121, 120}, + {0, 123, 122}, + {0, 125, 124}, + {0, 127, 126}, + {0, 129, 128}, + {0, 131, 130}, + {0, 133, 132}, + {0, 135, 134}, + {0, 137, 136}, + {0, 139, 138}, + {0, 141, 140}, + {0, 143, 142}, + {0, 145, 144}, + {0, 147, 146}, + {0, 33, 148}, + {0, 4, 149}, + {0, 78, 150}, + {0, 152, 151}, + {0, 154, 153}, + })); + + codecs.emplace(std::pair(SpvOpCompositeConstruct, 5), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(11, { + {0, 0, 0}, + {679771963, 0, 0}, + {789872778, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 6, 2}, + {0, 4, 7}, + {0, 1, 8}, + {0, 9, 5}, + {0, 3, 10}, + })); + + codecs.emplace(std::pair(SpvOpCompositeExtract, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(49, { + {0, 0, 0}, + {126463145, 0, 0}, + {171307615, 0, 0}, + {342159236, 0, 0}, + {354479447, 0, 0}, + {593829839, 0, 0}, + {743407979, 0, 0}, + {898191441, 0, 0}, + {900522183, 0, 0}, + {1265796414, 0, 0}, + {1287304304, 0, 0}, + {1356063462, 0, 0}, + {1368383673, 0, 0}, + {1526654696, 0, 0}, + {1766994680, 0, 0}, + {1793544760, 0, 0}, + {1811839150, 0, 0}, + {2234361374, 0, 0}, + {2279700640, 0, 0}, + {2383939514, 0, 0}, + {2780898906, 0, 0}, + {2996594997, 0, 0}, + {3413713311, 0, 0}, + {3554463148, 0, 0}, + {3635542517, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 11, 15}, + {0, 20, 14}, + {0, 7, 18}, + {0, 6, 1}, + {0, 12, 10}, + {0, 23, 19}, + {0, 13, 5}, + {0, 24, 17}, + {0, 21, 3}, + {0, 22, 16}, + {0, 26, 2}, + {0, 27, 8}, + {0, 4, 28}, + {0, 29, 9}, + {0, 31, 30}, + {0, 33, 32}, + {0, 35, 34}, + {0, 37, 36}, + {0, 39, 38}, + {0, 41, 40}, + {0, 43, 42}, + {0, 45, 44}, + {0, 47, 46}, + {0, 25, 48}, + })); + + codecs.emplace(std::pair(SpvOpCompositeExtract, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(153, { + {0, 0, 0}, + {13107491, 0, 0}, + {257136089, 0, 0}, + {293528591, 0, 0}, + {321459212, 0, 0}, + {425022309, 0, 0}, + {490769168, 0, 0}, + {495107308, 0, 0}, + {517919178, 0, 0}, + {617312262, 0, 0}, + {708736129, 0, 0}, + {753756604, 0, 0}, + {765238787, 0, 0}, + {796985462, 0, 0}, + {819503463, 0, 0}, + {850497536, 0, 0}, + {948086521, 0, 0}, + {1004589179, 0, 0}, + {1120149824, 0, 0}, + {1165671422, 0, 0}, + {1203545131, 0, 0}, + {1297165140, 0, 0}, + {1335363438, 0, 0}, + {1351676723, 0, 0}, + {1391866096, 0, 0}, + {1584369690, 0, 0}, + {1631216488, 0, 0}, + {1691646294, 0, 0}, + {1779143013, 0, 0}, + {1858116930, 0, 0}, + {1890300748, 0, 0}, + {1915438939, 0, 0}, + {1918742169, 0, 0}, + {1922045399, 0, 0}, + {1961990747, 0, 0}, + {2037710159, 0, 0}, + {2037814253, 0, 0}, + {2043873558, 0, 0}, + {2096388952, 0, 0}, + {2169307971, 0, 0}, + {2257843797, 0, 0}, + {2262220987, 0, 0}, + {2338272340, 0, 0}, + {2405770322, 0, 0}, + {2498042266, 0, 0}, + {2563789125, 0, 0}, + {2588618056, 0, 0}, + {2645120714, 0, 0}, + {2864863800, 0, 0}, + {2909957084, 0, 0}, + {2975894973, 0, 0}, + {3041450802, 0, 0}, + {3151638847, 0, 0}, + {3187066832, 0, 0}, + {3244716568, 0, 0}, + {3271748023, 0, 0}, + {3304438238, 0, 0}, + {3312467582, 0, 0}, + {3325419312, 0, 0}, + {3370185097, 0, 0}, + {3419674548, 0, 0}, + {3435931956, 0, 0}, + {3504158761, 0, 0}, + {3602522282, 0, 0}, + {3653059026, 0, 0}, + {3716353056, 0, 0}, + {3782099915, 0, 0}, + {3838648480, 0, 0}, + {3847846774, 0, 0}, + {3913593633, 0, 0}, + {3989799199, 0, 0}, + {3997038726, 0, 0}, + {4046301857, 0, 0}, + {4092654294, 0, 0}, + {4176581069, 0, 0}, + {4242327928, 0, 0}, + {4285652249, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 74, 38}, + {0, 12, 56}, + {0, 28, 24}, + {0, 60, 43}, + {0, 65, 72}, + {0, 18, 2}, + {0, 52, 3}, + {0, 19, 10}, + {0, 49, 36}, + {0, 67, 66}, + {0, 41, 17}, + {0, 53, 11}, + {0, 29, 68}, + {0, 26, 55}, + {0, 70, 76}, + {0, 73, 47}, + {0, 51, 22}, + {0, 39, 21}, + {0, 5, 9}, + {0, 40, 48}, + {0, 59, 44}, + {0, 6, 69}, + {0, 32, 31}, + {0, 4, 33}, + {0, 13, 54}, + {0, 14, 50}, + {0, 35, 75}, + {0, 58, 23}, + {0, 16, 34}, + {0, 27, 63}, + {0, 45, 61}, + {0, 20, 46}, + {0, 71, 1}, + {0, 79, 78}, + {0, 81, 80}, + {0, 83, 82}, + {0, 84, 8}, + {0, 86, 85}, + {0, 88, 87}, + {0, 90, 89}, + {0, 92, 91}, + {0, 94, 93}, + {0, 96, 95}, + {0, 98, 97}, + {0, 64, 99}, + {0, 101, 100}, + {0, 103, 102}, + {0, 105, 104}, + {0, 106, 62}, + {0, 108, 107}, + {0, 110, 109}, + {0, 7, 111}, + {0, 113, 112}, + {0, 115, 114}, + {0, 117, 116}, + {0, 119, 118}, + {0, 121, 120}, + {0, 123, 122}, + {0, 30, 124}, + {0, 126, 125}, + {0, 128, 127}, + {0, 130, 129}, + {0, 132, 131}, + {0, 134, 133}, + {0, 135, 25}, + {0, 57, 136}, + {0, 138, 137}, + {0, 42, 139}, + {0, 37, 140}, + {0, 142, 141}, + {0, 143, 15}, + {0, 145, 144}, + {0, 147, 146}, + {0, 149, 148}, + {0, 151, 150}, + {0, 152, 77}, + })); + + codecs.emplace(std::pair(SpvOpCompositeExtract, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(47, { + {0, 0, 0}, + {545678922, 0, 0}, + {630592085, 0, 0}, + {679771963, 0, 0}, + {899570100, 0, 0}, + {906176560, 0, 0}, + {929101967, 0, 0}, + {1100599986, 0, 0}, + {1103903216, 0, 0}, + {1107206446, 0, 0}, + {1369578001, 0, 0}, + {1372881231, 0, 0}, + {2320303498, 0, 0}, + {2926633629, 0, 0}, + {3249265647, 0, 0}, + {3334207724, 0, 0}, + {3486057732, 0, 0}, + {3674863070, 0, 0}, + {3705139860, 0, 0}, + {3800912395, 0, 0}, + {3802564010, 0, 0}, + {3822983876, 0, 0}, + {4141567741, 0, 0}, + {4292991777, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 9, 17}, + {0, 20, 11}, + {0, 25, 5}, + {0, 2, 14}, + {0, 23, 13}, + {0, 16, 26}, + {0, 27, 24}, + {0, 28, 8}, + {0, 29, 18}, + {0, 22, 30}, + {0, 6, 31}, + {0, 21, 32}, + {0, 3, 33}, + {0, 35, 34}, + {0, 1, 12}, + {0, 10, 36}, + {0, 37, 19}, + {0, 4, 15}, + {0, 39, 38}, + {0, 7, 40}, + {0, 42, 41}, + {0, 44, 43}, + {0, 46, 45}, + })); + + codecs.emplace(std::pair(SpvOpCompositeInsert, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(103, { + {0, 0, 0}, + {125792961, 0, 0}, + {132755933, 0, 0}, + {156014509, 0, 0}, + {436066778, 0, 0}, + {463084678, 0, 0}, + {531559080, 0, 0}, + {565233904, 0, 0}, + {578132535, 0, 0}, + {600906020, 0, 0}, + {602222721, 0, 0}, + {694743357, 0, 0}, + {760554870, 0, 0}, + {996663016, 0, 0}, + {1022309772, 0, 0}, + {1351676723, 0, 0}, + {1496901698, 0, 0}, + {1502470404, 0, 0}, + {1522901980, 0, 0}, + {1548254487, 0, 0}, + {1637661947, 0, 0}, + {1788504755, 0, 0}, + {2092468906, 0, 0}, + {2094647776, 0, 0}, + {2127660080, 0, 0}, + {2213946343, 0, 0}, + {2225172640, 0, 0}, + {2259467579, 0, 0}, + {2263866576, 0, 0}, + {2600961503, 0, 0}, + {2727022058, 0, 0}, + {2752967311, 0, 0}, + {2864705739, 0, 0}, + {3021406120, 0, 0}, + {3044723416, 0, 0}, + {3052439312, 0, 0}, + {3136865519, 0, 0}, + {3297860332, 0, 0}, + {3352361837, 0, 0}, + {3670298840, 0, 0}, + {3712946115, 0, 0}, + {3732709413, 0, 0}, + {3764662384, 0, 0}, + {3788324110, 0, 0}, + {3928555688, 0, 0}, + {4083347580, 0, 0}, + {4098876453, 0, 0}, + {4147239510, 0, 0}, + {4199470013, 0, 0}, + {4211577142, 0, 0}, + {4218799564, 0, 0}, + {4290374884, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 4, 2}, + {0, 9, 8}, + {0, 17, 10}, + {0, 20, 18}, + {0, 22, 21}, + {0, 26, 23}, + {0, 31, 29}, + {0, 35, 34}, + {0, 45, 36}, + {0, 5, 3}, + {0, 12, 6}, + {0, 15, 14}, + {0, 25, 19}, + {0, 28, 27}, + {0, 38, 33}, + {0, 43, 39}, + {0, 47, 46}, + {0, 50, 49}, + {0, 7, 51}, + {0, 1, 48}, + {0, 37, 24}, + {0, 44, 42}, + {0, 13, 11}, + {0, 41, 40}, + {0, 54, 53}, + {0, 56, 55}, + {0, 58, 57}, + {0, 60, 59}, + {0, 62, 61}, + {0, 64, 63}, + {0, 66, 65}, + {0, 68, 67}, + {0, 70, 69}, + {0, 72, 71}, + {0, 30, 16}, + {0, 73, 32}, + {0, 75, 74}, + {0, 77, 76}, + {0, 79, 78}, + {0, 81, 80}, + {0, 83, 82}, + {0, 85, 84}, + {0, 87, 86}, + {0, 89, 88}, + {0, 91, 90}, + {0, 93, 92}, + {0, 95, 94}, + {0, 97, 96}, + {0, 99, 98}, + {0, 101, 100}, + {0, 52, 102}, + })); + + codecs.emplace(std::pair(SpvOpCompositeInsert, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(93, { + {0, 0, 0}, + {17185761, 0, 0}, + {117250846, 0, 0}, + {296981500, 0, 0}, + {330388453, 0, 0}, + {346929928, 0, 0}, + {533021259, 0, 0}, + {564302770, 0, 0}, + {680157484, 0, 0}, + {721450866, 0, 0}, + {798549062, 0, 0}, + {853200279, 0, 0}, + {864295921, 0, 0}, + {900522183, 0, 0}, + {973908139, 0, 0}, + {983243705, 0, 0}, + {1033363654, 0, 0}, + {1037370721, 0, 0}, + {1464587427, 0, 0}, + {1670691893, 0, 0}, + {1686512349, 0, 0}, + {1849065716, 0, 0}, + {1917602962, 0, 0}, + {1965902997, 0, 0}, + {2121980967, 0, 0}, + {2311072371, 0, 0}, + {2339901602, 0, 0}, + {2517964682, 0, 0}, + {2542834724, 0, 0}, + {2558655180, 0, 0}, + {2736881867, 0, 0}, + {2855506940, 0, 0}, + {2888753905, 0, 0}, + {2950446516, 0, 0}, + {3044188332, 0, 0}, + {3079287749, 0, 0}, + {3153451899, 0, 0}, + {3214537066, 0, 0}, + {3234673086, 0, 0}, + {3349230696, 0, 0}, + {3504158761, 0, 0}, + {3570411982, 0, 0}, + {3652695478, 0, 0}, + {3764205609, 0, 0}, + {3940720663, 0, 0}, + {4180570743, 0, 0}, + {4221373527, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 24, 18}, + {0, 4, 2}, + {0, 15, 14}, + {0, 21, 20}, + {0, 29, 26}, + {0, 42, 36}, + {0, 7, 45}, + {0, 37, 9}, + {0, 8, 5}, + {0, 32, 11}, + {0, 39, 38}, + {0, 12, 10}, + {0, 28, 19}, + {0, 1, 46}, + {0, 17, 6}, + {0, 30, 23}, + {0, 44, 33}, + {0, 35, 13}, + {0, 16, 48}, + {0, 50, 49}, + {0, 52, 51}, + {0, 54, 53}, + {0, 55, 40}, + {0, 57, 56}, + {0, 59, 58}, + {0, 61, 60}, + {0, 25, 22}, + {0, 63, 62}, + {0, 3, 64}, + {0, 66, 65}, + {0, 68, 67}, + {0, 70, 69}, + {0, 34, 71}, + {0, 73, 72}, + {0, 75, 74}, + {0, 77, 76}, + {0, 27, 43}, + {0, 79, 78}, + {0, 81, 80}, + {0, 83, 82}, + {0, 84, 31}, + {0, 86, 85}, + {0, 41, 87}, + {0, 89, 88}, + {0, 91, 90}, + {0, 47, 92}, + })); + + codecs.emplace(std::pair(SpvOpCompositeInsert, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(115, { + {0, 0, 0}, + {132755933, 0, 0}, + {156014509, 0, 0}, + {255227811, 0, 0}, + {371186900, 0, 0}, + {371428004, 0, 0}, + {374731234, 0, 0}, + {531559080, 0, 0}, + {565233904, 0, 0}, + {578132535, 0, 0}, + {591140762, 0, 0}, + {600906020, 0, 0}, + {602222721, 0, 0}, + {656610661, 0, 0}, + {760554870, 0, 0}, + {996663016, 0, 0}, + {1022309772, 0, 0}, + {1496901698, 0, 0}, + {1502470404, 0, 0}, + {1522901980, 0, 0}, + {1536350567, 0, 0}, + {1543280290, 0, 0}, + {1548254487, 0, 0}, + {1788504755, 0, 0}, + {2064733527, 0, 0}, + {2092468906, 0, 0}, + {2094647776, 0, 0}, + {2162986400, 0, 0}, + {2225172640, 0, 0}, + {2259467579, 0, 0}, + {2263866576, 0, 0}, + {2360004627, 0, 0}, + {2507709226, 0, 0}, + {2600961503, 0, 0}, + {2727022058, 0, 0}, + {2752967311, 0, 0}, + {2864705739, 0, 0}, + {3021406120, 0, 0}, + {3052439312, 0, 0}, + {3136865519, 0, 0}, + {3297860332, 0, 0}, + {3352361837, 0, 0}, + {3598957382, 0, 0}, + {3619787319, 0, 0}, + {3655201337, 0, 0}, + {3670298840, 0, 0}, + {3774892253, 0, 0}, + {3788324110, 0, 0}, + {3808408202, 0, 0}, + {3951925872, 0, 0}, + {3952316364, 0, 0}, + {4098876453, 0, 0}, + {4147239510, 0, 0}, + {4199470013, 0, 0}, + {4211577142, 0, 0}, + {4217306348, 0, 0}, + {4218799564, 0, 0}, + {4290374884, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 6, 43}, + {0, 4, 1}, + {0, 11, 9}, + {0, 13, 12}, + {0, 19, 18}, + {0, 25, 23}, + {0, 28, 26}, + {0, 35, 33}, + {0, 39, 38}, + {0, 2, 49}, + {0, 7, 3}, + {0, 16, 14}, + {0, 29, 22}, + {0, 37, 30}, + {0, 45, 41}, + {0, 51, 47}, + {0, 54, 52}, + {0, 57, 56}, + {0, 53, 8}, + {0, 32, 10}, + {0, 42, 40}, + {0, 24, 46}, + {0, 15, 50}, + {0, 55, 20}, + {0, 59, 44}, + {0, 61, 60}, + {0, 63, 62}, + {0, 65, 64}, + {0, 67, 66}, + {0, 69, 68}, + {0, 71, 70}, + {0, 73, 72}, + {0, 75, 74}, + {0, 77, 76}, + {0, 31, 17}, + {0, 36, 34}, + {0, 79, 78}, + {0, 81, 80}, + {0, 27, 82}, + {0, 5, 21}, + {0, 48, 83}, + {0, 85, 84}, + {0, 87, 86}, + {0, 89, 88}, + {0, 91, 90}, + {0, 93, 92}, + {0, 95, 94}, + {0, 97, 96}, + {0, 99, 98}, + {0, 101, 100}, + {0, 103, 102}, + {0, 105, 104}, + {0, 107, 106}, + {0, 109, 108}, + {0, 111, 110}, + {0, 113, 112}, + {0, 58, 114}, + })); + + codecs.emplace(std::pair(SpvOpCompositeInsert, 3), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(7, { + {0, 0, 0}, + {3866587616, 0, 0}, + {3868239231, 0, 0}, + {3869890846, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 4}, + {0, 2, 5}, + {0, 3, 6}, + })); + + codecs.emplace(std::pair(SpvOpSampledImage, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(9, { + {0, 0, 0}, + {1164218401, 0, 0}, + {2036361232, 0, 0}, + {2637132451, 0, 0}, + {3237903670, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 4, 5}, + {0, 3, 6}, + {0, 1, 7}, + {0, 2, 8}, + })); + + codecs.emplace(std::pair(SpvOpSampledImage, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(7, { + {0, 0, 0}, + {543558236, 0, 0}, + {1069781886, 0, 0}, + {1596005536, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 4}, + {0, 2, 5}, + {0, 1, 6}, + })); + + codecs.emplace(std::pair(SpvOpSampledImage, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {1949759310, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpSampledImage, 3), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpImageSampleImplicitLod, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(87, { + {0, 0, 0}, + {236660303, 0, 0}, + {347505241, 0, 0}, + {426360862, 0, 0}, + {439998433, 0, 0}, + {488500848, 0, 0}, + {495107308, 0, 0}, + {868652905, 0, 0}, + {1191735827, 0, 0}, + {1265998516, 0, 0}, + {1309728002, 0, 0}, + {1365842164, 0, 0}, + {1396344138, 0, 0}, + {1508074873, 0, 0}, + {1553476262, 0, 0}, + {1642818143, 0, 0}, + {1851510470, 0, 0}, + {1858116930, 0, 0}, + {1863199739, 0, 0}, + {1979978194, 0, 0}, + {1986584654, 0, 0}, + {2092100514, 0, 0}, + {2098706974, 0, 0}, + {2231688008, 0, 0}, + {2232491275, 0, 0}, + {2329992200, 0, 0}, + {2637935122, 0, 0}, + {2693892518, 0, 0}, + {2759250216, 0, 0}, + {2839765116, 0, 0}, + {2855895374, 0, 0}, + {2913136690, 0, 0}, + {3012980338, 0, 0}, + {3327770644, 0, 0}, + {3362344229, 0, 0}, + {3398925952, 0, 0}, + {3448018532, 0, 0}, + {3457985288, 0, 0}, + {3566035349, 0, 0}, + {3657635382, 0, 0}, + {3702405475, 0, 0}, + {3757479030, 0, 0}, + {3797204453, 0, 0}, + {4291477370, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 32, 28}, + {0, 9, 35}, + {0, 31, 11}, + {0, 10, 30}, + {0, 25, 21}, + {0, 40, 2}, + {0, 15, 19}, + {0, 24, 36}, + {0, 42, 4}, + {0, 18, 16}, + {0, 29, 26}, + {0, 43, 7}, + {0, 45, 8}, + {0, 37, 13}, + {0, 47, 46}, + {0, 48, 33}, + {0, 49, 14}, + {0, 3, 22}, + {0, 50, 12}, + {0, 41, 39}, + {0, 51, 34}, + {0, 52, 20}, + {0, 54, 53}, + {0, 56, 55}, + {0, 58, 57}, + {0, 60, 59}, + {0, 61, 23}, + {0, 63, 62}, + {0, 65, 64}, + {0, 27, 66}, + {0, 67, 38}, + {0, 68, 17}, + {0, 70, 69}, + {0, 72, 71}, + {0, 74, 73}, + {0, 76, 75}, + {0, 5, 77}, + {0, 78, 1}, + {0, 80, 79}, + {0, 82, 81}, + {0, 83, 6}, + {0, 85, 84}, + {0, 44, 86}, + })); + + codecs.emplace(std::pair(SpvOpImageSampleImplicitLod, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(15, { + {0, 0, 0}, + {883854656, 0, 0}, + {1962971231, 0, 0}, + {2036361232, 0, 0}, + {2356768706, 0, 0}, + {2637132451, 0, 0}, + {3237903670, 0, 0}, + {3829682756, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 8, 2}, + {0, 6, 9}, + {0, 10, 7}, + {0, 4, 5}, + {0, 12, 11}, + {0, 3, 13}, + {0, 14, 1}, + })); + + codecs.emplace(std::pair(SpvOpImageSampleImplicitLod, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(87, { + {0, 0, 0}, + {150685616, 0, 0}, + {255302575, 0, 0}, + {414620710, 0, 0}, + {557400685, 0, 0}, + {575205902, 0, 0}, + {618761615, 0, 0}, + {646282397, 0, 0}, + {686024761, 0, 0}, + {740921498, 0, 0}, + {921246433, 0, 0}, + {1057578789, 0, 0}, + {1162127370, 0, 0}, + {1329499601, 0, 0}, + {1352628475, 0, 0}, + {1502028603, 0, 0}, + {1519723107, 0, 0}, + {1543798545, 0, 0}, + {1545450160, 0, 0}, + {1570165302, 0, 0}, + {1600392975, 0, 0}, + {1641415225, 0, 0}, + {2204920111, 0, 0}, + {2257971049, 0, 0}, + {2276405827, 0, 0}, + {2339018837, 0, 0}, + {2340670452, 0, 0}, + {2517964682, 0, 0}, + {2532518896, 0, 0}, + {2674090849, 0, 0}, + {2754074729, 0, 0}, + {2804281092, 0, 0}, + {2816338013, 0, 0}, + {2841008029, 0, 0}, + {3234673086, 0, 0}, + {3249261197, 0, 0}, + {3619787319, 0, 0}, + {3627739127, 0, 0}, + {3669223677, 0, 0}, + {3787567939, 0, 0}, + {3898287302, 0, 0}, + {4142016703, 0, 0}, + {4237092412, 0, 0}, + {4285779501, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 16, 15}, + {0, 2, 33}, + {0, 41, 35}, + {0, 32, 30}, + {0, 39, 38}, + {0, 5, 1}, + {0, 9, 43}, + {0, 40, 22}, + {0, 29, 12}, + {0, 4, 3}, + {0, 25, 37}, + {0, 34, 26}, + {0, 45, 19}, + {0, 31, 24}, + {0, 47, 46}, + {0, 48, 20}, + {0, 49, 6}, + {0, 8, 21}, + {0, 50, 11}, + {0, 13, 10}, + {0, 51, 42}, + {0, 52, 23}, + {0, 54, 53}, + {0, 56, 55}, + {0, 58, 57}, + {0, 60, 59}, + {0, 61, 28}, + {0, 63, 62}, + {0, 65, 64}, + {0, 17, 66}, + {0, 67, 18}, + {0, 68, 7}, + {0, 70, 69}, + {0, 72, 71}, + {0, 74, 73}, + {0, 76, 75}, + {0, 14, 77}, + {0, 78, 27}, + {0, 80, 79}, + {0, 82, 81}, + {0, 83, 36}, + {0, 85, 84}, + {0, 44, 86}, + })); + + codecs.emplace(std::pair(SpvOpImageSampleImplicitLod, 3), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(7, { + {0, 0, 0}, + {2855506940, 0, 0}, + {3266548732, 0, 0}, + {3732640764, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 1}, + {0, 5, 4}, + {0, 3, 6}, + })); + + codecs.emplace(std::pair(SpvOpImageSampleImplicitLod, 5), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpImageSampleExplicitLod, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(139, { + {0, 0, 0}, + {27177503, 0, 0}, + {30663912, 0, 0}, + {151672195, 0, 0}, + {162608772, 0, 0}, + {180913835, 0, 0}, + {371621315, 0, 0}, + {414444763, 0, 0}, + {421602934, 0, 0}, + {443347828, 0, 0}, + {458937500, 0, 0}, + {587888644, 0, 0}, + {601656217, 0, 0}, + {665789406, 0, 0}, + {712168842, 0, 0}, + {730943059, 0, 0}, + {750870327, 0, 0}, + {875212982, 0, 0}, + {899320334, 0, 0}, + {973908139, 0, 0}, + {989813600, 0, 0}, + {1057606514, 0, 0}, + {1171541710, 0, 0}, + {1243764146, 0, 0}, + {1310404265, 0, 0}, + {1366337101, 0, 0}, + {1443547269, 0, 0}, + {1472185378, 0, 0}, + {1473799048, 0, 0}, + {1543935193, 0, 0}, + {1572834111, 0, 0}, + {1623013158, 0, 0}, + {1686512349, 0, 0}, + {1705716306, 0, 0}, + {1747355813, 0, 0}, + {1755165354, 0, 0}, + {1781864804, 0, 0}, + {1916983087, 0, 0}, + {1941403425, 0, 0}, + {2023008475, 0, 0}, + {2043684541, 0, 0}, + {2274226560, 0, 0}, + {2285438321, 0, 0}, + {2315690100, 0, 0}, + {2344328209, 0, 0}, + {2414725163, 0, 0}, + {2493146691, 0, 0}, + {2495155989, 0, 0}, + {2558655180, 0, 0}, + {2577859137, 0, 0}, + {2857814560, 0, 0}, + {2895151306, 0, 0}, + {2986830770, 0, 0}, + {3006548167, 0, 0}, + {3127329373, 0, 0}, + {3157581152, 0, 0}, + {3216471040, 0, 0}, + {3296722158, 0, 0}, + {3367298820, 0, 0}, + {3376009661, 0, 0}, + {3450001968, 0, 0}, + {3526837441, 0, 0}, + {3609540589, 0, 0}, + {3743398113, 0, 0}, + {3858973601, 0, 0}, + {3953984401, 0, 0}, + {3999472204, 0, 0}, + {4088613871, 0, 0}, + {4184019303, 0, 0}, + {4258229445, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 31, 16}, + {0, 58, 47}, + {0, 21, 61}, + {0, 6, 14}, + {0, 65, 23}, + {0, 35, 5}, + {0, 2, 7}, + {0, 10, 25}, + {0, 40, 22}, + {0, 9, 50}, + {0, 20, 11}, + {0, 38, 36}, + {0, 13, 12}, + {0, 67, 28}, + {0, 71, 68}, + {0, 73, 72}, + {0, 3, 29}, + {0, 27, 8}, + {0, 44, 37}, + {0, 74, 63}, + {0, 76, 75}, + {0, 18, 1}, + {0, 78, 77}, + {0, 80, 79}, + {0, 82, 81}, + {0, 26, 15}, + {0, 83, 43}, + {0, 85, 84}, + {0, 19, 86}, + {0, 48, 32}, + {0, 33, 46}, + {0, 87, 49}, + {0, 89, 88}, + {0, 91, 90}, + {0, 41, 30}, + {0, 52, 42}, + {0, 64, 55}, + {0, 92, 53}, + {0, 94, 93}, + {0, 51, 39}, + {0, 45, 95}, + {0, 66, 54}, + {0, 97, 96}, + {0, 57, 98}, + {0, 99, 69}, + {0, 101, 100}, + {0, 56, 102}, + {0, 4, 59}, + {0, 34, 17}, + {0, 103, 24}, + {0, 105, 104}, + {0, 107, 106}, + {0, 109, 108}, + {0, 60, 110}, + {0, 111, 62}, + {0, 113, 112}, + {0, 115, 114}, + {0, 117, 116}, + {0, 119, 118}, + {0, 121, 120}, + {0, 123, 122}, + {0, 125, 124}, + {0, 127, 126}, + {0, 129, 128}, + {0, 70, 130}, + {0, 132, 131}, + {0, 134, 133}, + {0, 136, 135}, + {0, 138, 137}, + })); + + codecs.emplace(std::pair(SpvOpImageSampleExplicitLod, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(11, { + {0, 0, 0}, + {883854656, 0, 0}, + {1962971231, 0, 0}, + {2036361232, 0, 0}, + {2366506734, 0, 0}, + {3829682756, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 4, 2}, + {0, 6, 7}, + {0, 8, 5}, + {0, 3, 9}, + {0, 1, 10}, + })); + + codecs.emplace(std::pair(SpvOpImageSampleExplicitLod, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(73, { + {0, 0, 0}, + {178571546, 0, 0}, + {223310468, 0, 0}, + {388034151, 0, 0}, + {449954059, 0, 0}, + {694743357, 0, 0}, + {797415788, 0, 0}, + {835638766, 0, 0}, + {1002144380, 0, 0}, + {1221183390, 0, 0}, + {1570165302, 0, 0}, + {1663234329, 0, 0}, + {1750829822, 0, 0}, + {1894133125, 0, 0}, + {1967643923, 0, 0}, + {1980341560, 0, 0}, + {2278706468, 0, 0}, + {2326990117, 0, 0}, + {2464905186, 0, 0}, + {2511346984, 0, 0}, + {2517964682, 0, 0}, + {2616085763, 0, 0}, + {2710583246, 0, 0}, + {2745872368, 0, 0}, + {2924263085, 0, 0}, + {3027500544, 0, 0}, + {3044723416, 0, 0}, + {3202324433, 0, 0}, + {3289213933, 0, 0}, + {3323682385, 0, 0}, + {3366848728, 0, 0}, + {3417583519, 0, 0}, + {3732916270, 0, 0}, + {3787909072, 0, 0}, + {3877813395, 0, 0}, + {4028028350, 0, 0}, + {4178218543, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 36, 31}, + {0, 15, 3}, + {0, 17, 1}, + {0, 24, 12}, + {0, 35, 34}, + {0, 28, 27}, + {0, 21, 38}, + {0, 6, 13}, + {0, 14, 7}, + {0, 39, 25}, + {0, 40, 30}, + {0, 42, 41}, + {0, 32, 43}, + {0, 23, 9}, + {0, 11, 44}, + {0, 45, 22}, + {0, 47, 46}, + {0, 2, 16}, + {0, 49, 48}, + {0, 4, 50}, + {0, 51, 18}, + {0, 53, 52}, + {0, 33, 54}, + {0, 26, 55}, + {0, 57, 56}, + {0, 5, 58}, + {0, 59, 8}, + {0, 19, 60}, + {0, 10, 61}, + {0, 29, 62}, + {0, 37, 63}, + {0, 65, 64}, + {0, 67, 66}, + {0, 20, 68}, + {0, 70, 69}, + {0, 72, 71}, + })); + + codecs.emplace(std::pair(SpvOpImageSampleExplicitLod, 3), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {2855506940, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpImageSampleExplicitLod, 5), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(13, { + {0, 0, 0}, + {3533637837, 0, 0}, + {3535289452, 0, 0}, + {3536941067, 0, 0}, + {3538592682, 0, 0}, + {3540244297, 0, 0}, + {3541895912, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 7}, + {0, 2, 8}, + {0, 9, 3}, + {0, 4, 10}, + {0, 5, 11}, + {0, 12, 6}, + })); + + codecs.emplace(std::pair(SpvOpImageSampleExplicitLod, 6), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(9, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 4, 5}, + {0, 2, 6}, + {0, 1, 3}, + {0, 8, 7}, + })); + + codecs.emplace(std::pair(SpvOpFAdd, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(45, { + {0, 0, 0}, + {328661377, 0, 0}, + {464259778, 0, 0}, + {920941800, 0, 0}, + {969500141, 0, 0}, + {1449907751, 0, 0}, + {1451831482, 0, 0}, + {1543798545, 0, 0}, + {1545450160, 0, 0}, + {1626224034, 0, 0}, + {1669930486, 0, 0}, + {1770165905, 0, 0}, + {2278571792, 0, 0}, + {2432827426, 0, 0}, + {2656211099, 0, 0}, + {2736844435, 0, 0}, + {2870852215, 0, 0}, + {2919626325, 0, 0}, + {2923708820, 0, 0}, + {3325419312, 0, 0}, + {3678875745, 0, 0}, + {4182141402, 0, 0}, + {4241374559, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 2, 6}, + {0, 9, 13}, + {0, 5, 15}, + {0, 4, 11}, + {0, 20, 22}, + {0, 10, 1}, + {0, 18, 14}, + {0, 16, 3}, + {0, 12, 21}, + {0, 8, 7}, + {0, 24, 17}, + {0, 19, 25}, + {0, 27, 26}, + {0, 29, 28}, + {0, 31, 30}, + {0, 33, 32}, + {0, 35, 34}, + {0, 37, 36}, + {0, 39, 38}, + {0, 41, 40}, + {0, 43, 42}, + {0, 23, 44}, + })); + + codecs.emplace(std::pair(SpvOpFAdd, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(89, { + {0, 0, 0}, + {135920445, 0, 0}, + {176166202, 0, 0}, + {294390719, 0, 0}, + {296981500, 0, 0}, + {743407979, 0, 0}, + {810488476, 0, 0}, + {837715723, 0, 0}, + {885020215, 0, 0}, + {922996215, 0, 0}, + {959681532, 0, 0}, + {963902061, 0, 0}, + {1136775085, 0, 0}, + {1189681639, 0, 0}, + {1203545131, 0, 0}, + {1297294717, 0, 0}, + {1317058015, 0, 0}, + {1352397672, 0, 0}, + {1367301635, 0, 0}, + {1412908157, 0, 0}, + {1570165302, 0, 0}, + {1763758554, 0, 0}, + {1791427568, 0, 0}, + {1992893964, 0, 0}, + {2013867381, 0, 0}, + {2096388952, 0, 0}, + {2219733501, 0, 0}, + {2383939514, 0, 0}, + {2517964682, 0, 0}, + {2555315060, 0, 0}, + {2572638469, 0, 0}, + {2762094724, 0, 0}, + {2770161927, 0, 0}, + {2855506940, 0, 0}, + {3044188332, 0, 0}, + {3187066832, 0, 0}, + {3319278167, 0, 0}, + {3653838348, 0, 0}, + {3675926744, 0, 0}, + {3701632935, 0, 0}, + {3712946115, 0, 0}, + {3732709413, 0, 0}, + {3743748793, 0, 0}, + {3783543823, 0, 0}, + {3930727258, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 15, 12}, + {0, 38, 16}, + {0, 41, 40}, + {0, 1, 33}, + {0, 21, 34}, + {0, 9, 2}, + {0, 24, 7}, + {0, 39, 44}, + {0, 29, 22}, + {0, 17, 19}, + {0, 36, 32}, + {0, 26, 18}, + {0, 30, 3}, + {0, 11, 8}, + {0, 42, 35}, + {0, 46, 31}, + {0, 27, 5}, + {0, 48, 47}, + {0, 28, 49}, + {0, 51, 50}, + {0, 52, 23}, + {0, 54, 53}, + {0, 13, 14}, + {0, 6, 55}, + {0, 57, 56}, + {0, 59, 58}, + {0, 60, 43}, + {0, 62, 61}, + {0, 37, 63}, + {0, 65, 64}, + {0, 67, 66}, + {0, 69, 68}, + {0, 70, 4}, + {0, 10, 71}, + {0, 72, 20}, + {0, 74, 73}, + {0, 76, 75}, + {0, 78, 77}, + {0, 80, 79}, + {0, 81, 25}, + {0, 83, 82}, + {0, 85, 84}, + {0, 87, 86}, + {0, 45, 88}, + })); + + codecs.emplace(std::pair(SpvOpFAdd, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(103, { + {0, 0, 0}, + {126463145, 0, 0}, + {220008971, 0, 0}, + {246375791, 0, 0}, + {503145996, 0, 0}, + {628331516, 0, 0}, + {643418617, 0, 0}, + {743407979, 0, 0}, + {837715723, 0, 0}, + {858902117, 0, 0}, + {870594305, 0, 0}, + {939671928, 0, 0}, + {959681532, 0, 0}, + {1051471757, 0, 0}, + {1092948665, 0, 0}, + {1097775533, 0, 0}, + {1136775085, 0, 0}, + {1140367371, 0, 0}, + {1332643570, 0, 0}, + {1367301635, 0, 0}, + {1558001705, 0, 0}, + {1684282922, 0, 0}, + {2096388952, 0, 0}, + {2183547611, 0, 0}, + {2219733501, 0, 0}, + {2358141757, 0, 0}, + {2359973133, 0, 0}, + {2383939514, 0, 0}, + {2444465148, 0, 0}, + {2517964682, 0, 0}, + {2567901801, 0, 0}, + {2598189097, 0, 0}, + {2655147757, 0, 0}, + {2683080096, 0, 0}, + {2705434194, 0, 0}, + {2738307068, 0, 0}, + {2780898906, 0, 0}, + {3030911670, 0, 0}, + {3032677281, 0, 0}, + {3063300848, 0, 0}, + {3277199633, 0, 0}, + {3289969989, 0, 0}, + {3401762422, 0, 0}, + {3436143898, 0, 0}, + {3560552546, 0, 0}, + {3656163446, 0, 0}, + {3675926744, 0, 0}, + {3701632935, 0, 0}, + {3743748793, 0, 0}, + {3752211294, 0, 0}, + {3794803132, 0, 0}, + {4241374559, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 21}, + {0, 17, 11}, + {0, 36, 35}, + {0, 46, 45}, + {0, 50, 49}, + {0, 9, 3}, + {0, 20, 47}, + {0, 37, 31}, + {0, 2, 34}, + {0, 40, 13}, + {0, 51, 32}, + {0, 41, 10}, + {0, 38, 19}, + {0, 18, 44}, + {0, 43, 16}, + {0, 48, 24}, + {0, 26, 5}, + {0, 53, 8}, + {0, 15, 7}, + {0, 25, 23}, + {0, 54, 27}, + {0, 56, 55}, + {0, 58, 57}, + {0, 60, 59}, + {0, 39, 42}, + {0, 62, 61}, + {0, 30, 63}, + {0, 4, 64}, + {0, 65, 28}, + {0, 66, 22}, + {0, 68, 67}, + {0, 69, 14}, + {0, 70, 33}, + {0, 71, 6}, + {0, 73, 72}, + {0, 75, 74}, + {0, 29, 76}, + {0, 78, 77}, + {0, 80, 79}, + {0, 82, 81}, + {0, 84, 83}, + {0, 86, 85}, + {0, 88, 87}, + {0, 90, 89}, + {0, 91, 12}, + {0, 93, 92}, + {0, 95, 94}, + {0, 97, 96}, + {0, 99, 98}, + {0, 101, 100}, + {0, 52, 102}, + })); + + codecs.emplace(std::pair(SpvOpFAdd, 3), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(9, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 5}, + {0, 4, 6}, + {0, 1, 7}, + {0, 2, 8}, + })); + + codecs.emplace(std::pair(SpvOpFSub, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(159, { + {0, 0, 0}, + {50385656, 0, 0}, + {117250846, 0, 0}, + {171494987, 0, 0}, + {195244192, 0, 0}, + {210754155, 0, 0}, + {265392489, 0, 0}, + {333855951, 0, 0}, + {416853049, 0, 0}, + {529068443, 0, 0}, + {533021259, 0, 0}, + {615982737, 0, 0}, + {660038281, 0, 0}, + {663341511, 0, 0}, + {669812542, 0, 0}, + {716890919, 0, 0}, + {1081536219, 0, 0}, + {1119744229, 0, 0}, + {1123617794, 0, 0}, + {1139547465, 0, 0}, + {1162789888, 0, 0}, + {1178317551, 0, 0}, + {1190147516, 0, 0}, + {1193734351, 0, 0}, + {1215030156, 0, 0}, + {1220749418, 0, 0}, + {1318479490, 0, 0}, + {1461398554, 0, 0}, + {1486207619, 0, 0}, + {1551372768, 0, 0}, + {1763758554, 0, 0}, + {1797960910, 0, 0}, + {1850331254, 0, 0}, + {1894417995, 0, 0}, + {1964254745, 0, 0}, + {1965902997, 0, 0}, + {1989327599, 0, 0}, + {2095027856, 0, 0}, + {2123683379, 0, 0}, + {2124837447, 0, 0}, + {2137526937, 0, 0}, + {2269114589, 0, 0}, + {2269130237, 0, 0}, + {2330636993, 0, 0}, + {2481746922, 0, 0}, + {2503770904, 0, 0}, + {2589449658, 0, 0}, + {2603020391, 0, 0}, + {2604576561, 0, 0}, + {2795773560, 0, 0}, + {2835131395, 0, 0}, + {2852854788, 0, 0}, + {2890638791, 0, 0}, + {2895413148, 0, 0}, + {2950446516, 0, 0}, + {2963744582, 0, 0}, + {3079287749, 0, 0}, + {3088785099, 0, 0}, + {3280064277, 0, 0}, + {3335250889, 0, 0}, + {3510242586, 0, 0}, + {3517169445, 0, 0}, + {3518703473, 0, 0}, + {3536471583, 0, 0}, + {3579593979, 0, 0}, + {3591222197, 0, 0}, + {3673811979, 0, 0}, + {3727034815, 0, 0}, + {3730093054, 0, 0}, + {3898287302, 0, 0}, + {3944781937, 0, 0}, + {3950980241, 0, 0}, + {4033586023, 0, 0}, + {4041974454, 0, 0}, + {4052965752, 0, 0}, + {4083161638, 0, 0}, + {4167600590, 0, 0}, + {4185661467, 0, 0}, + {4237092412, 0, 0}, + {4244540017, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 44, 18}, + {0, 69, 57}, + {0, 24, 16}, + {0, 79, 5}, + {0, 59, 4}, + {0, 76, 40}, + {0, 53, 45}, + {0, 14, 2}, + {0, 62, 61}, + {0, 33, 75}, + {0, 38, 37}, + {0, 42, 58}, + {0, 66, 47}, + {0, 63, 67}, + {0, 1, 7}, + {0, 10, 3}, + {0, 13, 12}, + {0, 23, 22}, + {0, 32, 28}, + {0, 36, 35}, + {0, 72, 49}, + {0, 74, 73}, + {0, 77, 55}, + {0, 27, 41}, + {0, 31, 15}, + {0, 6, 54}, + {0, 78, 17}, + {0, 81, 56}, + {0, 83, 82}, + {0, 85, 84}, + {0, 48, 30}, + {0, 71, 60}, + {0, 65, 51}, + {0, 87, 86}, + {0, 50, 34}, + {0, 89, 88}, + {0, 90, 9}, + {0, 25, 8}, + {0, 92, 91}, + {0, 93, 26}, + {0, 95, 94}, + {0, 52, 39}, + {0, 29, 20}, + {0, 97, 96}, + {0, 99, 98}, + {0, 101, 100}, + {0, 64, 102}, + {0, 104, 103}, + {0, 106, 105}, + {0, 21, 107}, + {0, 108, 68}, + {0, 109, 46}, + {0, 110, 11}, + {0, 112, 111}, + {0, 114, 113}, + {0, 116, 115}, + {0, 117, 70}, + {0, 43, 118}, + {0, 120, 119}, + {0, 122, 121}, + {0, 124, 123}, + {0, 126, 125}, + {0, 128, 127}, + {0, 129, 19}, + {0, 131, 130}, + {0, 133, 132}, + {0, 135, 134}, + {0, 137, 136}, + {0, 139, 138}, + {0, 141, 140}, + {0, 143, 142}, + {0, 145, 144}, + {0, 147, 146}, + {0, 149, 148}, + {0, 151, 150}, + {0, 153, 152}, + {0, 155, 154}, + {0, 157, 156}, + {0, 158, 80}, + })); + + codecs.emplace(std::pair(SpvOpFSub, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(103, { + {0, 0, 0}, + {50998433, 0, 0}, + {171494987, 0, 0}, + {249378857, 0, 0}, + {296981500, 0, 0}, + {508007510, 0, 0}, + {610429940, 0, 0}, + {660038281, 0, 0}, + {663341511, 0, 0}, + {836581417, 0, 0}, + {1027242654, 0, 0}, + {1167160774, 0, 0}, + {1191015885, 0, 0}, + {1200870684, 0, 0}, + {1203545131, 0, 0}, + {1265796414, 0, 0}, + {1319785741, 0, 0}, + {1669959736, 0, 0}, + {1684282922, 0, 0}, + {1752686878, 0, 0}, + {1850331254, 0, 0}, + {1901166356, 0, 0}, + {1906988301, 0, 0}, + {2055836767, 0, 0}, + {2095027856, 0, 0}, + {2096388952, 0, 0}, + {2144962711, 0, 0}, + {2217833278, 0, 0}, + {2500819054, 0, 0}, + {2525173102, 0, 0}, + {2575525651, 0, 0}, + {2660843182, 0, 0}, + {2855506940, 0, 0}, + {2918750759, 0, 0}, + {2919787747, 0, 0}, + {3091876332, 0, 0}, + {3187066832, 0, 0}, + {3244209297, 0, 0}, + {3423702268, 0, 0}, + {3508792859, 0, 0}, + {3548535223, 0, 0}, + {3619787319, 0, 0}, + {3653838348, 0, 0}, + {3692647551, 0, 0}, + {3713290482, 0, 0}, + {3753486980, 0, 0}, + {3783756895, 0, 0}, + {3797961332, 0, 0}, + {3836822275, 0, 0}, + {4043078107, 0, 0}, + {4052965752, 0, 0}, + {4091394002, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 31, 49}, + {0, 24, 19}, + {0, 46, 45}, + {0, 6, 48}, + {0, 12, 33}, + {0, 17, 21}, + {0, 43, 11}, + {0, 7, 2}, + {0, 9, 8}, + {0, 28, 13}, + {0, 44, 38}, + {0, 30, 50}, + {0, 26, 22}, + {0, 29, 51}, + {0, 34, 37}, + {0, 53, 40}, + {0, 23, 54}, + {0, 55, 25}, + {0, 27, 18}, + {0, 1, 10}, + {0, 57, 56}, + {0, 59, 58}, + {0, 5, 47}, + {0, 60, 20}, + {0, 62, 61}, + {0, 64, 63}, + {0, 66, 65}, + {0, 67, 39}, + {0, 69, 68}, + {0, 16, 70}, + {0, 3, 71}, + {0, 73, 72}, + {0, 41, 15}, + {0, 35, 74}, + {0, 76, 75}, + {0, 78, 77}, + {0, 36, 79}, + {0, 81, 80}, + {0, 83, 82}, + {0, 14, 84}, + {0, 86, 85}, + {0, 88, 87}, + {0, 32, 89}, + {0, 42, 90}, + {0, 92, 91}, + {0, 94, 93}, + {0, 96, 95}, + {0, 98, 97}, + {0, 52, 99}, + {0, 100, 4}, + {0, 102, 101}, + })); + + codecs.emplace(std::pair(SpvOpFSub, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(157, { + {0, 0, 0}, + {49456560, 0, 0}, + {170690025, 0, 0}, + {243178923, 0, 0}, + {295017943, 0, 0}, + {296981500, 0, 0}, + {330249537, 0, 0}, + {435256475, 0, 0}, + {443558693, 0, 0}, + {456043370, 0, 0}, + {470277359, 0, 0}, + {592180731, 0, 0}, + {663258455, 0, 0}, + {706238670, 0, 0}, + {810488476, 0, 0}, + {870594305, 0, 0}, + {877895868, 0, 0}, + {900522183, 0, 0}, + {1077859090, 0, 0}, + {1082941229, 0, 0}, + {1104362365, 0, 0}, + {1132589448, 0, 0}, + {1173092699, 0, 0}, + {1203545131, 0, 0}, + {1265796414, 0, 0}, + {1278818058, 0, 0}, + {1285705317, 0, 0}, + {1319785741, 0, 0}, + {1382106590, 0, 0}, + {1461897718, 0, 0}, + {1474506522, 0, 0}, + {1530183840, 0, 0}, + {1558001705, 0, 0}, + {1558990974, 0, 0}, + {1616846013, 0, 0}, + {1633850097, 0, 0}, + {1684282922, 0, 0}, + {1725011064, 0, 0}, + {1767704813, 0, 0}, + {1923453688, 0, 0}, + {1941148668, 0, 0}, + {1955104493, 0, 0}, + {2022961611, 0, 0}, + {2162274327, 0, 0}, + {2212501241, 0, 0}, + {2219733501, 0, 0}, + {2234361374, 0, 0}, + {2272221101, 0, 0}, + {2305269460, 0, 0}, + {2488410748, 0, 0}, + {2566666743, 0, 0}, + {2598189097, 0, 0}, + {2775815164, 0, 0}, + {2793529873, 0, 0}, + {2844616706, 0, 0}, + {2970183398, 0, 0}, + {3103302036, 0, 0}, + {3110479131, 0, 0}, + {3115038057, 0, 0}, + {3116932970, 0, 0}, + {3152745753, 0, 0}, + {3187066832, 0, 0}, + {3244209297, 0, 0}, + {3383007207, 0, 0}, + {3392887901, 0, 0}, + {3508792859, 0, 0}, + {3737376990, 0, 0}, + {3753486980, 0, 0}, + {3765247327, 0, 0}, + {3817149113, 0, 0}, + {3839047923, 0, 0}, + {3886529747, 0, 0}, + {4044928561, 0, 0}, + {4061558677, 0, 0}, + {4069720347, 0, 0}, + {4069810315, 0, 0}, + {4128942283, 0, 0}, + {4164704452, 0, 0}, + {4273793488, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 74, 47}, + {0, 34, 33}, + {0, 36, 14}, + {0, 61, 48}, + {0, 13, 31}, + {0, 39, 25}, + {0, 37, 29}, + {0, 65, 54}, + {0, 4, 73}, + {0, 38, 10}, + {0, 15, 43}, + {0, 6, 35}, + {0, 9, 16}, + {0, 30, 19}, + {0, 49, 44}, + {0, 57, 53}, + {0, 60, 58}, + {0, 72, 66}, + {0, 59, 76}, + {0, 1, 68}, + {0, 70, 42}, + {0, 63, 3}, + {0, 28, 69}, + {0, 17, 55}, + {0, 45, 64}, + {0, 81, 80}, + {0, 7, 82}, + {0, 12, 11}, + {0, 21, 50}, + {0, 83, 18}, + {0, 22, 84}, + {0, 85, 26}, + {0, 20, 86}, + {0, 87, 40}, + {0, 56, 88}, + {0, 90, 89}, + {0, 92, 91}, + {0, 93, 2}, + {0, 95, 94}, + {0, 97, 96}, + {0, 98, 41}, + {0, 100, 99}, + {0, 101, 52}, + {0, 103, 102}, + {0, 77, 71}, + {0, 104, 78}, + {0, 105, 46}, + {0, 32, 8}, + {0, 106, 51}, + {0, 108, 107}, + {0, 23, 109}, + {0, 110, 27}, + {0, 112, 111}, + {0, 113, 75}, + {0, 115, 114}, + {0, 117, 116}, + {0, 119, 118}, + {0, 121, 120}, + {0, 123, 122}, + {0, 124, 62}, + {0, 126, 125}, + {0, 128, 127}, + {0, 67, 129}, + {0, 131, 130}, + {0, 5, 132}, + {0, 134, 133}, + {0, 136, 135}, + {0, 138, 137}, + {0, 139, 24}, + {0, 141, 140}, + {0, 143, 142}, + {0, 145, 144}, + {0, 147, 146}, + {0, 149, 148}, + {0, 151, 150}, + {0, 153, 152}, + {0, 79, 154}, + {0, 156, 155}, + })); + + codecs.emplace(std::pair(SpvOpFSub, 3), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(9, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 4, 5}, + {0, 3, 6}, + {0, 1, 7}, + {0, 8, 2}, + })); + + codecs.emplace(std::pair(SpvOpFMul, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(41, { + {0, 0, 0}, + {342197850, 0, 0}, + {885020215, 0, 0}, + {963902061, 0, 0}, + {1041368449, 0, 0}, + {1352397672, 0, 0}, + {1791427568, 0, 0}, + {2013867381, 0, 0}, + {2513230733, 0, 0}, + {2555315060, 0, 0}, + {2562485583, 0, 0}, + {2567901801, 0, 0}, + {2655147757, 0, 0}, + {2680283743, 0, 0}, + {2752766693, 0, 0}, + {2806716850, 0, 0}, + {3030911670, 0, 0}, + {3401762422, 0, 0}, + {3697738938, 0, 0}, + {4164704452, 0, 0}, + {4273793488, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 14, 10}, + {0, 7, 16}, + {0, 1, 15}, + {0, 9, 6}, + {0, 4, 12}, + {0, 18, 5}, + {0, 13, 2}, + {0, 19, 3}, + {0, 17, 20}, + {0, 23, 22}, + {0, 24, 8}, + {0, 26, 25}, + {0, 27, 11}, + {0, 29, 28}, + {0, 31, 30}, + {0, 33, 32}, + {0, 35, 34}, + {0, 37, 36}, + {0, 39, 38}, + {0, 21, 40}, + })); + + codecs.emplace(std::pair(SpvOpFMul, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(129, { + {0, 0, 0}, + {126463145, 0, 0}, + {129135650, 0, 0}, + {200922300, 0, 0}, + {328661377, 0, 0}, + {354479447, 0, 0}, + {360730278, 0, 0}, + {451264926, 0, 0}, + {529068443, 0, 0}, + {593829839, 0, 0}, + {742917749, 0, 0}, + {761731755, 0, 0}, + {810488476, 0, 0}, + {870594305, 0, 0}, + {894529125, 0, 0}, + {959681532, 0, 0}, + {1054461787, 0, 0}, + {1077859090, 0, 0}, + {1086964761, 0, 0}, + {1158929937, 0, 0}, + {1168927492, 0, 0}, + {1196280518, 0, 0}, + {1203545131, 0, 0}, + {1367301635, 0, 0}, + {1508550646, 0, 0}, + {1618544981, 0, 0}, + {1661163736, 0, 0}, + {1684282922, 0, 0}, + {1766994680, 0, 0}, + {1830851200, 0, 0}, + {1901166356, 0, 0}, + {1955104493, 0, 0}, + {2055836767, 0, 0}, + {2096388952, 0, 0}, + {2100052708, 0, 0}, + {2161102232, 0, 0}, + {2197904616, 0, 0}, + {2262137600, 0, 0}, + {2278571792, 0, 0}, + {2281956980, 0, 0}, + {2438466459, 0, 0}, + {2443959748, 0, 0}, + {2517964682, 0, 0}, + {2557754096, 0, 0}, + {2622612602, 0, 0}, + {2660843182, 0, 0}, + {2736844435, 0, 0}, + {2780898906, 0, 0}, + {3044188332, 0, 0}, + {3059119137, 0, 0}, + {3194725903, 0, 0}, + {3270430997, 0, 0}, + {3337532056, 0, 0}, + {3407526215, 0, 0}, + {3496407048, 0, 0}, + {3504158761, 0, 0}, + {3534518722, 0, 0}, + {3570411982, 0, 0}, + {3701632935, 0, 0}, + {3929248764, 0, 0}, + {3944781937, 0, 0}, + {3970432934, 0, 0}, + {4008405264, 0, 0}, + {4245257809, 0, 0}, + {4253051659, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 6, 26}, + {0, 46, 24}, + {0, 64, 50}, + {0, 7, 17}, + {0, 40, 57}, + {0, 56, 49}, + {0, 34, 10}, + {0, 32, 61}, + {0, 36, 44}, + {0, 8, 43}, + {0, 4, 18}, + {0, 25, 23}, + {0, 9, 54}, + {0, 45, 41}, + {0, 13, 21}, + {0, 47, 31}, + {0, 39, 53}, + {0, 11, 3}, + {0, 29, 20}, + {0, 38, 58}, + {0, 37, 14}, + {0, 66, 52}, + {0, 67, 35}, + {0, 48, 68}, + {0, 1, 69}, + {0, 70, 28}, + {0, 27, 63}, + {0, 72, 71}, + {0, 74, 73}, + {0, 75, 60}, + {0, 77, 76}, + {0, 5, 51}, + {0, 15, 78}, + {0, 30, 79}, + {0, 55, 80}, + {0, 42, 81}, + {0, 83, 82}, + {0, 85, 84}, + {0, 86, 2}, + {0, 19, 16}, + {0, 87, 59}, + {0, 62, 88}, + {0, 90, 89}, + {0, 22, 91}, + {0, 93, 92}, + {0, 95, 94}, + {0, 97, 96}, + {0, 99, 98}, + {0, 101, 100}, + {0, 12, 102}, + {0, 104, 103}, + {0, 33, 105}, + {0, 107, 106}, + {0, 109, 108}, + {0, 111, 110}, + {0, 113, 112}, + {0, 115, 114}, + {0, 117, 116}, + {0, 119, 118}, + {0, 121, 120}, + {0, 123, 122}, + {0, 125, 124}, + {0, 127, 126}, + {0, 65, 128}, + })); + + codecs.emplace(std::pair(SpvOpFMul, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(127, { + {0, 0, 0}, + {13319433, 0, 0}, + {15502752, 0, 0}, + {162608772, 0, 0}, + {171307615, 0, 0}, + {296981500, 0, 0}, + {354479447, 0, 0}, + {413918748, 0, 0}, + {443490822, 0, 0}, + {487719832, 0, 0}, + {593829839, 0, 0}, + {615982737, 0, 0}, + {703543228, 0, 0}, + {810488476, 0, 0}, + {870594305, 0, 0}, + {875212982, 0, 0}, + {959681532, 0, 0}, + {1019457583, 0, 0}, + {1203545131, 0, 0}, + {1278448636, 0, 0}, + {1325348861, 0, 0}, + {1368383673, 0, 0}, + {1400019344, 0, 0}, + {1646147798, 0, 0}, + {1679946323, 0, 0}, + {1684282922, 0, 0}, + {1747355813, 0, 0}, + {1755648697, 0, 0}, + {1793544760, 0, 0}, + {1811839150, 0, 0}, + {1901166356, 0, 0}, + {1947620272, 0, 0}, + {1992893964, 0, 0}, + {2042001863, 0, 0}, + {2096388952, 0, 0}, + {2123388694, 0, 0}, + {2128251367, 0, 0}, + {2130747644, 0, 0}, + {2135340676, 0, 0}, + {2161102232, 0, 0}, + {2443959748, 0, 0}, + {2513230733, 0, 0}, + {2557754096, 0, 0}, + {2580096524, 0, 0}, + {2589449658, 0, 0}, + {2614879967, 0, 0}, + {2698156268, 0, 0}, + {2970183398, 0, 0}, + {3002890475, 0, 0}, + {3133016299, 0, 0}, + {3142155593, 0, 0}, + {3187066832, 0, 0}, + {3266548732, 0, 0}, + {3287039847, 0, 0}, + {3357301402, 0, 0}, + {3413713311, 0, 0}, + {3434076295, 0, 0}, + {3496407048, 0, 0}, + {3504158761, 0, 0}, + {3882634684, 0, 0}, + {3929248764, 0, 0}, + {3987079331, 0, 0}, + {4076840151, 0, 0}, + {4243119782, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 31, 8}, + {0, 14, 56}, + {0, 7, 12}, + {0, 9, 30}, + {0, 42, 36}, + {0, 19, 11}, + {0, 22, 40}, + {0, 15, 3}, + {0, 57, 26}, + {0, 58, 61}, + {0, 55, 51}, + {0, 48, 34}, + {0, 20, 1}, + {0, 24, 23}, + {0, 46, 35}, + {0, 59, 49}, + {0, 21, 63}, + {0, 62, 44}, + {0, 6, 50}, + {0, 28, 18}, + {0, 66, 65}, + {0, 41, 32}, + {0, 39, 54}, + {0, 53, 67}, + {0, 68, 37}, + {0, 33, 69}, + {0, 43, 70}, + {0, 71, 38}, + {0, 72, 27}, + {0, 13, 47}, + {0, 45, 73}, + {0, 75, 74}, + {0, 76, 5}, + {0, 77, 17}, + {0, 79, 78}, + {0, 52, 80}, + {0, 2, 81}, + {0, 83, 82}, + {0, 85, 84}, + {0, 87, 86}, + {0, 4, 88}, + {0, 16, 29}, + {0, 90, 89}, + {0, 92, 91}, + {0, 94, 93}, + {0, 60, 95}, + {0, 97, 96}, + {0, 98, 10}, + {0, 25, 99}, + {0, 101, 100}, + {0, 103, 102}, + {0, 105, 104}, + {0, 107, 106}, + {0, 109, 108}, + {0, 111, 110}, + {0, 113, 112}, + {0, 115, 114}, + {0, 117, 116}, + {0, 119, 118}, + {0, 121, 120}, + {0, 123, 122}, + {0, 125, 124}, + {0, 64, 126}, + })); + + codecs.emplace(std::pair(SpvOpFMul, 3), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(9, { + {0, 0, 0}, + {679771963, 0, 0}, + {1951208733, 0, 0}, + {2320303498, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 4, 5}, + {0, 3, 6}, + {0, 7, 1}, + {0, 2, 8}, + })); + + codecs.emplace(std::pair(SpvOpFDiv, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(153, { + {0, 0, 0}, + {10142671, 0, 0}, + {27865391, 0, 0}, + {29517006, 0, 0}, + {41739659, 0, 0}, + {97231530, 0, 0}, + {171334650, 0, 0}, + {200553094, 0, 0}, + {257136089, 0, 0}, + {294390719, 0, 0}, + {375530199, 0, 0}, + {380957745, 0, 0}, + {388034151, 0, 0}, + {455591063, 0, 0}, + {462664429, 0, 0}, + {491456522, 0, 0}, + {502863753, 0, 0}, + {626480004, 0, 0}, + {643418617, 0, 0}, + {651464351, 0, 0}, + {701281393, 0, 0}, + {744817486, 0, 0}, + {783918780, 0, 0}, + {862784766, 0, 0}, + {930804377, 0, 0}, + {952536201, 0, 0}, + {955476870, 0, 0}, + {1043738701, 0, 0}, + {1047011733, 0, 0}, + {1080545747, 0, 0}, + {1137442027, 0, 0}, + {1235468610, 0, 0}, + {1412908157, 0, 0}, + {1431749301, 0, 0}, + {1434223270, 0, 0}, + {1440646342, 0, 0}, + {1508570930, 0, 0}, + {1510422521, 0, 0}, + {1548121999, 0, 0}, + {1582841441, 0, 0}, + {1612225949, 0, 0}, + {1665981878, 0, 0}, + {1680746207, 0, 0}, + {1696076631, 0, 0}, + {1702168830, 0, 0}, + {1761469971, 0, 0}, + {1799299383, 0, 0}, + {1910240213, 0, 0}, + {1917451875, 0, 0}, + {1945006185, 0, 0}, + {1998444837, 0, 0}, + {2045285083, 0, 0}, + {2217966239, 0, 0}, + {2279273489, 0, 0}, + {2289803479, 0, 0}, + {2348676810, 0, 0}, + {2353194283, 0, 0}, + {2403632109, 0, 0}, + {2409539315, 0, 0}, + {2414984922, 0, 0}, + {2477389837, 0, 0}, + {2524531022, 0, 0}, + {2573160348, 0, 0}, + {2639720559, 0, 0}, + {2773229577, 0, 0}, + {2796513469, 0, 0}, + {2881225774, 0, 0}, + {2890570341, 0, 0}, + {2952850186, 0, 0}, + {3023287679, 0, 0}, + {3118548424, 0, 0}, + {3877813395, 0, 0}, + {3931288033, 0, 0}, + {3972309363, 0, 0}, + {4117704995, 0, 0}, + {4140081844, 0, 0}, + {4258414038, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 74, 53}, + {0, 58, 52}, + {0, 65, 60}, + {0, 41, 5}, + {0, 1, 67}, + {0, 24, 28}, + {0, 27, 26}, + {0, 55, 31}, + {0, 36, 61}, + {0, 13, 49}, + {0, 56, 48}, + {0, 16, 64}, + {0, 76, 42}, + {0, 45, 29}, + {0, 23, 6}, + {0, 72, 12}, + {0, 35, 19}, + {0, 20, 7}, + {0, 21, 46}, + {0, 71, 78}, + {0, 80, 79}, + {0, 47, 17}, + {0, 81, 70}, + {0, 34, 25}, + {0, 83, 82}, + {0, 85, 84}, + {0, 37, 86}, + {0, 87, 73}, + {0, 10, 4}, + {0, 40, 30}, + {0, 88, 57}, + {0, 54, 89}, + {0, 50, 90}, + {0, 11, 91}, + {0, 39, 15}, + {0, 59, 44}, + {0, 92, 66}, + {0, 69, 93}, + {0, 95, 94}, + {0, 14, 96}, + {0, 98, 97}, + {0, 62, 51}, + {0, 100, 99}, + {0, 102, 101}, + {0, 104, 103}, + {0, 32, 43}, + {0, 105, 38}, + {0, 107, 106}, + {0, 109, 108}, + {0, 22, 9}, + {0, 33, 110}, + {0, 2, 111}, + {0, 112, 3}, + {0, 114, 113}, + {0, 116, 115}, + {0, 68, 63}, + {0, 118, 117}, + {0, 120, 119}, + {0, 121, 8}, + {0, 123, 122}, + {0, 125, 124}, + {0, 127, 126}, + {0, 129, 128}, + {0, 131, 130}, + {0, 133, 132}, + {0, 75, 18}, + {0, 135, 134}, + {0, 137, 136}, + {0, 139, 138}, + {0, 141, 140}, + {0, 143, 142}, + {0, 145, 144}, + {0, 147, 146}, + {0, 149, 148}, + {0, 150, 77}, + {0, 152, 151}, + })); + + codecs.emplace(std::pair(SpvOpFDiv, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(131, { + {0, 0, 0}, + {5908395, 0, 0}, + {139011596, 0, 0}, + {296981500, 0, 0}, + {342615870, 0, 0}, + {370232173, 0, 0}, + {492958971, 0, 0}, + {528662843, 0, 0}, + {551924251, 0, 0}, + {604894932, 0, 0}, + {610429940, 0, 0}, + {780957373, 0, 0}, + {810488476, 0, 0}, + {872544165, 0, 0}, + {878733439, 0, 0}, + {918849409, 0, 0}, + {959681532, 0, 0}, + {1013756921, 0, 0}, + {1038982109, 0, 0}, + {1081611718, 0, 0}, + {1125913837, 0, 0}, + {1209418480, 0, 0}, + {1318081294, 0, 0}, + {1367301635, 0, 0}, + {1417425499, 0, 0}, + {1625742020, 0, 0}, + {1684282922, 0, 0}, + {1746004874, 0, 0}, + {1758287856, 0, 0}, + {1777640493, 0, 0}, + {2066323109, 0, 0}, + {2094550054, 0, 0}, + {2096388952, 0, 0}, + {2144962711, 0, 0}, + {2434845539, 0, 0}, + {2480811229, 0, 0}, + {2552825357, 0, 0}, + {2636946065, 0, 0}, + {2651956495, 0, 0}, + {2669086217, 0, 0}, + {2680819379, 0, 0}, + {2709694527, 0, 0}, + {2715304020, 0, 0}, + {2790648021, 0, 0}, + {2802261839, 0, 0}, + {2806296851, 0, 0}, + {2864543087, 0, 0}, + {2952260510, 0, 0}, + {2963184673, 0, 0}, + {3091876332, 0, 0}, + {3098991995, 0, 0}, + {3131890669, 0, 0}, + {3138977758, 0, 0}, + {3198541202, 0, 0}, + {3260579369, 0, 0}, + {3263841912, 0, 0}, + {3335250889, 0, 0}, + {3345856521, 0, 0}, + {3381478137, 0, 0}, + {3489269251, 0, 0}, + {3510242586, 0, 0}, + {3820814597, 0, 0}, + {3900859293, 0, 0}, + {4041974454, 0, 0}, + {4244540017, 0, 0}, + {4265894873, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 15, 52}, + {0, 20, 18}, + {0, 39, 29}, + {0, 9, 43}, + {0, 22, 13}, + {0, 46, 27}, + {0, 51, 48}, + {0, 19, 57}, + {0, 34, 24}, + {0, 64, 59}, + {0, 5, 7}, + {0, 38, 37}, + {0, 45, 47}, + {0, 2, 56}, + {0, 67, 8}, + {0, 17, 68}, + {0, 69, 61}, + {0, 70, 6}, + {0, 55, 54}, + {0, 72, 71}, + {0, 4, 73}, + {0, 74, 40}, + {0, 30, 11}, + {0, 42, 36}, + {0, 75, 58}, + {0, 31, 76}, + {0, 1, 77}, + {0, 44, 14}, + {0, 78, 50}, + {0, 79, 23}, + {0, 26, 80}, + {0, 81, 12}, + {0, 83, 82}, + {0, 84, 21}, + {0, 32, 85}, + {0, 87, 86}, + {0, 35, 10}, + {0, 88, 62}, + {0, 90, 89}, + {0, 41, 91}, + {0, 92, 53}, + {0, 93, 63}, + {0, 95, 94}, + {0, 33, 96}, + {0, 98, 97}, + {0, 99, 3}, + {0, 100, 28}, + {0, 101, 49}, + {0, 102, 60}, + {0, 104, 103}, + {0, 106, 105}, + {0, 108, 107}, + {0, 110, 109}, + {0, 65, 111}, + {0, 25, 112}, + {0, 114, 113}, + {0, 116, 115}, + {0, 117, 16}, + {0, 119, 118}, + {0, 121, 120}, + {0, 123, 122}, + {0, 125, 124}, + {0, 127, 126}, + {0, 128, 66}, + {0, 130, 129}, + })); + + codecs.emplace(std::pair(SpvOpFDiv, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(95, { + {0, 0, 0}, + {116093251, 0, 0}, + {149720480, 0, 0}, + {183103444, 0, 0}, + {251209228, 0, 0}, + {296981500, 0, 0}, + {357505993, 0, 0}, + {394654115, 0, 0}, + {410274915, 0, 0}, + {452208841, 0, 0}, + {788046331, 0, 0}, + {797934924, 0, 0}, + {810488476, 0, 0}, + {1144188012, 0, 0}, + {1220127364, 0, 0}, + {1321616112, 0, 0}, + {1324351672, 0, 0}, + {1348149915, 0, 0}, + {1459457331, 0, 0}, + {1465623797, 0, 0}, + {1531216990, 0, 0}, + {1543672828, 0, 0}, + {1578775276, 0, 0}, + {1738815671, 0, 0}, + {1904128160, 0, 0}, + {2071351379, 0, 0}, + {2119793999, 0, 0}, + {2274779301, 0, 0}, + {2291766425, 0, 0}, + {2357410109, 0, 0}, + {2438466459, 0, 0}, + {2496463830, 0, 0}, + {2630220147, 0, 0}, + {2682510803, 0, 0}, + {3047649911, 0, 0}, + {3085703811, 0, 0}, + {3235459678, 0, 0}, + {3261703164, 0, 0}, + {3331487616, 0, 0}, + {3462674048, 0, 0}, + {3570219049, 0, 0}, + {3585315836, 0, 0}, + {3602108619, 0, 0}, + {3724004880, 0, 0}, + {3931641900, 0, 0}, + {3955205564, 0, 0}, + {4073492988, 0, 0}, + {4127308103, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 24, 37}, + {0, 13, 38}, + {0, 17, 39}, + {0, 35, 23}, + {0, 18, 36}, + {0, 46, 19}, + {0, 20, 33}, + {0, 47, 6}, + {0, 1, 45}, + {0, 3, 27}, + {0, 8, 49}, + {0, 50, 29}, + {0, 10, 51}, + {0, 43, 31}, + {0, 53, 52}, + {0, 54, 26}, + {0, 7, 55}, + {0, 56, 32}, + {0, 57, 41}, + {0, 59, 58}, + {0, 61, 60}, + {0, 63, 62}, + {0, 64, 25}, + {0, 2, 34}, + {0, 65, 14}, + {0, 67, 66}, + {0, 12, 21}, + {0, 9, 68}, + {0, 69, 16}, + {0, 71, 70}, + {0, 72, 44}, + {0, 11, 73}, + {0, 74, 30}, + {0, 4, 75}, + {0, 28, 15}, + {0, 76, 42}, + {0, 5, 77}, + {0, 78, 40}, + {0, 80, 79}, + {0, 82, 81}, + {0, 22, 83}, + {0, 85, 84}, + {0, 86, 48}, + {0, 88, 87}, + {0, 90, 89}, + {0, 92, 91}, + {0, 94, 93}, + })); + + codecs.emplace(std::pair(SpvOpFDiv, 3), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(7, { + {0, 0, 0}, + {679771963, 0, 0}, + {2320303498, 0, 0}, + {3334207724, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 3, 4}, + {0, 2, 5}, + {0, 1, 6}, + })); + + codecs.emplace(std::pair(SpvOpVectorTimesScalar, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(121, { + {0, 0, 0}, + {14113753, 0, 0}, + {102358168, 0, 0}, + {179458548, 0, 0}, + {330388453, 0, 0}, + {386525753, 0, 0}, + {470277359, 0, 0}, + {497658126, 0, 0}, + {508007510, 0, 0}, + {815034111, 0, 0}, + {826214242, 0, 0}, + {849867303, 0, 0}, + {885645401, 0, 0}, + {939415664, 0, 0}, + {968885186, 0, 0}, + {1105835505, 0, 0}, + {1159301677, 0, 0}, + {1461897718, 0, 0}, + {1482251215, 0, 0}, + {1486206763, 0, 0}, + {1527762373, 0, 0}, + {1558990974, 0, 0}, + {1618754372, 0, 0}, + {1669959736, 0, 0}, + {1752686878, 0, 0}, + {2004567202, 0, 0}, + {2055637638, 0, 0}, + {2113506324, 0, 0}, + {2154320787, 0, 0}, + {2162274327, 0, 0}, + {2306141594, 0, 0}, + {2345566651, 0, 0}, + {2457690657, 0, 0}, + {2473053808, 0, 0}, + {2500422644, 0, 0}, + {2504802016, 0, 0}, + {2506771164, 0, 0}, + {2793529873, 0, 0}, + {2801333547, 0, 0}, + {2879050471, 0, 0}, + {3032677281, 0, 0}, + {3045470312, 0, 0}, + {3181546731, 0, 0}, + {3240977890, 0, 0}, + {3262572726, 0, 0}, + {3307100165, 0, 0}, + {3425841570, 0, 0}, + {3560552546, 0, 0}, + {3641833815, 0, 0}, + {3652695478, 0, 0}, + {3782362128, 0, 0}, + {3797961332, 0, 0}, + {3837583704, 0, 0}, + {3886529747, 0, 0}, + {3907920335, 0, 0}, + {4043078107, 0, 0}, + {4044928561, 0, 0}, + {4069720347, 0, 0}, + {4180570743, 0, 0}, + {4245743275, 0, 0}, + {4285201458, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 44, 28}, + {0, 13, 45}, + {0, 19, 15}, + {0, 32, 31}, + {0, 43, 42}, + {0, 16, 52}, + {0, 33, 22}, + {0, 57, 55}, + {0, 24, 21}, + {0, 2, 59}, + {0, 10, 3}, + {0, 18, 12}, + {0, 41, 39}, + {0, 60, 46}, + {0, 4, 25}, + {0, 58, 49}, + {0, 14, 1}, + {0, 27, 17}, + {0, 50, 36}, + {0, 23, 54}, + {0, 5, 30}, + {0, 11, 7}, + {0, 38, 29}, + {0, 37, 8}, + {0, 48, 56}, + {0, 20, 6}, + {0, 34, 26}, + {0, 63, 62}, + {0, 65, 64}, + {0, 67, 66}, + {0, 69, 68}, + {0, 71, 70}, + {0, 73, 72}, + {0, 75, 74}, + {0, 9, 76}, + {0, 78, 77}, + {0, 80, 79}, + {0, 82, 81}, + {0, 84, 83}, + {0, 40, 35}, + {0, 85, 47}, + {0, 86, 51}, + {0, 88, 87}, + {0, 90, 89}, + {0, 53, 91}, + {0, 93, 92}, + {0, 95, 94}, + {0, 97, 96}, + {0, 99, 98}, + {0, 101, 100}, + {0, 103, 102}, + {0, 105, 104}, + {0, 107, 106}, + {0, 109, 108}, + {0, 111, 110}, + {0, 113, 112}, + {0, 115, 114}, + {0, 117, 116}, + {0, 119, 118}, + {0, 61, 120}, + })); + + codecs.emplace(std::pair(SpvOpVectorTimesScalar, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(127, { + {0, 0, 0}, + {100979271, 0, 0}, + {269576093, 0, 0}, + {314809953, 0, 0}, + {354479447, 0, 0}, + {497658126, 0, 0}, + {882718761, 0, 0}, + {968885186, 0, 0}, + {973908139, 0, 0}, + {1019457583, 0, 0}, + {1191015885, 0, 0}, + {1266262705, 0, 0}, + {1310404265, 0, 0}, + {1325348861, 0, 0}, + {1367301635, 0, 0}, + {1368383673, 0, 0}, + {1570165302, 0, 0}, + {1618544981, 0, 0}, + {1646147798, 0, 0}, + {1674464100, 0, 0}, + {1679946323, 0, 0}, + {1686512349, 0, 0}, + {1766401548, 0, 0}, + {1774052499, 0, 0}, + {1788301425, 0, 0}, + {2023008475, 0, 0}, + {2055836767, 0, 0}, + {2096388952, 0, 0}, + {2123388694, 0, 0}, + {2129301998, 0, 0}, + {2212501241, 0, 0}, + {2274226560, 0, 0}, + {2362972044, 0, 0}, + {2378763734, 0, 0}, + {2506771164, 0, 0}, + {2558655180, 0, 0}, + {2622612602, 0, 0}, + {2660843182, 0, 0}, + {2698156268, 0, 0}, + {2801333547, 0, 0}, + {2850246066, 0, 0}, + {2895151306, 0, 0}, + {2970183398, 0, 0}, + {2986830770, 0, 0}, + {3001444829, 0, 0}, + {3133016299, 0, 0}, + {3152745753, 0, 0}, + {3187066832, 0, 0}, + {3261122899, 0, 0}, + {3496407048, 0, 0}, + {3513669836, 0, 0}, + {3536390697, 0, 0}, + {3570411982, 0, 0}, + {3653838348, 0, 0}, + {3713290482, 0, 0}, + {3858973601, 0, 0}, + {3873587660, 0, 0}, + {3877583949, 0, 0}, + {3882634684, 0, 0}, + {3907920335, 0, 0}, + {3997432565, 0, 0}, + {4169226615, 0, 0}, + {4219766939, 0, 0}, + {4243119782, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 25, 12}, + {0, 41, 29}, + {0, 56, 44}, + {0, 1, 3}, + {0, 48, 24}, + {0, 33, 60}, + {0, 8, 50}, + {0, 35, 21}, + {0, 11, 7}, + {0, 34, 23}, + {0, 59, 57}, + {0, 10, 62}, + {0, 40, 2}, + {0, 5, 49}, + {0, 39, 17}, + {0, 9, 61}, + {0, 30, 6}, + {0, 19, 46}, + {0, 53, 54}, + {0, 31, 52}, + {0, 55, 43}, + {0, 66, 65}, + {0, 16, 67}, + {0, 51, 68}, + {0, 70, 69}, + {0, 26, 36}, + {0, 72, 71}, + {0, 74, 73}, + {0, 76, 75}, + {0, 78, 77}, + {0, 80, 79}, + {0, 82, 81}, + {0, 37, 83}, + {0, 85, 84}, + {0, 13, 86}, + {0, 20, 18}, + {0, 38, 28}, + {0, 58, 45}, + {0, 87, 63}, + {0, 15, 88}, + {0, 32, 22}, + {0, 89, 4}, + {0, 90, 14}, + {0, 91, 42}, + {0, 93, 92}, + {0, 95, 94}, + {0, 97, 96}, + {0, 99, 98}, + {0, 101, 100}, + {0, 103, 102}, + {0, 105, 104}, + {0, 107, 106}, + {0, 109, 108}, + {0, 111, 110}, + {0, 113, 112}, + {0, 115, 114}, + {0, 27, 47}, + {0, 117, 116}, + {0, 119, 118}, + {0, 121, 120}, + {0, 123, 122}, + {0, 125, 124}, + {0, 126, 64}, + })); + + codecs.emplace(std::pair(SpvOpVectorTimesScalar, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(137, { + {0, 0, 0}, + {11698369, 0, 0}, + {146392076, 0, 0}, + {151810803, 0, 0}, + {223800276, 0, 0}, + {227103506, 0, 0}, + {253329281, 0, 0}, + {346929928, 0, 0}, + {461040879, 0, 0}, + {629859130, 0, 0}, + {680157484, 0, 0}, + {783918780, 0, 0}, + {810488476, 0, 0}, + {824323032, 0, 0}, + {870594305, 0, 0}, + {959681532, 0, 0}, + {975807626, 0, 0}, + {1081642571, 0, 0}, + {1084574846, 0, 0}, + {1094817798, 0, 0}, + {1141965917, 0, 0}, + {1164137269, 0, 0}, + {1166917451, 0, 0}, + {1204787336, 0, 0}, + {1232501371, 0, 0}, + {1318479490, 0, 0}, + {1369818198, 0, 0}, + {1372785527, 0, 0}, + {1526654696, 0, 0}, + {1543672828, 0, 0}, + {1548121999, 0, 0}, + {1635292159, 0, 0}, + {1641070431, 0, 0}, + {1684282922, 0, 0}, + {1767704813, 0, 0}, + {1781765116, 0, 0}, + {1838763297, 0, 0}, + {1901166356, 0, 0}, + {1904846533, 0, 0}, + {2011183308, 0, 0}, + {2032069771, 0, 0}, + {2071351379, 0, 0}, + {2087004702, 0, 0}, + {2244928358, 0, 0}, + {2314864456, 0, 0}, + {2374216296, 0, 0}, + {2394332122, 0, 0}, + {2443610186, 0, 0}, + {2524697596, 0, 0}, + {2526961521, 0, 0}, + {2568098594, 0, 0}, + {2807907995, 0, 0}, + {3103302036, 0, 0}, + {3117071189, 0, 0}, + {3188115516, 0, 0}, + {3417584874, 0, 0}, + {3554463148, 0, 0}, + {3561482820, 0, 0}, + {3691770462, 0, 0}, + {3729929345, 0, 0}, + {3733675151, 0, 0}, + {3831290364, 0, 0}, + {3866493821, 0, 0}, + {3929248764, 0, 0}, + {4060703604, 0, 0}, + {4092487128, 0, 0}, + {4167600590, 0, 0}, + {4214779116, 0, 0}, + {4248015868, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 36, 13}, + {0, 49, 60}, + {0, 51, 9}, + {0, 3, 62}, + {0, 67, 41}, + {0, 4, 31}, + {0, 66, 5}, + {0, 55, 32}, + {0, 2, 1}, + {0, 30, 16}, + {0, 7, 38}, + {0, 19, 10}, + {0, 34, 20}, + {0, 45, 46}, + {0, 22, 11}, + {0, 25, 23}, + {0, 40, 39}, + {0, 21, 57}, + {0, 6, 35}, + {0, 61, 8}, + {0, 52, 26}, + {0, 70, 59}, + {0, 71, 14}, + {0, 68, 47}, + {0, 73, 72}, + {0, 29, 74}, + {0, 76, 75}, + {0, 77, 17}, + {0, 79, 78}, + {0, 81, 80}, + {0, 82, 18}, + {0, 83, 42}, + {0, 85, 84}, + {0, 87, 86}, + {0, 27, 37}, + {0, 53, 43}, + {0, 89, 88}, + {0, 64, 54}, + {0, 90, 65}, + {0, 92, 91}, + {0, 58, 93}, + {0, 56, 48}, + {0, 94, 28}, + {0, 96, 95}, + {0, 98, 97}, + {0, 44, 99}, + {0, 101, 100}, + {0, 15, 12}, + {0, 103, 102}, + {0, 104, 33}, + {0, 106, 105}, + {0, 108, 107}, + {0, 24, 109}, + {0, 111, 110}, + {0, 113, 112}, + {0, 114, 50}, + {0, 116, 115}, + {0, 118, 117}, + {0, 120, 119}, + {0, 122, 121}, + {0, 124, 123}, + {0, 126, 125}, + {0, 128, 127}, + {0, 129, 63}, + {0, 131, 130}, + {0, 133, 132}, + {0, 135, 134}, + {0, 136, 69}, + })); + + codecs.emplace(std::pair(SpvOpVectorTimesScalar, 3), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {1951208733, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpDot, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(97, { + {0, 0, 0}, + {78001013, 0, 0}, + {170690025, 0, 0}, + {206688607, 0, 0}, + {443490822, 0, 0}, + {461476226, 0, 0}, + {537830163, 0, 0}, + {669982125, 0, 0}, + {790502615, 0, 0}, + {805072272, 0, 0}, + {1173092699, 0, 0}, + {1220643281, 0, 0}, + {1448448666, 0, 0}, + {1466804584, 0, 0}, + {1473411044, 0, 0}, + {1515695460, 0, 0}, + {1587730355, 0, 0}, + {1625742020, 0, 0}, + {2071351379, 0, 0}, + {2250055803, 0, 0}, + {2291766425, 0, 0}, + {2416108131, 0, 0}, + {2427834344, 0, 0}, + {2436009347, 0, 0}, + {2455417440, 0, 0}, + {2480811229, 0, 0}, + {2654325647, 0, 0}, + {2919796598, 0, 0}, + {3047649911, 0, 0}, + {3088511797, 0, 0}, + {3104643263, 0, 0}, + {3198541202, 0, 0}, + {3204986803, 0, 0}, + {3272233597, 0, 0}, + {3383007207, 0, 0}, + {3602108619, 0, 0}, + {3622349409, 0, 0}, + {3714664910, 0, 0}, + {3717942504, 0, 0}, + {3732000233, 0, 0}, + {3759072440, 0, 0}, + {3765247327, 0, 0}, + {3805423332, 0, 0}, + {3829325073, 0, 0}, + {3866493821, 0, 0}, + {4058280485, 0, 0}, + {4061558677, 0, 0}, + {4148979936, 0, 0}, + {4155586396, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 13, 38}, + {0, 39, 14}, + {0, 44, 9}, + {0, 48, 47}, + {0, 23, 15}, + {0, 33, 25}, + {0, 1, 42}, + {0, 5, 46}, + {0, 31, 3}, + {0, 36, 28}, + {0, 16, 12}, + {0, 32, 22}, + {0, 41, 21}, + {0, 6, 50}, + {0, 51, 29}, + {0, 45, 34}, + {0, 37, 8}, + {0, 19, 52}, + {0, 11, 4}, + {0, 43, 40}, + {0, 27, 53}, + {0, 54, 10}, + {0, 24, 55}, + {0, 57, 56}, + {0, 58, 26}, + {0, 2, 59}, + {0, 61, 60}, + {0, 63, 62}, + {0, 65, 64}, + {0, 20, 66}, + {0, 30, 35}, + {0, 67, 17}, + {0, 68, 7}, + {0, 70, 69}, + {0, 71, 18}, + {0, 73, 72}, + {0, 75, 74}, + {0, 77, 76}, + {0, 79, 78}, + {0, 81, 80}, + {0, 83, 82}, + {0, 85, 84}, + {0, 87, 86}, + {0, 89, 88}, + {0, 91, 90}, + {0, 93, 92}, + {0, 95, 94}, + {0, 49, 96}, + })); + + codecs.emplace(std::pair(SpvOpDot, 1), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(117, { + {0, 0, 0}, + {50385656, 0, 0}, + {181902171, 0, 0}, + {560078433, 0, 0}, + {615982737, 0, 0}, + {674428451, 0, 0}, + {837715723, 0, 0}, + {886972033, 0, 0}, + {900101778, 0, 0}, + {983299427, 0, 0}, + {1237148906, 0, 0}, + {1364157225, 0, 0}, + {1367301635, 0, 0}, + {1380160211, 0, 0}, + {1451831482, 0, 0}, + {1499923635, 0, 0}, + {1570165302, 0, 0}, + {1735295265, 0, 0}, + {1766401548, 0, 0}, + {1796311149, 0, 0}, + {1826456251, 0, 0}, + {1839669171, 0, 0}, + {2012838864, 0, 0}, + {2024071551, 0, 0}, + {2096388952, 0, 0}, + {2161102232, 0, 0}, + {2197874825, 0, 0}, + {2279700640, 0, 0}, + {2289183712, 0, 0}, + {2351620600, 0, 0}, + {2362972044, 0, 0}, + {2472176885, 0, 0}, + {2477434291, 0, 0}, + {2530899578, 0, 0}, + {2531826164, 0, 0}, + {2558133383, 0, 0}, + {2589449658, 0, 0}, + {2621255555, 0, 0}, + {2622612602, 0, 0}, + {2872580757, 0, 0}, + {2881302403, 0, 0}, + {2891091137, 0, 0}, + {2923708820, 0, 0}, + {2936040203, 0, 0}, + {2970183398, 0, 0}, + {3187066832, 0, 0}, + {3224952074, 0, 0}, + {3244383472, 0, 0}, + {3261122899, 0, 0}, + {3362830643, 0, 0}, + {3538158875, 0, 0}, + {3635542517, 0, 0}, + {3682213068, 0, 0}, + {3721902098, 0, 0}, + {3826846522, 0, 0}, + {3877583949, 0, 0}, + {3997432565, 0, 0}, + {4093615095, 0, 0}, + {4106828015, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 52, 28}, + {0, 33, 20}, + {0, 46, 57}, + {0, 47, 54}, + {0, 21, 17}, + {0, 31, 58}, + {0, 12, 53}, + {0, 29, 3}, + {0, 35, 34}, + {0, 48, 41}, + {0, 8, 5}, + {0, 7, 55}, + {0, 37, 32}, + {0, 60, 38}, + {0, 61, 16}, + {0, 14, 62}, + {0, 23, 63}, + {0, 13, 19}, + {0, 64, 9}, + {0, 65, 39}, + {0, 2, 66}, + {0, 67, 42}, + {0, 69, 68}, + {0, 25, 70}, + {0, 1, 49}, + {0, 6, 71}, + {0, 72, 15}, + {0, 73, 11}, + {0, 75, 74}, + {0, 77, 76}, + {0, 4, 78}, + {0, 56, 50}, + {0, 80, 79}, + {0, 10, 81}, + {0, 83, 82}, + {0, 85, 84}, + {0, 86, 27}, + {0, 43, 40}, + {0, 88, 87}, + {0, 44, 24}, + {0, 30, 89}, + {0, 51, 36}, + {0, 45, 90}, + {0, 18, 91}, + {0, 93, 92}, + {0, 22, 94}, + {0, 26, 95}, + {0, 97, 96}, + {0, 99, 98}, + {0, 101, 100}, + {0, 103, 102}, + {0, 105, 104}, + {0, 107, 106}, + {0, 109, 108}, + {0, 111, 110}, + {0, 113, 112}, + {0, 59, 114}, + {0, 116, 115}, + })); + + codecs.emplace(std::pair(SpvOpDot, 2), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(179, { + {0, 0, 0}, + {27177503, 0, 0}, + {50385656, 0, 0}, + {129748122, 0, 0}, + {139011596, 0, 0}, + {162608772, 0, 0}, + {181902171, 0, 0}, + {225200779, 0, 0}, + {342159236, 0, 0}, + {386293029, 0, 0}, + {429023543, 0, 0}, + {443558693, 0, 0}, + {504514034, 0, 0}, + {615982737, 0, 0}, + {669812542, 0, 0}, + {674428451, 0, 0}, + {837715723, 0, 0}, + {861753115, 0, 0}, + {875212982, 0, 0}, + {876867882, 0, 0}, + {899320334, 0, 0}, + {900101778, 0, 0}, + {938517572, 0, 0}, + {1347339159, 0, 0}, + {1356063462, 0, 0}, + {1373856501, 0, 0}, + {1376656865, 0, 0}, + {1451831482, 0, 0}, + {1522979646, 0, 0}, + {1548491889, 0, 0}, + {1570165302, 0, 0}, + {1735295265, 0, 0}, + {1747355813, 0, 0}, + {1766401548, 0, 0}, + {1871105284, 0, 0}, + {1918742169, 0, 0}, + {1922045399, 0, 0}, + {1978689945, 0, 0}, + {2024071551, 0, 0}, + {2059975069, 0, 0}, + {2076833303, 0, 0}, + {2096388952, 0, 0}, + {2181030375, 0, 0}, + {2197874825, 0, 0}, + {2362972044, 0, 0}, + {2414725163, 0, 0}, + {2517964682, 0, 0}, + {2564745684, 0, 0}, + {2577387676, 0, 0}, + {2589449658, 0, 0}, + {2604242419, 0, 0}, + {2683080096, 0, 0}, + {2696349144, 0, 0}, + {2763960513, 0, 0}, + {2817823941, 0, 0}, + {2852854788, 0, 0}, + {2891091137, 0, 0}, + {2919626325, 0, 0}, + {2923708820, 0, 0}, + {2936040203, 0, 0}, + {2963744582, 0, 0}, + {2970183398, 0, 0}, + {2984459037, 0, 0}, + {2996594997, 0, 0}, + {3015046341, 0, 0}, + {3055195668, 0, 0}, + {3127329373, 0, 0}, + {3187066832, 0, 0}, + {3193597927, 0, 0}, + {3200890815, 0, 0}, + {3224258475, 0, 0}, + {3224480461, 0, 0}, + {3261122899, 0, 0}, + {3609540589, 0, 0}, + {3619404941, 0, 0}, + {3619626927, 0, 0}, + {3727034815, 0, 0}, + {3742724777, 0, 0}, + {3742946763, 0, 0}, + {3836179806, 0, 0}, + {3913885196, 0, 0}, + {3927338499, 0, 0}, + {3927466635, 0, 0}, + {3997432565, 0, 0}, + {3999472204, 0, 0}, + {4010499223, 0, 0}, + {4032662899, 0, 0}, + {4110915453, 0, 0}, + {4145966869, 0, 0}, + {4228303141, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 23, 87}, + {0, 9, 28}, + {0, 42, 17}, + {0, 74, 70}, + {0, 86, 77}, + {0, 18, 5}, + {0, 31, 32}, + {0, 34, 3}, + {0, 38, 68}, + {0, 50, 29}, + {0, 72, 62}, + {0, 21, 15}, + {0, 14, 54}, + {0, 56, 22}, + {0, 48, 88}, + {0, 2, 76}, + {0, 6, 47}, + {0, 26, 79}, + {0, 65, 12}, + {0, 37, 81}, + {0, 91, 60}, + {0, 30, 92}, + {0, 25, 7}, + {0, 45, 40}, + {0, 66, 52}, + {0, 71, 69}, + {0, 78, 75}, + {0, 84, 82}, + {0, 94, 93}, + {0, 27, 95}, + {0, 97, 96}, + {0, 99, 98}, + {0, 100, 39}, + {0, 55, 101}, + {0, 58, 102}, + {0, 89, 103}, + {0, 35, 11}, + {0, 104, 36}, + {0, 53, 10}, + {0, 1, 64}, + {0, 73, 20}, + {0, 105, 13}, + {0, 107, 106}, + {0, 8, 16}, + {0, 24, 19}, + {0, 85, 63}, + {0, 109, 108}, + {0, 111, 110}, + {0, 4, 112}, + {0, 114, 113}, + {0, 116, 115}, + {0, 118, 117}, + {0, 83, 119}, + {0, 121, 120}, + {0, 123, 122}, + {0, 49, 44}, + {0, 124, 57}, + {0, 125, 59}, + {0, 126, 67}, + {0, 128, 127}, + {0, 130, 129}, + {0, 132, 131}, + {0, 134, 133}, + {0, 135, 51}, + {0, 137, 136}, + {0, 138, 61}, + {0, 43, 41}, + {0, 140, 139}, + {0, 142, 141}, + {0, 144, 143}, + {0, 146, 145}, + {0, 148, 147}, + {0, 149, 33}, + {0, 80, 150}, + {0, 152, 151}, + {0, 154, 153}, + {0, 156, 155}, + {0, 158, 157}, + {0, 160, 159}, + {0, 162, 161}, + {0, 164, 163}, + {0, 166, 165}, + {0, 168, 167}, + {0, 46, 169}, + {0, 171, 170}, + {0, 90, 172}, + {0, 174, 173}, + {0, 176, 175}, + {0, 178, 177}, + })); + + codecs.emplace(std::pair(SpvOpDot, 3), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {1036475267, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpLabel, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(3, { + {0, 0, 0}, + {1036475267, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 1, 2}, + })); + + codecs.emplace(std::pair(SpvOpBranch, 0), std::move(codec)); + } + + { + std::unique_ptr> codec(new HuffmanCodec(119, { + {0, 0, 0}, + {57149555, 0, 0}, + {139011596, 0, 0}, + {255835594, 0, 0}, + {330249537, 0, 0}, + {388686774, 0, 0}, + {508217552, 0, 0}, + {550831114, 0, 0}, + {559246409, 0, 0}, + {599185303, 0, 0}, + {649208064, 0, 0}, + {679061455, 0, 0}, + {810488476, 0, 0}, + {951841533, 0, 0}, + {1008886329, 0, 0}, + {1022544883, 0, 0}, + {1215030156, 0, 0}, + {1305703280, 0, 0}, + {1367301635, 0, 0}, + {1453447304, 0, 0}, + {1487177499, 0, 0}, + {1603937321, 0, 0}, + {1617826947, 0, 0}, + {1643868273, 0, 0}, + {1672607981, 0, 0}, + {1681941034, 0, 0}, + {1755165354, 0, 0}, + {1781864804, 0, 0}, + {1795715718, 0, 0}, + {1977038330, 0, 0}, + {2096388952, 0, 0}, + {2204920111, 0, 0}, + {2244470522, 0, 0}, + {2330636993, 0, 0}, + {2400601988, 0, 0}, + {2424848261, 0, 0}, + {2603020391, 0, 0}, + {2622612602, 0, 0}, + {2645135839, 0, 0}, + {2660843182, 0, 0}, + {2708915136, 0, 0}, + {2724166585, 0, 0}, + {2728667725, 0, 0}, + {2890638791, 0, 0}, + {2901034693, 0, 0}, + {2941648648, 0, 0}, + {2970183398, 0, 0}, + {2998120306, 0, 0}, + {3123244280, 0, 0}, + {3187066832, 0, 0}, + {3209399506, 0, 0}, + {3230260738, 0, 0}, + {3344189994, 0, 0}, + {3345707173, 0, 0}, + {3367298820, 0, 0}, + {3397078357, 0, 0}, + {3569736966, 0, 0}, + {3816961131, 0, 0}, + {4091670162, 0, 0}, + {4237497041, 0, 0}, + {1111111111111111111, 0, 0}, + {0, 17, 44}, + {0, 25, 20}, + {0, 29, 34}, + {0, 18, 2}, + {0, 54, 49}, + {0, 28, 7}, + {0, 47, 52}, + {0, 23, 56}, + {0, 55, 26}, + {0, 24, 61}, + {0, 13, 62}, + {0, 63, 45}, + {0, 27, 15}, + {0, 64, 8}, + {0, 65, 59}, + {0, 35, 22}, + {0, 53, 38}, + {0, 58, 51}, + {0, 11, 66}, + {0, 10, 3}, + {0, 46, 67}, + {0, 69, 68}, + {0, 1, 50}, + {0, 42, 19}, + {0, 70, 6}, + {0, 31, 71}, + {0, 16, 72}, + {0, 74, 73}, + {0, 76, 75}, + {0, 78, 77}, + {0, 79, 4}, + {0, 5, 37}, + {0, 14, 36}, + {0, 80, 57}, + {0, 81, 48}, + {0, 83, 82}, + {0, 39, 84}, + {0, 86, 85}, + {0, 40, 87}, + {0, 89, 88}, + {0, 91, 90}, + {0, 93, 92}, + {0, 21, 9}, + {0, 41, 32}, + {0, 12, 43}, + {0, 95, 94}, + {0, 97, 96}, + {0, 99, 98}, + {0, 100, 33}, + {0, 60, 101}, + {0, 103, 102}, + {0, 105, 104}, + {0, 107, 106}, + {0, 109, 108}, + {0, 110, 30}, + {0, 112, 111}, + {0, 114, 113}, + {0, 116, 115}, + {0, 118, 117}, + })); + + codecs.emplace(std::pair(SpvOpReturnValue, 0), std::move(codec)); + } + + return codecs; +} + +std::unordered_set GetDescriptorsWithCodingScheme() { + std::unordered_set descriptors_with_coding_scheme = { + 3816961131, + 3569736966, + 3397078357, + 3344189994, + 3230260738, + 2941648648, + 2901034693, + 2728667725, + 2400601988, + 1795715718, + 1681941034, + 1487177499, + 1453447304, + 679061455, + 649208064, + 559246409, + 388686774, + 4228303141, + 4110915453, + 4010499223, + 3927466635, + 3927338499, + 3836179806, + 3742724777, + 3619404941, + 3224480461, + 3224258475, + 3200890815, + 3742946763, + 3193597927, + 2604242419, + 2577387676, + 2181030375, + 1376656865, + 1347339159, + 938517572, + 876867882, + 429023543, + 129748122, + 4106828015, + 4093615095, + 3826846522, + 3721902098, + 3244383472, + 2891091137, + 2872580757, + 2558133383, + 2477434291, + 1839669171, + 2059975069, + 1735295265, + 1364157225, + 1237148906, + 886972033, + 674428451, + 4148979936, + 3805423332, + 3732000233, + 3717942504, + 3714664910, + 3622349409, + 3272233597, + 3204986803, + 3088511797, + 1672607981, + 2416108131, + 2250055803, + 1796311149, + 1515695460, + 537830163, + 461476226, + 206688607, + 78001013, + 3866493821, + 3417584874, + 3188115516, + 2526961521, + 2443610186, + 2394332122, + 2374216296, + 2032069771, + 2011183308, + 1904846533, + 1641070431, + 1635292159, + 1372785527, + 1369818198, + 1204787336, + 1826456251, + 1164137269, + 1081642571, + 629859130, + 253329281, + 227103506, + 11698369, + 4219766939, + 4169226615, + 3997432565, + 3873587660, + 3513669836, + 3261122899, + 2129301998, + 1774052499, + 1266262705, + 4285201458, + 4245743275, + 3907920335, + 3837583704, + 3641833815, + 3307100165, + 1232501371, + 3262572726, + 3045470312, + 2879050471, + 2801333547, + 2506771164, + 2504802016, + 2500422644, + 2473053808, + 2457690657, + 2345566651, + 2306141594, + 2154320787, + 2055637638, + 1527762373, + 1486206763, + 1159301677, + 1105835505, + 968885186, + 885645401, + 849867303, + 815034111, + 497658126, + 386525753, + 179458548, + 102358168, + 4127308103, + 4073492988, + 1473411044, + 805072272, + 3724004880, + 3602108619, + 3585315836, + 3331487616, + 3261703164, + 3235459678, + 3085703811, + 3047649911, + 2357410109, + 2291766425, + 2071351379, + 1904128160, + 1738815671, + 1531216990, + 1465623797, + 1324351672, + 1220127364, + 1144188012, + 183103444, + 116093251, + 3900859293, + 3345856521, + 3691770462, + 3263841912, + 3198541202, + 3098991995, + 3682213068, + 2963184673, + 2864543087, + 2802261839, + 2790648021, + 900101778, + 2715304020, + 100979271, + 2709694527, + 2669086217, + 2531826164, + 2651956495, + 2552825357, + 2480811229, + 3138977758, + 2434845539, + 2066323109, + 1777640493, + 1758287856, + 1746004874, + 3945482286, + 3932146199, + 3129573055, + 3126269825, + 3716914380, + 985750227, + 1543672828, + 3189039115, + 1839499483, + 2696349144, + 1536350567, + 3971481069, + 3001444829, + 4028622909, + 215293834, + 213642219, + 153085016, + 1189681639, + 165054168, + 29517006, + 2614879967, + 27865391, + 1649426421, + 4239834800, + 1947620272, + 28782128, + 3207966516, + 3713290482, + 2042001863, + 2724166585, + 2356768706, + 1793544760, + 4092654294, + 2157103435, + 2087004702, + 2043873558, + 27177503, + 1033363654, + 4214779116, + 408465899, + 451264926, + 2377112119, + 1182296898, + 760554870, + 3566035349, + 2630220147, + 4192247221, + 1572088444, + 3538592682, + 769422756, + 1674803691, + 630964591, + 3458449569, + 565334834, + 137840602, + 3955205564, + 2009007457, + 1258105452, + 333554713, + 3923810593, + 126463145, + 3445109809, + 2966409025, + 2849215484, + 1910240213, + 3131890669, + 586244865, + 2320303498, + 3116932970, + 1317265040, + 2812498065, + 1466938734, + 4064212479, + 2613179511, + 2095546797, + 1671139745, + 2568512089, + 3695940604, + 1119069977, + 215027449, + 4123141705, + 3495546641, + 1978689945, + 3202324433, + 3783543823, + 2674422363, + 1352628475, + 1290956281, + 1894417995, + 740921498, + 4211577142, + 1033081852, + 3884846406, + 3253403867, + 2790624748, + 2538917932, + 2144962711, + 3323202731, + 4290024976, + 2564745684, + 2963744582, + 2443959748, + 354479447, + 750870327, + 1918481917, + 4032662899, + 3587381650, + 2414725163, + 1081611718, + 1625742020, + 2308565678, + 1871105284, + 2807907995, + 2121980967, + 1054641568, + 413918748, + 1917336504, + 1816558243, + 4130950286, + 1522979646, + 1669959736, + 1320550031, + 3104643263, + 3823959661, + 3525913657, + 3584683259, + 2918750759, + 3536390697, + 94303122, + 3296691317, + 801484894, + 2496463830, + 3266028549, + 3085157904, + 973908139, + 3787909072, + 3107413701, + 2378763734, + 920604853, + 2516325050, + 1838993983, + 1603937321, + 3183924418, + 1945006185, + 3982311384, + 2682510803, + 680388473, + 979993429, + 2405770322, + 461040879, + 2817579280, + 14113753, + 2894979602, + 168339452, + 951841533, + 4154758669, + 2637132451, + 3877583949, + 1949856502, + 922996215, + 3941049054, + 4182141402, + 2262220987, + 1957218950, + 2094550054, + 1846856260, + 3499234137, + 3086631065, + 3054834317, + 593829839, + 522971108, + 1162127370, + 4233562270, + 2780190687, + 1558345254, + 3716353056, + 3518630848, + 1158929937, + 2038205856, + 86116519, + 4185661467, + 975807626, + 3910458990, + 4124281183, + 3361419439, + 171334650, + 2590402790, + 2890570341, + 2303184249, + 385229009, + 1998433745, + 1717510093, + 4022124023, + 1429389803, + 945128292, + 904486530, + 3869890846, + 619875033, + 459968607, + 3743748793, + 359054425, + 1417363940, + 3653985133, + 255835594, + 1047011733, + 2763232252, + 1329499601, + 328661377, + 2162274327, + 2100532220, + 4255182614, + 4243119782, + 3982047273, + 4053789056, + 401211099, + 950731750, + 1319785741, + 32085358, + 3882634684, + 3117071189, + 3554463148, + 3570219049, + 3535289452, + 2314864456, + 3913885196, + 2763960513, + 1079999262, + 27130513, + 3033873113, + 2976581453, + 2598189097, + 595410904, + 1572834111, + 13319433, + 1084574846, + 2123388694, + 560078433, + 1679946323, + 3518703473, + 184634770, + 296981500, + 1646147798, + 455591063, + 1325348861, + 3224952074, + 1027242654, + 2281956980, + 4221373527, + 1289566249, + 4044928561, + 882718761, + 1510333659, + 836581417, + 1901166356, + 2276405827, + 4052965752, + 1155765244, + 503145996, + 251209228, + 495107308, + 3944781937, + 37459569, + 4248015868, + 4198082194, + 1302400505, + 4106658327, + 680016782, + 2319227476, + 2738307068, + 3929248764, + 2850246066, + 1824526196, + 3912967080, + 3044723416, + 3133016299, + 2517964682, + 3647586740, + 3653838348, + 929101967, + 3571454885, + 2806296851, + 977312655, + 646282397, + 3448018532, + 824323032, + 204234270, + 1579585816, + 3712763835, + 1212872174, + 3953984401, + 3168953855, + 2944827576, + 1582841441, + 2796901051, + 3323682385, + 1317058015, + 2557550659, + 1620634991, + 2986830770, + 2490492987, + 1817271123, + 40653745, + 1696076631, + 2466126792, + 4169878842, + 3251128023, + 2444465148, + 678695941, + 2481746922, + 2836440943, + 774727851, + 2246405597, + 4028028350, + 2524697596, + 1977038330, + 2817823941, + 2219733501, + 688216667, + 3634598908, + 3232633974, + 2724625059, + 3269075805, + 3732640764, + 2263349224, + 1680746207, + 2414984922, + 2507457870, + 50998433, + 3092528578, + 3712946115, + 1543935193, + 807276090, + 1221183390, + 172029722, + 2122275289, + 3990925720, + 2261697609, + 2736881867, + 295017943, + 3278176820, + 3748965853, + 3174324790, + 1103903216, + 3184177968, + 1113409935, + 2299842241, + 2162986400, + 1538342947, + 4056442905, + 1631434666, + 205885885, + 1594733696, + 1955104493, + 1022309772, + 3820814597, + 993150979, + 1209418480, + 1784441183, + 3958731802, + 2250225826, + 3065160070, + 2024071551, + 107497541, + 628544021, + 2732195517, + 4241486415, + 3969279737, + 870594305, + 2916400082, + 1193734351, + 3202349435, + 3831290364, + 3282979782, + 3928764629, + 1308462133, + 3216471040, + 2433519008, + 2022961611, + 3604842236, + 3374978006, + 2855895374, + 3496407048, + 1482251215, + 3994511488, + 2997832431, + 1132589448, + 1348149915, + 2092468906, + 2451531615, + 779021139, + 3730093054, + 3413713311, + 1022915255, + 2204920111, + 2660843182, + 1080545747, + 1642805350, + 1766422419, + 4141567741, + 1558990974, + 4185590212, + 2841468319, + 701281393, + 3325419312, + 451957774, + 357505993, + 1156369516, + 3187387500, + 2259467579, + 2678954464, + 3154597438, + 543558236, + 2359973133, + 1990431740, + 2705477184, + 1041368449, + 3122368657, + 3181646225, + 1094423548, + 2955375511, + 2888125966, + 153013225, + 2936040203, + 1758530522, + 573901046, + 3030911670, + 1675922848, + 4235213885, + 4091916710, + 2633682514, + 4254584852, + 2328748202, + 3357301402, + 3877813395, + 2004567202, + 2496297824, + 3334207724, + 1600149091, + 293528591, + 1782996825, + 3757282300, + 1107206446, + 1092948665, + 1797960910, + 1206726575, + 1496351055, + 3021406120, + 99347751, + 3797204453, + 1468919488, + 797415788, + 1314843976, + 2934934694, + 490769168, + 1474506522, + 3811268385, + 864295921, + 3081676220, + 151810803, + 2588618056, + 2998120306, + 416853049, + 3495967422, + 3233393284, + 508007510, + 759277550, + 1971252067, + 869050696, + 810488476, + 745556697, + 789872778, + 3362723943, + 1617826947, + 3260309823, + 2197904616, + 1199157863, + 1643868273, + 2430404313, + 321630747, + 2503194620, + 3194725903, + 2881225774, + 3997952447, + 1389644742, + 2713718873, + 3585511591, + 1684282922, + 3366848728, + 284226441, + 1541020250, + 4018237905, + 1369578001, + 2424848261, + 2654325647, + 1626224034, + 1081536219, + 309040124, + 123060826, + 3997038726, + 1670691893, + 1543280290, + 443347828, + 1776629361, + 3118548424, + 478440524, + 679771963, + 3729929345, + 4244789645, + 2366506734, + 2838165089, + 1619778288, + 1313182965, + 3240680626, + 1323407757, + 883854656, + 2194691858, + 15502752, + 3760372982, + 1366337101, + 3656163446, + 295018543, + 825595257, + 57149555, + 2563789125, + 2353194283, + 2636942752, + 4026740269, + 3570411982, + 123108003, + 3782362128, + 1280126114, + 1410849099, + 4228502127, + 3609540589, + 3365041621, + 269823086, + 348988933, + 1636389511, + 2936586309, + 2761603302, + 2318200267, + 449954059, + 2895413148, + 1755165354, + 4274214049, + 778500192, + 3345707173, + 3732136051, + 721450866, + 1600392975, + 2466255445, + 4050155669, + 3541895912, + 1139547465, + 394654115, + 1380991098, + 3516240523, + 2234361374, + 1094817798, + 744817486, + 3564402361, + 1452222566, + 1851510470, + 3619787319, + 4265894873, + 216945449, + 3061690214, + 2910557180, + 255227811, + 4167600590, + 1587209598, + 3157581152, + 3184381405, + 2572638469, + 615748604, + 2532518896, + 1774874546, + 599185303, + 1561718045, + 1742737136, + 1674464100, + 3136865519, + 706016261, + 2793529873, + 3504981554, + 4155122613, + 2080953106, + 1104362365, + 2879917501, + 850497536, + 1392080469, + 1287937401, + 718877177, + 1917966999, + 1822823090, + 3701632935, + 3591222197, + 2817335337, + 1941148668, + 3110479131, + 3289213933, + 583624926, + 468372467, + 1633850097, + 2110223508, + 898191441, + 112745085, + 4018820793, + 3085119011, + 2919626325, + 3094857332, + 2348201466, + 2192810893, + 4163160985, + 1269075360, + 3952316364, + 2881886868, + 439764402, + 1584774136, + 169674806, + 3759072440, + 102542696, + 2996180816, + 804899022, + 1015552308, + 963902061, + 3504158761, + 2002490364, + 2806716850, + 265778447, + 4083122425, + 181902171, + 1238120570, + 75986790, + 1265796414, + 899570100, + 2988365258, + 3655201337, + 3654061472, + 3061856840, + 1077859090, + 615341051, + 3678875745, + 3349230696, + 3647606635, + 2549309392, + 1508570930, + 1766401548, + 1448448666, + 1499923635, + 2882994691, + 3674863070, + 3056042030, + 4240893633, + 1395113939, + 2964622752, + 1951208733, + 3536941067, + 4176581069, + 1203545131, + 3092754101, + 246375791, + 2736026107, + 1069781886, + 3687777340, + 1564342316, + 535067202, + 1395923345, + 3240977890, + 1447712361, + 2602027658, + 718301639, + 3123244280, + 1032593647, + 2840366496, + 2680819379, + 3839389658, + 277023757, + 1172110445, + 1755648697, + 2472176885, + 223800276, + 625975427, + 976111724, + 4145966869, + 2789375411, + 618087261, + 249378857, + 4058280485, + 827698488, + 1558001705, + 3561482820, + 2562485583, + 4243138030, + 615982737, + 1220643281, + 150685616, + 3091876332, + 1040775722, + 669982125, + 4116080964, + 3582002820, + 910398460, + 1036475267, + 3800912395, + 146392076, + 1686512349, + 2326636627, + 2839816704, + 3502816184, + 226836633, + 3953733490, + 257136089, + 819503463, + 2863084840, + 1949759310, + 210754155, + 1367301635, + 3822983876, + 4273793488, + 3635397748, + 3930494584, + 3127921440, + 3167253437, + 3868239231, + 1859128680, + 3480031018, + 3810805277, + 2677252364, + 156014509, + 3627739127, + 2321729979, + 1146476634, + 4039938779, + 1964254745, + 2055836767, + 119981689, + 2629265310, + 2448331885, + 3737376990, + 144116905, + 2272221101, + 2197874825, + 1277245109, + 2503770904, + 360730278, + 3489360962, + 1166917451, + 707478563, + 4155586396, + 162255877, + 347505241, + 4215670524, + 3187066832, + 2399809085, + 2754074729, + 4060703604, + 628331516, + 1304296041, + 616435646, + 4080527786, + 1443829854, + 2512398201, + 708736129, + 13107491, + 3794803132, + 2049792025, + 2455417440, + 3367313400, + 3357250579, + 3694383800, + 2339901602, + 3242843022, + 2282454607, + 1243764146, + 835458563, + 1297706389, + 464259778, + 1766994680, + 1294403159, + 2568098594, + 3107165180, + 4040340620, + 3352361837, + 1031290113, + 2903897222, + 1677700667, + 3160388974, + 107544081, + 3044188332, + 2285081596, + 2835131395, + 2984459037, + 4174489262, + 1236389532, + 2938237924, + 321459212, + 3407526215, + 300939750, + 3441531391, + 2909957084, + 3192069648, + 1849065716, + 2524531022, + 505940164, + 4121643374, + 3774892253, + 3197739982, + 2161102232, + 2715370488, + 1992893964, + 1781864804, + 587888644, + 1039111164, + 4237497041, + 451382997, + 969500141, + 1415510495, + 3743398113, + 3027538652, + 2525173102, + 1708264968, + 3366040354, + 1100599986, + 188347929, + 2597020383, + 2705434194, + 2593884753, + 3472123498, + 2975894973, + 3152745753, + 1154919607, + 1930923350, + 3287039847, + 1372881231, + 2280400314, + 3369343584, + 2351620600, + 2645135839, + 2752766693, + 1471851763, + 1989520052, + 1141965917, + 1503477720, + 653708953, + 1765126703, + 2432827426, + 95470391, + 2567901801, + 2589449658, + 4218799564, + 3249265647, + 3673811979, + 210116709, + 1593584949, + 1791352211, + 3457985288, + 3345288309, + 531559080, + 2491124112, + 3410158390, + 4224872590, + 3705139860, + 162608772, + 4258229445, + 925559698, + 3928842969, + 4253051659, + 3633746133, + 3867307935, + 3560665067, + 798915737, + 2945369269, + 2677264274, + 2278571792, + 177111659, + 85880059, + 1297165140, + 1630583316, + 2232491275, + 1848784182, + 2487708241, + 626480004, + 3427283542, + 2108571893, + 304448521, + 3332104493, + 2244470522, + 436416061, + 221900294, + 1502470404, + 3552593177, + 440421571, + 450406196, + 503094540, + 3836822275, + 2708915136, + 3750617468, + 1119744229, + 3614752756, + 921246433, + 2285438321, + 626892406, + 2362972044, + 72782198, + 2929019254, + 2795773560, + 907126242, + 155458798, + 2798552666, + 1404739463, + 4285652249, + 1998444837, + 908777857, + 872544165, + 910429472, + 135486769, + 3457269042, + 426360862, + 1725011064, + 296836635, + 1322549027, + 2044728014, + 1530183840, + 529742207, + 4272200782, + 1341516288, + 2608484640, + 41739659, + 3260579369, + 2745872368, + 2894051250, + 862784766, + 3077271274, + 3094180193, + 3619626927, + 3745223676, + 2976066508, + 2854085372, + 2959147533, + 3266548732, + 1776526161, + 3712296962, + 1955871800, + 2580096524, + 2507709226, + 3564865233, + 948086521, + 1548254487, + 142465290, + 1472185378, + 1459457331, + 2274226560, + 3153451899, + 492958971, + 3563213618, + 1285705317, + 410274915, + 3710645347, + 1309728002, + 2119793999, + 1343794461, + 4024173916, + 2383939514, + 955476870, + 2698156268, + 35240468, + 2655147757, + 3764205609, + 3802564010, + 170690025, + 2311941439, + 3181546731, + 3866587616, + 3648138580, + 93914936, + 170378107, + 2120623674, + 1064945649, + 1618754372, + 244668133, + 247698428, + 3669223677, + 470277359, + 1781765116, + 1691572958, + 1373856501, + 2668769415, + 1087394637, + 1009983433, + 2180701723, + 4008405264, + 2831059514, + 2645120714, + 2649103430, + 2664825925, + 790502615, + 1739837626, + 2293247016, + 1784648440, + 1887808856, + 1788504755, + 112452386, + 1979978194, + 3462674048, + 2170273742, + 538168945, + 753954113, + 374731234, + 3715846592, + 1962971231, + 1860649552, + 1378082995, + 665789406, + 1717555224, + 139011596, + 1375043498, + 1618544981, + 1889460471, + 2262321736, + 1788301425, + 1652168174, + 2668680621, + 2636946065, + 2856623532, + 2759951687, + 959681532, + 3209399506, + 3055195668, + 1227221002, + 508217552, + 3289969989, + 243178923, + 2956189845, + 3075866530, + 2274779301, + 3940720663, + 3998230222, + 1178317551, + 4016096296, + 1545450160, + 2842919847, + 314809953, + 2952850186, + 3747079365, + 4147239510, + 169135842, + 1332643570, + 2994529201, + 973521782, + 1584369690, + 1043738701, + 2851900832, + 290391815, + 283209196, + 2468230023, + 1164221089, + 1991787192, + 3358097187, + 51041423, + 52882140, + 2339018837, + 2053214130, + 3757479030, + 158160339, + 853200279, + 1986584654, + 438318340, + 827246872, + 3299488628, + 2924263085, + 3472029049, + 2736844435, + 677668732, + 604894932, + 1158021131, + 1400019344, + 2268204687, + 1450415100, + 3854557817, + 1543646433, + 1278448636, + 342615870, + 1554194368, + 3080024605, + 3423702268, + 1675764636, + 1622381564, + 2078849875, + 2113115132, + 1380160211, + 3132876285, + 125015036, + 269576093, + 94145952, + 2777172031, + 2683080096, + 3812456892, + 488500848, + 3270430997, + 2895151306, + 116376005, + 400248103, + 406044930, + 1616846013, + 10142671, + 763027711, + 225200779, + 1062250709, + 2013867381, + 2113506324, + 1692932387, + 1827244161, + 3124618210, + 2096472894, + 2924146124, + 2128251367, + 2433358586, + 1939359710, + 2593325766, + 2879917723, + 694743357, + 2902069960, + 220008971, + 3090408469, + 917019124, + 1705716306, + 3263901372, + 3347863687, + 3447882276, + 1661163736, + 3617689692, + 3928555688, + 1057578789, + 435256475, + 4101009465, + 1941403425, + 198967948, + 3733675151, + 2043684541, + 3517169445, + 2226776400, + 2853403709, + 529383565, + 2807448986, + 4234287173, + 1019457583, + 1022544883, + 2493146691, + 1054461787, + 1008886329, + 1136775085, + 1191015885, + 1196280518, + 1979847999, + 50385656, + 1918742169, + 3999472204, + 3697687030, + 2220475432, + 2358141757, + 2360004627, + 4245257809, + 236660303, + 429277936, + 342159236, + 2622612602, + 371428004, + 373079619, + 643418617, + 2095027856, + 1071164424, + 1136911283, + 1548491889, + 2169307971, + 375530199, + 1510422521, + 3151638847, + 1698730948, + 2231688008, + 2604576561, + 2771938750, + 2996594997, + 289648234, + 348584153, + 2748350697, + 2926633629, + 2123683379, + 369686787, + 742917749, + 3538158875, + 2937761472, + 1545298048, + 1321616112, + 2855506940, + 900522183, + 1578775276, + 2217833278, + 2012838864, + 3753486980, + 2839765116, + 2464905186, + 2621255555, + 1305703280, + 861753115, + 3319278167, + 3063300848, + 149720480, + 1082941229, + 3337532056, + 2248357849, + 3675926744, + 1508550646, + 2289803479, + 3456899824, + 3931641900, + 3970432934, + 3419674548, + 1093210099, + 456043370, + 848380423, + 1287304304, + 1526654696, + 2055664760, + 1373166395, + 4291477370, + 2195550588, + 2847102741, + 3399062057, + 1641565587, + 2888753905, + 3579593979, + 3653059026, + 3757851979, + 2922615804, + 2919796598, + 1553476262, + 2566666743, + 3759503594, + 550831114, + 3761155209, + 3762806824, + 3902853271, + 4140081844, + 14244860, + 3847846774, + 150820676, + 1278818058, + 850592577, + 1206571206, + 1734446471, + 2117320444, + 1382106590, + 2436009347, + 2118972059, + 2951272396, + 36096192, + 117998987, + 473485679, + 2244928358, + 476788909, + 3489269251, + 610429940, + 480092139, + 481743754, + 871966503, + 918189168, + 601656217, + 933769938, + 939671928, + 1799299383, + 3312467582, + 1149665466, + 3006548167, + 1310740861, + 3602693817, + 1461645203, + 3367691969, + 1800404122, + 3486057732, + 1862284649, + 2076833303, + 2213411495, + 2805256437, + 3927915220, + 3000904950, + 2094647776, + 3333131702, + 1315613425, + 3752211294, + 603915804, + 3505028338, + 663258455, + 3322500634, + 1612225949, + 3606320646, + 157110413, + 1352397672, + 3861006967, + 452208841, + 18776483, + 1058429216, + 37009196, + 564884461, + 876864198, + 2952260510, + 2860348412, + 928261291, + 1164724902, + 2775815164, + 1332774287, + 780957373, + 939415664, + 1513770932, + 788046331, + 1692600167, + 4069810315, + 673708384, + 4024252457, + 1932614728, + 2148510256, + 3131224670, + 2388524817, + 2460489993, + 2676385521, + 826214242, + 3692647551, + 3063508455, + 3071766530, + 2063832060, + 1525861001, + 3073418145, + 837715723, + 3075069760, + 3076721375, + 3078372990, + 983243705, + 3083327835, + 171307615, + 1824016656, + 3084979450, + 1310404265, + 1775308984, + 3114708520, + 3116360135, + 3121314980, + 3134527900, + 1691646294, + 2804281092, + 97231530, + 3136179515, + 3204260786, + 3276225962, + 1220749418, + 3588205699, + 3874089391, + 4044115788, + 3268751013, + 743407979, + 166253838, + 1356063462, + 1368383673, + 2279700640, + 2130747644, + 3945795573, + 2780898906, + 3635542517, + 425022309, + 517919178, + 4061558677, + 2190437442, + 543621065, + 753756604, + 2500819054, + 1004589179, + 1165671422, + 30433743, + 3444275347, + 1335363438, + 1913735398, + 1265998516, + 3829325073, + 3662767579, + 463084678, + 1351676723, + 1391866096, + 3398925952, + 1631216488, + 815757910, + 1915438939, + 2427834344, + 1445161581, + 1890300748, + 2864863800, + 1961990747, + 575205902, + 2037710159, + 2037814253, + 617312262, + 3732916270, + 783918780, + 2257843797, + 2096388952, + 2338272340, + 1434223270, + 578132535, + 1980341560, + 1002144380, + 3244716568, + 4258414038, + 3271748023, + 3304438238, + 3717523241, + 3370185097, + 3435931956, + 1957265068, + 3602522282, + 2547657777, + 439998433, + 3838648480, + 3913593633, + 3989799199, + 906176560, + 1894133125, + 4046301857, + 4242327928, + 630592085, + 2693892518, + 4292991777, + 545678922, + 125792961, + 3015046341, + 132755933, + 2615111110, + 1570165302, + 1440646342, + 436066778, + 565233904, + 600906020, + 602222721, + 3951925872, + 1496901698, + 1522901980, + 2785441472, + 3041450802, + 1637661947, + 2127660080, + 3487022798, + 2269114589, + 1314834580, + 2315690100, + 3817149113, + 4091670162, + 1431749301, + 1858116930, + 2213946343, + 2225172640, + 2263866576, + 2727022058, + 2752967311, + 2864705739, + 3052439312, + 3510257966, + 2614053317, + 3297860332, + 3670298840, + 3732709413, + 3788324110, + 4098876453, + 4290374884, + 1623013158, + 3381478137, + 17185761, + 3931288033, + 2890638791, + 330388453, + 346929928, + 2022347217, + 4083347580, + 533021259, + 564302770, + 1917602962, + 680157484, + 3264086791, + 3727034815, + 798549062, + 3068463300, + 669812542, + 1965902997, + 2311072371, + 3079287749, + 2542834724, + 1587730355, + 2558655180, + 1838763297, + 4172568578, + 2160380860, + 2950446516, + 1830851200, + 3214537066, + 3234673086, + 3652695478, + 3103302036, + 3465954368, + 4180570743, + 3534518722, + 371186900, + 4091394002, + 1013756921, + 443558693, + 591140762, + 656610661, + 2064733527, + 3808408202, + 983299427, + 4217306348, + 1164218401, + 2036361232, + 3237903670, + 2970183398, + 2293637521, + 135920445, + 1596005536, + 868652905, + 1191735827, + 3987079331, + 1365842164, + 1508074873, + 1642818143, + 3436143898, + 4105051793, + 1863199739, + 3425841570, + 1070791291, + 2135340676, + 2639720559, + 3364388739, + 3797761273, + 2092100514, + 2098706974, + 2329992200, + 414444763, + 2759250216, + 2913136690, + 3012980338, + 3327770644, + 4128942283, + 3362344229, + 161668409, + 3401762422, + 2852854788, + 4237092412, + 1245448751, + 3702405475, + 918849409, + 3829682756, + 1612361408, + 255302575, + 414620710, + 386293029, + 618761615, + 686024761, + 744062262, + 1502028603, + 1543798545, + 1641415225, + 1548121999, + 2257971049, + 2124837447, + 878733439, + 2340670452, + 2674090849, + 3118011750, + 2816338013, + 178571546, + 2841008029, + 3249261197, + 370232173, + 4092487128, + 3787567939, + 3898287302, + 4142016703, + 4285779501, + 30663912, + 151672195, + 180913835, + 3534235309, + 34183582, + 4083161638, + 651464351, + 1410311776, + 371621315, + 421602934, + 458937500, + 2710583246, + 712168842, + 730943059, + 1519723107, + 875212982, + 1247793383, + 4217322139, + 989813600, + 1057606514, + 3764662384, + 1443547269, + 3066811685, + 3598957382, + 1791427568, + 1171541710, + 3930727258, + 1473799048, + 1296054774, + 1747355813, + 765238787, + 2023008475, + 1190147516, + 2344328209, + 2495155989, + 2577859137, + 2857814560, + 3127329373, + 3296722158, + 2773229577, + 3376009661, + 3450001968, + 920941800, + 3526837441, + 3858973601, + 1702168830, + 4088613871, + 1464587427, + 223310468, + 388034151, + 2346547796, + 1663234329, + 1750829822, + 1967643923, + 2881302403, + 2278706468, + 2326990117, + 2511346984, + 3088785099, + 2616085763, + 3027500544, + 3417583519, + 4178218543, + 1412908157, + 797934924, + 3533637837, + 1449907751, + 3362830643, + 1451831482, + 2637935122, + 3070114915, + 3023287679, + 551924251, + 1669930486, + 46736908, + 2870852215, + 1120149824, + 2923708820, + 3887377256, + 3464197236, + 4241374559, + 527665290, + 996663016, + 885020215, + 1763758554, + 3059119137, + 2555315060, + 2762094724, + 2530899578, + 2770161927, + 2262137600, + 3547456240, + 858902117, + 1140367371, + 1215030156, + 443490822, + 294390719, + 3032677281, + 1917451875, + 4184019303, + 3277199633, + 1271484400, + 1297294717, + 3560552546, + 171494987, + 195244192, + 3002890475, + 1811839150, + 265392489, + 1461398554, + 3205759417, + 333855951, + 529068443, + 660038281, + 557400685, + 663341511, + 930804377, + 1922045399, + 716890919, + 162167595, + 1654776395, + 1779143013, + 1123617794, + 2984325996, + 1162789888, + 1318479490, + 1235468610, + 3561562003, + 1486207619, + 1551372768, + 1850331254, + 3255947500, + 1037370721, + 1989327599, + 2137526937, + 835638766, + 2269130237, + 1962162282, + 3244209297, + 2330636993, + 3095831808, + 1396344138, + 2603020391, + 3434076295, + 3280064277, + 2656211099, + 3335250889, + 2550961007, + 3510242586, + 3536471583, + 3950980241, + 4033586023, + 117250846, + 3088282680, + 4041974454, + 4244540017, + 1167160774, + 899320334, + 1200870684, + 1752686878, + 1906988301, + 3804101227, + 2575525651, + 2919787747, + 3508792859, + 3548535223, + 3783756895, + 3797961332, + 4043078107, + 3115038057, + 2313593054, + 49456560, + 592180731, + 1051471757, + 1097775533, + 706238670, + 877895868, + 1173092699, + 1461897718, + 1767704813, + 1770165905, + 1923453688, + 2212501241, + 2305269460, + 2488410748, + 3782099915, + 2844616706, + 3383007207, + 3392887901, + 504514034, + 3765247327, + 1000070091, + 3727494858, + 3657635382, + 3839047923, + 3886529747, + 4069720347, + 4164704452, + 342197850, + 3540244297, + 2513230733, + 4117704995, + 3367298820, + 2680283743, + 3119663365, + 3697738938, + 545363837, + 163402553, + 5908395, + 129135650, + 2289183712, + 200922300, + 761731755, + 894529125, + 1086964761, + 1168927492, + 2100052708, + 2438466459, + 3390051757, + 2498042266, + 2557754096, + 2600961503, + 487719832, + 703543228, + 2726532092, + 4199470013, + 3142155593, + 2550501832, + 4076840151, + 200553094, + 380957745, + 572905105, + 462664429, + 1466804584, + 330249537, + 2605012269, + 491456522, + 4126287524, + 502863753, + 952536201, + 3510682541, + 1137442027, + 1665981878, + 1761469971, + 3085467405, + 2045285083, + 796985462, + 3433956341, + 2217966239, + 2183547611, + 2279273489, + 1916983087, + 2348676810, + 2403632109, + 2409539315, + 545986953, + 176166202, + 2477389837, + 2573160348, + 2796513469, + 3972309363, + 528662843, + 1038982109, + 1125913837, + 1318081294, + 1417425499, + }; + return descriptors_with_coding_scheme; +} diff --git a/3rdparty/spirv-tools/tools/dis/dis.cpp b/3rdparty/spirv-tools/tools/dis/dis.cpp new file mode 100644 index 000000000..6a2e26932 --- /dev/null +++ b/3rdparty/spirv-tools/tools/dis/dis.cpp @@ -0,0 +1,209 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) +#include // Need fileno +#include +#endif + +#include +#include +#include +#include + +#include "spirv-tools/libspirv.h" +#include "tools/io.h" + +static void print_usage(char* argv0) { + printf( + R"(%s - Disassemble a SPIR-V binary module + +Usage: %s [options] [] + +The SPIR-V binary is read from . If no file is specified, +or if the filename is "-", then the binary is read from standard input. + +Options: + + -h, --help Print this help. + --version Display disassembler version information. + + -o Set the output filename. + Output goes to standard output if this option is + not specified, or if the filename is "-". + + --color Force color output. The default when printing to a terminal. + Overrides a previous --no-color option. + --no-color Don't print in color. Overrides a previous --color option. + The default when output goes to something other than a + terminal (e.g. a file, a pipe, or a shell redirection). + + --no-indent Don't indent instructions. + + --no-header Don't output the header as leading comments. + + --raw-id Show raw Id values instead of friendly names. + + --offsets Show byte offsets for each instruction. +)", + argv0, argv0); +} + +static const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_3; + +int main(int argc, char** argv) { + const char* inFile = nullptr; + const char* outFile = nullptr; + + bool color_is_possible = +#if SPIRV_COLOR_TERMINAL + true; +#else + false; +#endif + bool force_color = false; + bool force_no_color = false; + + bool allow_indent = true; + bool show_byte_offsets = false; + bool no_header = false; + bool friendly_names = true; + + for (int argi = 1; argi < argc; ++argi) { + if ('-' == argv[argi][0]) { + switch (argv[argi][1]) { + case 'h': + print_usage(argv[0]); + return 0; + case 'o': { + if (!outFile && argi + 1 < argc) { + outFile = argv[++argi]; + } else { + print_usage(argv[0]); + return 1; + } + } break; + case '-': { + // Long options + if (0 == strcmp(argv[argi], "--no-color")) { + force_no_color = true; + force_color = false; + } else if (0 == strcmp(argv[argi], "--color")) { + force_no_color = false; + force_color = true; + } else if (0 == strcmp(argv[argi], "--no-indent")) { + allow_indent = false; + } else if (0 == strcmp(argv[argi], "--offsets")) { + show_byte_offsets = true; + } else if (0 == strcmp(argv[argi], "--no-header")) { + no_header = true; + } else if (0 == strcmp(argv[argi], "--raw-id")) { + friendly_names = false; + } else if (0 == strcmp(argv[argi], "--help")) { + print_usage(argv[0]); + return 0; + } else if (0 == strcmp(argv[argi], "--version")) { + printf("%s\n", spvSoftwareVersionDetailsString()); + printf("Target: %s\n", + spvTargetEnvDescription(kDefaultEnvironment)); + return 0; + } else { + print_usage(argv[0]); + return 1; + } + } break; + case 0: { + // Setting a filename of "-" to indicate stdin. + if (!inFile) { + inFile = argv[argi]; + } else { + fprintf(stderr, "error: More than one input file specified\n"); + return 1; + } + } break; + default: + print_usage(argv[0]); + return 1; + } + } else { + if (!inFile) { + inFile = argv[argi]; + } else { + fprintf(stderr, "error: More than one input file specified\n"); + return 1; + } + } + } + + uint32_t options = SPV_BINARY_TO_TEXT_OPTION_NONE; + + if (allow_indent) options |= SPV_BINARY_TO_TEXT_OPTION_INDENT; + + if (show_byte_offsets) options |= SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET; + + if (no_header) options |= SPV_BINARY_TO_TEXT_OPTION_NO_HEADER; + + if (friendly_names) options |= SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES; + + if (!outFile || (0 == strcmp("-", outFile))) { + // Print to standard output. + options |= SPV_BINARY_TO_TEXT_OPTION_PRINT; + + if (color_is_possible && !force_no_color) { + bool output_is_tty = true; +#if defined(_POSIX_VERSION) + output_is_tty = isatty(fileno(stdout)); +#endif + if (output_is_tty || force_color) { + options |= SPV_BINARY_TO_TEXT_OPTION_COLOR; + } + } + } + + // Read the input binary. + std::vector contents; + if (!ReadFile(inFile, "rb", &contents)) return 1; + + // If printing to standard output, then spvBinaryToText should + // do the printing. In particular, colour printing on Windows is + // controlled by modifying console objects synchronously while + // outputting to the stream rather than by injecting escape codes + // into the output stream. + // If the printing option is off, then save the text in memory, so + // it can be emitted later in this function. + const bool print_to_stdout = SPV_BINARY_TO_TEXT_OPTION_PRINT & options; + spv_text text = nullptr; + spv_text* textOrNull = print_to_stdout ? nullptr : &text; + spv_diagnostic diagnostic = nullptr; + spv_context context = spvContextCreate(kDefaultEnvironment); + spv_result_t error = + spvBinaryToText(context, contents.data(), contents.size(), options, + textOrNull, &diagnostic); + spvContextDestroy(context); + if (error) { + spvDiagnosticPrint(diagnostic); + spvDiagnosticDestroy(diagnostic); + return error; + } + + if (!print_to_stdout) { + if (!WriteFile(outFile, "w", text->str, text->length)) { + spvTextDestroy(text); + return 1; + } + } + spvTextDestroy(text); + + return 0; +} diff --git a/3rdparty/spirv-tools/tools/emacs/50spirv-tools.el b/3rdparty/spirv-tools/tools/emacs/50spirv-tools.el new file mode 100644 index 000000000..1d4fbeef8 --- /dev/null +++ b/3rdparty/spirv-tools/tools/emacs/50spirv-tools.el @@ -0,0 +1,40 @@ +;; Copyright (c) 2016 LunarG Inc. +;; +;; Licensed under the Apache License, Version 2.0 (the "License"); +;; you may not use this file except in compliance with the License. +;; You may obtain a copy of the License at +;; +;; http://www.apache.org/licenses/LICENSE-2.0 +;; +;; Unless required by applicable law or agreed to in writing, software +;; distributed under the License is distributed on an "AS IS" BASIS, +;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +;; See the License for the specific language governing permissions and +;; limitations under the License. + +;; Upon loading a file with the .spv extension into emacs, the file +;; will be disassembled using spirv-dis, and the result colorized with +;; asm-mode in emacs. The file may be edited within the constraints +;; of validity, and when re-saved will be re-assembled using spirv-as. + +;; Note that symbol IDs are not preserved through a load/edit/save operation. +;; This may change if the ability is added to spirv-as. + +;; It is required that those tools be in your PATH. If that is not the case +;; when starting emacs, the path can be modified as in this example: +;; (setenv "PATH" (concat (getenv "PATH") ":/path/to/spirv/tools")) +;; +;; See https://github.com/KhronosGroup/SPIRV-Tools/issues/359 + +(require 'jka-compr) +(require 'asm-mode) + +(add-to-list 'jka-compr-compression-info-list + '["\\.spv\\'" + "Assembling SPIRV" "spirv-as" ("-o" "-") + "Disassembling SPIRV" "spirv-dis" ("--no-color" "--raw-id") + t nil "\003\002\043\007"]) + +(add-to-list 'auto-mode-alist '("\\.spv\\'" . asm-mode)) + +(jka-compr-update) diff --git a/3rdparty/spirv-tools/tools/emacs/CMakeLists.txt b/3rdparty/spirv-tools/tools/emacs/CMakeLists.txt new file mode 100644 index 000000000..ecd7c277a --- /dev/null +++ b/3rdparty/spirv-tools/tools/emacs/CMakeLists.txt @@ -0,0 +1,48 @@ +# Copyright (c) 2016 LunarG Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Install a script for use with the auto-compression feature of emacs(1). +# Upon loading a file with the .spv extension, the file will be disassembled +# using spirv-dis, and the result colorized with asm-mode in emacs. The file +# may be edited within the constraints of validity, and when re-saved will be +# re-assembled using spirv-as. + +# It is required that those tools be in your PATH. If that is not the case +# when starting emacs, the path can be modified as in this example: +# (setenv "PATH" (concat (getenv "PATH") ":/path/to/spirv/tools")) +# +# See https://github.com/KhronosGroup/SPIRV-Tools/issues/359 + +# This is an absolute directory, and ignores CMAKE_INSTALL_PREFIX, or +# it will not be found by emacs upon startup. It is only installed if +# both of the following are true: +# 1. SPIRV_TOOLS_INSTALL_EMACS_HELPERS is defined +# 2. The directory /etc/emacs/site-start.d already exists at the time of +# cmake invocation (not at the time of make install). This is +# typically true if emacs is installed on the system. + +# Note that symbol IDs are not preserved through a load/edit/save operation. +# This may change if the ability is added to spirv-as. + +option(SPIRV_TOOLS_INSTALL_EMACS_HELPERS + "Install Emacs helper to disassemble/assemble SPIR-V binaries on file load/save." + ${SPIRV_TOOLS_INSTALL_EMACS_HELPERS}) +if (${SPIRV_TOOLS_INSTALL_EMACS_HELPERS}) + if(EXISTS /etc/emacs/site-start.d) + if(ENABLE_SPIRV_TOOLS_INSTALL) + install(FILES 50spirv-tools.el DESTINATION /etc/emacs/site-start.d) + endif(ENABLE_SPIRV_TOOLS_INSTALL) + endif() +endif() + diff --git a/3rdparty/spirv-tools/tools/io.h b/3rdparty/spirv-tools/tools/io.h new file mode 100644 index 000000000..690b5c368 --- /dev/null +++ b/3rdparty/spirv-tools/tools/io.h @@ -0,0 +1,77 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_TOOLS_IO_H_ +#define LIBSPIRV_TOOLS_IO_H_ + +#include +#include +#include + +// Appends the content from the file named as |filename| to |data|, assuming +// each element in the file is of type |T|. The file is opened with the given +// |mode|. If |filename| is nullptr or "-", reads from the standard input. If +// any error occurs, writes error messages to standard error and returns false. +template +bool ReadFile(const char* filename, const char* mode, std::vector* data) { + const int buf_size = 1024; + const bool use_file = filename && strcmp("-", filename); + if (FILE* fp = (use_file ? fopen(filename, mode) : stdin)) { + T buf[buf_size]; + while (size_t len = fread(buf, sizeof(T), buf_size, fp)) { + data->insert(data->end(), buf, buf + len); + } + if (ftell(fp) == -1L) { + if (ferror(fp)) { + fprintf(stderr, "error: error reading file '%s'\n", filename); + return false; + } + } else { + if (sizeof(T) != 1 && (ftell(fp) % sizeof(T))) { + fprintf(stderr, "error: corrupted word found in file '%s'\n", filename); + return false; + } + } + if (use_file) fclose(fp); + } else { + fprintf(stderr, "error: file does not exist '%s'\n", filename); + return false; + } + return true; +} + +// Writes the given |data| into the file named as |filename| using the given +// |mode|, assuming |data| is an array of |count| elements of type |T|. If +// |filename| is nullptr or "-", writes to standard output. If any error occurs, +// returns false and outputs error message to standard error. +template +bool WriteFile(const char* filename, const char* mode, const T* data, + size_t count) { + const bool use_stdout = + !filename || (filename[0] == '-' && filename[1] == '\0'); + if (FILE* fp = (use_stdout ? stdout : fopen(filename, mode))) { + size_t written = fwrite(data, sizeof(T), count, fp); + if (count != written) { + fprintf(stderr, "error: could not write to file '%s'\n", filename); + return false; + } + if (!use_stdout) fclose(fp); + } else { + fprintf(stderr, "error: could not open file '%s'\n", filename); + return false; + } + return true; +} + +#endif // LIBSPIRV_TOOLS_IO_H_ diff --git a/3rdparty/spirv-tools/tools/lesspipe/CMakeLists.txt b/3rdparty/spirv-tools/tools/lesspipe/CMakeLists.txt new file mode 100644 index 000000000..484e51e58 --- /dev/null +++ b/3rdparty/spirv-tools/tools/lesspipe/CMakeLists.txt @@ -0,0 +1,28 @@ +# Copyright (c) 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Install a script for use with the LESSOPEN of less(1). +# For example, after installation into /usr/local do: +# export LESSOPEN='|/usr/local/bin "%s"' +# less -R foo.spv +# +# See https://github.com/KhronosGroup/SPIRV-Tools/issues/359 + +# The script will be installed with everyone having read and execute +# permissions. +# We have a .sh extension because Windows users often configure +# executable settings via filename extension. +if(ENABLE_SPIRV_TOOLS_INSTALL) + install(PROGRAMS spirv-lesspipe.sh DESTINATION ${CMAKE_INSTALL_BINDIR}) +endif(ENABLE_SPIRV_TOOLS_INSTALL) diff --git a/3rdparty/spirv-tools/tools/lesspipe/spirv-lesspipe.sh b/3rdparty/spirv-tools/tools/lesspipe/spirv-lesspipe.sh new file mode 100644 index 000000000..81e335501 --- /dev/null +++ b/3rdparty/spirv-tools/tools/lesspipe/spirv-lesspipe.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# Copyright (c) 2016 The Khronos Group Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# A script for automatically disassembling a .spv file +# for less(1). This assumes spirv-dis is on our PATH. +# +# See https://github.com/KhronosGroup/SPIRV-Tools/issues/359 + +case "$1" in + *.spv) spirv-dis "$@" 2>/dev/null;; + *) exit 1;; +esac + +exit $? + diff --git a/3rdparty/spirv-tools/tools/link/linker.cpp b/3rdparty/spirv-tools/tools/link/linker.cpp new file mode 100644 index 000000000..fb44a37ad --- /dev/null +++ b/3rdparty/spirv-tools/tools/link/linker.cpp @@ -0,0 +1,159 @@ +// Copyright (c) 2017 Pierre Moreau +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "source/spirv_target_env.h" +#include "source/table.h" +#include "spirv-tools/libspirv.hpp" +#include "spirv-tools/linker.hpp" +#include "tools/io.h" + +void print_usage(char* argv0) { + printf( + R"(%s - Link SPIR-V binary files together. + +USAGE: %s [options] [ ...] + +The SPIR-V binaries are read from the different . + +NOTE: The linker is a work in progress. + +Options: + -h, --help Print this help. + -o Name of the resulting linked SPIR-V binary. + --create-library Link the binaries into a library, keeping all exported symbols. + --allow-partial-linkage Allow partial linkage by accepting imported symbols to be unresolved. + --verify-ids Verify that IDs in the resulting modules are truly unique. + --version Display linker version information + --target-env {vulkan1.0|spv1.0|spv1.1|spv1.2|opencl2.1|opencl2.2} + Use Vulkan1.0/SPIR-V1.0/SPIR-V1.1/SPIR-V1.2/OpenCL-2.1/OpenCL2.2 validation rules. +)", + argv0, argv0); +} + +int main(int argc, char** argv) { + std::vector inFiles; + const char* outFile = nullptr; + spv_target_env target_env = SPV_ENV_UNIVERSAL_1_0; + spvtools::LinkerOptions options; + bool continue_processing = true; + int return_code = 0; + + for (int argi = 1; continue_processing && argi < argc; ++argi) { + const char* cur_arg = argv[argi]; + if ('-' == cur_arg[0]) { + if (0 == strcmp(cur_arg, "-o")) { + if (argi + 1 < argc) { + if (!outFile) { + outFile = argv[++argi]; + } else { + fprintf(stderr, "error: More than one output file specified\n"); + continue_processing = false; + return_code = 1; + } + } else { + fprintf(stderr, "error: Missing argument to %s\n", cur_arg); + continue_processing = false; + return_code = 1; + } + } else if (0 == strcmp(cur_arg, "--create-library")) { + options.SetCreateLibrary(true); + } else if (0 == strcmp(cur_arg, "--verify-ids")) { + options.SetVerifyIds(true); + } else if (0 == strcmp(cur_arg, "--allow-partial-linkage")) { + options.SetAllowPartialLinkage(true); + } else if (0 == strcmp(cur_arg, "--version")) { + printf("%s\n", spvSoftwareVersionDetailsString()); + // TODO(dneto): Add OpenCL 2.2 at least. + printf("Targets:\n %s\n %s\n %s\n", + spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_1), + spvTargetEnvDescription(SPV_ENV_VULKAN_1_0), + spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_2)); + continue_processing = false; + return_code = 0; + } else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) { + print_usage(argv[0]); + continue_processing = false; + return_code = 0; + } else if (0 == strcmp(cur_arg, "--target-env")) { + if (argi + 1 < argc) { + const auto env_str = argv[++argi]; + if (!spvParseTargetEnv(env_str, &target_env)) { + fprintf(stderr, "error: Unrecognized target env: %s\n", env_str); + continue_processing = false; + return_code = 1; + } + } else { + fprintf(stderr, "error: Missing argument to --target-env\n"); + continue_processing = false; + return_code = 1; + } + } + } else { + inFiles.push_back(cur_arg); + } + } + + // Exit if command line parsing was not successful. + if (!continue_processing) { + return return_code; + } + + if (inFiles.empty()) { + fprintf(stderr, "error: No input file specified\n"); + return 1; + } + + std::vector> contents(inFiles.size()); + for (size_t i = 0u; i < inFiles.size(); ++i) { + if (!ReadFile(inFiles[i], "rb", &contents[i])) return 1; + } + + const spvtools::MessageConsumer consumer = [](spv_message_level_t level, + const char*, + const spv_position_t& position, + const char* message) { + switch (level) { + case SPV_MSG_FATAL: + case SPV_MSG_INTERNAL_ERROR: + case SPV_MSG_ERROR: + std::cerr << "error: " << position.index << ": " << message + << std::endl; + break; + case SPV_MSG_WARNING: + std::cout << "warning: " << position.index << ": " << message + << std::endl; + break; + case SPV_MSG_INFO: + std::cout << "info: " << position.index << ": " << message << std::endl; + break; + default: + break; + } + }; + spvtools::Context context(target_env); + context.SetMessageConsumer(consumer); + + std::vector linkingResult; + spv_result_t status = Link(context, contents, &linkingResult, options); + + if (!WriteFile(outFile, "wb", linkingResult.data(), + linkingResult.size())) + return 1; + + return status == SPV_SUCCESS ? 0 : 1; +} diff --git a/3rdparty/spirv-tools/tools/opt/opt.cpp b/3rdparty/spirv-tools/tools/opt/opt.cpp new file mode 100644 index 000000000..110e4b968 --- /dev/null +++ b/3rdparty/spirv-tools/tools/opt/opt.cpp @@ -0,0 +1,653 @@ +// Copyright (c) 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "opt/set_spec_constant_default_value_pass.h" +#include "spirv-tools/optimizer.hpp" + +#include "message.h" +#include "tools/io.h" + +using namespace spvtools; + +namespace { + +// Status and actions to perform after parsing command-line arguments. +enum OptActions { OPT_CONTINUE, OPT_STOP }; + +struct OptStatus { + OptActions action; + int code; +}; + +std::string GetListOfPassesAsString(const spvtools::Optimizer& optimizer) { + std::stringstream ss; + for (const auto& name : optimizer.GetPassNames()) { + ss << "\n\t\t" << name; + } + return ss.str(); +} + +const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_3; + +std::string GetLegalizationPasses() { + spvtools::Optimizer optimizer(kDefaultEnvironment); + optimizer.RegisterLegalizationPasses(); + return GetListOfPassesAsString(optimizer); +} + +std::string GetOptimizationPasses() { + spvtools::Optimizer optimizer(kDefaultEnvironment); + optimizer.RegisterPerformancePasses(); + return GetListOfPassesAsString(optimizer); +} + +std::string GetSizePasses() { + spvtools::Optimizer optimizer(kDefaultEnvironment); + optimizer.RegisterSizePasses(); + return GetListOfPassesAsString(optimizer); +} + +void PrintUsage(const char* program) { + // NOTE: Please maintain flags in lexicographical order. + printf( + R"(%s - Optimize a SPIR-V binary file. + +USAGE: %s [options] [] -o + +The SPIR-V binary is read from . If no file is specified, +or if is "-", then the binary is read from standard input. +if is "-", then the optimized output is written to +standard output. + +NOTE: The optimizer is a work in progress. + +Options (in lexicographical order): + --ccp + Apply the conditional constant propagation transform. This will + propagate constant values throughout the program, and simplify + expressions and conditional jumps with known predicate + values. Performed on entry point call tree functions and + exported functions. + --cfg-cleanup + Cleanup the control flow graph. This will remove any unnecessary + code from the CFG like unreachable code. Performed on entry + point call tree functions and exported functions. + --compact-ids + Remap result ids to a compact range starting from %%1 and without + any gaps. + --convert-local-access-chains + Convert constant index access chain loads/stores into + equivalent load/stores with inserts and extracts. Performed + on function scope variables referenced only with load, store, + and constant index access chains in entry point call tree + functions. + --copy-propagate-arrays + Does propagation of memory references when an array is a copy of + another. It will only propagate an array if the source is never + written to, and the only store to the target is the copy. + --eliminate-common-uniform + Perform load/load elimination for duplicate uniform values. + Converts any constant index access chain uniform loads into + its equivalent load and extract. Some loads will be moved + to facilitate sharing. Performed only on entry point + call tree functions. + --eliminate-dead-branches + Convert conditional branches with constant condition to the + indicated unconditional brranch. Delete all resulting dead + code. Performed only on entry point call tree functions. + --eliminate-dead-code-aggressive + Delete instructions which do not contribute to a function's + output. Performed only on entry point call tree functions. + --eliminate-dead-const + Eliminate dead constants. + --eliminate-dead-functions + Deletes functions that cannot be reached from entry points or + exported functions. + --eliminate-dead-insert + Deletes unreferenced inserts into composites, most notably + unused stores to vector components, that are not removed by + aggressive dead code elimination. + --eliminate-dead-variables + Deletes module scope variables that are not referenced. + --eliminate-insert-extract + Replace extract from a sequence of inserts with the + corresponding value. Performed only on entry point call tree + functions. + --eliminate-local-multi-store + Replace stores and loads of function scope variables that are + stored multiple times. Performed on variables referenceed only + with loads and stores. Performed only on entry point call tree + functions. + --eliminate-local-single-block + Perform single-block store/load and load/load elimination. + Performed only on function scope variables in entry point + call tree functions. + --eliminate-local-single-store + Replace stores and loads of function scope variables that are + only stored once. Performed on variables referenceed only with + loads and stores. Performed only on entry point call tree + functions. + --flatten-decorations + Replace decoration groups with repeated OpDecorate and + OpMemberDecorate instructions. + --fold-spec-const-op-composite + Fold the spec constants defined by OpSpecConstantOp or + OpSpecConstantComposite instructions to front-end constants + when possible. + --freeze-spec-const + Freeze the values of specialization constants to their default + values. + --if-conversion + Convert if-then-else like assignments into OpSelect. + --inline-entry-points-exhaustive + Exhaustively inline all function calls in entry point call tree + functions. Currently does not inline calls to functions with + early return in a loop. + --legalize-hlsl + Runs a series of optimizations that attempts to take SPIR-V + generated by and HLSL front-end and generate legal Vulkan SPIR-V. + The optimizations are: + %s + + Note this does not guarantee legal code. This option implies + --skip-validation. + --local-redundancy-elimination + Looks for instructions in the same basic block that compute the + same value, and deletes the redundant ones. + --loop-unroll + Fully unrolls loops marked with the Unroll flag + --loop-unroll-partial + Partially unrolls loops marked with the Unroll flag. Takes an + additional non-0 integer argument to set the unroll factor, or + how many times a loop body should be duplicated + --merge-blocks + Join two blocks into a single block if the second has the + first as its only predecessor. Performed only on entry point + call tree functions. + --merge-return + Changes functions that have multiple return statements so they + have a single return statement. + + For structured control flow it is assumed that the only + unreachable blocks in the function are trivial merge and continue + blocks. + + A trivial merge block contains the label and an OpUnreachable + instructions, nothing else. A trivial continue block contain a + label and an OpBranch to the header, nothing else. + + These conditions are guaranteed to be met after running + dead-branch elimination. + --loop-unswitch + Hoists loop-invariant conditionals out of loops by duplicating + the loop on each branch of the conditional and adjusting each + copy of the loop. + -O + Optimize for performance. Apply a sequence of transformations + in an attempt to improve the performance of the generated + code. For this version of the optimizer, this flag is equivalent + to specifying the following optimization code names: + %s + -Os + Optimize for size. Apply a sequence of transformations in an + attempt to minimize the size of the generated code. For this + version of the optimizer, this flag is equivalent to specifying + the following optimization code names: + %s + + NOTE: The specific transformations done by -O and -Os change + from release to release. + -Oconfig= + Apply the sequence of transformations indicated in . + This file contains a sequence of strings separated by whitespace + (tabs, newlines or blanks). Each string is one of the flags + accepted by spirv-opt. Optimizations will be applied in the + sequence they appear in the file. This is equivalent to + specifying all the flags on the command line. For example, + given the file opts.cfg with the content: + + --inline-entry-points-exhaustive + --eliminate-dead-code-aggressive + + The following two invocations to spirv-opt are equivalent: + + $ spirv-opt -Oconfig=opts.cfg program.spv + + $ spirv-opt --inline-entry-points-exhaustive \ + --eliminate-dead-code-aggressive program.spv + + Lines starting with the character '#' in the configuration + file indicate a comment and will be ignored. + + The -O, -Os, and -Oconfig flags act as macros. Using one of them + is equivalent to explicitly inserting the underlying flags at + that position in the command line. For example, the invocation + 'spirv-opt --merge-blocks -O ...' applies the transformation + --merge-blocks followed by all the transformations implied by + -O. + --print-all + Print SPIR-V assembly to standard error output before each pass + and after the last pass. + --private-to-local + Change the scope of private variables that are used in a single + function to that function. + --remove-duplicates + Removes duplicate types, decorations, capabilities and extension + instructions. + --redundancy-elimination + Looks for instructions in the same function that compute the + same value, and deletes the redundant ones. + --relax-struct-store + Allow store from one struct type to a different type with + compatible layout and members. This option is forwarded to the + validator. + --replace-invalid-opcode + Replaces instructions whose opcode is valid for shader modules, + but not for the current shader stage. To have an effect, all + entry points must have the same execution model. + --ssa-rewrite + Replace loads and stores to function local variables with + operations on SSA IDs. + --scalar-replacement + Replace aggregate function scope variables that are only accessed + via their elements with new function variables representing each + element. + --set-spec-const-default-value ": ..." + Set the default values of the specialization constants with + : pairs specified in a double-quoted + string. : pairs must be separated by + blank spaces, and in each pair, spec id and default value must + be separated with colon ':' without any blank spaces in between. + e.g.: --set-spec-const-default-value "1:100 2:400" + --simplify-instructions + Will simplify all instructions in the function as much as + possible. + --skip-validation + Will not validate the SPIR-V before optimizing. If the SPIR-V + is invalid, the optimizer may fail or generate incorrect code. + This options should be used rarely, and with caution. + --strength-reduction + Replaces instructions with equivalent and less expensive ones. + --strip-debug + Remove all debug instructions. + --strip-reflect + Remove all reflection information. For now, this covers + reflection information defined by SPV_GOOGLE_hlsl_functionality1. + --time-report + Print the resource utilization of each pass (e.g., CPU time, + RSS) to standard error output. Currently it supports only Unix + systems. This option is the same as -ftime-report in GCC. It + prints CPU/WALL/USR/SYS time (and RSS if possible), but note that + USR/SYS time are returned by getrusage() and can have a small + error. + --workaround-1209 + Rewrites instructions for which there are known driver bugs to + avoid triggering those bugs. + Current workarounds: Avoid OpUnreachable in loops. + --unify-const + Remove the duplicated constants. + -h, --help + Print this help. + --version + Display optimizer version information. +)", + program, program, GetLegalizationPasses().c_str(), + GetOptimizationPasses().c_str(), GetSizePasses().c_str()); +} + +// Reads command-line flags the file specified in |oconfig_flag|. This string +// is assumed to have the form "-Oconfig=FILENAME". This function parses the +// string and extracts the file name after the '=' sign. +// +// Flags found in |FILENAME| are pushed at the end of the vector |file_flags|. +// +// This function returns true on success, false on failure. +bool ReadFlagsFromFile(const char* oconfig_flag, + std::vector* file_flags) { + const char* fname = strchr(oconfig_flag, '='); + if (fname == nullptr || fname[0] != '=') { + fprintf(stderr, "error: Invalid -Oconfig flag %s\n", oconfig_flag); + return false; + } + fname++; + + std::ifstream input_file; + input_file.open(fname); + if (input_file.fail()) { + fprintf(stderr, "error: Could not open file '%s'\n", fname); + return false; + } + + while (!input_file.eof()) { + std::string flag; + input_file >> flag; + if (flag.length() > 0 && flag[0] != '#') { + file_flags->push_back(flag); + } + } + + return true; +} + +OptStatus ParseFlags(int argc, const char** argv, Optimizer* optimizer, + const char** in_file, const char** out_file, + spv_validator_options options, bool* skip_validator); + +// Parses and handles the -Oconfig flag. |prog_name| contains the name of +// the spirv-opt binary (used to build a new argv vector for the recursive +// invocation to ParseFlags). |opt_flag| contains the -Oconfig=FILENAME flag. +// |optimizer|, |in_file| and |out_file| are as in ParseFlags. +// +// This returns the same OptStatus instance returned by ParseFlags. +OptStatus ParseOconfigFlag(const char* prog_name, const char* opt_flag, + Optimizer* optimizer, const char** in_file, + const char** out_file) { + std::vector flags; + flags.push_back(prog_name); + + std::vector file_flags; + if (!ReadFlagsFromFile(opt_flag, &file_flags)) { + fprintf(stderr, + "error: Could not read optimizer flags from configuration file\n"); + return {OPT_STOP, 1}; + } + flags.insert(flags.end(), file_flags.begin(), file_flags.end()); + + const char** new_argv = new const char*[flags.size()]; + for (size_t i = 0; i < flags.size(); i++) { + if (flags[i].find("-Oconfig=") != std::string::npos) { + fprintf(stderr, + "error: Flag -Oconfig= may not be used inside the configuration " + "file\n"); + return {OPT_STOP, 1}; + } + new_argv[i] = flags[i].c_str(); + } + + bool skip_validator = false; + return ParseFlags(static_cast(flags.size()), new_argv, optimizer, + in_file, out_file, nullptr, &skip_validator); +} + +OptStatus ParseLoopUnrollPartialArg(int argc, const char** argv, int argi, + Optimizer* optimizer) { + if (argi < argc) { + int factor = atoi(argv[argi]); + if (factor != 0) { + optimizer->RegisterPass(CreateLoopUnrollPass(false, factor)); + return {OPT_CONTINUE, 0}; + } + } + fprintf(stderr, + "error: --loop-unroll-partial must be followed by a non-0 " + "integer\n"); + return {OPT_STOP, 1}; +} + +// Parses command-line flags. |argc| contains the number of command-line flags. +// |argv| points to an array of strings holding the flags. |optimizer| is the +// Optimizer instance used to optimize the program. +// +// On return, this function stores the name of the input program in |in_file|. +// The name of the output file in |out_file|. The return value indicates whether +// optimization should continue and a status code indicating an error or +// success. +OptStatus ParseFlags(int argc, const char** argv, Optimizer* optimizer, + const char** in_file, const char** out_file, + spv_validator_options options, bool* skip_validator) { + for (int argi = 1; argi < argc; ++argi) { + const char* cur_arg = argv[argi]; + if ('-' == cur_arg[0]) { + if (0 == strcmp(cur_arg, "--version")) { + printf("%s\n", spvSoftwareVersionDetailsString()); + return {OPT_STOP, 0}; + } else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) { + PrintUsage(argv[0]); + return {OPT_STOP, 0}; + } else if (0 == strcmp(cur_arg, "-o")) { + if (!*out_file && argi + 1 < argc) { + *out_file = argv[++argi]; + } else { + PrintUsage(argv[0]); + return {OPT_STOP, 1}; + } + } else if (0 == strcmp(cur_arg, "--strip-debug")) { + optimizer->RegisterPass(CreateStripDebugInfoPass()); + } else if (0 == strcmp(cur_arg, "--strip-reflect")) { + optimizer->RegisterPass(CreateStripReflectInfoPass()); + } else if (0 == strcmp(cur_arg, "--set-spec-const-default-value")) { + if (++argi < argc) { + auto spec_ids_vals = + opt::SetSpecConstantDefaultValuePass::ParseDefaultValuesString( + argv[argi]); + if (!spec_ids_vals) { + fprintf(stderr, + "error: Invalid argument for " + "--set-spec-const-default-value: %s\n", + argv[argi]); + return {OPT_STOP, 1}; + } + optimizer->RegisterPass( + CreateSetSpecConstantDefaultValuePass(std::move(*spec_ids_vals))); + } else { + fprintf( + stderr, + "error: Expected a string of : pairs."); + return {OPT_STOP, 1}; + } + } else if (0 == strcmp(cur_arg, "--if-conversion")) { + optimizer->RegisterPass(CreateIfConversionPass()); + } else if (0 == strcmp(cur_arg, "--freeze-spec-const")) { + optimizer->RegisterPass(CreateFreezeSpecConstantValuePass()); + } else if (0 == strcmp(cur_arg, "--inline-entry-points-exhaustive")) { + optimizer->RegisterPass(CreateInlineExhaustivePass()); + } else if (0 == strcmp(cur_arg, "--inline-entry-points-opaque")) { + optimizer->RegisterPass(CreateInlineOpaquePass()); + } else if (0 == strcmp(cur_arg, "--convert-local-access-chains")) { + optimizer->RegisterPass(CreateLocalAccessChainConvertPass()); + } else if (0 == strcmp(cur_arg, "--eliminate-dead-code-aggressive")) { + optimizer->RegisterPass(CreateAggressiveDCEPass()); + } else if (0 == strcmp(cur_arg, "--eliminate-insert-extract")) { + optimizer->RegisterPass(CreateInsertExtractElimPass()); + } else if (0 == strcmp(cur_arg, "--eliminate-local-single-block")) { + optimizer->RegisterPass(CreateLocalSingleBlockLoadStoreElimPass()); + } else if (0 == strcmp(cur_arg, "--eliminate-local-single-store")) { + optimizer->RegisterPass(CreateLocalSingleStoreElimPass()); + } else if (0 == strcmp(cur_arg, "--merge-blocks")) { + optimizer->RegisterPass(CreateBlockMergePass()); + } else if (0 == strcmp(cur_arg, "--merge-return")) { + optimizer->RegisterPass(CreateMergeReturnPass()); + } else if (0 == strcmp(cur_arg, "--eliminate-dead-branches")) { + optimizer->RegisterPass(CreateDeadBranchElimPass()); + } else if (0 == strcmp(cur_arg, "--eliminate-dead-functions")) { + optimizer->RegisterPass(CreateEliminateDeadFunctionsPass()); + } else if (0 == strcmp(cur_arg, "--eliminate-local-multi-store")) { + optimizer->RegisterPass(CreateLocalMultiStoreElimPass()); + } else if (0 == strcmp(cur_arg, "--eliminate-common-uniform")) { + optimizer->RegisterPass(CreateCommonUniformElimPass()); + } else if (0 == strcmp(cur_arg, "--eliminate-dead-const")) { + optimizer->RegisterPass(CreateEliminateDeadConstantPass()); + } else if (0 == strcmp(cur_arg, "--eliminate-dead-inserts")) { + optimizer->RegisterPass(CreateDeadInsertElimPass()); + } else if (0 == strcmp(cur_arg, "--eliminate-dead-variables")) { + optimizer->RegisterPass(CreateDeadVariableEliminationPass()); + } else if (0 == strcmp(cur_arg, "--fold-spec-const-op-composite")) { + optimizer->RegisterPass(CreateFoldSpecConstantOpAndCompositePass()); + } else if (0 == strcmp(cur_arg, "--loop-unswitch")) { + optimizer->RegisterPass(CreateLoopUnswitchPass()); + } else if (0 == strcmp(cur_arg, "--scalar-replacement")) { + optimizer->RegisterPass(CreateScalarReplacementPass()); + } else if (0 == strcmp(cur_arg, "--strength-reduction")) { + optimizer->RegisterPass(CreateStrengthReductionPass()); + } else if (0 == strcmp(cur_arg, "--unify-const")) { + optimizer->RegisterPass(CreateUnifyConstantPass()); + } else if (0 == strcmp(cur_arg, "--flatten-decorations")) { + optimizer->RegisterPass(CreateFlattenDecorationPass()); + } else if (0 == strcmp(cur_arg, "--compact-ids")) { + optimizer->RegisterPass(CreateCompactIdsPass()); + } else if (0 == strcmp(cur_arg, "--cfg-cleanup")) { + optimizer->RegisterPass(CreateCFGCleanupPass()); + } else if (0 == strcmp(cur_arg, "--local-redundancy-elimination")) { + optimizer->RegisterPass(CreateLocalRedundancyEliminationPass()); + } else if (0 == strcmp(cur_arg, "--loop-invariant-code-motion")) { + optimizer->RegisterPass(CreateLoopInvariantCodeMotionPass()); + } else if (0 == strcmp(cur_arg, "--redundancy-elimination")) { + optimizer->RegisterPass(CreateRedundancyEliminationPass()); + } else if (0 == strcmp(cur_arg, "--private-to-local")) { + optimizer->RegisterPass(CreatePrivateToLocalPass()); + } else if (0 == strcmp(cur_arg, "--remove-duplicates")) { + optimizer->RegisterPass(CreateRemoveDuplicatesPass()); + } else if (0 == strcmp(cur_arg, "--workaround-1209")) { + optimizer->RegisterPass(CreateWorkaround1209Pass()); + } else if (0 == strcmp(cur_arg, "--relax-struct-store")) { + options->relax_struct_store = true; + } else if (0 == strcmp(cur_arg, "--replace-invalid-opcode")) { + optimizer->RegisterPass(CreateReplaceInvalidOpcodePass()); + } else if (0 == strcmp(cur_arg, "--simplify-instructions")) { + optimizer->RegisterPass(CreateSimplificationPass()); + } else if (0 == strcmp(cur_arg, "--ssa-rewrite")) { + optimizer->RegisterPass(CreateSSARewritePass()); + } else if (0 == strcmp(cur_arg, "--copy-propagate-arrays")) { + optimizer->RegisterPass(CreateCopyPropagateArraysPass()); + } else if (0 == strcmp(cur_arg, "--loop-unroll")) { + optimizer->RegisterPass(CreateLoopUnrollPass(true)); + } else if (0 == strcmp(cur_arg, "--loop-unroll-partial")) { + OptStatus status = + ParseLoopUnrollPartialArg(argc, argv, ++argi, optimizer); + if (status.action != OPT_CONTINUE) { + return status; + } + } else if (0 == strcmp(cur_arg, "--skip-validation")) { + *skip_validator = true; + } else if (0 == strcmp(cur_arg, "-O")) { + optimizer->RegisterPerformancePasses(); + } else if (0 == strcmp(cur_arg, "-Os")) { + optimizer->RegisterSizePasses(); + } else if (0 == strcmp(cur_arg, "--legalize-hlsl")) { + *skip_validator = true; + optimizer->RegisterLegalizationPasses(); + } else if (0 == strncmp(cur_arg, "-Oconfig=", sizeof("-Oconfig=") - 1)) { + OptStatus status = + ParseOconfigFlag(argv[0], cur_arg, optimizer, in_file, out_file); + if (status.action != OPT_CONTINUE) { + return status; + } + } else if (0 == strcmp(cur_arg, "--ccp")) { + optimizer->RegisterPass(CreateCCPPass()); + } else if (0 == strcmp(cur_arg, "--print-all")) { + optimizer->SetPrintAll(&std::cerr); + } else if (0 == strcmp(cur_arg, "--time-report")) { + optimizer->SetTimeReport(&std::cerr); + } else if ('\0' == cur_arg[1]) { + // Setting a filename of "-" to indicate stdin. + if (!*in_file) { + *in_file = cur_arg; + } else { + fprintf(stderr, "error: More than one input file specified\n"); + return {OPT_STOP, 1}; + } + } else { + fprintf( + stderr, + "error: Unknown flag '%s'. Use --help for a list of valid flags\n", + cur_arg); + return {OPT_STOP, 1}; + } + } else { + if (!*in_file) { + *in_file = cur_arg; + } else { + fprintf(stderr, "error: More than one input file specified\n"); + return {OPT_STOP, 1}; + } + } + } + + return {OPT_CONTINUE, 0}; +} + +} // namespace + +int main(int argc, const char** argv) { + const char* in_file = nullptr; + const char* out_file = nullptr; + bool skip_validator = false; + + spv_target_env target_env = kDefaultEnvironment; + spv_validator_options options = spvValidatorOptionsCreate(); + + spvtools::Optimizer optimizer(target_env); + optimizer.SetMessageConsumer([](spv_message_level_t level, const char* source, + const spv_position_t& position, + const char* message) { + std::cerr << StringifyMessage(level, source, position, message) + << std::endl; + }); + + OptStatus status = ParseFlags(argc, argv, &optimizer, &in_file, &out_file, + options, &skip_validator); + + if (status.action == OPT_STOP) { + return status.code; + } + + if (out_file == nullptr) { + fprintf(stderr, "error: -o required\n"); + return 1; + } + + std::vector binary; + if (!ReadFile(in_file, "rb", &binary)) { + return 1; + } + + if (!skip_validator) { + // Let's do validation first. + spv_context context = spvContextCreate(target_env); + spv_diagnostic diagnostic = nullptr; + spv_const_binary_t binary_struct = {binary.data(), binary.size()}; + spv_result_t error = + spvValidateWithOptions(context, options, &binary_struct, &diagnostic); + if (error) { + spvDiagnosticPrint(diagnostic); + spvDiagnosticDestroy(diagnostic); + spvValidatorOptionsDestroy(options); + spvContextDestroy(context); + return error; + } + spvDiagnosticDestroy(diagnostic); + spvValidatorOptionsDestroy(options); + spvContextDestroy(context); + } + + // By using the same vector as input and output, we save time in the case + // that there was no change. + bool ok = optimizer.Run(binary.data(), binary.size(), &binary); + + if (!WriteFile(out_file, "wb", binary.data(), binary.size())) { + return 1; + } + + return ok ? 0 : 1; +} diff --git a/3rdparty/spirv-tools/tools/stats/stats.cpp b/3rdparty/spirv-tools/tools/stats/stats.cpp new file mode 100644 index 000000000..2e525fabb --- /dev/null +++ b/3rdparty/spirv-tools/tools/stats/stats.cpp @@ -0,0 +1,273 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include + +#include "source/spirv_stats.h" +#include "source/table.h" +#include "spirv-tools/libspirv.h" +#include "stats_analyzer.h" +#include "tools/io.h" + +using libspirv::SpirvStats; + +namespace { + +struct ScopedContext { + ScopedContext(spv_target_env env) : context(spvContextCreate(env)) {} + ~ScopedContext() { spvContextDestroy(context); } + spv_context context; +}; + +void PrintUsage(char* argv0) { + printf( + R"(%s - Collect statistics from one or more SPIR-V binary file(s). + +USAGE: %s [options] [] + +TIP: In order to collect statistics from all .spv files under current dir use +find . -name "*.spv" -print0 | xargs -0 -s 2000000 %s + +Options: + -h, --help + Print this help. + + -v, --verbose + Print additional info to stderr. + + --codegen_opcode_hist + Output generated C++ code for opcode histogram. + This flag disables non-C++ output. + + --codegen_opcode_and_num_operands_hist + Output generated C++ code for opcode_and_num_operands + histogram. + This flag disables non-C++ output. + + --codegen_opcode_and_num_operands_markov_huffman_codecs + Output generated C++ code for Huffman codecs of + opcode_and_num_operands Markov chain. + This flag disables non-C++ output. + + --codegen_literal_string_huffman_codecs + Output generated C++ code for Huffman codecs for + literal strings. + This flag disables non-C++ output. + + --codegen_non_id_word_huffman_codecs + Output generated C++ code for Huffman codecs for + single-word non-id slots. + This flag disables non-C++ output. + + --codegen_id_descriptor_huffman_codecs + Output generated C++ code for Huffman codecs for + common id descriptors. + This flag disables non-C++ output. +)", + argv0, argv0, argv0); +} + +void DiagnosticsMessageHandler(spv_message_level_t level, const char*, + const spv_position_t& position, + const char* message) { + switch (level) { + case SPV_MSG_FATAL: + case SPV_MSG_INTERNAL_ERROR: + case SPV_MSG_ERROR: + std::cerr << "error: " << position.index << ": " << message << std::endl; + break; + case SPV_MSG_WARNING: + std::cout << "warning: " << position.index << ": " << message + << std::endl; + break; + case SPV_MSG_INFO: + std::cout << "info: " << position.index << ": " << message << std::endl; + break; + default: + break; + } +} + +} // namespace + +int main(int argc, char** argv) { + bool continue_processing = true; + int return_code = 0; + + bool expect_output_path = false; + bool verbose = false; + bool export_text = true; + bool codegen_opcode_hist = false; + bool codegen_opcode_and_num_operands_hist = false; + bool codegen_opcode_and_num_operands_markov_huffman_codecs = false; + bool codegen_literal_string_huffman_codecs = false; + bool codegen_non_id_word_huffman_codecs = false; + bool codegen_id_descriptor_huffman_codecs = false; + + std::vector paths; + const char* output_path = nullptr; + + for (int argi = 1; continue_processing && argi < argc; ++argi) { + const char* cur_arg = argv[argi]; + if ('-' == cur_arg[0]) { + if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) { + PrintUsage(argv[0]); + continue_processing = false; + return_code = 0; + } else if (0 == strcmp(cur_arg, "--codegen_opcode_hist")) { + codegen_opcode_hist = true; + export_text = false; + } else if (0 == + strcmp(cur_arg, "--codegen_opcode_and_num_operands_hist")) { + codegen_opcode_and_num_operands_hist = true; + export_text = false; + } else if (strcmp( + "--codegen_opcode_and_num_operands_markov_huffman_codecs", + cur_arg) == 0) { + codegen_opcode_and_num_operands_markov_huffman_codecs = true; + export_text = false; + } else if (0 == + strcmp(cur_arg, "--codegen_literal_string_huffman_codecs")) { + codegen_literal_string_huffman_codecs = true; + export_text = false; + } else if (0 == strcmp(cur_arg, "--codegen_non_id_word_huffman_codecs")) { + codegen_non_id_word_huffman_codecs = true; + export_text = false; + } else if (0 == + strcmp(cur_arg, "--codegen_id_descriptor_huffman_codecs")) { + codegen_id_descriptor_huffman_codecs = true; + export_text = false; + } else if (0 == strcmp(cur_arg, "--verbose") || + 0 == strcmp(cur_arg, "-v")) { + verbose = true; + } else if (0 == strcmp(cur_arg, "--output") || + 0 == strcmp(cur_arg, "-o")) { + expect_output_path = true; + } else { + PrintUsage(argv[0]); + continue_processing = false; + return_code = 1; + } + } else { + if (expect_output_path) { + output_path = cur_arg; + expect_output_path = false; + } else { + paths.push_back(cur_arg); + } + } + } + + // Exit if command line parsing was not successful. + if (!continue_processing) { + return return_code; + } + + std::cerr << "Processing " << paths.size() << " files..." << std::endl; + + ScopedContext ctx(SPV_ENV_UNIVERSAL_1_1); + libspirv::SetContextMessageConsumer(ctx.context, DiagnosticsMessageHandler); + + libspirv::SpirvStats stats; + stats.opcode_markov_hist.resize(1); + + for (size_t index = 0; index < paths.size(); ++index) { + const size_t kMilestonePeriod = 1000; + if (verbose) { + if (index % kMilestonePeriod == kMilestonePeriod - 1) + std::cerr << "Processed " << index + 1 << " files..." << std::endl; + } + + const char* path = paths[index]; + std::vector contents; + if (!ReadFile(path, "rb", &contents)) return 1; + + if (SPV_SUCCESS != libspirv::AggregateStats(*ctx.context, contents.data(), + contents.size(), nullptr, + &stats)) { + std::cerr << "error: Failed to aggregate stats for " << path << std::endl; + return 1; + } + } + + StatsAnalyzer analyzer(stats); + + std::ofstream fout; + if (output_path) { + fout.open(output_path); + if (!fout.is_open()) { + std::cerr << "error: Failed to open " << output_path << std::endl; + return 1; + } + } + + std::ostream& out = fout.is_open() ? fout : std::cout; + + if (export_text) { + out << std::endl; + analyzer.WriteVersion(out); + analyzer.WriteGenerator(out); + + out << std::endl; + analyzer.WriteCapability(out); + + out << std::endl; + analyzer.WriteExtension(out); + + out << std::endl; + analyzer.WriteOpcode(out); + + out << std::endl; + analyzer.WriteOpcodeMarkov(out); + + out << std::endl; + analyzer.WriteConstantLiterals(out); + } + + if (codegen_opcode_hist) { + out << std::endl; + analyzer.WriteCodegenOpcodeHist(out); + } + + if (codegen_opcode_and_num_operands_hist) { + out << std::endl; + analyzer.WriteCodegenOpcodeAndNumOperandsHist(out); + } + + if (codegen_opcode_and_num_operands_markov_huffman_codecs) { + out << std::endl; + analyzer.WriteCodegenOpcodeAndNumOperandsMarkovHuffmanCodecs(out); + } + + if (codegen_literal_string_huffman_codecs) { + out << std::endl; + analyzer.WriteCodegenLiteralStringHuffmanCodecs(out); + } + + if (codegen_non_id_word_huffman_codecs) { + out << std::endl; + analyzer.WriteCodegenNonIdWordHuffmanCodecs(out); + } + + if (codegen_id_descriptor_huffman_codecs) { + out << std::endl; + analyzer.WriteCodegenIdDescriptorHuffmanCodecs(out); + } + + return 0; +} diff --git a/3rdparty/spirv-tools/tools/stats/stats_analyzer.cpp b/3rdparty/spirv-tools/tools/stats/stats_analyzer.cpp new file mode 100644 index 000000000..7ce56c965 --- /dev/null +++ b/3rdparty/spirv-tools/tools/stats/stats_analyzer.cpp @@ -0,0 +1,874 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "stats_analyzer.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "latest_version_spirv_header.h" +#include "source/comp/markv_model.h" +#include "source/enum_string_mapping.h" +#include "source/opcode.h" +#include "source/operand.h" +#include "source/spirv_constant.h" +#include "source/util/huffman_codec.h" + +using libspirv::SpirvStats; +using spvutils::HuffmanCodec; + +namespace { + +// Signals that the value is not in the coding scheme and a fallback method +// needs to be used. +const uint64_t kMarkvNoneOfTheAbove = + spvtools::MarkvModel::GetMarkvNoneOfTheAbove(); + +inline uint32_t CombineOpcodeAndNumOperands(uint32_t opcode, + uint32_t num_operands) { + return opcode | (num_operands << 16); +} + +// Returns all SPIR-V v1.2 opcodes. +std::vector GetAllOpcodes() { + return std::vector({ + SpvOpNop, + SpvOpUndef, + SpvOpSourceContinued, + SpvOpSource, + SpvOpSourceExtension, + SpvOpName, + SpvOpMemberName, + SpvOpString, + SpvOpLine, + SpvOpExtension, + SpvOpExtInstImport, + SpvOpExtInst, + SpvOpMemoryModel, + SpvOpEntryPoint, + SpvOpExecutionMode, + SpvOpCapability, + SpvOpTypeVoid, + SpvOpTypeBool, + SpvOpTypeInt, + SpvOpTypeFloat, + SpvOpTypeVector, + SpvOpTypeMatrix, + SpvOpTypeImage, + SpvOpTypeSampler, + SpvOpTypeSampledImage, + SpvOpTypeArray, + SpvOpTypeRuntimeArray, + SpvOpTypeStruct, + SpvOpTypeOpaque, + SpvOpTypePointer, + SpvOpTypeFunction, + SpvOpTypeEvent, + SpvOpTypeDeviceEvent, + SpvOpTypeReserveId, + SpvOpTypeQueue, + SpvOpTypePipe, + SpvOpTypeForwardPointer, + SpvOpConstantTrue, + SpvOpConstantFalse, + SpvOpConstant, + SpvOpConstantComposite, + SpvOpConstantSampler, + SpvOpConstantNull, + SpvOpSpecConstantTrue, + SpvOpSpecConstantFalse, + SpvOpSpecConstant, + SpvOpSpecConstantComposite, + SpvOpSpecConstantOp, + SpvOpFunction, + SpvOpFunctionParameter, + SpvOpFunctionEnd, + SpvOpFunctionCall, + SpvOpVariable, + SpvOpImageTexelPointer, + SpvOpLoad, + SpvOpStore, + SpvOpCopyMemory, + SpvOpCopyMemorySized, + SpvOpAccessChain, + SpvOpInBoundsAccessChain, + SpvOpPtrAccessChain, + SpvOpArrayLength, + SpvOpGenericPtrMemSemantics, + SpvOpInBoundsPtrAccessChain, + SpvOpDecorate, + SpvOpMemberDecorate, + SpvOpDecorationGroup, + SpvOpGroupDecorate, + SpvOpGroupMemberDecorate, + SpvOpVectorExtractDynamic, + SpvOpVectorInsertDynamic, + SpvOpVectorShuffle, + SpvOpCompositeConstruct, + SpvOpCompositeExtract, + SpvOpCompositeInsert, + SpvOpCopyObject, + SpvOpTranspose, + SpvOpSampledImage, + SpvOpImageSampleImplicitLod, + SpvOpImageSampleExplicitLod, + SpvOpImageSampleDrefImplicitLod, + SpvOpImageSampleDrefExplicitLod, + SpvOpImageSampleProjImplicitLod, + SpvOpImageSampleProjExplicitLod, + SpvOpImageSampleProjDrefImplicitLod, + SpvOpImageSampleProjDrefExplicitLod, + SpvOpImageFetch, + SpvOpImageGather, + SpvOpImageDrefGather, + SpvOpImageRead, + SpvOpImageWrite, + SpvOpImage, + SpvOpImageQueryFormat, + SpvOpImageQueryOrder, + SpvOpImageQuerySizeLod, + SpvOpImageQuerySize, + SpvOpImageQueryLod, + SpvOpImageQueryLevels, + SpvOpImageQuerySamples, + SpvOpConvertFToU, + SpvOpConvertFToS, + SpvOpConvertSToF, + SpvOpConvertUToF, + SpvOpUConvert, + SpvOpSConvert, + SpvOpFConvert, + SpvOpQuantizeToF16, + SpvOpConvertPtrToU, + SpvOpSatConvertSToU, + SpvOpSatConvertUToS, + SpvOpConvertUToPtr, + SpvOpPtrCastToGeneric, + SpvOpGenericCastToPtr, + SpvOpGenericCastToPtrExplicit, + SpvOpBitcast, + SpvOpSNegate, + SpvOpFNegate, + SpvOpIAdd, + SpvOpFAdd, + SpvOpISub, + SpvOpFSub, + SpvOpIMul, + SpvOpFMul, + SpvOpUDiv, + SpvOpSDiv, + SpvOpFDiv, + SpvOpUMod, + SpvOpSRem, + SpvOpSMod, + SpvOpFRem, + SpvOpFMod, + SpvOpVectorTimesScalar, + SpvOpMatrixTimesScalar, + SpvOpVectorTimesMatrix, + SpvOpMatrixTimesVector, + SpvOpMatrixTimesMatrix, + SpvOpOuterProduct, + SpvOpDot, + SpvOpIAddCarry, + SpvOpISubBorrow, + SpvOpUMulExtended, + SpvOpSMulExtended, + SpvOpAny, + SpvOpAll, + SpvOpIsNan, + SpvOpIsInf, + SpvOpIsFinite, + SpvOpIsNormal, + SpvOpSignBitSet, + SpvOpLessOrGreater, + SpvOpOrdered, + SpvOpUnordered, + SpvOpLogicalEqual, + SpvOpLogicalNotEqual, + SpvOpLogicalOr, + SpvOpLogicalAnd, + SpvOpLogicalNot, + SpvOpSelect, + SpvOpIEqual, + SpvOpINotEqual, + SpvOpUGreaterThan, + SpvOpSGreaterThan, + SpvOpUGreaterThanEqual, + SpvOpSGreaterThanEqual, + SpvOpULessThan, + SpvOpSLessThan, + SpvOpULessThanEqual, + SpvOpSLessThanEqual, + SpvOpFOrdEqual, + SpvOpFUnordEqual, + SpvOpFOrdNotEqual, + SpvOpFUnordNotEqual, + SpvOpFOrdLessThan, + SpvOpFUnordLessThan, + SpvOpFOrdGreaterThan, + SpvOpFUnordGreaterThan, + SpvOpFOrdLessThanEqual, + SpvOpFUnordLessThanEqual, + SpvOpFOrdGreaterThanEqual, + SpvOpFUnordGreaterThanEqual, + SpvOpShiftRightLogical, + SpvOpShiftRightArithmetic, + SpvOpShiftLeftLogical, + SpvOpBitwiseOr, + SpvOpBitwiseXor, + SpvOpBitwiseAnd, + SpvOpNot, + SpvOpBitFieldInsert, + SpvOpBitFieldSExtract, + SpvOpBitFieldUExtract, + SpvOpBitReverse, + SpvOpBitCount, + SpvOpDPdx, + SpvOpDPdy, + SpvOpFwidth, + SpvOpDPdxFine, + SpvOpDPdyFine, + SpvOpFwidthFine, + SpvOpDPdxCoarse, + SpvOpDPdyCoarse, + SpvOpFwidthCoarse, + SpvOpEmitVertex, + SpvOpEndPrimitive, + SpvOpEmitStreamVertex, + SpvOpEndStreamPrimitive, + SpvOpControlBarrier, + SpvOpMemoryBarrier, + SpvOpAtomicLoad, + SpvOpAtomicStore, + SpvOpAtomicExchange, + SpvOpAtomicCompareExchange, + SpvOpAtomicCompareExchangeWeak, + SpvOpAtomicIIncrement, + SpvOpAtomicIDecrement, + SpvOpAtomicIAdd, + SpvOpAtomicISub, + SpvOpAtomicSMin, + SpvOpAtomicUMin, + SpvOpAtomicSMax, + SpvOpAtomicUMax, + SpvOpAtomicAnd, + SpvOpAtomicOr, + SpvOpAtomicXor, + SpvOpPhi, + SpvOpLoopMerge, + SpvOpSelectionMerge, + SpvOpLabel, + SpvOpBranch, + SpvOpBranchConditional, + SpvOpSwitch, + SpvOpKill, + SpvOpReturn, + SpvOpReturnValue, + SpvOpUnreachable, + SpvOpLifetimeStart, + SpvOpLifetimeStop, + SpvOpGroupAsyncCopy, + SpvOpGroupWaitEvents, + SpvOpGroupAll, + SpvOpGroupAny, + SpvOpGroupBroadcast, + SpvOpGroupIAdd, + SpvOpGroupFAdd, + SpvOpGroupFMin, + SpvOpGroupUMin, + SpvOpGroupSMin, + SpvOpGroupFMax, + SpvOpGroupUMax, + SpvOpGroupSMax, + SpvOpReadPipe, + SpvOpWritePipe, + SpvOpReservedReadPipe, + SpvOpReservedWritePipe, + SpvOpReserveReadPipePackets, + SpvOpReserveWritePipePackets, + SpvOpCommitReadPipe, + SpvOpCommitWritePipe, + SpvOpIsValidReserveId, + SpvOpGetNumPipePackets, + SpvOpGetMaxPipePackets, + SpvOpGroupReserveReadPipePackets, + SpvOpGroupReserveWritePipePackets, + SpvOpGroupCommitReadPipe, + SpvOpGroupCommitWritePipe, + SpvOpEnqueueMarker, + SpvOpEnqueueKernel, + SpvOpGetKernelNDrangeSubGroupCount, + SpvOpGetKernelNDrangeMaxSubGroupSize, + SpvOpGetKernelWorkGroupSize, + SpvOpGetKernelPreferredWorkGroupSizeMultiple, + SpvOpRetainEvent, + SpvOpReleaseEvent, + SpvOpCreateUserEvent, + SpvOpIsValidEvent, + SpvOpSetUserEventStatus, + SpvOpCaptureEventProfilingInfo, + SpvOpGetDefaultQueue, + SpvOpBuildNDRange, + SpvOpImageSparseSampleImplicitLod, + SpvOpImageSparseSampleExplicitLod, + SpvOpImageSparseSampleDrefImplicitLod, + SpvOpImageSparseSampleDrefExplicitLod, + SpvOpImageSparseSampleProjImplicitLod, + SpvOpImageSparseSampleProjExplicitLod, + SpvOpImageSparseSampleProjDrefImplicitLod, + SpvOpImageSparseSampleProjDrefExplicitLod, + SpvOpImageSparseFetch, + SpvOpImageSparseGather, + SpvOpImageSparseDrefGather, + SpvOpImageSparseTexelsResident, + SpvOpNoLine, + SpvOpAtomicFlagTestAndSet, + SpvOpAtomicFlagClear, + SpvOpImageSparseRead, + SpvOpSizeOf, + SpvOpTypePipeStorage, + SpvOpConstantPipeStorage, + SpvOpCreatePipeFromPipeStorage, + SpvOpGetKernelLocalSizeForSubgroupCount, + SpvOpGetKernelMaxNumSubgroups, + SpvOpTypeNamedBarrier, + SpvOpNamedBarrierInitialize, + SpvOpMemoryNamedBarrier, + SpvOpModuleProcessed, + SpvOpExecutionModeId, + SpvOpDecorateId, + SpvOpSubgroupBallotKHR, + SpvOpSubgroupFirstInvocationKHR, + SpvOpSubgroupAllKHR, + SpvOpSubgroupAnyKHR, + SpvOpSubgroupAllEqualKHR, + SpvOpSubgroupReadInvocationKHR, + }); +} + +std::string GetVersionString(uint32_t word) { + std::stringstream ss; + ss << "Version " << SPV_SPIRV_VERSION_MAJOR_PART(word) << "." + << SPV_SPIRV_VERSION_MINOR_PART(word); + return ss.str(); +} + +std::string GetGeneratorString(uint32_t word) { + return spvGeneratorStr(SPV_GENERATOR_TOOL_PART(word)); +} + +std::string GetOpcodeString(uint32_t word) { + return spvOpcodeString(static_cast(word)); +} + +std::string GetCapabilityString(uint32_t word) { + return libspirv::CapabilityToString(static_cast(word)); +} + +template +std::string KeyIsLabel(T key) { + std::stringstream ss; + ss << key; + return ss.str(); +} + +template +std::unordered_map GetRecall( + const std::unordered_map& hist, uint64_t total) { + std::unordered_map freq; + for (const auto& pair : hist) { + const double frequency = + static_cast(pair.second) / static_cast(total); + freq.emplace(pair.first, frequency); + } + return freq; +} + +template +std::unordered_map GetPrevalence( + const std::unordered_map& hist) { + uint64_t total = 0; + for (const auto& pair : hist) { + total += pair.second; + } + + return GetRecall(hist, total); +} + +// Writes |freq| to |out| sorted by frequency in the following format: +// LABEL3 70% +// LABEL1 20% +// LABEL2 10% +// |label_from_key| is used to convert |Key| to label. +template +void WriteFreq(std::ostream& out, const std::unordered_map& freq, + std::string (*label_from_key)(Key), double threshold = 0.001) { + std::vector> sorted_freq(freq.begin(), freq.end()); + std::sort(sorted_freq.begin(), sorted_freq.end(), + [](const std::pair& left, + const std::pair& right) { + return left.second > right.second; + }); + + for (const auto& pair : sorted_freq) { + if (pair.second < threshold) break; + out << label_from_key(pair.first) << " " << pair.second * 100.0 << "%" + << std::endl; + } +} + +// Writes |hist| to |out| sorted by count in the following format: +// LABEL3 100 +// LABEL1 50 +// LABEL2 10 +// |label_from_key| is used to convert |Key| to label. +template +void WriteHist(std::ostream& out, const std::unordered_map& hist, + std::string (*label_from_key)(Key)) { + std::vector> sorted_hist(hist.begin(), hist.end()); + std::sort(sorted_hist.begin(), sorted_hist.end(), + [](const std::pair& left, + const std::pair& right) { + return left.second > right.second; + }); + + for (const auto& pair : sorted_hist) { + out << label_from_key(pair.first) << " " << pair.second << std::endl; + } +} + +} // namespace + +StatsAnalyzer::StatsAnalyzer(const SpirvStats& stats) : stats_(stats) { + num_modules_ = 0; + for (const auto& pair : stats_.version_hist) { + num_modules_ += pair.second; + } + + version_freq_ = GetRecall(stats_.version_hist, num_modules_); + generator_freq_ = GetRecall(stats_.generator_hist, num_modules_); + capability_freq_ = GetRecall(stats_.capability_hist, num_modules_); + extension_freq_ = GetRecall(stats_.extension_hist, num_modules_); + opcode_freq_ = GetPrevalence(stats_.opcode_hist); +} + +void StatsAnalyzer::WriteVersion(std::ostream& out) { + WriteFreq(out, version_freq_, GetVersionString); +} + +void StatsAnalyzer::WriteGenerator(std::ostream& out) { + WriteFreq(out, generator_freq_, GetGeneratorString); +} + +void StatsAnalyzer::WriteCapability(std::ostream& out) { + WriteFreq(out, capability_freq_, GetCapabilityString); +} + +void StatsAnalyzer::WriteExtension(std::ostream& out) { + WriteFreq(out, extension_freq_, KeyIsLabel); +} + +void StatsAnalyzer::WriteOpcode(std::ostream& out) { + out << "Total unique opcodes used: " << opcode_freq_.size() << std::endl; + WriteFreq(out, opcode_freq_, GetOpcodeString); +} + +void StatsAnalyzer::WriteConstantLiterals(std::ostream& out) { + out << "Constant literals" << std::endl; + + out << "Float 32" << std::endl; + WriteFreq(out, GetPrevalence(stats_.f32_constant_hist), KeyIsLabel); + + out << std::endl << "Float 64" << std::endl; + WriteFreq(out, GetPrevalence(stats_.f64_constant_hist), KeyIsLabel); + + out << std::endl << "Unsigned int 16" << std::endl; + WriteFreq(out, GetPrevalence(stats_.u16_constant_hist), KeyIsLabel); + + out << std::endl << "Signed int 16" << std::endl; + WriteFreq(out, GetPrevalence(stats_.s16_constant_hist), KeyIsLabel); + + out << std::endl << "Unsigned int 32" << std::endl; + WriteFreq(out, GetPrevalence(stats_.u32_constant_hist), KeyIsLabel); + + out << std::endl << "Signed int 32" << std::endl; + WriteFreq(out, GetPrevalence(stats_.s32_constant_hist), KeyIsLabel); + + out << std::endl << "Unsigned int 64" << std::endl; + WriteFreq(out, GetPrevalence(stats_.u64_constant_hist), KeyIsLabel); + + out << std::endl << "Signed int 64" << std::endl; + WriteFreq(out, GetPrevalence(stats_.s64_constant_hist), KeyIsLabel); +} + +void StatsAnalyzer::WriteOpcodeMarkov(std::ostream& out) { + if (stats_.opcode_markov_hist.empty()) return; + + const std::unordered_map>& + cue_to_hist = stats_.opcode_markov_hist[0]; + + // Sort by prevalence of the opcodes in opcode_freq_ (descending). + std::vector>> + sorted_cue_to_hist(cue_to_hist.begin(), cue_to_hist.end()); + std::sort( + sorted_cue_to_hist.begin(), sorted_cue_to_hist.end(), + [this](const std::pair>& + left, + const std::pair>& + right) { + const double lf = opcode_freq_[left.first]; + const double rf = opcode_freq_[right.first]; + if (lf == rf) return right.first > left.first; + return lf > rf; + }); + + for (const auto& kv : sorted_cue_to_hist) { + const uint32_t cue = kv.first; + const double kFrequentEnoughToAnalyze = 0.0001; + if (opcode_freq_[cue] < kFrequentEnoughToAnalyze) continue; + + const std::unordered_map& hist = kv.second; + + uint32_t total = 0; + for (const auto& pair : hist) { + total += pair.second; + } + + std::vector> sorted_hist(hist.begin(), + hist.end()); + std::sort(sorted_hist.begin(), sorted_hist.end(), + [](const std::pair& left, + const std::pair& right) { + if (left.second == right.second) + return right.first > left.first; + return left.second > right.second; + }); + + for (const auto& pair : sorted_hist) { + const double prior = opcode_freq_[pair.first]; + const double posterior = + static_cast(pair.second) / static_cast(total); + out << GetOpcodeString(cue) << " -> " << GetOpcodeString(pair.first) + << " " << posterior * 100 << "% (base rate " << prior * 100 + << "%, pair occurrences " << pair.second << ")" << std::endl; + } + } +} + +void StatsAnalyzer::WriteCodegenOpcodeHist(std::ostream& out) { + auto all_opcodes = GetAllOpcodes(); + + // uint64_t is used because kMarkvNoneOfTheAbove is outside of uint32_t range. + out << "std::map GetOpcodeHist() {\n" + << " return std::map({\n"; + + uint32_t total = 0; + for (const auto& kv : stats_.opcode_hist) { + total += kv.second; + } + + for (uint32_t opcode : all_opcodes) { + const auto it = stats_.opcode_hist.find(opcode); + const uint32_t count = it == stats_.opcode_hist.end() ? 0 : it->second; + const double kMaxValue = 1000.0; + uint32_t value = uint32_t(kMaxValue * double(count) / double(total)); + if (value == 0) value = 1; + out << " { SpvOp" << GetOpcodeString(opcode) << ", " << value << " },\n"; + } + + // Add kMarkvNoneOfTheAbove as a signal for unknown opcode. + out << " { kMarkvNoneOfTheAbove, " << 10 << " },\n"; + out << " });\n}\n"; +} + +void StatsAnalyzer::WriteCodegenOpcodeAndNumOperandsHist(std::ostream& out) { + out << "std::map GetOpcodeAndNumOperandsHist() {\n" + << " return std::map({\n"; + + uint32_t total = 0; + for (const auto& kv : stats_.opcode_and_num_operands_hist) { + total += kv.second; + } + + uint32_t left_out = 0; + + for (const auto& kv : stats_.opcode_and_num_operands_hist) { + const uint32_t count = kv.second; + const double kFrequentEnoughToAnalyze = 0.001; + const uint32_t opcode_and_num_operands = kv.first; + const uint32_t opcode = opcode_and_num_operands & 0xFFFF; + const uint32_t num_operands = opcode_and_num_operands >> 16; + + if (opcode == SpvOpTypeStruct || + double(count) / double(total) < kFrequentEnoughToAnalyze) { + left_out += count; + continue; + } + + out << " { CombineOpcodeAndNumOperands(SpvOp" + << spvOpcodeString(SpvOp(opcode)) << ", " << num_operands << "), " + << count << " },\n"; + } + + // Heuristic. + const uint32_t none_of_the_above = std::max(1, int(left_out + total * 0.01)); + out << " { kMarkvNoneOfTheAbove, " << none_of_the_above << " },\n"; + out << " });\n}\n"; +} + +void StatsAnalyzer::WriteCodegenOpcodeAndNumOperandsMarkovHuffmanCodecs( + std::ostream& out) { + out << "std::map>>\n" + << "GetOpcodeAndNumOperandsMarkovHuffmanCodecs() {\n" + << " std::map>> " + << "codecs;\n"; + + for (const auto& kv : stats_.opcode_and_num_operands_markov_hist) { + const uint32_t prev_opcode = kv.first; + const double kFrequentEnoughToAnalyze = 0.001; + if (opcode_freq_[prev_opcode] < kFrequentEnoughToAnalyze) continue; + + const std::unordered_map& hist = kv.second; + + uint32_t total = 0; + for (const auto& pair : hist) { + total += pair.second; + } + + uint32_t left_out = 0; + + std::map processed_hist; + for (const auto& pair : hist) { + const uint32_t opcode_and_num_operands = pair.first; + const uint32_t opcode = opcode_and_num_operands & 0xFFFF; + + if (opcode == SpvOpTypeStruct) continue; + + const uint32_t num_operands = opcode_and_num_operands >> 16; + const uint32_t count = pair.second; + const double posterior_freq = double(count) / double(total); + + if (opcode_freq_[opcode] < kFrequentEnoughToAnalyze && + posterior_freq < kFrequentEnoughToAnalyze) { + left_out += count; + continue; + } + processed_hist.emplace(CombineOpcodeAndNumOperands(opcode, num_operands), + count); + } + + // Heuristic. + processed_hist.emplace(kMarkvNoneOfTheAbove, + std::max(1, int(left_out + total * 0.01))); + + HuffmanCodec codec(processed_hist); + + out << " {\n"; + out << " std::unique_ptr> " + << "codec(new HuffmanCodec"; + out << codec.SerializeToText(4); + out << ");\n" << std::endl; + out << " codecs.emplace(SpvOp" << GetOpcodeString(prev_opcode) + << ", std::move(codec));\n"; + out << " }\n\n"; + } + + out << " return codecs;\n}\n"; +} + +void StatsAnalyzer::WriteCodegenLiteralStringHuffmanCodecs(std::ostream& out) { + out << "std::map>>\n" + << "GetLiteralStringHuffmanCodecs() {\n" + << " std::map>> " + << "codecs;\n"; + + for (const auto& kv : stats_.literal_strings_hist) { + const uint32_t opcode = kv.first; + + if (opcode == SpvOpName || opcode == SpvOpMemberName) continue; + + const double kOpcodeFrequentEnoughToAnalyze = 0.001; + if (opcode_freq_[opcode] < kOpcodeFrequentEnoughToAnalyze) continue; + + const std::unordered_map& hist = kv.second; + + uint32_t total = 0; + for (const auto& pair : hist) { + total += pair.second; + } + + uint32_t left_out = 0; + + std::map processed_hist; + for (const auto& pair : hist) { + const uint32_t count = pair.second; + const double freq = double(count) / double(total); + const double kStringFrequentEnoughToAnalyze = 0.001; + if (freq < kStringFrequentEnoughToAnalyze) { + left_out += count; + continue; + } + processed_hist.emplace(pair.first, count); + } + + // Heuristic. + processed_hist.emplace("kMarkvNoneOfTheAbove", + std::max(1, int(left_out + total * 0.01))); + + HuffmanCodec codec(processed_hist); + + out << " {\n"; + out << " std::unique_ptr> " + << "codec(new HuffmanCodec"; + out << codec.SerializeToText(4); + out << ");\n" << std::endl; + out << " codecs.emplace(SpvOp" << spvOpcodeString(SpvOp(opcode)) + << ", std::move(codec));\n"; + out << " }\n\n"; + } + + out << " return codecs;\n}\n"; +} + +void StatsAnalyzer::WriteCodegenNonIdWordHuffmanCodecs(std::ostream& out) { + out << "std::map, " + << "std::unique_ptr>>\n" + << "GetNonIdWordHuffmanCodecs() {\n" + << " std::map, " + << "std::unique_ptr>> codecs;\n"; + + for (const auto& kv : stats_.operand_slot_non_id_words_hist) { + const auto& opcode_and_index = kv.first; + const uint32_t opcode = opcode_and_index.first; + const uint32_t index = opcode_and_index.second; + + const double kOpcodeFrequentEnoughToAnalyze = 0.001; + if (opcode_freq_[opcode] < kOpcodeFrequentEnoughToAnalyze) continue; + + const std::map& hist = kv.second; + + uint32_t total = 0; + for (const auto& pair : hist) { + total += pair.second; + } + + uint32_t left_out = 0; + + std::map processed_hist; + for (const auto& pair : hist) { + const uint32_t word = pair.first; + const uint32_t count = pair.second; + const double freq = double(count) / double(total); + const double kWordFrequentEnoughToAnalyze = 0.003; + if (freq < kWordFrequentEnoughToAnalyze) { + left_out += count; + continue; + } + processed_hist.emplace(word, count); + } + + // Heuristic. + processed_hist.emplace(kMarkvNoneOfTheAbove, + std::max(1, int(left_out + total * 0.01))); + + HuffmanCodec codec(processed_hist); + + out << " {\n"; + out << " std::unique_ptr> " + << "codec(new HuffmanCodec"; + out << codec.SerializeToText(4); + out << ");\n" << std::endl; + out << " codecs.emplace(std::pair(SpvOp" + << spvOpcodeString(SpvOp(opcode)) << ", " << index + << "), std::move(codec));\n"; + out << " }\n\n"; + } + + out << " return codecs;\n}\n"; +} + +void StatsAnalyzer::WriteCodegenIdDescriptorHuffmanCodecs(std::ostream& out) { + out << "std::map, " + << "std::unique_ptr>>\n" + << "GetIdDescriptorHuffmanCodecs() {\n" + << " std::map, " + << "std::unique_ptr>> codecs;\n"; + + std::unordered_set descriptors_with_coding_scheme; + + for (const auto& kv : stats_.operand_slot_id_descriptor_hist) { + const auto& opcode_and_index = kv.first; + const uint32_t opcode = opcode_and_index.first; + const uint32_t index = opcode_and_index.second; + + const double kOpcodeFrequentEnoughToAnalyze = 0.003; + if (opcode_freq_[opcode] < kOpcodeFrequentEnoughToAnalyze) continue; + + const std::map& hist = kv.second; + + uint32_t total = 0; + for (const auto& pair : hist) { + total += pair.second; + } + + uint32_t left_out = 0; + + std::map processed_hist; + for (const auto& pair : hist) { + const uint32_t descriptor = pair.first; + const uint32_t count = pair.second; + const double freq = double(count) / double(total); + const double kDescriptorFrequentEnoughToAnalyze = 0.003; + if (freq < kDescriptorFrequentEnoughToAnalyze) { + left_out += count; + continue; + } + processed_hist.emplace(descriptor, count); + descriptors_with_coding_scheme.insert(descriptor); + } + + // Heuristic. + processed_hist.emplace(kMarkvNoneOfTheAbove, + std::max(1, int(left_out + total * 0.01))); + + HuffmanCodec codec(processed_hist); + + out << " {\n"; + out << " std::unique_ptr> " + << "codec(new HuffmanCodec"; + out << codec.SerializeToText(4); + out << ");\n" << std::endl; + out << " codecs.emplace(std::pair(SpvOp" + << spvOpcodeString(SpvOp(opcode)) << ", " << index + << "), std::move(codec));\n"; + out << " }\n\n"; + } + + out << " return codecs;\n}\n"; + + out << "\nstd::unordered_set GetDescriptorsWithCodingScheme() {\n" + << " std::unordered_set descriptors_with_coding_scheme = {\n"; + for (uint32_t descriptor : descriptors_with_coding_scheme) { + out << " " << descriptor << ",\n"; + } + out << " };\n"; + out << " return descriptors_with_coding_scheme;\n}\n"; +} diff --git a/3rdparty/spirv-tools/tools/stats/stats_analyzer.h b/3rdparty/spirv-tools/tools/stats/stats_analyzer.h new file mode 100644 index 000000000..a3b91baef --- /dev/null +++ b/3rdparty/spirv-tools/tools/stats/stats_analyzer.h @@ -0,0 +1,80 @@ +// Copyright (c) 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_TOOLS_STATS_STATS_ANALYZER_H_ +#define LIBSPIRV_TOOLS_STATS_STATS_ANALYZER_H_ + +#include + +#include "source/spirv_stats.h" + +class StatsAnalyzer { + public: + explicit StatsAnalyzer(const libspirv::SpirvStats& stats); + + // Writes respective histograms to |out|. + void WriteVersion(std::ostream& out); + void WriteGenerator(std::ostream& out); + void WriteCapability(std::ostream& out); + void WriteExtension(std::ostream& out); + void WriteOpcode(std::ostream& out); + void WriteConstantLiterals(std::ostream& out); + + // Writes first order Markov analysis to |out|. + // stats_.opcode_markov_hist needs to contain raw data for at least one + // level. + void WriteOpcodeMarkov(std::ostream& out); + + // Writes C++ code containing a function returning opcode histogram. + void WriteCodegenOpcodeHist(std::ostream& out); + + // Writes C++ code containing a function returning opcode_and_num_operands + // histogram. + void WriteCodegenOpcodeAndNumOperandsHist(std::ostream& out); + + // Writes C++ code containing a function returning a map of Huffman codecs + // for opcode_and_num_operands. Each Huffman codec is created for a specific + // previous opcode. + // TODO(atgoo@github.com) Write code which would contain pregenerated Huffman + // codecs, instead of code which would generate them every time. + void WriteCodegenOpcodeAndNumOperandsMarkovHuffmanCodecs(std::ostream& out); + + // Writes C++ code containing a function returning a map of Huffman codecs + // for literal strings. Each Huffman codec is created for a specific opcode. + // I.e. OpExtension and OpExtInstImport would use different codecs. + void WriteCodegenLiteralStringHuffmanCodecs(std::ostream& out); + + // Writes C++ code containing a function returning a map of Huffman codecs + // for single-word non-id operands. Each Huffman codec is created for a + // specific operand slot (opcode and operand number). + void WriteCodegenNonIdWordHuffmanCodecs(std::ostream& out); + + // Writes C++ code containing a function returning a map of Huffman codecs + // for common id descriptors. Each Huffman codec is created for a + // specific operand slot (opcode and operand number). + void WriteCodegenIdDescriptorHuffmanCodecs(std::ostream& out); + + private: + const libspirv::SpirvStats& stats_; + + uint32_t num_modules_; + + std::unordered_map version_freq_; + std::unordered_map generator_freq_; + std::unordered_map capability_freq_; + std::unordered_map extension_freq_; + std::unordered_map opcode_freq_; +}; + +#endif // LIBSPIRV_TOOLS_STATS_STATS_ANALYZER_H_ diff --git a/3rdparty/spirv-tools/tools/val/val.cpp b/3rdparty/spirv-tools/tools/val/val.cpp new file mode 100644 index 000000000..be10aaf27 --- /dev/null +++ b/3rdparty/spirv-tools/tools/val/val.cpp @@ -0,0 +1,183 @@ +// Copyright (c) 2015-2016 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include + +#include "source/spirv_target_env.h" +#include "source/spirv_validator_options.h" +#include "spirv-tools/libspirv.hpp" +#include "tools/io.h" + +void print_usage(char* argv0) { + printf( + R"(%s - Validate a SPIR-V binary file. + +USAGE: %s [options] [] + +The SPIR-V binary is read from . If no file is specified, +or if the filename is "-", then the binary is read from standard input. + +NOTE: The validator is a work in progress. + +Options: + -h, --help Print this help. + --max-struct-members + --max-struct-depth + --max-local-variables + --max-global-variables + --max-switch-branches + --max-function-args + --max-control-flow-nesting-depth + --max-access-chain-indexes + --relax-logcial-pointer Allow allocating an object of a pointer type and returning + a pointer value from a function in logical addressing mode + --relax-struct-store Allow store from one struct type to a + different type with compatible layout and + members. + --version Display validator version information. + --target-env {vulkan1.0|spv1.0|spv1.1|spv1.2} + Use Vulkan1.0/SPIR-V1.0/SPIR-V1.1/SPIR-V1.2 validation rules. +)", + argv0, argv0); +} + +int main(int argc, char** argv) { + const char* inFile = nullptr; + spv_target_env target_env = SPV_ENV_UNIVERSAL_1_3; + spvtools::ValidatorOptions options; + bool continue_processing = true; + int return_code = 0; + + for (int argi = 1; continue_processing && argi < argc; ++argi) { + const char* cur_arg = argv[argi]; + if ('-' == cur_arg[0]) { + if (0 == strncmp(cur_arg, "--max-", 6)) { + if (argi + 1 < argc) { + spv_validator_limit limit_type; + if (spvParseUniversalLimitsOptions(cur_arg, &limit_type)) { + uint32_t limit = 0; + if (sscanf(argv[++argi], "%u", &limit)) { + options.SetUniversalLimit(limit_type, limit); + } else { + fprintf(stderr, "error: missing argument to %s\n", cur_arg); + continue_processing = false; + return_code = 1; + } + } else { + fprintf(stderr, "error: unrecognized option: %s\n", cur_arg); + continue_processing = false; + return_code = 1; + } + } else { + fprintf(stderr, "error: Missing argument to %s\n", cur_arg); + continue_processing = false; + return_code = 1; + } + } else if (0 == strcmp(cur_arg, "--version")) { + printf("%s\n", spvSoftwareVersionDetailsString()); + printf("Targets:\n %s\n %s\n %s\n %s\n %s\n %s\n %s\n", + spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_0), + spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_1), + spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_2), + spvTargetEnvDescription(SPV_ENV_UNIVERSAL_1_3), + spvTargetEnvDescription(SPV_ENV_OPENCL_2_2), + spvTargetEnvDescription(SPV_ENV_VULKAN_1_0), + spvTargetEnvDescription(SPV_ENV_VULKAN_1_1)); + continue_processing = false; + return_code = 0; + } else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) { + print_usage(argv[0]); + continue_processing = false; + return_code = 0; + } else if (0 == strcmp(cur_arg, "--target-env")) { + if (argi + 1 < argc) { + const auto env_str = argv[++argi]; + if (!spvParseTargetEnv(env_str, &target_env)) { + fprintf(stderr, "error: Unrecognized target env: %s\n", env_str); + continue_processing = false; + return_code = 1; + } + } else { + fprintf(stderr, "error: Missing argument to --target-env\n"); + continue_processing = false; + return_code = 1; + } + } else if (0 == strcmp(cur_arg, "--relax-logical-pointer")) { + options.SetRelaxLogicalPointer(true); + } else if (0 == strcmp(cur_arg, "--relax-struct-store")) { + options.SetRelaxStructStore(true); + } else if (0 == cur_arg[1]) { + // Setting a filename of "-" to indicate stdin. + if (!inFile) { + inFile = cur_arg; + } else { + fprintf(stderr, "error: More than one input file specified\n"); + continue_processing = false; + return_code = 1; + } + } else { + print_usage(argv[0]); + continue_processing = false; + return_code = 1; + } + } else { + if (!inFile) { + inFile = cur_arg; + } else { + fprintf(stderr, "error: More than one input file specified\n"); + continue_processing = false; + return_code = 1; + } + } + } + + // Exit if command line parsing was not successful. + if (!continue_processing) { + return return_code; + } + + std::vector contents; + if (!ReadFile(inFile, "rb", &contents)) return 1; + + spvtools::SpirvTools tools(target_env); + tools.SetMessageConsumer([](spv_message_level_t level, const char*, + const spv_position_t& position, + const char* message) { + switch (level) { + case SPV_MSG_FATAL: + case SPV_MSG_INTERNAL_ERROR: + case SPV_MSG_ERROR: + std::cerr << "error: " << position.index << ": " << message + << std::endl; + break; + case SPV_MSG_WARNING: + std::cout << "warning: " << position.index << ": " << message + << std::endl; + break; + case SPV_MSG_INFO: + std::cout << "info: " << position.index << ": " << message << std::endl; + break; + default: + break; + } + }); + + bool succeed = tools.Validate(contents.data(), contents.size(), options); + + return !succeed; +} diff --git a/3rdparty/spirv-tools/utils/check_code_format.sh b/3rdparty/spirv-tools/utils/check_code_format.sh new file mode 100644 index 000000000..a6a58796a --- /dev/null +++ b/3rdparty/spirv-tools/utils/check_code_format.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# Copyright (c) 2017 Google Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Script to determine if source code in Pull Request is properly formatted. +# Exits with non 0 exit code if formatting is needed. +# +# This script assumes to be invoked at the project root directory. + +FILES_TO_CHECK=$(git diff --name-only master | grep -E ".*\.(cpp|cc|c\+\+|cxx|c|h|hpp)$") + +if [ -z "${FILES_TO_CHECK}" ]; then + echo "No source code to check for formatting." + exit 0 +fi + +FORMAT_DIFF=$(git diff -U0 master -- ${FILES_TO_CHECK} | python ./utils/clang-format-diff.py -p1 -style=file) + +if [ -z "${FORMAT_DIFF}" ]; then + echo "All source code in PR properly formatted." + exit 0 +else + echo "Found formatting errors!" + echo "${FORMAT_DIFF}" + exit 1 +fi diff --git a/3rdparty/spirv-tools/utils/check_copyright.py b/3rdparty/spirv-tools/utils/check_copyright.py new file mode 100644 index 000000000..85ea12891 --- /dev/null +++ b/3rdparty/spirv-tools/utils/check_copyright.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python +# Copyright (c) 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Checks for copyright notices in all the files that need them under the +current directory. Optionally insert them. When inserting, replaces +an MIT or Khronos free use license with Apache 2. +""" +from __future__ import print_function + +import argparse +import fileinput +import fnmatch +import inspect +import os +import re +import sys + +# List of designated copyright owners. +AUTHORS = ['The Khronos Group Inc.', + 'LunarG Inc.', + 'Google Inc.', + 'Google LLC', + 'Pierre Moreau'] +CURRENT_YEAR='2018' + +YEARS = '(2014-2016|2015-2016|2016|2016-2017|2017|2018)' +COPYRIGHT_RE = re.compile( + 'Copyright \(c\) {} ({})'.format(YEARS, '|'.join(AUTHORS))) + +MIT_BEGIN_RE = re.compile('Permission is hereby granted, ' + 'free of charge, to any person obtaining a') +MIT_END_RE = re.compile('MATERIALS OR THE USE OR OTHER DEALINGS IN ' + 'THE MATERIALS.') +APACHE2_BEGIN_RE = re.compile('Licensed under the Apache License, ' + 'Version 2.0 \(the "License"\);') +APACHE2_END_RE = re.compile('limitations under the License.') + +LICENSED = """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.""" +LICENSED_LEN = 10 # Number of lines in LICENSED + + +def find(top, filename_glob, skip_glob_dir_list, skip_glob_files_list): + """Returns files in the tree rooted at top matching filename_glob but not + in directories matching skip_glob_dir_list nor files matching + skip_glob_dir_list.""" + + file_list = [] + for path, dirs, files in os.walk(top): + for glob in skip_glob_dir_list: + for match in fnmatch.filter(dirs, glob): + dirs.remove(match) + for filename in fnmatch.filter(files, filename_glob): + full_file = os.path.join(path, filename) + if full_file not in skip_glob_files_list: + file_list.append(full_file) + return file_list + + +def filtered_descendants(glob): + """Returns glob-matching filenames under the current directory, but skips + some irrelevant paths.""" + return find('.', glob, ['third_party', 'external', 'CompilerIdCXX', + 'build*', 'out*'], ['./utils/clang-format-diff.py']) + + +def skip(line): + """Returns true if line is all whitespace or shebang.""" + stripped = line.lstrip() + return stripped == '' or stripped.startswith('#!') + + +def comment(text, prefix): + """Returns commented-out text. + + Each line of text will be prefixed by prefix and a space character. Any + trailing whitespace will be trimmed. + """ + accum = ['{} {}'.format(prefix, line).rstrip() for line in text.split('\n')] + return '\n'.join(accum) + + +def insert_copyright(author, glob, comment_prefix): + """Finds all glob-matching files under the current directory and inserts the + copyright message, and license notice. An MIT license or Khronos free + use license (modified MIT) is replaced with an Apache 2 license. + + The copyright message goes into the first non-whitespace, non-shebang line + in a file. The license notice follows it. Both are prefixed on each line + by comment_prefix and a space. + """ + + copyright = comment('Copyright (c) {} {}'.format(CURRENT_YEAR, author), + comment_prefix) + '\n\n' + licensed = comment(LICENSED, comment_prefix) + '\n\n' + for file in filtered_descendants(glob): + # Parsing states are: + # 0 Initial: Have not seen a copyright declaration. + # 1 Seen a copyright line and no other interesting lines + # 2 In the middle of an MIT or Khronos free use license + # 9 Exited any of the above + state = 0 + update_file = False + for line in fileinput.input(file, inplace=1): + emit = True + if state is 0: + if COPYRIGHT_RE.search(line): + state = 1 + elif skip(line): + pass + else: + # Didn't see a copyright. Inject copyright and license. + sys.stdout.write(copyright) + sys.stdout.write(licensed) + # Assume there isn't a previous license notice. + state = 1 + elif state is 1: + if MIT_BEGIN_RE.search(line): + state = 2 + emit = False + elif APACHE2_BEGIN_RE.search(line): + # Assume an Apache license is preceded by a copyright + # notice. So just emit it like the rest of the file. + state = 9 + elif state is 2: + # Replace the MIT license with Apache 2 + emit = False + if MIT_END_RE.search(line): + state = 9 + sys.stdout.write(licensed) + if emit: + sys.stdout.write(line) + + +def alert_if_no_copyright(glob, comment_prefix): + """Prints names of all files missing either a copyright or Apache 2 license. + + Finds all glob-matching files under the current directory and checks if they + contain the copyright message and license notice. Prints the names of all the + files that don't meet both criteria. + + Returns the total number of file names printed. + """ + printed_count = 0 + for file in filtered_descendants(glob): + has_copyright = False + has_apache2 = False + line_num = 0 + apache_expected_end = 0 + with open(file) as contents: + for line in contents: + line_num += 1 + if COPYRIGHT_RE.search(line): + has_copyright = True + if APACHE2_BEGIN_RE.search(line): + apache_expected_end = line_num + LICENSED_LEN + if (line_num is apache_expected_end) and APACHE2_END_RE.search(line): + has_apache2 = True + if not (has_copyright and has_apache2): + message = file + if not has_copyright: + message += ' has no copyright' + if not has_apache2: + message += ' has no Apache 2 license notice' + print(message) + printed_count += 1 + return printed_count + + +class ArgParser(argparse.ArgumentParser): + def __init__(self): + super(ArgParser, self).__init__( + description=inspect.getdoc(sys.modules[__name__])) + self.add_argument('--update', dest='author', action='store', + help='For files missing a copyright notice, insert ' + 'one for the given author, and add a license ' + 'notice. The author must be in the AUTHORS ' + 'list in the script.') + + +def main(): + glob_comment_pairs = [('*.h', '//'), ('*.hpp', '//'), ('*.sh', '#'), + ('*.py', '#'), ('*.cpp', '//'), + ('CMakeLists.txt', '#')] + argparser = ArgParser() + args = argparser.parse_args() + + if args.author: + if args.author not in AUTHORS: + print('error: --update argument must be in the AUTHORS list in ' + 'check_copyright.py: {}'.format(AUTHORS)) + sys.exit(1) + for pair in glob_comment_pairs: + insert_copyright(args.author, *pair) + sys.exit(0) + else: + count = sum([alert_if_no_copyright(*p) for p in glob_comment_pairs]) + sys.exit(count > 0) + + +if __name__ == '__main__': + main() diff --git a/3rdparty/spirv-tools/utils/check_symbol_exports.py b/3rdparty/spirv-tools/utils/check_symbol_exports.py new file mode 100644 index 000000000..624b33d95 --- /dev/null +++ b/3rdparty/spirv-tools/utils/check_symbol_exports.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python +# Copyright (c) 2017 Google Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Checks names of global exports from a library.""" + +from __future__ import print_function + +import os.path +import re +import subprocess +import sys + + +PROG = 'check_symbol_exports' + + +def command_output(cmd, directory): + """Runs a command in a directory and returns its standard output stream. + + Captures the standard error stream. + + Raises a RuntimeError if the command fails to launch or otherwise fails. + """ + p = subprocess.Popen(cmd, + cwd=directory, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + (stdout, _) = p.communicate() + if p.returncode != 0: + raise RuntimeError('Failed to run %s in %s' % (cmd, directory)) + return stdout + + +def check_library(library): + """Scans the given library file for global exports. If all such + exports are namespaced or begin with spv (in either C or C++ styles) + then return 0. Otherwise emit a message and return 1.""" + + # The pattern for a global symbol record + symbol_pattern = re.compile(r'^[0-aA-Fa-f]+ g *F \.text.*[0-9A-Fa-f]+ +(.*)') + + # Ok patterns are as follows, assuming Itanium name mangling: + # spv[A-Z] : extern "C" symbol starting with spv + # _ZN : something in a namespace + # _Z[0-9]+spv[A-Z_] : C++ symbol starting with spv[A-Z_] + symbol_ok_pattern = re.compile(r'^(spv[A-Z]|_ZN|_Z[0-9]+spv[A-Z_])') + seen = set() + result = 0 + for line in command_output(['objdump', '-t', library], '.').split('\n'): + match = symbol_pattern.search(line) + if match: + symbol = match.group(1) + if symbol not in seen: + seen.add(symbol) + #print("look at '{}'".format(symbol)) + if not symbol_ok_pattern.match(symbol): + print('{}: error: Unescaped exported symbol: {}'.format(PROG, symbol)) + result = 1 + return result + + +def main(): + import argparse + parser = argparse.ArgumentParser(description='Check global names exported from a library') + parser.add_argument('library', help='The static library to examine') + args = parser.parse_args() + + if not os.path.isfile(args.library): + print('{}: error: {} does not exist'.format(PROG, args.library)) + sys.exit(1) + + if os.name is 'posix': + status = check_library(args.library) + sys.exit(status) + else: + print('Passing test since not on Posix') + sys.exit(0) + + +if __name__ == '__main__': + main() diff --git a/3rdparty/spirv-tools/utils/generate_grammar_tables.py b/3rdparty/spirv-tools/utils/generate_grammar_tables.py new file mode 100644 index 000000000..ae948d388 --- /dev/null +++ b/3rdparty/spirv-tools/utils/generate_grammar_tables.py @@ -0,0 +1,749 @@ +#!/usr/bin/env python +# Copyright (c) 2016 Google Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Generates various info tables from SPIR-V JSON grammar.""" + +from __future__ import print_function + +import errno +import json +import os.path +import re + +# Prefix for all C variables generated by this script. +PYGEN_VARIABLE_PREFIX = 'pygen_variable' + +# Extensions to recognize, but which don't necessarily come from the SPIR-V +# core or KHR grammar files. Get this list from the SPIR-V registery web page. +# NOTE: Only put things on this list if it is not in those grammar files. +EXTENSIONS_FROM_SPIRV_REGISTRY_AND_NOT_FROM_GRAMMARS = """ +SPV_AMD_gcn_shader +SPV_AMD_gpu_shader_half_float +SPV_AMD_gpu_shader_int16 +SPV_AMD_shader_trinary_minmax +""" + + +def make_path_to_file(f): + """Makes all ancestor directories to the given file, if they + don't yet exist. + + Arguments: + f: The file whose ancestor directories are to be created. + """ + dir = os.path.dirname(os.path.abspath(f)) + try: + os.makedirs(dir) + except OSError as e: + if e.errno == errno.EEXIST and os.path.isdir(dir): + pass + else: + raise + + +def convert_min_required_version(version): + """Converts the minimal required SPIR-V version encoded in the + grammar to the symbol in SPIRV-Tools""" + if version is None: + return 'SPV_SPIRV_VERSION_WORD(1, 0)' + if version == 'None': + return '0xffffffffu' + return 'SPV_SPIRV_VERSION_WORD({})'.format(version.replace('.', ',')) + + +def compose_capability_list(caps): + """Returns a string containing a braced list of capabilities as enums. + + Arguments: + - caps: a sequence of capability names + + Returns: + a string containing the braced list of SpvCapability* enums named by caps. + """ + return "{" + ", ".join(['SpvCapability{}'.format(c) for c in caps]) + "}" + + +def get_capability_array_name(caps): + """Returns the name of the array containing all the given capabilities. + + Args: + - caps: a sequence of capability names + """ + if not caps: + return 'nullptr' + return '{}_caps_{}'.format(PYGEN_VARIABLE_PREFIX, ''.join(caps)) + + +def generate_capability_arrays(caps): + """Returns the arrays of capabilities. + + Arguments: + - caps: a sequence of sequence of capability names + """ + caps = sorted(set([tuple(c) for c in caps if c])) + arrays = [ + 'static const SpvCapability {}[] = {};'.format( + get_capability_array_name(c), compose_capability_list(c)) + for c in caps] + return '\n'.join(arrays) + + +def compose_extension_list(exts): + """Returns a string containing a braced list of extensions as enums. + + Arguments: + - exts: a sequence of extension names + + Returns: + a string containing the braced list of extensions named by exts. + """ + return "{" + ", ".join( + ['libspirv::Extension::k{}'.format(e) for e in exts]) + "}" + + +def get_extension_array_name(extensions): + """Returns the name of the array containing all the given extensions. + + Args: + - extensions: a sequence of extension names + """ + if not extensions: + return 'nullptr' + else: + return '{}_exts_{}'.format( + PYGEN_VARIABLE_PREFIX, ''.join(extensions)) + + +def generate_extension_arrays(extensions): + """Returns the arrays of extensions. + + Arguments: + - caps: a sequence of sequence of extension names + """ + extensions = sorted(set([tuple(e) for e in extensions if e])) + arrays = [ + 'static const libspirv::Extension {}[] = {};'.format( + get_extension_array_name(e), compose_extension_list(e)) + for e in extensions] + return '\n'.join(arrays) + + +def convert_operand_kind(operand_tuple): + """Returns the corresponding operand type used in spirv-tools for + the given operand kind and quantifier used in the JSON grammar. + + Arguments: + - operand_tuple: a tuple of two elements: + - operand kind: used in the JSON grammar + - quantifier: '', '?', or '*' + + Returns: + a string of the enumerant name in spv_operand_type_t + """ + kind, quantifier = operand_tuple + # The following cases are where we differ between the JSON grammar and + # spirv-tools. + if kind == 'IdResultType': + kind = 'TypeId' + elif kind == 'IdResult': + kind = 'ResultId' + elif kind == 'IdMemorySemantics' or kind == 'MemorySemantics': + kind = 'MemorySemanticsId' + elif kind == 'IdScope' or kind == 'Scope': + kind = 'ScopeId' + elif kind == 'IdRef': + kind = 'Id' + + elif kind == 'ImageOperands': + kind = 'Image' + elif kind == 'Dim': + kind = 'Dimensionality' + elif kind == 'ImageFormat': + kind = 'SamplerImageFormat' + elif kind == 'KernelEnqueueFlags': + kind = 'KernelEnqFlags' + + elif kind == 'LiteralExtInstInteger': + kind = 'ExtensionInstructionNumber' + elif kind == 'LiteralSpecConstantOpInteger': + kind = 'SpecConstantOpNumber' + elif kind == 'LiteralContextDependentNumber': + kind = 'TypedLiteralNumber' + + elif kind == 'PairLiteralIntegerIdRef': + kind = 'LiteralIntegerId' + elif kind == 'PairIdRefLiteralInteger': + kind = 'IdLiteralInteger' + elif kind == 'PairIdRefIdRef': # Used by OpPhi in the grammar + kind = 'Id' + + if kind == 'FPRoundingMode': + kind = 'FpRoundingMode' + elif kind == 'FPFastMathMode': + kind = 'FpFastMathMode' + + if quantifier == '?': + kind = 'Optional{}'.format(kind) + elif quantifier == '*': + kind = 'Variable{}'.format(kind) + + return 'SPV_OPERAND_TYPE_{}'.format( + re.sub(r'([a-z])([A-Z])', r'\1_\2', kind).upper()) + + +class InstInitializer(object): + """Instances holds a SPIR-V instruction suitable for printing as + the initializer for spv_opcode_desc_t.""" + + def __init__(self, opname, caps, exts, operands, version): + """Initialization. + + Arguments: + - opname: opcode name (with the 'Op' prefix) + - caps: a sequence of capability names required by this opcode + - exts: a sequence of names of extensions enabling this enumerant + - operands: a sequence of (operand-kind, operand-quantifier) tuples + - version: minimal SPIR-V version required for this opcode + """ + + assert opname.startswith('Op') + self.opname = opname[2:] # Remove the "Op" prefix. + self.num_caps = len(caps) + self.caps_mask = get_capability_array_name(caps) + self.num_exts = len(exts) + self.exts = get_extension_array_name(exts) + self.operands = [convert_operand_kind(o) for o in operands] + + self.fix_syntax() + + operands = [o[0] for o in operands] + self.ref_type_id = 'IdResultType' in operands + self.def_result_id = 'IdResult' in operands + + self.version = convert_min_required_version(version) + + def fix_syntax(self): + """Fix an instruction's syntax, adjusting for differences between + the officially released grammar and how SPIRV-Tools uses the grammar. + + Fixes: + - ExtInst should not end with SPV_OPERAND_VARIABLE_ID. + https://github.com/KhronosGroup/SPIRV-Tools/issues/233 + """ + if (self.opname == 'ExtInst' + and self.operands[-1] == 'SPV_OPERAND_TYPE_VARIABLE_ID'): + self.operands.pop() + + def __str__(self): + template = ['{{"{opname}"', 'SpvOp{opname}', + '{num_caps}', '{caps_mask}', + '{num_operands}', '{{{operands}}}', + '{def_result_id}', '{ref_type_id}', + '{num_exts}', '{exts}', + '{min_version}}}'] + return ', '.join(template).format( + opname=self.opname, + num_caps=self.num_caps, + caps_mask=self.caps_mask, + num_operands=len(self.operands), + operands=', '.join(self.operands), + def_result_id=(1 if self.def_result_id else 0), + ref_type_id=(1 if self.ref_type_id else 0), + num_exts=self.num_exts, + exts=self.exts, + min_version=self.version) + + +class ExtInstInitializer(object): + """Instances holds a SPIR-V extended instruction suitable for printing as + the initializer for spv_ext_inst_desc_t.""" + + def __init__(self, opname, opcode, caps, operands): + """Initialization. + + Arguments: + - opname: opcode name + - opcode: enumerant value for this opcode + - caps: a sequence of capability names required by this opcode + - operands: a sequence of (operand-kind, operand-quantifier) tuples + """ + self.opname = opname + self.opcode = opcode + self.num_caps = len(caps) + self.caps_mask = get_capability_array_name(caps) + self.operands = [convert_operand_kind(o) for o in operands] + self.operands.append('SPV_OPERAND_TYPE_NONE') + + def __str__(self): + template = ['{{"{opname}"', '{opcode}', '{num_caps}', '{caps_mask}', + '{{{operands}}}}}'] + return ', '.join(template).format( + opname=self.opname, + opcode=self.opcode, + num_caps=self.num_caps, + caps_mask=self.caps_mask, + operands=', '.join(self.operands)) + + +def generate_instruction(inst, is_ext_inst): + """Returns the C initializer for the given SPIR-V instruction. + + Arguments: + - inst: a dict containing information about a SPIR-V instruction + - is_ext_inst: a bool indicating whether |inst| is an extended + instruction. + + Returns: + a string containing the C initializer for spv_opcode_desc_t or + spv_ext_inst_desc_t + """ + opname = inst.get('opname') + opcode = inst.get('opcode') + caps = inst.get('capabilities', []) + exts = inst.get('extensions', []) + operands = inst.get('operands', {}) + operands = [(o['kind'], o.get('quantifier', '')) for o in operands] + min_version = inst.get('version', None) + + assert opname is not None + + if is_ext_inst: + return str(ExtInstInitializer(opname, opcode, caps, operands)) + else: + return str(InstInitializer(opname, caps, exts, operands, min_version)) + + +def generate_instruction_table(inst_table): + """Returns the info table containing all SPIR-V instructions, + sorted by opcode, and prefixed by capability arrays. + + Note: + - the built-in sorted() function is guaranteed to be stable. + https://docs.python.org/3/library/functions.html#sorted + + Arguments: + - inst_table: a list containing all SPIR-V instructions. + """ + inst_table = sorted(inst_table, key=lambda k: (k['opcode'], k['opname'])) + + caps_arrays = generate_capability_arrays( + [inst.get('capabilities', []) for inst in inst_table]) + exts_arrays = generate_extension_arrays( + [inst.get('extensions', []) for inst in inst_table]) + + insts = [generate_instruction(inst, False) for inst in inst_table] + insts = ['static const spv_opcode_desc_t kOpcodeTableEntries[] = {{\n' + ' {}\n}};'.format(',\n '.join(insts))] + + return '{}\n\n{}\n\n{}'.format(caps_arrays, exts_arrays, '\n'.join(insts)) + + +def generate_extended_instruction_table(inst_table, set_name): + """Returns the info table containing all SPIR-V extended instructions, + sorted by opcode, and prefixed by capability arrays. + + Arguments: + - inst_table: a list containing all SPIR-V instructions. + - set_name: the name of the extended instruction set. + """ + inst_table = sorted(inst_table, key=lambda k: k['opcode']) + caps = [inst.get('capabilities', []) for inst in inst_table] + caps_arrays = generate_capability_arrays(caps) + insts = [generate_instruction(inst, True) for inst in inst_table] + insts = ['static const spv_ext_inst_desc_t {}_entries[] = {{\n' + ' {}\n}};'.format(set_name, ',\n '.join(insts))] + + return '{}\n\n{}'.format(caps_arrays, '\n'.join(insts)) + + +class EnumerantInitializer(object): + """Prints an enumerant as the initializer for spv_operand_desc_t.""" + + def __init__(self, enumerant, value, caps, exts, parameters, version): + """Initialization. + + Arguments: + - enumerant: enumerant name + - value: enumerant value + - caps: a sequence of capability names required by this enumerant + - exts: a sequence of names of extensions enabling this enumerant + - parameters: a sequence of (operand-kind, operand-quantifier) tuples + - version: minimal SPIR-V version required for this opcode + """ + self.enumerant = enumerant + self.value = value + self.num_caps = len(caps) + self.caps = get_capability_array_name(caps) + self.num_exts = len(exts) + self.exts = get_extension_array_name(exts) + self.parameters = [convert_operand_kind(p) for p in parameters] + self.version = convert_min_required_version(version) + + def __str__(self): + template = ['{{"{enumerant}"', '{value}', '{num_caps}', + '{caps}', '{num_exts}', '{exts}', + '{{{parameters}}}', '{min_version}}}'] + return ', '.join(template).format( + enumerant=self.enumerant, + value=self.value, + num_caps=self.num_caps, + caps=self.caps, + num_exts=self.num_exts, + exts=self.exts, + parameters=', '.join(self.parameters), + min_version=self.version) + + +def generate_enum_operand_kind_entry(entry): + """Returns the C initializer for the given operand enum entry. + + Arguments: + - entry: a dict containing information about an enum entry + + Returns: + a string containing the C initializer for spv_operand_desc_t + """ + enumerant = entry.get('enumerant') + value = entry.get('value') + caps = entry.get('capabilities', []) + exts = entry.get('extensions', []) + params = entry.get('parameters', []) + params = [p.get('kind') for p in params] + params = zip(params, [''] * len(params)) + version = entry.get('version', None) + + assert enumerant is not None + assert value is not None + + return str(EnumerantInitializer( + enumerant, value, caps, exts, params, version)) + + +def generate_enum_operand_kind(enum): + """Returns the C definition for the given operand kind.""" + kind = enum.get('kind') + assert kind is not None + + # Sort all enumerants first according to their values and then + # their names so that the symbols with the same values are + # grouped together. + if enum.get('category') == 'ValueEnum': + functor = lambda k: (k['value'], k['enumerant']) + else: + functor = lambda k: (int(k['value'], 16), k['enumerant']) + entries = sorted(enum.get('enumerants', []), key=functor) + + name = '{}_{}Entries'.format(PYGEN_VARIABLE_PREFIX, kind) + entries = [' {}'.format(generate_enum_operand_kind_entry(e)) + for e in entries] + + template = ['static const spv_operand_desc_t {name}[] = {{', + '{entries}', '}};'] + entries = '\n'.join(template).format( + name=name, + entries=',\n'.join(entries)) + + return kind, name, entries + + +def generate_operand_kind_table(enums): + """Returns the info table containing all SPIR-V operand kinds.""" + # We only need to output info tables for those operand kinds that are enums. + enums = [e for e in enums if e.get('category') in ['ValueEnum', 'BitEnum']] + + caps = [entry.get('capabilities', []) + for enum in enums + for entry in enum.get('enumerants', [])] + caps_arrays = generate_capability_arrays(caps) + + exts = [entry.get('extensions', []) + for enum in enums + for entry in enum.get('enumerants', [])] + exts_arrays = generate_extension_arrays(exts) + + enums = [generate_enum_operand_kind(e) for e in enums] + # We have three operand kinds that requires their optional counterpart to + # exist in the operand info table. + three_optional_enums = ['ImageOperands', 'AccessQualifier', 'MemoryAccess'] + three_optional_enums = [e for e in enums if e[0] in three_optional_enums] + enums.extend(three_optional_enums) + + enum_kinds, enum_names, enum_entries = zip(*enums) + # Mark the last three as optional ones. + enum_quantifiers = [''] * (len(enums) - 3) + ['?'] * 3 + # And we don't want redefinition of them. + enum_entries = enum_entries[:-3] + enum_kinds = [convert_operand_kind(e) + for e in zip(enum_kinds, enum_quantifiers)] + table_entries = zip(enum_kinds, enum_names, enum_names) + table_entries = [' {{{}, ARRAY_SIZE({}), {}}}'.format(*e) + for e in table_entries] + + template = [ + 'static const spv_operand_desc_group_t {p}_OperandInfoTable[] = {{', + '{enums}', '}};'] + table = '\n'.join(template).format( + p=PYGEN_VARIABLE_PREFIX, enums=',\n'.join(table_entries)) + + return '\n\n'.join((caps_arrays,) + (exts_arrays,) + enum_entries + (table,)) + + +def get_extension_list(instructions, operand_kinds): + """Returns extensions as an alphabetically sorted list of strings.""" + + things_with_an_extensions_field = [item for item in instructions] + + enumerants = sum([item.get('enumerants', []) for item in operand_kinds], []) + + things_with_an_extensions_field.extend(enumerants) + + extensions = sum([item.get('extensions', []) + for item in things_with_an_extensions_field + if item.get('extensions')], []) + + for item in EXTENSIONS_FROM_SPIRV_REGISTRY_AND_NOT_FROM_GRAMMARS.split(): + # If it's already listed in a grammar, then don't put it in the + # special exceptions list. + assert item not in extensions, "Extension %s is already in a grammar file" % item + + extensions.extend(EXTENSIONS_FROM_SPIRV_REGISTRY_AND_NOT_FROM_GRAMMARS.split()) + + # Validator would ignore type declaration unique check. Should only be used + # for legacy autogenerated test files containing multiple instances of the + # same type declaration, if fixing the test by other methods is too + # difficult. Shouldn't be used for any other reasons. + extensions.append('SPV_VALIDATOR_ignore_type_decl_unique') + + return sorted(set(extensions)) + + +def get_capabilities(operand_kinds): + """Returns capabilities as a list of JSON objects, in order of + appearance. + """ + enumerants = sum([item.get('enumerants', []) for item in operand_kinds + if item.get('kind') in ['Capability']], []) + return enumerants + + +def generate_extension_enum(extensions): + """Returns enumeration containing extensions declared in the grammar.""" + return ',\n'.join(['k' + extension for extension in extensions]) + + +def generate_extension_to_string_mapping(extensions): + """Returns mapping function from extensions to corresponding strings.""" + function = 'const char* ExtensionToString(Extension extension) {\n' + function += ' switch (extension) {\n' + template = ' case Extension::k{extension}:\n' \ + ' return "{extension}";\n' + function += ''.join([template.format(extension=extension) + for extension in extensions]) + function += ' };\n\n return "";\n}' + return function + + +def generate_string_to_extension_mapping(extensions): + """Returns mapping function from strings to corresponding extensions.""" + + function = ''' + bool GetExtensionFromString(const char* str, Extension* extension) {{ + static const char* known_ext_strs[] = {{ {strs} }}; + static const Extension known_ext_ids[] = {{ {ids} }}; + const auto b = std::begin(known_ext_strs); + const auto e = std::end(known_ext_strs); + const auto found = std::equal_range( + b, e, str, [](const char* str1, const char* str2) {{ + return std::strcmp(str1, str2) < 0; + }}); + if (found.first == e || found.first == found.second) return false; + + *extension = known_ext_ids[found.first - b]; + return true; + }} + '''.format(strs=', '.join(['"{}"'.format(e) for e in extensions]), + ids=', '.join(['Extension::k{}'.format(e) for e in extensions])) + + return function + + +def generate_capability_to_string_mapping(operand_kinds): + """Returns mapping function from capabilities to corresponding strings. + We take care to avoid emitting duplicate values. + """ + function = 'const char* CapabilityToString(SpvCapability capability) {\n' + function += ' switch (capability) {\n' + template = ' case SpvCapability{capability}:\n' \ + ' return "{capability}";\n' + emitted = set() # The values of capabilities we already have emitted + for capability in get_capabilities(operand_kinds): + value = capability.get('value') + if value not in emitted: + emitted.add(value) + function += template.format(capability=capability.get('enumerant')) + function += ' case SpvCapabilityMax:\n' \ + ' assert(0 && "Attempting to convert SpvCapabilityMax to string");\n' \ + ' return "";\n' + function += ' };\n\n return "";\n}' + return function + + +def generate_all_string_enum_mappings(extensions, operand_kinds): + """Returns all string-to-enum / enum-to-string mapping tables.""" + tables = [] + tables.append(generate_extension_to_string_mapping(extensions)) + tables.append(generate_string_to_extension_mapping(extensions)) + tables.append(generate_capability_to_string_mapping(operand_kinds)) + return '\n\n'.join(tables) + + +def main(): + import argparse + parser = argparse.ArgumentParser(description='Generate SPIR-V info tables') + + parser.add_argument('--spirv-core-grammar', metavar='', + type=str, required=False, + help='input JSON grammar file for core SPIR-V ' + 'instructions') + parser.add_argument('--extinst-debuginfo-grammar', metavar='', + type=str, required=False, default=None, + help='input JSON grammar file for DebugInfo extended ' + 'instruction set') + parser.add_argument('--extinst-glsl-grammar', metavar='', + type=str, required=False, default=None, + help='input JSON grammar file for GLSL extended ' + 'instruction set') + parser.add_argument('--extinst-opencl-grammar', metavar='', + type=str, required=False, default=None, + help='input JSON grammar file for OpenCL extended ' + 'instruction set') + + parser.add_argument('--core-insts-output', metavar='', + type=str, required=False, default=None, + help='output file for core SPIR-V instructions') + parser.add_argument('--glsl-insts-output', metavar='', + type=str, required=False, default=None, + help='output file for GLSL extended instruction set') + parser.add_argument('--opencl-insts-output', metavar='', + type=str, required=False, default=None, + help='output file for OpenCL extended instruction set') + parser.add_argument('--operand-kinds-output', metavar='', + type=str, required=False, default=None, + help='output file for operand kinds') + parser.add_argument('--extension-enum-output', metavar='', + type=str, required=False, default=None, + help='output file for extension enumeration') + parser.add_argument('--enum-string-mapping-output', metavar='', + type=str, required=False, default=None, + help='output file for enum-string mappings') + parser.add_argument('--extinst-vendor-grammar', metavar='', + type=str, required=False, default=None, + help='input JSON grammar file for vendor extended ' + 'instruction set'), + parser.add_argument('--vendor-insts-output', metavar='', + type=str, required=False, default=None, + help='output file for vendor extended instruction set') + args = parser.parse_args() + + if (args.core_insts_output is None) != \ + (args.operand_kinds_output is None): + print('error: --core-insts-output and --operand-kinds-output ' + 'should be specified together.') + exit(1) + if args.operand_kinds_output and not (args.spirv_core_grammar and args.extinst_debuginfo_grammar): + print('error: --operand-kinds-output requires --spirv-core-grammar ' + 'and --exinst-debuginfo-grammar') + exit(1) + if (args.glsl_insts_output is None) != \ + (args.extinst_glsl_grammar is None): + print('error: --glsl-insts-output and --extinst-glsl-grammar ' + 'should be specified together.') + exit(1) + if (args.opencl_insts_output is None) != \ + (args.extinst_opencl_grammar is None): + print('error: --opencl-insts-output and --extinst-opencl-grammar ' + 'should be specified together.') + exit(1) + if (args.vendor_insts_output is None) != \ + (args.extinst_vendor_grammar is None): + print('error: --vendor-insts-output and ' + '--extinst-vendor-grammar should be specified together.') + exit(1) + if all([args.core_insts_output is None, + args.glsl_insts_output is None, + args.opencl_insts_output is None, + args.vendor_insts_output is None, + args.extension_enum_output is None, + args.enum_string_mapping_output is None]): + print('error: at least one output should be specified.') + exit(1) + + if args.spirv_core_grammar is not None: + with open(args.spirv_core_grammar) as json_file: + core_grammar = json.loads(json_file.read()) + with open(args.extinst_debuginfo_grammar) as debuginfo_json_file: + debuginfo_grammar = json.loads(debuginfo_json_file.read()) + instructions = [] + instructions.extend(core_grammar['instructions']) + instructions.extend(debuginfo_grammar['instructions']) + operand_kinds = [] + operand_kinds.extend(core_grammar['operand_kinds']) + operand_kinds.extend(debuginfo_grammar['operand_kinds']) + extensions = get_extension_list(instructions, operand_kinds) + if args.core_insts_output is not None: + make_path_to_file(args.core_insts_output) + make_path_to_file(args.operand_kinds_output) + print(generate_instruction_table(core_grammar['instructions']), + file=open(args.core_insts_output, 'w')) + print(generate_operand_kind_table(operand_kinds), + file=open(args.operand_kinds_output, 'w')) + if args.extension_enum_output is not None: + make_path_to_file(args.extension_enum_output) + print(generate_extension_enum(extensions), + file=open(args.extension_enum_output, 'w')) + if args.enum_string_mapping_output is not None: + make_path_to_file(args.enum_string_mapping_output) + print(generate_all_string_enum_mappings(extensions, operand_kinds), + file=open(args.enum_string_mapping_output, 'w')) + + if args.extinst_glsl_grammar is not None: + with open(args.extinst_glsl_grammar) as json_file: + grammar = json.loads(json_file.read()) + make_path_to_file(args.glsl_insts_output) + print(generate_extended_instruction_table( + grammar['instructions'], "glsl"), + file=open(args.glsl_insts_output, 'w')) + + if args.extinst_opencl_grammar is not None: + with open(args.extinst_opencl_grammar) as json_file: + grammar = json.loads(json_file.read()) + make_path_to_file(args.opencl_insts_output) + print(generate_extended_instruction_table( + grammar['instructions'], "opencl"), + file=open(args.opencl_insts_output, 'w')) + + if args.extinst_vendor_grammar is not None: + with open(args.extinst_vendor_grammar) as json_file: + grammar = json.loads(json_file.read()) + make_path_to_file(args.vendor_insts_output) + name = args.extinst_vendor_grammar + start = name.find("extinst.") + len("extinst.") + name = name[start:-len(".grammar.json")].replace("-", "_") + print(generate_extended_instruction_table( + grammar['instructions'], name), + file=open(args.vendor_insts_output, 'w')) + + +if __name__ == '__main__': + main() diff --git a/3rdparty/spirv-tools/utils/generate_language_headers.py b/3rdparty/spirv-tools/utils/generate_language_headers.py new file mode 100644 index 000000000..1886bf4f3 --- /dev/null +++ b/3rdparty/spirv-tools/utils/generate_language_headers.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python +# Copyright (c) 2017 Google Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Generates language headers from a JSON grammar file""" + +from __future__ import print_function + +import errno +import json +import os.path +import re + + +def make_path_to_file(f): + """Makes all ancestor directories to the given file, if they + don't yet exist. + + Arguments: + f: The file whose ancestor directories are to be created. + """ + dir = os.path.dirname(os.path.abspath(f)) + try: + os.makedirs(dir) + except OSError as e: + if e.errno == errno.EEXIST and os.path.isdir(dir): + pass + else: + raise + +class ExtInstGrammar: + """The grammar for an extended instruction set""" + + def __init__(self, name, copyright, instructions, operand_kinds, version = None, revision = None): + self.name = name + self.copyright = copyright + self.instructions = instructions + self.operand_kinds = operand_kinds + self.version = version + self.revision = revision + + +class LangGenerator: + """A language-specific generator""" + + def __init__(self): + self.upper_case_initial = re.compile('^[A-Z]') + pass + + def comment_prefix(self): + return "" + + def namespace_prefix(self): + return "" + + def uses_guards(self): + return False + + def cpp_guard_preamble(self): + return "" + + def cpp_guard_postamble(self): + return "" + + def enum_value(self, prefix, name, value): + if self.upper_case_initial.match(name): + use_name = name + else: + use_name = '_' + name + + return " {}{} = {},".format(prefix, use_name, value) + + def generate(self, grammar): + """Returns a string that is the language-specific header for the given grammar""" + + parts = [] + if grammar.copyright: + parts.extend(["{}{}".format(self.comment_prefix(), f) for f in grammar.copyright]) + parts.append('') + + guard = 'SPIRV_EXTINST_{}_H_'.format(grammar.name) + if self.uses_guards: + parts.append('#ifndef {}'.format(guard)) + parts.append('#define {}'.format(guard)) + parts.append('') + + parts.append(self.cpp_guard_preamble()) + + if grammar.version: + parts.append(self.const_definition(grammar.name, 'Version', grammar.version)) + + if grammar.revision is not None: + parts.append(self.const_definition(grammar.name, 'Revision', grammar.revision)) + + parts.append('') + + if grammar.instructions: + parts.append(self.enum_prefix(grammar.name, 'Instructions')) + for inst in grammar.instructions: + parts.append(self.enum_value(grammar.name, inst['opname'], inst['opcode'])) + parts.append(self.enum_end(grammar.name, 'Instructions')) + parts.append('') + + if grammar.operand_kinds: + for kind in grammar.operand_kinds: + parts.append(self.enum_prefix(grammar.name, kind['kind'])) + for e in kind['enumerants']: + parts.append(self.enum_value(grammar.name, e['enumerant'], e['value'])) + parts.append(self.enum_end(grammar.name, kind['kind'])) + parts.append('') + + parts.append(self.cpp_guard_postamble()) + + if self.uses_guards: + parts.append('#endif // {}'.format(guard)) + + return '\n'.join(parts) + + +class CLikeGenerator(LangGenerator): + def uses_guards(self): + return True + + def comment_prefix(self): + return "// " + + def const_definition(self, prefix, var, value): + # Use an anonymous enum. Don't use a static const int variable because + # that can bloat binary size. + return 'enum {0} {1}{2} = {3}, {1}{2}_BitWidthPadding = 0x7fffffff {4};'.format( + '{', prefix, var, value, '}') + + def enum_prefix(self, prefix, name): + return 'enum {}{} {}'.format(prefix, name, '{') + + def enum_end(self, prefix, enum): + return ' {}{}Max = 0x7ffffff\n{};\n'.format(prefix, enum, '}') + + def cpp_guard_preamble(self): + return '#ifdef __cplusplus\nextern "C" {\n#endif\n' + + def cpp_guard_postamble(self): + return '#ifdef __cplusplus\n}\n#endif\n' + + +class CGenerator(CLikeGenerator): + pass + + +def main(): + import argparse + parser = argparse.ArgumentParser(description='Generate language headers from a JSON grammar') + + parser.add_argument('--extinst-name', + type=str, required=True, + help='The name to use in tokens') + parser.add_argument('--extinst-grammar', metavar='', + type=str, required=True, + help='input JSON grammar file for extended instruction set') + parser.add_argument('--extinst-output-base', metavar='', + type=str, required=True, + help='Basename of the language-specific output file.') + args = parser.parse_args() + + with open(args.extinst_grammar) as json_file: + grammar_json = json.loads(json_file.read()) + grammar = ExtInstGrammar(name = args.extinst_name, + copyright = grammar_json['copyright'], + instructions = grammar_json['instructions'], + operand_kinds = grammar_json['operand_kinds'], + version = grammar_json['version'], + revision = grammar_json['revision']) + make_path_to_file(args.extinst_output_base) + print(CGenerator().generate(grammar), file=open(args.extinst_output_base + '.h', 'w')) + + +if __name__ == '__main__': + main() diff --git a/3rdparty/spirv-tools/utils/generate_registry_tables.py b/3rdparty/spirv-tools/utils/generate_registry_tables.py new file mode 100644 index 000000000..8b1c35721 --- /dev/null +++ b/3rdparty/spirv-tools/utils/generate_registry_tables.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +# Copyright (c) 2016 Google Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Generates the vendor tool table from the SPIR-V XML registry.""" + +from __future__ import print_function + +import distutils.dir_util +import os.path +import xml.etree.ElementTree + + +def generate_vendor_table(registry): + """Returns a list of C style initializers for the registered vendors + and their tools. + + Args: + registry: The SPIR-V XMLregistry as an xml.ElementTree + """ + + lines = [] + for ids in registry.iter('ids'): + if 'vendor' == ids.attrib['type']: + for an_id in ids.iter('id'): + value = an_id.attrib['value'] + vendor = an_id.attrib['vendor'] + if 'tool' in an_id.attrib: + tool = an_id.attrib['tool'] + vendor_tool = vendor + ' ' + tool + else: + tool = '' + vendor_tool = vendor + line = '{' + '{}, "{}", "{}", "{}"'.format(value, + vendor, + tool, + vendor_tool) + '},' + lines.append(line) + return '\n'.join(lines) + + +def main(): + import argparse + parser = argparse.ArgumentParser(description= + 'Generate tables from SPIR-V XML registry') + parser.add_argument('--xml', metavar='', + type=str, required=True, + help='SPIR-V XML Registry file') + parser.add_argument('--generator-output', metavar='', + type=str, required=True, + help='output file for SPIR-V generators table') + args = parser.parse_args() + + with open(args.xml) as xml_in: + registry = xml.etree.ElementTree.fromstring(xml_in.read()) + + distutils.dir_util.mkpath(os.path.dirname(args.generator_output)) + print(generate_vendor_table(registry), file=open(args.generator_output, 'w')) + + +if __name__ == '__main__': + main() diff --git a/3rdparty/spirv-tools/utils/generate_vim_syntax.py b/3rdparty/spirv-tools/utils/generate_vim_syntax.py new file mode 100644 index 000000000..03c0b478b --- /dev/null +++ b/3rdparty/spirv-tools/utils/generate_vim_syntax.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python +# Copyright (c) 2016 Google Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Generates Vim syntax rules for SPIR-V assembly (.spvasm) files""" + +from __future__ import print_function + +import json + +PREAMBLE="""" Vim syntax file +" Language: spvasm +" Generated by SPIRV-Tools + +if version < 600 + syntax clear +elseif exists("b:current_syntax") + finish +endif + +syn case match +""" + +POSTAMBLE=""" + +syntax keyword spvasmTodo TODO FIXME contained + +syn match spvasmIdNumber /%\d\+\>/ + +" The assembler treats the leading minus sign as part of the number token. +" This applies to integers, and to floats below. +syn match spvasmNumber /-\?\<\d\+\>/ + +" Floating point literals. +" In general, C++ requires at least digit in the mantissa, and the +" floating point is optional. This applies to both the regular decimal float +" case and the hex float case. + +" First case: digits before the optional decimal, no trailing digits. +syn match spvasmFloat /-\?\d\+\.\?\(e[+-]\d\+\)\?/ +" Second case: optional digits before decimal, trailing digits +syn match spvasmFloat /-\?\d*\.\d\+\(e[+-]\d\+\)\?/ + +" First case: hex digits before the optional decimal, no trailing hex digits. +syn match spvasmFloat /-\?0[xX]\\x\+\.\?p[-+]\d\+/ +" Second case: optional hex digits before decimal, trailing hex digits +syn match spvasmFloat /-\?0[xX]\\x*\.\\x\+p[-+]\d\+/ + +syn match spvasmComment /;.*$/ contains=spvasmTodo +syn region spvasmString start=/"/ skip=/\\\\"/ end=/"/ +syn match spvasmId /%[a-zA-Z_][a-zA-Z_0-9]*/ + +" Highlight unknown constants and statements as errors +syn match spvasmError /[a-zA-Z][a-zA-Z_0-9]*/ + + +if version >= 508 || !exists("did_c_syn_inits") + if version < 508 + let did_c_syn_inits = 1 + command -nargs=+ HiLink hi link + else + command -nargs=+ HiLink hi def link + endif + + HiLink spvasmStatement Statement + HiLink spvasmNumber Number + HiLink spvasmComment Comment + HiLink spvasmString String + HiLink spvasmFloat Float + HiLink spvasmConstant Constant + HiLink spvasmIdNumber Identifier + HiLink spvasmId Identifier + HiLink spvasmTodo Todo + + delcommand HiLink +endif + +let b:current_syntax = "spvasm" +""" + +# This list is taken from the description of OpSpecConstantOp in SPIR-V 1.1. +# TODO(dneto): Propose that this information be embedded in the grammar file. +SPEC_CONSTANT_OP_OPCODES = """ + OpSConvert, OpFConvert + OpSNegate, OpNot + OpIAdd, OpISub + OpIMul, OpUDiv, OpSDiv, OpUMod, OpSRem, OpSMod + OpShiftRightLogical, OpShiftRightArithmetic, OpShiftLeftLogical + OpBitwiseOr, OpBitwiseXor, OpBitwiseAnd + OpVectorShuffle, OpCompositeExtract, OpCompositeInsert + OpLogicalOr, OpLogicalAnd, OpLogicalNot, + OpLogicalEqual, OpLogicalNotEqual + OpSelect + OpIEqual, OpINotEqual + OpULessThan, OpSLessThan + OpUGreaterThan, OpSGreaterThan + OpULessThanEqual, OpSLessThanEqual + OpUGreaterThanEqual, OpSGreaterThanEqual + + OpQuantizeToF16 + + OpConvertFToS, OpConvertSToF + OpConvertFToU, OpConvertUToF + OpUConvert + OpConvertPtrToU, OpConvertUToPtr + OpGenericCastToPtr, OpPtrCastToGeneric + OpBitcast + OpFNegate + OpFAdd, OpFSub + OpFMul, OpFDiv + OpFRem, OpFMod + OpAccessChain, OpInBoundsAccessChain + OpPtrAccessChain, OpInBoundsPtrAccessChain""" + + +def EmitAsStatement(name): + """Emits the given name as a statement token""" + print('syn keyword spvasmStatement', name) + + +def EmitAsEnumerant(name): + """Emits the given name as an named operand token""" + print('syn keyword spvasmConstant', name) + + +def main(): + """Parses arguments, then generates the Vim syntax rules for SPIR-V assembly + on stdout.""" + import argparse + parser = argparse.ArgumentParser(description='Generate SPIR-V info tables') + parser.add_argument('--spirv-core-grammar', metavar='', + type=str, required=True, + help='input JSON grammar file for core SPIR-V ' + 'instructions') + parser.add_argument('--extinst-glsl-grammar', metavar='', + type=str, required=False, default=None, + help='input JSON grammar file for GLSL extended ' + 'instruction set') + parser.add_argument('--extinst-opencl-grammar', metavar='', + type=str, required=False, default=None, + help='input JSON grammar file for OpenGL extended ' + 'instruction set') + parser.add_argument('--extinst-debuginfo-grammar', metavar='', + type=str, required=False, default=None, + help='input JSON grammar file for DebugInfo extended ' + 'instruction set') + args = parser.parse_args() + + # Generate the syntax rules. + print(PREAMBLE) + + core = json.loads(open(args.spirv_core_grammar).read()) + print('\n" Core instructions') + for inst in core["instructions"]: + EmitAsStatement(inst['opname']) + print('\n" Core operand enums') + for operand_kind in core["operand_kinds"]: + if 'enumerants' in operand_kind: + for e in operand_kind['enumerants']: + EmitAsEnumerant(e['enumerant']) + + if args.extinst_glsl_grammar is not None: + print('\n" GLSL.std.450 extended instructions') + glsl = json.loads(open(args.extinst_glsl_grammar).read()) + # These opcodes are really enumerant operands for the OpExtInst + # instruction. + for inst in glsl["instructions"]: + EmitAsEnumerant(inst['opname']) + + if args.extinst_opencl_grammar is not None: + print('\n" OpenCL.std extended instructions') + opencl = json.loads(open(args.extinst_opencl_grammar).read()) + for inst in opencl["instructions"]: + EmitAsEnumerant(inst['opname']) + + if args.extinst_debuginfo_grammar is not None: + print('\n" DebugInfo extended instructions') + debuginfo = json.loads(open(args.extinst_debuginfo_grammar).read()) + for inst in debuginfo["instructions"]: + EmitAsEnumerant(inst['opname']) + print('\n" DebugInfo operand enums') + for operand_kind in debuginfo["operand_kinds"]: + if 'enumerants' in operand_kind: + for e in operand_kind['enumerants']: + EmitAsEnumerant(e['enumerant']) + + print('\n" OpSpecConstantOp opcodes') + for word in SPEC_CONSTANT_OP_OPCODES.split(' '): + stripped = word.strip('\n,') + if stripped != "": + # Treat as an enumerant, but without the leading "Op" + EmitAsEnumerant(stripped[2:]) + print(POSTAMBLE) + + +if __name__ == '__main__': + main() diff --git a/3rdparty/spirv-tools/utils/update_build_version.py b/3rdparty/spirv-tools/utils/update_build_version.py new file mode 100644 index 000000000..d71aeccd9 --- /dev/null +++ b/3rdparty/spirv-tools/utils/update_build_version.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python + +# Copyright (c) 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Updates an output file with version info unless the new content is the same +# as the existing content. +# +# Args: +# +# The output file will contain a line of text consisting of two C source syntax +# string literals separated by a comma: +# - The software version deduced from the CHANGES file in the given directory. +# - A longer string with the project name, the software version number, and +# git commit information for the directory. The commit information +# is the output of "git describe" if that succeeds, or "git rev-parse HEAD" +# if that succeeds, or otherwise a message containing the phrase +# "unknown hash". +# The string contents are escaped as necessary. + +from __future__ import print_function + +import datetime +import errno +import os +import os.path +import re +import subprocess +import sys +import time + + +def mkdir_p(directory): + """Make the directory, and all its ancestors as required. Any of the + directories are allowed to already exist.""" + + if directory == "": + # We're being asked to make the current directory. + return + + try: + os.makedirs(directory) + except OSError as e: + if e.errno == errno.EEXIST and os.path.isdir(directory): + pass + else: + raise + + +def command_output(cmd, directory): + """Runs a command in a directory and returns its standard output stream. + + Captures the standard error stream. + + Raises a RuntimeError if the command fails to launch or otherwise fails. + """ + p = subprocess.Popen(cmd, + cwd=directory, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + (stdout, _) = p.communicate() + if p.returncode != 0: + raise RuntimeError('Failed to run %s in %s' % (cmd, directory)) + return stdout + + +def deduce_software_version(directory): + """Returns a software version number parsed from the CHANGES file + in the given directory. + + The CHANGES file describes most recent versions first. + """ + + # Match the first well-formed version-and-date line. + # Allow trailing whitespace in the checked-out source code has + # unexpected carriage returns on a linefeed-only system such as + # Linux. + pattern = re.compile(r'^(v\d+\.\d+(-dev)?) \d\d\d\d-\d\d-\d\d\s*$') + changes_file = os.path.join(directory, 'CHANGES') + with open(changes_file, mode='rU') as f: + for line in f.readlines(): + match = pattern.match(line) + if match: + return match.group(1) + raise Exception('No version number found in {}'.format(changes_file)) + + +def describe(directory): + """Returns a string describing the current Git HEAD version as descriptively + as possible. + + Runs 'git describe', or alternately 'git rev-parse HEAD', in directory. If + successful, returns the output; otherwise returns 'unknown hash, '.""" + try: + # decode() is needed here for Python3 compatibility. In Python2, + # str and bytes are the same type, but not in Python3. + # Popen.communicate() returns a bytes instance, which needs to be + # decoded into text data first in Python3. And this decode() won't + # hurt Python2. + return command_output(['git', 'describe'], directory).rstrip().decode() + except: + try: + return command_output( + ['git', 'rev-parse', 'HEAD'], directory).rstrip().decode() + except: + # This is the fallback case where git gives us no information, + # e.g. because the source tree might not be in a git tree. + # In this case, usually use a timestamp. However, to ensure + # reproducible builds, allow the builder to override the wall + # clock time with enviornment variable SOURCE_DATE_EPOCH + # containing a (presumably) fixed timestamp. + timestamp = int(os.environ.get('SOURCE_DATE_EPOCH', time.time())) + formatted = datetime.date.fromtimestamp(timestamp).isoformat() + return 'unknown hash, {}'.format(formatted) + + +def main(): + if len(sys.argv) != 3: + print('usage: {} '.format(sys.argv[0])) + sys.exit(1) + + output_file = sys.argv[2] + mkdir_p(os.path.dirname(output_file)) + + software_version = deduce_software_version(sys.argv[1]) + new_content = '"{}", "SPIRV-Tools {} {}"\n'.format( + software_version, software_version, + describe(sys.argv[1]).replace('"', '\\"')) + + if os.path.isfile(output_file): + with open(output_file, 'r') as f: + if new_content == f.read(): + return + + with open(output_file, 'w') as f: + f.write(new_content) + +if __name__ == '__main__': + main() diff --git a/scripts/shaderc.lua b/scripts/shaderc.lua index def94a8ae..3f26ece15 100644 --- a/scripts/shaderc.lua +++ b/scripts/shaderc.lua @@ -7,11 +7,167 @@ group "tools/shaderc" local GLSL_OPTIMIZER = path.join(BGFX_DIR, "3rdparty/glsl-optimizer") local FCPP_DIR = path.join(BGFX_DIR, "3rdparty/fcpp") +local GLSLANG = path.join(BGFX_DIR, "3rdparty/glslang") +local SPIRV_TOOLS = path.join(BGFX_DIR, "3rdparty/spirv-tools") + +project "spirv-opt" + kind "StaticLib" + + includedirs { + path.join(SPIRV_TOOLS, "include"), + path.join(SPIRV_TOOLS, "include/generated"), + path.join(SPIRV_TOOLS, "source"), + path.join(SPIRV_TOOLS, "external/SPIRV-Headers/include"), + } + + files { + path.join(SPIRV_TOOLS, "source/opt/**.cpp"), + path.join(SPIRV_TOOLS, "source/opt/**.h"), + + -- libspirv + path.join(SPIRV_TOOLS, "source/util/bitutils.h"), + path.join(SPIRV_TOOLS, "source/util/bit_stream.h"), + path.join(SPIRV_TOOLS, "source/util/hex_float.h"), + path.join(SPIRV_TOOLS, "source/util/parse_number.h"), + path.join(SPIRV_TOOLS, "source/util/string_utils.h"), + path.join(SPIRV_TOOLS, "source/util/timer.h"), + path.join(SPIRV_TOOLS, "source/assembly_grammar.h"), + path.join(SPIRV_TOOLS, "source/binary.h"), + path.join(SPIRV_TOOLS, "source/cfa.h"), + path.join(SPIRV_TOOLS, "source/diagnostic.h"), + path.join(SPIRV_TOOLS, "source/disassemble.h"), + path.join(SPIRV_TOOLS, "source/enum_set.h"), + path.join(SPIRV_TOOLS, "source/enum_string_mapping.h"), + path.join(SPIRV_TOOLS, "source/ext_inst.h"), + path.join(SPIRV_TOOLS, "source/extensions.h"), + path.join(SPIRV_TOOLS, "source/id_descriptor.h"), + path.join(SPIRV_TOOLS, "source/instruction.h"), + path.join(SPIRV_TOOLS, "source/latest_version_glsl_std_450_header.h"), + path.join(SPIRV_TOOLS, "source/latest_version_opencl_std_header.h"), + path.join(SPIRV_TOOLS, "source/latest_version_spirv_header.h"), + path.join(SPIRV_TOOLS, "source/macro.h"), + path.join(SPIRV_TOOLS, "source/name_mapper.h"), + path.join(SPIRV_TOOLS, "source/opcode.h"), + path.join(SPIRV_TOOLS, "source/operand.h"), + path.join(SPIRV_TOOLS, "source/parsed_operand.h"), + path.join(SPIRV_TOOLS, "source/print.h"), + path.join(SPIRV_TOOLS, "source/spirv_constant.h"), + path.join(SPIRV_TOOLS, "source/spirv_definition.h"), + path.join(SPIRV_TOOLS, "source/spirv_endian.h"), + path.join(SPIRV_TOOLS, "source/spirv_target_env.h"), + path.join(SPIRV_TOOLS, "source/spirv_validator_options.h"), + path.join(SPIRV_TOOLS, "source/table.h"), + path.join(SPIRV_TOOLS, "source/text.h"), + path.join(SPIRV_TOOLS, "source/text_handler.h"), + path.join(SPIRV_TOOLS, "source/validate.h"), + path.join(SPIRV_TOOLS, "source/util/bit_stream.cpp"), + path.join(SPIRV_TOOLS, "source/util/parse_number.cpp"), + path.join(SPIRV_TOOLS, "source/util/string_utils.cpp"), + path.join(SPIRV_TOOLS, "source/assembly_grammar.cpp"), + path.join(SPIRV_TOOLS, "source/binary.cpp"), + path.join(SPIRV_TOOLS, "source/diagnostic.cpp"), + path.join(SPIRV_TOOLS, "source/disassemble.cpp"), + path.join(SPIRV_TOOLS, "source/enum_string_mapping.cpp"), + path.join(SPIRV_TOOLS, "source/ext_inst.cpp"), + path.join(SPIRV_TOOLS, "source/extensions.cpp"), + path.join(SPIRV_TOOLS, "source/id_descriptor.cpp"), + path.join(SPIRV_TOOLS, "source/libspirv.cpp"), + path.join(SPIRV_TOOLS, "source/message.cpp"), + path.join(SPIRV_TOOLS, "source/name_mapper.cpp"), + path.join(SPIRV_TOOLS, "source/opcode.cpp"), + path.join(SPIRV_TOOLS, "source/operand.cpp"), + path.join(SPIRV_TOOLS, "source/parsed_operand.cpp"), + path.join(SPIRV_TOOLS, "source/print.cpp"), + path.join(SPIRV_TOOLS, "source/software_version.cpp"), + path.join(SPIRV_TOOLS, "source/spirv_endian.cpp"), + path.join(SPIRV_TOOLS, "source/spirv_stats.cpp"), + path.join(SPIRV_TOOLS, "source/spirv_target_env.cpp"), + path.join(SPIRV_TOOLS, "source/spirv_validator_options.cpp"), + path.join(SPIRV_TOOLS, "source/table.cpp"), + path.join(SPIRV_TOOLS, "source/text.cpp"), + path.join(SPIRV_TOOLS, "source/text_handler.cpp"), + path.join(SPIRV_TOOLS, "source/validate.cpp"), + path.join(SPIRV_TOOLS, "source/validate_adjacency.cpp"), + path.join(SPIRV_TOOLS, "source/validate_arithmetics.cpp"), + path.join(SPIRV_TOOLS, "source/validate_atomics.cpp"), + path.join(SPIRV_TOOLS, "source/validate_barriers.cpp"), + path.join(SPIRV_TOOLS, "source/validate_bitwise.cpp"), + path.join(SPIRV_TOOLS, "source/validate_builtins.cpp"), + path.join(SPIRV_TOOLS, "source/validate_capability.cpp"), + path.join(SPIRV_TOOLS, "source/validate_cfg.cpp"), + path.join(SPIRV_TOOLS, "source/validate_composites.cpp"), + path.join(SPIRV_TOOLS, "source/validate_conversion.cpp"), + path.join(SPIRV_TOOLS, "source/validate_datarules.cpp"), + path.join(SPIRV_TOOLS, "source/validate_decorations.cpp"), + path.join(SPIRV_TOOLS, "source/validate_derivatives.cpp"), + path.join(SPIRV_TOOLS, "source/validate_ext_inst.cpp"), + path.join(SPIRV_TOOLS, "source/validate_id.cpp"), + path.join(SPIRV_TOOLS, "source/validate_image.cpp"), + path.join(SPIRV_TOOLS, "source/validate_instruction.cpp"), + path.join(SPIRV_TOOLS, "source/validate_layout.cpp"), + path.join(SPIRV_TOOLS, "source/validate_literals.cpp"), + path.join(SPIRV_TOOLS, "source/validate_logicals.cpp"), + path.join(SPIRV_TOOLS, "source/validate_primitives.cpp"), + path.join(SPIRV_TOOLS, "source/validate_type_unique.cpp"), + path.join(SPIRV_TOOLS, "source/val/decoration.h"), + path.join(SPIRV_TOOLS, "source/val/basic_block.cpp"), + path.join(SPIRV_TOOLS, "source/val/construct.cpp"), + path.join(SPIRV_TOOLS, "source/val/function.cpp"), + path.join(SPIRV_TOOLS, "source/val/instruction.cpp"), + path.join(SPIRV_TOOLS, "source/val/validation_state.cpp"), + } + + configuration { "vs*" } + buildoptions { + "/wd4127", -- warning C4127: conditional expression is constant + "/wd4389", -- warning C4389: '==': signed/unsigned mismatch + "/wd4702", -- warning C4702: unreachable code + "/wd4706", -- warning C4706: assignment within conditional expression + } project "glslang" kind "StaticLib" - local GLSLANG = path.join(BGFX_DIR, "3rdparty/glslang") + defines { + "ENABLE_OPT=1", -- spirv-tools + } + + includedirs { + GLSLANG, + path.join(SPIRV_TOOLS, "include"), + path.join(SPIRV_TOOLS, "source"), + } + + files { + path.join(GLSLANG, "glslang/**.cpp"), + path.join(GLSLANG, "glslang/**.h"), + + path.join(GLSLANG, "hlsl/**.cpp"), + path.join(GLSLANG, "hlsl/**.h"), + + path.join(GLSLANG, "SPIRV/**.cpp"), + path.join(GLSLANG, "SPIRV/**.h"), + + path.join(GLSLANG, "OGLCompilersDLL/**.cpp"), + path.join(GLSLANG, "OGLCompilersDLL/**.h"), + } + + removefiles { + path.join(GLSLANG, "glslang/OSDependent/Unix/main.cpp"), + path.join(GLSLANG, "glslang/OSDependent/Windows/main.cpp"), + } + + configuration { "windows" } + removefiles { + path.join(GLSLANG, "glslang/OSDependent/Unix/**.cpp"), + path.join(GLSLANG, "glslang/OSDependent/Unix/**.h"), + } + + configuration { "not windows" } + removefiles { + path.join(GLSLANG, "glslang/OSDependent/Windows/**.cpp"), + path.join(GLSLANG, "glslang/OSDependent/Windows/**.h"), + } configuration { "vs*" } buildoptions { @@ -65,41 +221,6 @@ project "glslang" "ENABLE_HLSL=1", } - includedirs { - GLSLANG, - } - - files { - path.join(GLSLANG, "glslang/**.cpp"), - path.join(GLSLANG, "glslang/**.h"), - - path.join(GLSLANG, "hlsl/**.cpp"), - path.join(GLSLANG, "hlsl/**.h"), - - path.join(GLSLANG, "SPIRV/**.cpp"), - path.join(GLSLANG, "SPIRV/**.h"), - - path.join(GLSLANG, "OGLCompilersDLL/**.cpp"), - path.join(GLSLANG, "OGLCompilersDLL/**.h"), - } - - removefiles { - path.join(GLSLANG, "glslang/OSDependent/Unix/main.cpp"), - path.join(GLSLANG, "glslang/OSDependent/Windows/main.cpp"), - } - - configuration { "windows" } - removefiles { - path.join(GLSLANG, "glslang/OSDependent/Unix/**.cpp"), - path.join(GLSLANG, "glslang/OSDependent/Unix/**.h"), - } - - configuration { "not windows" } - removefiles { - path.join(GLSLANG, "glslang/OSDependent/Windows/**.cpp"), - path.join(GLSLANG, "glslang/OSDependent/Windows/**.h"), - } - configuration {} project "glsl-optimizer" @@ -177,7 +298,6 @@ project "glsl-optimizer" "-fno-strict-aliasing", -- glsl-optimizer has bugs if strict aliasing is used. "-Wno-implicit-fallthrough", - "-Wno-misleading-indentation", "-Wno-sign-compare", "-Wno-unused-function", "-Wno-unused-parameter", @@ -187,6 +307,16 @@ project "glsl-optimizer" "-Wshadow", -- glsl-optimizer is full of -Wshadow warnings ignore it. } + configuration { "osx" } + buildoptions { + "-Wno-deprecated-register", + } + + configuration { "mingw* or linux" } + buildoptions { + "-Wno-misleading-indentation", + } + configuration {} project "fcpp" @@ -250,6 +380,7 @@ project "shaderc" "fcpp", "glslang", "glsl-optimizer", + "spirv-opt", } files { diff --git a/tools/shaderc/shaderc_spirv.cpp b/tools/shaderc/shaderc_spirv.cpp index d9e5c46c5..44cc3d320 100644 --- a/tools/shaderc/shaderc_spirv.cpp +++ b/tools/shaderc/shaderc_spirv.cpp @@ -683,7 +683,11 @@ namespace bgfx { namespace spirv glslang::TIntermediate* intermediate = program->getIntermediate(stage); std::vector spirv; - glslang::GlslangToSpv(*intermediate, spirv); + + glslang::SpvOptions options; + options.disableOptimizer = false; + + glslang::GlslangToSpv(*intermediate, spirv, &options); spv::spirvbin_t spvBin; spvBin.remap( spirv