diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2021-11-30 21:31:12 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2021-11-30 21:31:12 +0000 |
commit | d850eb9b0048a9b84e7401656ece1af7fbc452b1 (patch) | |
tree | 91e55dcba56d758debfaff55f88b4558e6655143 | |
parent | 18d866c00b2f28fc562f2a085c3714d839463f82 (diff) | |
parent | df000f93d5c42cb983047fe3702afc2d4ae029da (diff) | |
download | spirv-tools-ndk-r24-release.tar.gz |
Snap for 7956253 from df000f93d5c42cb983047fe3702afc2d4ae029da to ndk-r24-releasendk-r24-rc1ndk-r24-beta2ndk-r24ndk-r24-release
Change-Id: I0ba3b524ad64da20e424243ef7845714a86c2f44
209 files changed, 10177 insertions, 2803 deletions
diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index 0a4cca05..00000000 --- a/.appveyor.yml +++ /dev/null @@ -1,90 +0,0 @@ -# Windows Build Configuration for AppVeyor -# http://www.appveyor.com/docs/appveyor-yml - -# version format -version: "{build}" - -# The most recent compiler gives the most interesting new results. -# Put it first so we get its feedback first. -os: - - Visual Studio 2017 - #- Visual Studio 2013 - -platform: - - x64 - -configuration: - - Debug - #- Release - -branches: - only: - - master - -# Travis advances the master-tot tag to current top of the tree after -# each push into the master branch, because it relies on that tag to -# upload build artifacts to the master-tot release. This will cause -# double testing for each push on Appveyor: one for the push, one for -# the tag advance. Disable testing tags. -skip_tags: true - -clone_depth: 1 - -matrix: - fast_finish: true # Show final status immediately if a test fails. - #exclude: - # - os: Visual Studio 2013 - # configuration: Debug - -# scripts that run after cloning repository -install: - # Install ninja - - set NINJA_URL="https://github.com/ninja-build/ninja/releases/download/v1.8.2/ninja-win.zip" - - appveyor DownloadFile %NINJA_URL% -FileName ninja.zip - - 7z x ninja.zip -oC:\ninja > nul - - set PATH=C:\ninja;C:\Python36;%PATH% - -before_build: - - git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers.git external/spirv-headers - - git clone https://github.com/google/googletest.git external/googletest - - cd external && cd googletest && git reset --hard 1fb1bb23bb8418dc73a5a9a82bbed31dc610fec7 && cd .. && cd .. - - 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 -DCMAKE_BUILD_TYPE=%CONFIGURATION% -DCMAKE_INSTALL_PREFIX=install -DRE2_BUILD_TESTING=OFF .. - - ninja install - -test_script: - - ctest -C %CONFIGURATION% --output-on-failure --timeout 310 - -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/.github/workflows/wasm.yml b/.github/workflows/wasm.yml new file mode 100644 index 00000000..d9a9c5cb --- /dev/null +++ b/.github/workflows/wasm.yml @@ -0,0 +1,14 @@ +name: Wasm Build + +on: [ push, pull_request ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Build web + run: docker-compose up + - name: Run tests + run: node test/wasm/test.js @@ -100,6 +100,7 @@ SPVTOOLS_OPT_SRC_FILES := \ source/opt/debug_info_manager.cpp \ source/opt/def_use_manager.cpp \ source/opt/desc_sroa.cpp \ + source/opt/desc_sroa_util.cpp \ source/opt/dominator_analysis.cpp \ source/opt/dominator_tree.cpp \ source/opt/eliminate_dead_constant_pass.cpp \ @@ -157,6 +158,7 @@ SPVTOOLS_OPT_SRC_FILES := \ source/opt/relax_float_ops_pass.cpp \ source/opt/remove_duplicates_pass.cpp \ source/opt/remove_unused_interface_variables_pass.cpp \ + source/opt/replace_desc_array_access_using_var_index.cpp \ source/opt/replace_invalid_opc.cpp \ source/opt/scalar_analysis.cpp \ source/opt/scalar_analysis_simplification.cpp \ @@ -184,7 +186,7 @@ SPV_GLSL_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/unified1/extinst.glsl.st SPV_OPENCL_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/unified1/extinst.opencl.std.100.grammar.json SPV_DEBUGINFO_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/unified1/extinst.debuginfo.grammar.json SPV_CLDEBUGINFO100_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/unified1/extinst.opencl.debuginfo.100.grammar.json -SPV_VKDEBUGINFO100_GRAMMAR=$(LOCAL_PATH)/source/extinst.nonsemantic.vulkan.debuginfo.100.grammar.json +SPV_VKDEBUGINFO100_GRAMMAR=$(SPVHEADERS_LOCAL_PATH)/include/spirv/unified1/extinst.nonsemantic.shader.debuginfo.100.grammar.json define gen_spvtools_grammar_tables $(call generate-file-dir,$(1)/core.insts-unified1.inc) @@ -216,7 +218,7 @@ $(LOCAL_PATH)/source/ext_inst.cpp: \ $(1)/opencl.std.insts.inc \ $(1)/debuginfo.insts.inc \ $(1)/opencl.debuginfo.100.insts.inc \ - $(1)/nonsemantic.vulkan.debuginfo.100.insts.inc \ + $(1)/nonsemantic.shader.debuginfo.100.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 \ @@ -246,7 +248,7 @@ endef # We generate language-specific headers for DebugInfo and OpenCL.DebugInfo.100 $(eval $(call gen_spvtools_lang_headers,$(SPVTOOLS_OUT_PATH),DebugInfo,$(SPV_DEBUGINFO_GRAMMAR))) $(eval $(call gen_spvtools_lang_headers,$(SPVTOOLS_OUT_PATH),OpenCLDebugInfo100,$(SPV_CLDEBUGINFO100_GRAMMAR))) -$(eval $(call gen_spvtools_lang_headers,$(SPVTOOLS_OUT_PATH),NonSemanticVulkanDebugInfo100,$(SPV_VKDEBUGINFO100_GRAMMAR))) +$(eval $(call gen_spvtools_lang_headers,$(SPVTOOLS_OUT_PATH),NonSemanticShaderDebugInfo100,$(SPV_VKDEBUGINFO100_GRAMMAR))) define gen_spvtools_vendor_tables @@ -261,22 +263,10 @@ $(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 -define gen_spvtools_vendor_tables_local -$(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 \ - --vendor-operand-kind-prefix=$(3) - @echo "[$(TARGET_ARCH_ABI)] Vendor extended instruction set: $(2) tables <= grammar" -$(LOCAL_PATH)/source/ext_inst.cpp: $(1)/$(2).insts.inc -endef # Vendor and debug extended instruction sets, with grammars from SPIRV-Tools source tree. $(eval $(call gen_spvtools_vendor_tables,$(SPVTOOLS_OUT_PATH),debuginfo,"")) $(eval $(call gen_spvtools_vendor_tables,$(SPVTOOLS_OUT_PATH),opencl.debuginfo.100,"CLDEBUG100_")) -$(eval $(call gen_spvtools_vendor_tables_local,$(SPVTOOLS_OUT_PATH),nonsemantic.vulkan.debuginfo.100,"VKDEBUG100_")) +$(eval $(call gen_spvtools_vendor_tables,$(SPVTOOLS_OUT_PATH),nonsemantic.shader.debuginfo.100,"SHDEBUG100_")) $(eval $(call gen_spvtools_vendor_tables,$(SPVTOOLS_OUT_PATH),spv-amd-gcn-shader,"")) $(eval $(call gen_spvtools_vendor_tables,$(SPVTOOLS_OUT_PATH),spv-amd-shader-ballot,"")) $(eval $(call gen_spvtools_vendor_tables,$(SPVTOOLS_OUT_PATH),spv-amd-shader-explicit-vertex-parameter,"")) diff --git a/BUILD.bazel b/BUILD.bazel index 68e612ac..b2031ded 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -3,7 +3,7 @@ load( "COMMON_COPTS", "DEBUGINFO_GRAMMAR_JSON_FILE", "CLDEBUGINFO100_GRAMMAR_JSON_FILE", - "VKDEBUGINFO100_GRAMMAR_JSON_FILE", + "SHDEBUGINFO100_GRAMMAR_JSON_FILE", "TEST_COPTS", "base_test", "generate_core_tables", @@ -12,7 +12,6 @@ load( "generate_glsl_tables", "generate_opencl_tables", "generate_vendor_tables", - "generate_vendor_tables_local", "link_test", "lint_test", "opt_test", @@ -62,7 +61,7 @@ generate_vendor_tables("debuginfo") generate_vendor_tables("opencl.debuginfo.100", "CLDEBUG100_") -generate_vendor_tables_local("nonsemantic.vulkan.debuginfo.100", "VKDEBUG100_") +generate_vendor_tables("nonsemantic.shader.debuginfo.100", "SHDEBUG100_") generate_vendor_tables("nonsemantic.clspvreflection") @@ -70,7 +69,7 @@ generate_extinst_lang_headers("DebugInfo", DEBUGINFO_GRAMMAR_JSON_FILE) generate_extinst_lang_headers("OpenCLDebugInfo100", CLDEBUGINFO100_GRAMMAR_JSON_FILE) -generate_extinst_lang_headers("NonSemanticVulkanDebugInfo100", VKDEBUGINFO100_GRAMMAR_JSON_FILE) +generate_extinst_lang_headers("NonSemanticShaderDebugInfo100", SHDEBUGINFO100_GRAMMAR_JSON_FILE) py_binary( name = "generate_registry_tables", @@ -108,14 +107,14 @@ cc_library( ":gen_enum_string_mapping", ":gen_extinst_lang_headers_DebugInfo", ":gen_extinst_lang_headers_OpenCLDebugInfo100", - ":gen_extinst_lang_headers_NonSemanticVulkanDebugInfo100", + ":gen_extinst_lang_headers_NonSemanticShaderDebugInfo100", ":gen_glsl_tables_unified1", ":gen_opencl_tables_unified1", ":gen_registry_tables", ":gen_vendor_tables_debuginfo", ":gen_vendor_tables_nonsemantic_clspvreflection", ":gen_vendor_tables_opencl_debuginfo_100", - ":gen_vendor_tables_nonsemantic_vulkan_debuginfo_100", + ":gen_vendor_tables_nonsemantic_shader_debuginfo_100", ":gen_vendor_tables_spv_amd_gcn_shader", ":gen_vendor_tables_spv_amd_shader_ballot", ":gen_vendor_tables_spv_amd_shader_explicit_vertex_parameter", @@ -225,29 +225,6 @@ template("spvtools_vendor_table") { } } -template("spvtools_vendor_table_local") { - assert(defined(invoker.name), "Need name in $target_name generation.") - - action("spvtools_vendor_tables_" + target_name) { - script = "utils/generate_grammar_tables.py" - - name = invoker.name - extinst_vendor_grammar = "source/extinst.${name}.grammar.json" - extinst_file = "${target_gen_dir}/${name}.insts.inc" - - args = [ - "--extinst-vendor-grammar", - rebase_path(extinst_vendor_grammar, root_build_dir), - "--vendor-insts-output", - rebase_path(extinst_file, root_build_dir), - "--vendor-operand-kind-prefix", - invoker.operand_kind_prefix, - ] - inputs = [ extinst_vendor_grammar ] - outputs = [ extinst_file ] - } -} - action("spvtools_generators_inc") { script = "utils/generate_registry_tables.py" @@ -300,8 +277,8 @@ spvtools_language_header("cldebuginfo100") { grammar_file = "${spirv_headers}/include/spirv/unified1/extinst.opencl.debuginfo.100.grammar.json" } spvtools_language_header("vkdebuginfo100") { - name = "NonSemanticVulkanDebugInfo100" - grammar_file = "source/extinst.nonsemantic.vulkan.debuginfo.100.grammar.json" + name = "NonSemanticShaderDebugInfo100" + grammar_file = "${spirv_headers}/include/spirv/unified1/extinst.nonsemantic.shader.debuginfo.100.grammar.json" } spvtools_vendor_tables = [ @@ -333,13 +310,12 @@ spvtools_vendor_tables = [ "nonsemantic.clspvreflection", "...nil...", ], + [ + "nonsemantic.shader.debuginfo.100", + "SHDEBUG100_", + ], ] -spvtools_vendor_tables_local = [ [ - "nonsemantic.vulkan.debuginfo.100", - "VKDEBUG100_", - ] ] - foreach(table_def, spvtools_vendor_tables) { spvtools_vendor_table(table_def[0]) { name = table_def[0] @@ -347,13 +323,6 @@ foreach(table_def, spvtools_vendor_tables) { } } -foreach(table_def, spvtools_vendor_tables_local) { - spvtools_vendor_table_local(table_def[0]) { - name = table_def[0] - operand_kind_prefix = table_def[1] - } -} - config("spvtools_public_config") { include_dirs = [ "include" ] } @@ -421,10 +390,6 @@ static_library("spvtools") { target_name = table_def[0] deps += [ ":spvtools_vendor_tables_$target_name" ] } - foreach(table_def, spvtools_vendor_tables_local) { - target_name = table_def[0] - deps += [ ":spvtools_vendor_tables_$target_name" ] - } sources = [ "source/assembly_grammar.cpp", @@ -610,14 +575,14 @@ static_library("spvtools_opt") { "source/opt/constants.h", "source/opt/control_dependence.cpp", "source/opt/control_dependence.h", - "source/opt/convert_to_sampled_image_pass.cpp", - "source/opt/convert_to_sampled_image_pass.h", "source/opt/convert_to_half_pass.cpp", "source/opt/convert_to_half_pass.h", + "source/opt/convert_to_sampled_image_pass.cpp", + "source/opt/convert_to_sampled_image_pass.h", "source/opt/copy_prop_arrays.cpp", "source/opt/copy_prop_arrays.h", - "source/opt/dataflow.h", "source/opt/dataflow.cpp", + "source/opt/dataflow.h", "source/opt/dead_branch_elim_pass.cpp", "source/opt/dead_branch_elim_pass.h", "source/opt/dead_insert_elim_pass.cpp", @@ -632,6 +597,8 @@ static_library("spvtools_opt") { "source/opt/def_use_manager.h", "source/opt/desc_sroa.cpp", "source/opt/desc_sroa.h", + "source/opt/desc_sroa_util.cpp", + "source/opt/desc_sroa_util.h", "source/opt/dominator_analysis.cpp", "source/opt/dominator_analysis.h", "source/opt/dominator_tree.cpp", @@ -751,6 +718,8 @@ static_library("spvtools_opt") { "source/opt/remove_duplicates_pass.h", "source/opt/remove_unused_interface_variables_pass.cpp", "source/opt/remove_unused_interface_variables_pass.h", + "source/opt/replace_desc_array_access_using_var_index.cpp", + "source/opt/replace_desc_array_access_using_var_index.h", "source/opt/replace_invalid_opc.cpp", "source/opt/replace_invalid_opc.h", "source/opt/scalar_analysis.cpp", @@ -1,5 +1,19 @@ Revision history for SPIRV-Tools +v2021.4 2021-11-11 + - General + - Add a WebAssembly build (#3752) + - Make cxx exceptions controllable (#4591) + - Validator + - Improve decoration validation (#4490) + - Optimizer + - Add spirv-opt pass to replace descriptor accesses based on variable indices (#4574) + - Do not fold snegate feeding sdiv (#4600) + - Handle overflowing id in merge return (#4606) + - Fuzzer + - Add libFuzzer target for spirv-fuzz (#4434) + - Linter + v2021.3 2021-08-24 - General - Initial support for SPV_KHR_integer_dot_product (#4327) diff --git a/CMakeLists.txt b/CMakeLists.txt index 12231724..70caf857 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,6 +84,8 @@ endif(SPIRV_BUILD_COMPRESSION) option(SPIRV_BUILD_FUZZER "Build spirv-fuzz" OFF) +set(SPIRV_LIB_FUZZING_ENGINE_LINK_OPTIONS "" CACHE STRING "Used by OSS-Fuzz to control, via link options, which fuzzing engine should be used") + option(SPIRV_BUILD_LIBFUZZER_TARGETS "Build libFuzzer targets" OFF) option(SPIRV_WERROR "Enable error on warning" ON) @@ -162,6 +164,7 @@ endif() # Note this target provides no API stability guarantees. # # Ideally, all of these will go away - see https://github.com/KhronosGroup/SPIRV-Tools/issues/3909. +option(ENABLE_EXCEPTIONS_ON_MSVC "Build SPIRV-TOOLS with c++ exceptions enabled in MSVC" ON) option(SPIRV_TOOLS_BUILD_STATIC "Build ${SPIRV_TOOLS}-static target. ${SPIRV_TOOLS} will alias to ${SPIRV_TOOLS}-static or ${SPIRV_TOOLS}-shared based on BUILD_SHARED_LIBS" ON) if(SPIRV_TOOLS_BUILD_STATIC) set(SPIRV_TOOLS_FULL_VISIBILITY ${SPIRV_TOOLS}-static) @@ -213,7 +216,9 @@ function(spvtools_default_compile_options TARGET) if (MSVC) # Specify /EHs for exception handling. This makes using SPIRV-Tools as # dependencies in other projects easier. - target_compile_options(${TARGET} PRIVATE /EHs) + if(ENABLE_EXCEPTIONS_ON_MSVC) + target_compile_options(${TARGET} PRIVATE /EHs) + endif() endif() # For MinGW cross compile, statically link to the C++ runtime. @@ -294,7 +299,7 @@ endif() # 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) + add_compile_options($<$<CONFIG:Debug>:-DSPIRV_CHECK_CONTEXT>) endif() # Precompiled header macro. Parameters are source file list and filename for pch cpp file. @@ -3,10 +3,10 @@ use_relative_paths = True vars = { 'github': 'https://github.com', - 'effcee_revision': '2ec8f8738118cc483b67c04a759fee53496c5659', - 'googletest_revision': 'b7d472f1225c5a64943821d8483fecb469d3f382', - 're2_revision': 'f8e389f3acdc2517562924239e2a188037393683', - 'spirv_headers_revision': 'e71feddb3f17c5586ff7f4cfb5ed1258b800574b', + 'effcee_revision': 'ddf5e2bb92957dc8a12c5392f8495333d6844133', + 'googletest_revision': 'bf0701daa9f5b30e5882e2f8f9a5280bcba87e77', + 're2_revision': '4244cd1cb492fa1d10986ec67f862964c073f844', + 'spirv_headers_revision': '814e728b30ddd0f4509233099a3ad96fd4318c07', } deps = { @@ -21,7 +21,6 @@ headers, and XML registry. ## Downloads -[![Build status](https://ci.appveyor.com/api/projects/status/gpue87cesrx3pi0d/branch/master?svg=true)](https://ci.appveyor.com/project/Khronoswebmaster/spirv-tools/branch/master) <img alt="Linux" src="kokoro/img/linux.png" width="20px" height="20px" hspace="2px"/>[![Linux Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_linux_clang_release.svg)](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_clang_release.html) <img alt="MacOS" src="kokoro/img/macos.png" width="20px" height="20px" hspace="2px"/>[![MacOS Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_macos_clang_release.svg)](https://storage.googleapis.com/spirv-tools/badges/build_link_macos_clang_release.html) <img alt="Windows" src="kokoro/img/windows.png" width="20px" height="20px" hspace="2px"/>[![Windows Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_windows_release.svg)](https://storage.googleapis.com/spirv-tools/badges/build_link_windows_vs2017_release.html) @@ -53,7 +52,7 @@ These versions undergo extra testing. Releases are not directly related to releases (or versions) of [SPIRV-Headers][spirv-headers]. Releases of SPIRV-Tools are tested against the version of SPIRV-Headers listed -in the DEPS file. +in the [DEPS](DEPS) file. The release generally uses the most recent compatible version of SPIRV-Headers available at the time of release. No version of SPIRV-Headers other than the one listed in the DEPS file is @@ -256,6 +255,34 @@ Contributions via merge request are welcome. Changes should: We intend to maintain a linear history on the GitHub `master` branch. +### Getting the source + +Example of getting sources, assuming SPIRV-Tools is configured as a standalone project: + + git clone https://github.com/KhronosGroup/SPIRV-Tools.git spirv-tools + cd spirv-tools + + # Check out sources for dependencies, at versions known to work together, + # as listed in the DEPS file. + python3 utils/git-sync-deps + +For some kinds of development, you may need the latest sources from the third-party projects: + + 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 + +#### 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`. + ### Source code organization * `example`: demo code of using SPIRV-Tools APIs @@ -274,14 +301,6 @@ We intend to maintain a linear history on the GitHub `master` branch. * `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 @@ -295,46 +314,12 @@ tests: `googletest` source into the `<spirv-dir>/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. - -### 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`. - - ## 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. +*Note*: Prebuilt binaries are available from the [downloads](docs/downloads.md) page. -In order to build the code, you first need to sync the external repositories -that it depends on. Assume that `<spirv-dir>` is the root directory of the -checked out code: - -```sh -cd <spirv-dir> -git clone https://github.com/KhronosGroup/SPIRV-Headers.git external/spirv-headers -git clone https://github.com/google/effcee.git external/effcee -git clone https://github.com/google/re2.git external/re2 -git clone https://github.com/google/googletest.git external/googletest # optional - -``` - -*Note*: -The script `utils/git-sync-deps` can be used to checkout and/or update the -contents of the repos under `external/` instead of manually maintaining them. +First [get the sources](#getting-the-source). +Then build using CMake, Bazel, Android ndk-build, or the Emscripten SDK. ### Build using CMake You can build the project using [CMake][cmake]: @@ -379,6 +364,30 @@ You can also use [Bazel](https://bazel.build/) to build the project. cd <spirv-dir> bazel build :all ``` +### Build a node.js package using Emscripten + +The SPIRV-Tools core library can be built to a WebAssembly [node.js](https://nodejs.org) +module. The resulting `SpirvTools` WebAssembly module only exports methods to +assemble and disassemble SPIR-V modules. + +First, make sure you have the [Emscripten SDK](https://emscripten.org). +Then: + +```sh +cd <spirv-dir> +./source/wasm/build.sh +``` + +The resulting node package, with JavaScript and TypeScript bindings, is +written to `<spirv-dir>/out/web`. + +Note: This builds the package locally. It does *not* publish it to [npm](https://npmjs.org). + +To test the result: + +```sh +node ./test/wasm/test.js +``` ### Tools you'll need @@ -392,15 +401,17 @@ suite. - [Bazel](https://bazel.build/) (optional): if building the source with Bazel, you need to install Bazel Version 0.29.1 on your machine. Other versions may also work, but are not verified. +- [Emscripten SDK](https://emscripten.org) (optional): if building the + WebAssembly module. SPIRV-Tools is regularly tested with the following compilers: On Linux -- GCC version 4.8.5 -- Clang version 3.8 +- GCC version 9.3 +- Clang version 10.0 On MacOS -- AppleClang 10.0 +- AppleClang 11.0 On Windows - Visual Studio 2015 @@ -435,7 +446,7 @@ 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. -### Android +### Android ndk-build SPIR-V Tools supports building static libraries `libSPIRV-Tools.a` and `libSPIRV-Tools-opt.a` for Android: @@ -456,11 +467,12 @@ $ANDROID_NDK/ndk-build -C ../android_test \ ``` ### Updating DEPS -Occasionally the entries in DEPS will need to be updated. This is done on demand -when there is a request to do this, often due to downstream breakages. There is -a script `utils/roll_deps.sh` provided, which will generate a patch with the -updated DEPS values. This will still need to be tested in your checkout to -confirm that there are no integration issues that need to be resolved. + +Occasionally the entries in [DEPS](DEPS) will need to be updated. This is done on +demand when there is a request to do this, often due to downstream breakages. +To update `DEPS`, run `utils/roll_deps.sh` and confirm that tests pass. +The script requires Chromium's +[`depot_tools`](https://chromium.googlesource.com/chromium/tools/depot_tools). ## Library @@ -743,4 +755,3 @@ limitations under the License. [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/build_defs.bzl b/build_defs.bzl index 519fa193..b2cd41b9 100644 --- a/build_defs.bzl +++ b/build_defs.bzl @@ -41,7 +41,7 @@ TEST_COPTS = COMMON_COPTS + select({ DEBUGINFO_GRAMMAR_JSON_FILE = "@spirv_headers//:spirv_ext_inst_debuginfo_grammar_unified1" CLDEBUGINFO100_GRAMMAR_JSON_FILE = "@spirv_headers//:spirv_ext_inst_opencl_debuginfo_100_grammar_unified1" -VKDEBUGINFO100_GRAMMAR_JSON_FILE = "source/extinst.nonsemantic.vulkan.debuginfo.100.grammar.json" +SHDEBUGINFO100_GRAMMAR_JSON_FILE = "@spirv_headers//:spirv_ext_inst_nonsemantic_shader_debuginfo_100_grammar_unified1" def generate_core_tables(version = None): if not version: @@ -165,28 +165,6 @@ def generate_vendor_tables(extension, operand_kind_prefix = ""): visibility = ["//visibility:private"], ) -def generate_vendor_tables_local(extension, operand_kind_prefix = ""): - if not extension: - fail("Must specify extension", "extension") - extension_rule = extension.replace("-", "_").replace(".", "_") - grammars = ["source/extinst.{}.grammar.json".format(extension)] - outs = ["{}.insts.inc".format(extension)] - prefices = [operand_kind_prefix] - fmtargs = grammars + outs + prefices - native.genrule( - name = "gen_vendor_tables_" + extension_rule, - srcs = grammars, - outs = outs, - cmd = ( - "$(location :generate_grammar_tables) " + - "--extinst-vendor-grammar=$(location {0}) " + - "--vendor-insts-output=$(location {1}) " + - "--vendor-operand-kind-prefix={2}" - ).format(*fmtargs), - tools = [":generate_grammar_tables"], - visibility = ["//visibility:private"], - ) - def generate_extinst_lang_headers(name, grammar = None): if not grammar: fail("Must specify grammar", "grammar") diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..fb6d114f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,10 @@ +version: "3" +services: + build: + image: emscripten/emsdk:2.0.2 + environment: + GITHUB_RUN_NUMBER: ${GITHUB_RUN_NUMBER:-} + working_dir: /app + command: ./source/wasm/build.sh + volumes: + - ./:/app diff --git a/docs/downloads.md b/docs/downloads.md index 9c7d8567..168937a7 100644 --- a/docs/downloads.md +++ b/docs/downloads.md @@ -1,14 +1,28 @@ # Downloads -Download the latest builds. -## Release +## Latest builds + +Download the latest builds of the [master](https://github.com/KhronosGroup/SPIRV-Tools/tree/master) branch. + +### Release build | Windows | Linux | MacOS | | --- | --- | --- | | [MSVC 2017](https://storage.googleapis.com/spirv-tools/badges/build_link_windows_vs2017_release.html) | [clang](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_clang_release.html) | [clang](https://storage.googleapis.com/spirv-tools/badges/build_link_macos_clang_release.html) | | | [gcc](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_gcc_release.html) | | -## Debug +### Debug build | Windows | Linux | MacOS | | --- | --- | --- | | [MSVC 2017](https://storage.googleapis.com/spirv-tools/badges/build_link_windows_vs2017_debug.html) | [clang](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_clang_debug.html) | [clang](https://storage.googleapis.com/spirv-tools/badges/build_link_macos_clang_debug.html) | | | [gcc](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_gcc_debug.html) | | + + +## Vulkan SDK + +SPIRV-Tools is published as part of the [LunarG Vulkan SDK](https://www.lunarg.com/vulkan-sdk/). +The Vulkan SDK is updated approximately every six weeks. + +## Android NDK + +SPIRV-Tools host executables, and library sources are published as +part of the [Android NDK](https://developer.android.com/ndk/downloads). diff --git a/include/spirv-tools/libspirv.h b/include/spirv-tools/libspirv.h index 999589e2..8df14f5f 100644 --- a/include/spirv-tools/libspirv.h +++ b/include/spirv-tools/libspirv.h @@ -309,7 +309,7 @@ typedef enum spv_ext_inst_type_t { SPV_EXT_INST_TYPE_DEBUGINFO, SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100, SPV_EXT_INST_TYPE_NONSEMANTIC_CLSPVREFLECTION, - SPV_EXT_INST_TYPE_NONSEMANTIC_VULKAN_DEBUGINFO_100, + SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100, // Multiple distinct extended instruction set types could return this // value, if they are prefixed with NonSemantic. and are otherwise @@ -516,6 +516,7 @@ typedef enum { SPV_ENV_UNIVERSAL_1_5, // SPIR-V 1.5 latest revision, no other restrictions. SPV_ENV_VULKAN_1_2, // Vulkan 1.2 latest revision. + SPV_ENV_MAX // Keep this as the last enum value. } spv_target_env; // SPIR-V Validator can be parameterized with the following Universal Limits. @@ -659,6 +660,11 @@ SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetWorkgroupScalarBlockLayout( SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetSkipBlockLayout( spv_validator_options options, bool val); +// Records whether or not the validator should allow the LocalSizeId +// decoration where the environment otherwise would not allow it. +SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetAllowLocalSizeId( + spv_validator_options options, bool val); + // Creates an optimizer options object with default options. Returns a valid // options object. The object remains valid until it is passed into // |spvOptimizerOptionsDestroy|. diff --git a/include/spirv-tools/libspirv.hpp b/include/spirv-tools/libspirv.hpp index 0c31a182..8dfb46b7 100644 --- a/include/spirv-tools/libspirv.hpp +++ b/include/spirv-tools/libspirv.hpp @@ -115,6 +115,12 @@ class ValidatorOptions { spvValidatorOptionsSetSkipBlockLayout(options_, val); } + // Enables LocalSizeId decorations where the environment would not otherwise + // allow them. + void SetAllowLocalSizeId(bool val) { + spvValidatorOptionsSetAllowLocalSizeId(options_, val); + } + // Records whether or not the validator should relax the rules on pointer // usage in logical addressing mode. // diff --git a/include/spirv-tools/linter.hpp b/include/spirv-tools/linter.hpp index 57d1b4e9..52ed5a46 100644 --- a/include/spirv-tools/linter.hpp +++ b/include/spirv-tools/linter.hpp @@ -35,7 +35,7 @@ class Linter { void SetMessageConsumer(MessageConsumer consumer); // Returns a reference to the registered message consumer. - const MessageConsumer& consumer() const; + const MessageConsumer& Consumer() const; bool Run(const uint32_t* binary, size_t binary_size); diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp index b1442dcf..21059cbe 100644 --- a/include/spirv-tools/optimizer.hpp +++ b/include/spirv-tools/optimizer.hpp @@ -29,7 +29,7 @@ namespace spvtools { namespace opt { class Pass; struct DescriptorSetAndBinding; -} +} // namespace opt // C++ interface for SPIR-V optimization functionalities. It wraps the context // (including target environment and the corresponding SPIR-V grammar) and @@ -514,7 +514,12 @@ Optimizer::PassToken CreateDeadInsertElimPass(); // 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(); +// +// If |preserve_interface| is true, all non-io variables in the entry point +// interface are considered live and are not eliminated. This mode is needed +// by GPU-Assisted validation instrumentation, where a change in the interface +// is not allowed. +Optimizer::PassToken CreateAggressiveDCEPass(bool preserve_interface = false); // Creates a remove-unused-interface-variables pass. // Removes variables referenced on the |OpEntryPoint| instruction that are not @@ -701,8 +706,11 @@ Optimizer::PassToken CreateVectorDCEPass(); // Create a pass to reduce the size of loads. // This pass looks for loads of structures where only a few of its members are // used. It replaces the loads feeding an OpExtract with an OpAccessChain and -// a load of the specific elements. -Optimizer::PassToken CreateReduceLoadSizePass(); +// a load of the specific elements. The parameter is a threshold to determine +// whether we have to replace the load or not. If the ratio of the used +// components of the load is less than the threshold, we replace the load. +Optimizer::PassToken CreateReduceLoadSizePass( + double load_replacement_threshold = 0.9); // Create a pass to combine chained access chains. // This pass looks for access chains fed by other access chains and combines @@ -825,6 +833,13 @@ Optimizer::PassToken CreateFixStorageClassPass(); // inclusive. Optimizer::PassToken CreateGraphicsRobustAccessPass(); +// Create a pass to replace a descriptor access using variable index. +// This pass replaces every access using a variable index to array variable +// |desc| that has a DescriptorSet and Binding decorations with a constant +// element of the array. In order to replace the access using a variable index +// with the constant element, it uses a switch statement. +Optimizer::PassToken CreateReplaceDescArrayAccessUsingVarIndexPass(); + // Create descriptor scalar replacement pass. // This pass replaces every array variable |desc| that has a DescriptorSet and // Binding decorations with a new variable for each element of the array. diff --git a/kokoro/linux-clang-ubsan/build.sh b/kokoro/linux-clang-ubsan/build.sh new file mode 100755 index 00000000..b5941e34 --- /dev/null +++ b/kokoro/linux-clang-ubsan/build.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# Copyright (c) 2021 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. +# +# Linux Build Script. + +# Fail on any error. +set -e +# Display commands being run. +set -x + +SCRIPT_DIR=`dirname "$BASH_SOURCE"` +source $SCRIPT_DIR/../scripts/linux/build.sh UBSAN clang cmake diff --git a/kokoro/linux-clang-ubsan/continuous.cfg b/kokoro/linux-clang-ubsan/continuous.cfg new file mode 100644 index 00000000..cb5535e1 --- /dev/null +++ b/kokoro/linux-clang-ubsan/continuous.cfg @@ -0,0 +1,16 @@ +# Copyright (c) 2021 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. + +# Continuous build configuration. +build_file: "SPIRV-Tools/kokoro/linux-clang-ubsan/build.sh" diff --git a/kokoro/linux-clang-ubsan/presubmit.cfg b/kokoro/linux-clang-ubsan/presubmit.cfg new file mode 100644 index 00000000..029c74a5 --- /dev/null +++ b/kokoro/linux-clang-ubsan/presubmit.cfg @@ -0,0 +1,16 @@ +# Copyright (c) 2021 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. + +# Presubmit build configuration. +build_file: "SPIRV-Tools/kokoro/linux-clang-ubsan/build.sh" diff --git a/kokoro/scripts/linux/build-docker.sh b/kokoro/scripts/linux/build-docker.sh index c6bbe951..8f76803c 100755 --- a/kokoro/scripts/linux/build-docker.sh +++ b/kokoro/scripts/linux/build-docker.sh @@ -58,7 +58,7 @@ if [ $TOOL = "cmake" ]; then using ninja-1.10.0 # Possible configurations are: - # ASAN, COVERAGE, RELEASE, DEBUG, DEBUG_EXCEPTION, RELEASE_MINGW + # ASAN, UBSAN, COVERAGE, RELEASE, DEBUG, DEBUG_EXCEPTION, RELEASE_MINGW BUILD_TYPE="Debug" if [ $CONFIG = "RELEASE" ] || [ $CONFIG = "RELEASE_MINGW" ]; then BUILD_TYPE="RelWithDebInfo" @@ -69,6 +69,13 @@ if [ $TOOL = "cmake" ]; then if [ $CONFIG = "ASAN" ]; then ADDITIONAL_CMAKE_FLAGS="-DSPIRV_USE_SANITIZER=address,bounds,null" [ $COMPILER = "clang" ] || { echo "$CONFIG requires clang"; exit 1; } + elif [ $CONFIG = "UBSAN" ]; then + # UBSan requires RTTI, and by default UBSan does not exit when errors are + # encountered - additional compiler options are required to force this. + # The -DSPIRV_USE_SANITIZER=undefined option instructs SPIR-V Tools to be + # built with UBSan enabled. + ADDITIONAL_CMAKE_FLAGS="-DSPIRV_USE_SANITIZER=undefined -DENABLE_RTTI=ON -DCMAKE_C_FLAGS=-fno-sanitize-recover=all -DCMAKE_CXX_FLAGS=-fno-sanitize-recover=all" + [ $COMPILER = "clang" ] || { echo "$CONFIG requires clang"; exit 1; } elif [ $CONFIG = "COVERAGE" ]; then ADDITIONAL_CMAKE_FLAGS="-DENABLE_CODE_COVERAGE=ON" SKIP_TESTS="True" diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 6530f060..331ff675 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -20,10 +20,7 @@ set(LANG_HEADER_PROCESSING_SCRIPT "${spirv-tools_SOURCE_DIR}/utils/generate_lang # Pull in grammar files that have migrated to SPIRV-Headers set(DEBUGINFO_GRAMMAR_JSON_FILE "${SPIRV_HEADER_INCLUDE_DIR}/spirv/unified1/extinst.debuginfo.grammar.json") set(CLDEBUGINFO100_GRAMMAR_JSON_FILE "${SPIRV_HEADER_INCLUDE_DIR}/spirv/unified1/extinst.opencl.debuginfo.100.grammar.json") - -# For now, assume the NonSemantic.Vulkan.DebugInfo grammar file is in the current directory. -# It will later migrate to SPIRV-Headers. -set(VKDEBUGINFO100_GRAMMAR_JSON_FILE "${CMAKE_CURRENT_SOURCE_DIR}/extinst.nonsemantic.vulkan.debuginfo.100.grammar.json") +set(VKDEBUGINFO100_GRAMMAR_JSON_FILE "${SPIRV_HEADER_INCLUDE_DIR}/spirv/unified1/extinst.nonsemantic.shader.debuginfo.100.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 @@ -154,11 +151,11 @@ spvtools_vendor_tables("spv-amd-gcn-shader" "spv-amd-gs" "") spvtools_vendor_tables("spv-amd-shader-ballot" "spv-amd-sb" "") spvtools_vendor_tables("debuginfo" "debuginfo" "") spvtools_vendor_tables("opencl.debuginfo.100" "cldi100" "CLDEBUG100_") -spvtools_vendor_tables("nonsemantic.vulkan.debuginfo.100" "vkdi100" "VKDEBUG100_") +spvtools_vendor_tables("nonsemantic.shader.debuginfo.100" "shdi100" "SHDEBUG100_") spvtools_vendor_tables("nonsemantic.clspvreflection" "clspvreflection" "") spvtools_extinst_lang_headers("DebugInfo" ${DEBUGINFO_GRAMMAR_JSON_FILE}) spvtools_extinst_lang_headers("OpenCLDebugInfo100" ${CLDEBUGINFO100_GRAMMAR_JSON_FILE}) -spvtools_extinst_lang_headers("NonSemanticVulkanDebugInfo100" ${VKDEBUGINFO100_GRAMMAR_JSON_FILE}) +spvtools_extinst_lang_headers("NonSemanticShaderDebugInfo100" ${VKDEBUGINFO100_GRAMMAR_JSON_FILE}) spvtools_vimsyntax("unified1" "1.0") add_custom_target(spirv-tools-vimsyntax DEPENDS ${VIMSYNTAX_FILE}) @@ -431,8 +428,10 @@ if(ENABLE_SPIRV_TOOLS_INSTALL) # Special config file for root library compared to other libs. file(WRITE ${CMAKE_BINARY_DIR}/${SPIRV_TOOLS}Config.cmake "include(\${CMAKE_CURRENT_LIST_DIR}/${SPIRV_TOOLS}Target.cmake)\n" - "set(${SPIRV_TOOLS}_LIBRARIES ${SPIRV_TOOLS})\n" - "get_target_property(${SPIRV_TOOLS}_INCLUDE_DIRS ${SPIRV_TOOLS} INTERFACE_INCLUDE_DIRECTORIES)\n") + "if(TARGET ${SPIRV_TOOLS})\n" + " set(${SPIRV_TOOLS}_LIBRARIES ${SPIRV_TOOLS})\n" + " get_target_property(${SPIRV_TOOLS}_INCLUDE_DIRS ${SPIRV_TOOLS} INTERFACE_INCLUDE_DIRECTORIES)\n" + "endif()\n") install(FILES ${CMAKE_BINARY_DIR}/${SPIRV_TOOLS}Config.cmake DESTINATION ${PACKAGE_DIR}) endif(ENABLE_SPIRV_TOOLS_INSTALL) diff --git a/source/binary.cpp b/source/binary.cpp index 090cccfe..93d5da7a 100644 --- a/source/binary.cpp +++ b/source/binary.cpp @@ -507,7 +507,8 @@ spv_result_t Parser::parseOperand(size_t inst_offset, case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: { assert(SpvOpSpecConstantOp == opcode); - if (grammar_.lookupSpecConstantOpcode(SpvOp(word))) { + if (word > static_cast<uint32_t>(SpvOp::SpvOpMax) || + grammar_.lookupSpecConstantOpcode(SpvOp(word))) { return diagnostic() << "Invalid " << spvOperandTypeStr(type) << ": " << word; } diff --git a/source/common_debug_info.h b/source/common_debug_info.h index 0ae85aa0..ffa5d340 100644 --- a/source/common_debug_info.h +++ b/source/common_debug_info.h @@ -18,8 +18,8 @@ #define SOURCE_COMMON_DEBUG_INFO_HEADER_H_ // This enum defines the known common set of instructions that are the same -// between OpenCL.DebugInfo.100 and NonSemantic.Vulkan.DebugInfo.100. -// note that NonSemantic.DebugInfo.100 instructions can still have slightly +// between OpenCL.DebugInfo.100 and NonSemantic.Shader.DebugInfo.100. +// Note that NonSemantic.Shader.* instructions can still have slightly // different encoding, as it does not use literals anywhere and only constants. enum CommonDebugInfoInstructions { CommonDebugInfoDebugInfoNone = 0, diff --git a/source/ext_inst.cpp b/source/ext_inst.cpp index 8246c204..812053ec 100644 --- a/source/ext_inst.cpp +++ b/source/ext_inst.cpp @@ -29,7 +29,7 @@ #include "debuginfo.insts.inc" #include "glsl.std.450.insts.inc" #include "nonsemantic.clspvreflection.insts.inc" -#include "nonsemantic.vulkan.debuginfo.100.insts.inc" +#include "nonsemantic.shader.debuginfo.100.insts.inc" #include "opencl.debuginfo.100.insts.inc" #include "opencl.std.insts.inc" @@ -56,9 +56,9 @@ static const spv_ext_inst_group_t kGroups_1_0[] = { debuginfo_entries}, {SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100, ARRAY_SIZE(opencl_debuginfo_100_entries), opencl_debuginfo_100_entries}, - {SPV_EXT_INST_TYPE_NONSEMANTIC_VULKAN_DEBUGINFO_100, - ARRAY_SIZE(nonsemantic_vulkan_debuginfo_100_entries), - nonsemantic_vulkan_debuginfo_100_entries}, + {SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100, + ARRAY_SIZE(nonsemantic_shader_debuginfo_100_entries), + nonsemantic_shader_debuginfo_100_entries}, {SPV_EXT_INST_TYPE_NONSEMANTIC_CLSPVREFLECTION, ARRAY_SIZE(nonsemantic_clspvreflection_entries), nonsemantic_clspvreflection_entries}, @@ -130,8 +130,8 @@ spv_ext_inst_type_t spvExtInstImportTypeGet(const char* name) { if (!strcmp("OpenCL.DebugInfo.100", name)) { return SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100; } - if (!strcmp("NonSemantic.Vulkan.DebugInfo.100", name)) { - return SPV_EXT_INST_TYPE_NONSEMANTIC_VULKAN_DEBUGINFO_100; + if (!strcmp("NonSemantic.Shader.DebugInfo.100", name)) { + return SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100; } if (!strncmp("NonSemantic.ClspvReflection.", name, 28)) { return SPV_EXT_INST_TYPE_NONSEMANTIC_CLSPVREFLECTION; @@ -146,7 +146,7 @@ spv_ext_inst_type_t spvExtInstImportTypeGet(const char* name) { bool spvExtInstIsNonSemantic(const spv_ext_inst_type_t type) { if (type == SPV_EXT_INST_TYPE_NONSEMANTIC_UNKNOWN || - type == SPV_EXT_INST_TYPE_NONSEMANTIC_VULKAN_DEBUGINFO_100 || + type == SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100 || type == SPV_EXT_INST_TYPE_NONSEMANTIC_CLSPVREFLECTION) { return true; } @@ -155,7 +155,7 @@ bool spvExtInstIsNonSemantic(const spv_ext_inst_type_t type) { bool spvExtInstIsDebugInfo(const spv_ext_inst_type_t type) { if (type == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100 || - type == SPV_EXT_INST_TYPE_NONSEMANTIC_VULKAN_DEBUGINFO_100 || + type == SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100 || type == SPV_EXT_INST_TYPE_DEBUGINFO) { return true; } diff --git a/source/extinst.nonsemantic.vulkan.debuginfo.100.grammar.json b/source/extinst.nonsemantic.vulkan.debuginfo.100.grammar.json deleted file mode 100644 index 1d7914d7..00000000 --- a/source/extinst.nonsemantic.vulkan.debuginfo.100.grammar.json +++ /dev/null @@ -1,638 +0,0 @@ -{ - "copyright" : [ - "Copyright (c) 2018 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" : 2, - "instructions" : [ - { - "opname" : "DebugInfoNone", - "opcode" : 0 - }, - { - "opname" : "DebugCompilationUnit", - "opcode" : 1, - "operands" : [ - { "kind" : "IdRef", "name" : "'Version'" }, - { "kind" : "IdRef", "name" : "'DWARF Version'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "IdRef", "name" : "'Language'" } - ] - }, - { - "opname" : "DebugTypeBasic", - "opcode" : 2, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Size'" }, - { "kind" : "IdRef", "name" : "'Encoding'" }, - { "kind" : "IdRef", "name" : "'Flags'" } - ] - }, - { - "opname" : "DebugTypePointer", - "opcode" : 3, - "operands" : [ - { "kind" : "IdRef", "name" : "'Base Type'" }, - { "kind" : "IdRef", "name" : "'Storage Class'" }, - { "kind" : "IdRef", "name" : "'Flags'" } - ] - }, - { - "opname" : "DebugTypeQualifier", - "opcode" : 4, - "operands" : [ - { "kind" : "IdRef", "name" : "'Base Type'" }, - { "kind" : "IdRef", "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" : "IdRef", "name" : "'Component Count'" } - ] - }, - { - "opname" : "DebugTypedef", - "opcode" : 7, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Base Type'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "IdRef", "name" : "'Line'" }, - { "kind" : "IdRef", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Parent'" } - ] - }, - { - "opname" : "DebugTypeFunction", - "opcode" : 8, - "operands" : [ - { "kind" : "IdRef", "name" : "'Flags'" }, - { "kind" : "IdRef", "name" : "'Return Type'" }, - { "kind" : "IdRef", "name" : "'Parameter Types'", "quantifier" : "*" } - ] - }, - { - "opname" : "DebugTypeEnum", - "opcode" : 9, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Underlying Type'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "IdRef", "name" : "'Line'" }, - { "kind" : "IdRef", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Parent'" }, - { "kind" : "IdRef", "name" : "'Size'" }, - { "kind" : "IdRef", "name" : "'Flags'" }, - { "kind" : "PairIdRefIdRef", "name" : "'Value, Name, Value, Name, ...'", "quantifier" : "*" } - ] - }, - { - "opname" : "DebugTypeComposite", - "opcode" : 10, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Tag'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "IdRef", "name" : "'Line'" }, - { "kind" : "IdRef", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Parent'" }, - { "kind" : "IdRef", "name" : "'Linkage Name'" }, - { "kind" : "IdRef", "name" : "'Size'" }, - { "kind" : "IdRef", "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" : "IdRef", "name" : "'Line'" }, - { "kind" : "IdRef", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Offset'" }, - { "kind" : "IdRef", "name" : "'Size'" }, - { "kind" : "IdRef", "name" : "'Flags'" }, - { "kind" : "IdRef", "name" : "'Value'", "quantifier" : "?" } - ] - }, - { - "opname" : "DebugTypeInheritance", - "opcode" : 12, - "operands" : [ - { "kind" : "IdRef", "name" : "'Parent'" }, - { "kind" : "IdRef", "name" : "'Offset'" }, - { "kind" : "IdRef", "name" : "'Size'" }, - { "kind" : "IdRef", "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" : "IdRef", "name" : "'Line'" }, - { "kind" : "IdRef", "name" : "'Column'" } - ] - }, - { - "opname" : "DebugTypeTemplateTemplateParameter", - "opcode" : 16, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Template Name'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "IdRef", "name" : "'Line'" }, - { "kind" : "IdRef", "name" : "'Column'" } - ] - }, - { - "opname" : "DebugTypeTemplateParameterPack", - "opcode" : 17, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "IdRef", "name" : "'Line'" }, - { "kind" : "IdRef", "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" : "IdRef", "name" : "'Line'" }, - { "kind" : "IdRef", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Parent'" }, - { "kind" : "IdRef", "name" : "'Linkage Name'" }, - { "kind" : "IdRef", "name" : "'Variable'" }, - { "kind" : "IdRef", "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" : "IdRef", "name" : "'Line'" }, - { "kind" : "IdRef", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Parent'" }, - { "kind" : "IdRef", "name" : "'Linkage Name'" }, - { "kind" : "IdRef", "name" : "'Flags'" } - ] - }, - { - "opname" : "DebugFunction", - "opcode" : 20, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Type'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "IdRef", "name" : "'Line'" }, - { "kind" : "IdRef", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Parent'" }, - { "kind" : "IdRef", "name" : "'Linkage Name'" }, - { "kind" : "IdRef", "name" : "'Flags'" }, - { "kind" : "IdRef", "name" : "'Scope Line'" }, - { "kind" : "IdRef", "name" : "'Declaration'", "quantifier" : "?" } - ] - }, - { - "opname" : "DebugLexicalBlock", - "opcode" : 21, - "operands" : [ - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "IdRef", "name" : "'Line'" }, - { "kind" : "IdRef", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Parent'" }, - { "kind" : "IdRef", "name" : "'Name'", "quantifier" : "?" } - ] - }, - { - "opname" : "DebugLexicalBlockDiscriminator", - "opcode" : 22, - "operands" : [ - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "IdRef", "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" : "IdRef", "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" : "IdRef", "name" : "'Line'" }, - { "kind" : "IdRef", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Parent'" }, - { "kind" : "IdRef", "name" : "'Flags'" }, - { "kind" : "IdRef", "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" : "'Local Variable'" }, - { "kind" : "IdRef", "name" : "'Value'" }, - { "kind" : "IdRef", "name" : "'Expression'" }, - { "kind" : "IdRef", "name" : "'Indexes'", "quantifier" : "*" } - ] - }, - { - "opname" : "DebugOperation", - "opcode" : 30, - "operands" : [ - { "kind" : "IdRef", "name" : "'OpCode'" }, - { "kind" : "IdRef", "name" : "'Operands ...'", "quantifier" : "*" } - ] - }, - { - "opname" : "DebugExpression", - "opcode" : 31, - "operands" : [ - { "kind" : "IdRef", "name" : "'Operands ...'", "quantifier" : "*" } - ] - }, - { - "opname" : "DebugMacroDef", - "opcode" : 32, - "operands" : [ - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "IdRef", "name" : "'Line'" }, - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Value'", "quantifier" : "?" } - ] - }, - { - "opname" : "DebugMacroUndef", - "opcode" : 33, - "operands" : [ - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "IdRef", "name" : "'Line'" }, - { "kind" : "IdRef", "name" : "'Macro'" } - ] - }, - { - "opname" : "DebugImportedEntity", - "opcode" : 34, - "operands" : [ - { "kind" : "IdRef", "name" : "'Name'" }, - { "kind" : "IdRef", "name" : "'Tag'" }, - { "kind" : "IdRef", "name" : "'Source'" }, - { "kind" : "IdRef", "name" : "'Entity'" }, - { "kind" : "IdRef", "name" : "'Line'" }, - { "kind" : "IdRef", "name" : "'Column'" }, - { "kind" : "IdRef", "name" : "'Parent'" } - ] - }, - { - "opname" : "DebugSource", - "opcode" : 35, - "operands" : [ - { "kind" : "IdRef", "name" : "'File'" }, - { "kind" : "IdRef", "name" : "'Text'", "quantifier" : "?" } - ] - }, - { - "opname" : "DebugFunctionDefinition", - "opcode" : 101, - "operands" : [ - { "kind" : "IdRef", "name" : "'Function'" }, - { "kind" : "IdRef", "name" : "'Definition'" } - ] - } - ], - "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" - }, - { - "enumerant" : "FlagIsEnumClass", - "value" : "0x4000" - }, - { - "enumerant" : "FlagTypePassByValue", - "value" : "0x8000" - }, - { - "enumerant" : "FlagTypePassByReference", - "value" : "0x10000" - } - ] - }, - { - "category" : "ValueEnum", - "kind" : "DebugBaseTypeAttributeEncoding", - "enumerants" : [ - { - "enumerant" : "Unspecified", - "value" : "0" - }, - { - "enumerant" : "Address", - "value" : "1" - }, - { - "enumerant" : "Boolean", - "value" : "2" - }, - { - "enumerant" : "Float", - "value" : "3" - }, - { - "enumerant" : "Signed", - "value" : "4" - }, - { - "enumerant" : "SignedChar", - "value" : "5" - }, - { - "enumerant" : "Unsigned", - "value" : "6" - }, - { - "enumerant" : "UnsignedChar", - "value" : "7" - } - ] - }, - { - "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" - }, - { - "enumerant" : "AtomicType", - "value" : "3" - } - ] - }, - { - "category" : "ValueEnum", - "kind" : "DebugOperation", - "enumerants" : [ - { - "enumerant" : "Deref", - "value" : "0" - }, - { - "enumerant" : "Plus", - "value" : "1" - }, - { - "enumerant" : "Minus", - "value" : "2" - }, - { - "enumerant" : "PlusUconst", - "value" : "3", - "parameters" : [ - { "kind" : "IdRef" } - ] - }, - { - "enumerant" : "BitPiece", - "value" : "4", - "parameters" : [ - { "kind" : "IdRef" }, - { "kind" : "IdRef" } - ] - }, - { - "enumerant" : "Swap", - "value" : "5" - }, - { - "enumerant" : "Xderef", - "value" : "6" - }, - { - "enumerant" : "StackValue", - "value" : "7" - }, - { - "enumerant" : "Constu", - "value" : "8", - "parameters" : [ - { "kind" : "IdRef" } - ] - }, - { - "enumerant" : "Fragment", - "value" : "9", - "parameters" : [ - { "kind" : "IdRef" }, - { "kind" : "IdRef" } - ] - } - ] - }, - { - "category" : "ValueEnum", - "kind" : "DebugImportedEntity", - "enumerants" : [ - { - "enumerant" : "ImportedModule", - "value" : "0" - }, - { - "enumerant" : "ImportedDeclaration", - "value" : "1" - } - ] - } - ] -} diff --git a/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp b/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp index 8965c15e..5c3b86b9 100644 --- a/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp +++ b/source/fuzz/fuzzer_pass_apply_id_synonyms.cpp @@ -198,7 +198,7 @@ bool FuzzerPassApplyIdSynonyms::DataDescriptorsHaveCompatibleTypes( GetIRContext(), base_object_type_id_2, dd2.index()); assert(type_id_1 && type_id_2 && "Data descriptors have invalid types"); - return TransformationReplaceIdWithSynonym::TypesAreCompatible( + return fuzzerutil::TypesAreCompatible( GetIRContext(), opcode, use_in_operand_index, type_id_1, type_id_2); } diff --git a/source/fuzz/fuzzer_pass_wrap_vector_synonym.cpp b/source/fuzz/fuzzer_pass_wrap_vector_synonym.cpp index 37c93b30..35adcfec 100644 --- a/source/fuzz/fuzzer_pass_wrap_vector_synonym.cpp +++ b/source/fuzz/fuzzer_pass_wrap_vector_synonym.cpp @@ -55,21 +55,12 @@ void FuzzerPassWrapVectorSynonym::Apply() { SpvOpCompositeConstruct, instruction_iterator)) { return; } - // Get the scalar type represented by the targeted instruction id. - uint32_t operand_type_id = instruction_iterator->type_id(); - // Get a random vector size from 2 to 4. - uint32_t vector_size = GetFuzzerContext()->GetWidthOfWrappingVector(); - - // Randomly choose a position that target ids should be placed at. - // The position is in range [0, n - 1], where n is the size of the - // vector. - uint32_t position = - GetFuzzerContext()->GetRandomIndexForWrappingVector(vector_size); - - // Target ids are the two scalar ids from the original instruction. - uint32_t target_id1 = instruction_iterator->GetSingleWordInOperand(0); - uint32_t target_id2 = instruction_iterator->GetSingleWordInOperand(1); + // Get the scalar operands from the original instruction. + opt::Instruction* operand1 = GetIRContext()->get_def_use_mgr()->GetDef( + instruction_iterator->GetSingleWordInOperand(0)); + opt::Instruction* operand2 = GetIRContext()->get_def_use_mgr()->GetDef( + instruction_iterator->GetSingleWordInOperand(1)); // We need to be able to make a synonym of the scalar operation's result // id, as well as the operand ids (for example, they cannot be @@ -80,16 +71,23 @@ void FuzzerPassWrapVectorSynonym::Apply() { return; } if (!fuzzerutil::CanMakeSynonymOf( - GetIRContext(), *GetTransformationContext(), - *GetIRContext()->get_def_use_mgr()->GetDef(target_id1))) { + GetIRContext(), *GetTransformationContext(), *operand1)) { return; } if (!fuzzerutil::CanMakeSynonymOf( - GetIRContext(), *GetTransformationContext(), - *GetIRContext()->get_def_use_mgr()->GetDef(target_id2))) { + GetIRContext(), *GetTransformationContext(), *operand2)) { return; } + // Get a random vector size from 2 to 4. + uint32_t vector_size = GetFuzzerContext()->GetWidthOfWrappingVector(); + + // Randomly choose a position that target ids should be placed at. + // The position is in range [0, n - 1], where n is the size of the + // vector. + uint32_t position = + GetFuzzerContext()->GetRandomIndexForWrappingVector(vector_size); + // Stores the ids of scalar constants. std::vector<uint32_t> vec1_components; std::vector<uint32_t> vec2_components; @@ -97,33 +95,42 @@ void FuzzerPassWrapVectorSynonym::Apply() { // Populate components based on vector type and size. for (uint32_t i = 0; i < vector_size; ++i) { if (i == position) { - vec1_components.emplace_back(target_id1); - vec2_components.emplace_back(target_id2); + vec1_components.emplace_back(operand1->result_id()); + vec2_components.emplace_back(operand2->result_id()); } else { vec1_components.emplace_back( - FindOrCreateZeroConstant(operand_type_id, true)); + FindOrCreateZeroConstant(operand1->type_id(), true)); vec2_components.emplace_back( - FindOrCreateZeroConstant(operand_type_id, true)); + FindOrCreateZeroConstant(operand2->type_id(), true)); } } // Add two OpCompositeConstruct to the module with result id returned. - const uint32_t vector_type_id = - FindOrCreateVectorType(operand_type_id, vector_size); + // The added vectors may have different types, for instance if the + // scalar instruction operates on integers with differing sign. // Add the first OpCompositeConstruct that wraps the id of the first // operand. uint32_t result_id1 = GetFuzzerContext()->GetFreshId(); ApplyTransformation(TransformationCompositeConstruct( - vector_type_id, vec1_components, instruction_descriptor, - result_id1)); + FindOrCreateVectorType(operand1->type_id(), vector_size), + vec1_components, instruction_descriptor, result_id1)); // Add the second OpCompositeConstruct that wraps the id of the second // operand. uint32_t result_id2 = GetFuzzerContext()->GetFreshId(); ApplyTransformation(TransformationCompositeConstruct( - vector_type_id, vec2_components, instruction_descriptor, - result_id2)); + FindOrCreateVectorType(operand2->type_id(), vector_size), + vec2_components, instruction_descriptor, result_id2)); + + // The result of the vector instruction that + // TransformationWrapVectorSynonym will create should be a vector of the + // right size, with the scalar instruction's result type as its element + // type. This can be distinct from the types of the operands, if the + // scalar instruction adds two signed integers and stores the result in + // an unsigned id, for example. A transformation is applied to add the + // right type to the module. + FindOrCreateVectorType(instruction_iterator->type_id(), vector_size); // Apply transformation to do vector operation and add synonym between // the result vector id and the id of the original instruction. diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp index ea7cde7f..1d368a9f 100644 --- a/source/fuzz/fuzzer_util.cpp +++ b/source/fuzz/fuzzer_util.cpp @@ -2018,6 +2018,93 @@ opt::Module::iterator GetFunctionIterator(opt::IRContext* ir_context, }); } +// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3582): Add all +// opcodes that are agnostic to signedness of operands to function. +// This is not exhaustive yet. +bool IsAgnosticToSignednessOfOperand(SpvOp opcode, + uint32_t use_in_operand_index) { + switch (opcode) { + case SpvOpSNegate: + case SpvOpNot: + case SpvOpIAdd: + case SpvOpISub: + case SpvOpIMul: + case SpvOpSDiv: + case SpvOpSRem: + case SpvOpSMod: + case SpvOpShiftRightLogical: + case SpvOpShiftRightArithmetic: + case SpvOpShiftLeftLogical: + case SpvOpBitwiseOr: + case SpvOpBitwiseXor: + case SpvOpBitwiseAnd: + case SpvOpIEqual: + case SpvOpINotEqual: + case SpvOpULessThan: + case SpvOpSLessThan: + case SpvOpUGreaterThan: + case SpvOpSGreaterThan: + case SpvOpULessThanEqual: + case SpvOpSLessThanEqual: + case SpvOpUGreaterThanEqual: + case SpvOpSGreaterThanEqual: + return true; + + case SpvOpAtomicStore: + case SpvOpAtomicExchange: + case SpvOpAtomicIAdd: + case SpvOpAtomicISub: + case SpvOpAtomicSMin: + case SpvOpAtomicUMin: + case SpvOpAtomicSMax: + case SpvOpAtomicUMax: + case SpvOpAtomicAnd: + case SpvOpAtomicOr: + case SpvOpAtomicXor: + case SpvOpAtomicFAddEXT: // Capability AtomicFloat32AddEXT, + // AtomicFloat64AddEXT. + assert(use_in_operand_index != 0 && + "Signedness check should not occur on a pointer operand."); + return use_in_operand_index == 1 || use_in_operand_index == 2; + + case SpvOpAtomicCompareExchange: + case SpvOpAtomicCompareExchangeWeak: // Capability Kernel. + assert(use_in_operand_index != 0 && + "Signedness check should not occur on a pointer operand."); + return use_in_operand_index >= 1 && use_in_operand_index <= 3; + + case SpvOpAtomicLoad: + case SpvOpAtomicIIncrement: + case SpvOpAtomicIDecrement: + case SpvOpAtomicFlagTestAndSet: // Capability Kernel. + case SpvOpAtomicFlagClear: // Capability Kernel. + assert(use_in_operand_index != 0 && + "Signedness check should not occur on a pointer operand."); + return use_in_operand_index >= 1; + + case SpvOpAccessChain: + // The signedness of indices does not matter. + return use_in_operand_index > 0; + + default: + // Conservatively assume that the id cannot be swapped in other + // instructions. + return false; + } +} + +bool TypesAreCompatible(opt::IRContext* ir_context, SpvOp opcode, + uint32_t use_in_operand_index, uint32_t type_id_1, + uint32_t type_id_2) { + assert(ir_context->get_type_mgr()->GetType(type_id_1) && + ir_context->get_type_mgr()->GetType(type_id_2) && + "Type ids are invalid"); + + return type_id_1 == type_id_2 || + (IsAgnosticToSignednessOfOperand(opcode, use_in_operand_index) && + fuzzerutil::TypesAreEqualUpToSign(ir_context, type_id_1, type_id_2)); +} + } // namespace fuzzerutil } // namespace fuzz } // namespace spvtools diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h index e4697a1a..54aa14a2 100644 --- a/source/fuzz/fuzzer_util.h +++ b/source/fuzz/fuzzer_util.h @@ -604,6 +604,21 @@ bool NewTerminatorPreservesDominationRules(opt::IRContext* ir_context, opt::Module::iterator GetFunctionIterator(opt::IRContext* ir_context, uint32_t function_id); +// Returns true if the instruction with opcode |opcode| does not change its +// behaviour depending on the signedness of the operand at +// |use_in_operand_index|. +// Assumes that the operand must be the id of an integer scalar or vector. +bool IsAgnosticToSignednessOfOperand(SpvOp opcode, + uint32_t use_in_operand_index); + +// Returns true if |type_id_1| and |type_id_2| represent compatible types +// given the context of the instruction with |opcode| (i.e. we can replace +// an operand of |opcode| of the first type with an id of the second type +// and vice-versa). +bool TypesAreCompatible(opt::IRContext* ir_context, SpvOp opcode, + uint32_t use_in_operand_index, uint32_t type_id_1, + uint32_t type_id_2); + } // namespace fuzzerutil } // namespace fuzz } // namespace spvtools diff --git a/source/fuzz/protobufs/spirvfuzz_protobufs.h b/source/fuzz/protobufs/spirvfuzz_protobufs.h index 429f341e..46c21881 100644 --- a/source/fuzz/protobufs/spirvfuzz_protobufs.h +++ b/source/fuzz/protobufs/spirvfuzz_protobufs.h @@ -24,6 +24,7 @@ #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunknown-warning-option" // Must come first +#pragma clang diagnostic ignored "-Wreserved-identifier" #pragma clang diagnostic ignored "-Wshadow" #pragma clang diagnostic ignored "-Wsuggest-destructor-override" #pragma clang diagnostic ignored "-Wunused-parameter" diff --git a/source/fuzz/transformation_replace_id_with_synonym.cpp b/source/fuzz/transformation_replace_id_with_synonym.cpp index 14555315..8d21d233 100644 --- a/source/fuzz/transformation_replace_id_with_synonym.cpp +++ b/source/fuzz/transformation_replace_id_with_synonym.cpp @@ -65,9 +65,10 @@ bool TransformationReplaceIdWithSynonym::IsApplicable( // If the id of interest and the synonym are scalar or vector integer // constants with different signedness, their use can only be swapped if the // instruction is agnostic to the signedness of the operand. - if (!TypesAreCompatible(ir_context, use_instruction->opcode(), - message_.id_use_descriptor().in_operand_index(), - type_id_of_interest, type_id_synonym)) { + if (!fuzzerutil::TypesAreCompatible( + ir_context, use_instruction->opcode(), + message_.id_use_descriptor().in_operand_index(), type_id_of_interest, + type_id_synonym)) { return false; } @@ -109,93 +110,6 @@ protobufs::Transformation TransformationReplaceIdWithSynonym::ToMessage() return result; } -// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3582): Add all -// opcodes that are agnostic to signedness of operands to function. -// This is not exhaustive yet. -bool TransformationReplaceIdWithSynonym::IsAgnosticToSignednessOfOperand( - SpvOp opcode, uint32_t use_in_operand_index) { - switch (opcode) { - case SpvOpSNegate: - case SpvOpNot: - case SpvOpIAdd: - case SpvOpISub: - case SpvOpIMul: - case SpvOpSDiv: - case SpvOpSRem: - case SpvOpSMod: - case SpvOpShiftRightLogical: - case SpvOpShiftRightArithmetic: - case SpvOpShiftLeftLogical: - case SpvOpBitwiseOr: - case SpvOpBitwiseXor: - case SpvOpBitwiseAnd: - case SpvOpIEqual: - case SpvOpINotEqual: - case SpvOpULessThan: - case SpvOpSLessThan: - case SpvOpUGreaterThan: - case SpvOpSGreaterThan: - case SpvOpULessThanEqual: - case SpvOpSLessThanEqual: - case SpvOpUGreaterThanEqual: - case SpvOpSGreaterThanEqual: - return true; - - case SpvOpAtomicStore: - case SpvOpAtomicExchange: - case SpvOpAtomicIAdd: - case SpvOpAtomicISub: - case SpvOpAtomicSMin: - case SpvOpAtomicUMin: - case SpvOpAtomicSMax: - case SpvOpAtomicUMax: - case SpvOpAtomicAnd: - case SpvOpAtomicOr: - case SpvOpAtomicXor: - case SpvOpAtomicFAddEXT: // Capability AtomicFloat32AddEXT, - // AtomicFloat64AddEXT. - assert(use_in_operand_index != 0 && - "Signedness check should not occur on a pointer operand."); - return use_in_operand_index == 1 || use_in_operand_index == 2; - - case SpvOpAtomicCompareExchange: - case SpvOpAtomicCompareExchangeWeak: // Capability Kernel. - assert(use_in_operand_index != 0 && - "Signedness check should not occur on a pointer operand."); - return use_in_operand_index >= 1 && use_in_operand_index <= 3; - - case SpvOpAtomicLoad: - case SpvOpAtomicIIncrement: - case SpvOpAtomicIDecrement: - case SpvOpAtomicFlagTestAndSet: // Capability Kernel. - case SpvOpAtomicFlagClear: // Capability Kernel. - assert(use_in_operand_index != 0 && - "Signedness check should not occur on a pointer operand."); - return use_in_operand_index >= 1; - - case SpvOpAccessChain: - // The signedness of indices does not matter. - return use_in_operand_index > 0; - - default: - // Conservatively assume that the id cannot be swapped in other - // instructions. - return false; - } -} - -bool TransformationReplaceIdWithSynonym::TypesAreCompatible( - opt::IRContext* ir_context, SpvOp opcode, uint32_t use_in_operand_index, - uint32_t type_id_1, uint32_t type_id_2) { - assert(ir_context->get_type_mgr()->GetType(type_id_1) && - ir_context->get_type_mgr()->GetType(type_id_2) && - "Type ids are invalid"); - - return type_id_1 == type_id_2 || - (IsAgnosticToSignednessOfOperand(opcode, use_in_operand_index) && - fuzzerutil::TypesAreEqualUpToSign(ir_context, type_id_1, type_id_2)); -} - std::unordered_set<uint32_t> TransformationReplaceIdWithSynonym::GetFreshIds() const { return std::unordered_set<uint32_t>(); diff --git a/source/fuzz/transformation_replace_id_with_synonym.h b/source/fuzz/transformation_replace_id_with_synonym.h index 1ac636b4..4570fcef 100644 --- a/source/fuzz/transformation_replace_id_with_synonym.h +++ b/source/fuzz/transformation_replace_id_with_synonym.h @@ -52,22 +52,7 @@ class TransformationReplaceIdWithSynonym : public Transformation { protobufs::Transformation ToMessage() const override; - // Returns true if |type_id_1| and |type_id_2| represent compatible types - // given the context of the instruction with |opcode| (i.e. we can replace - // an operand of |opcode| of the first type with an id of the second type - // and vice-versa). - static bool TypesAreCompatible(opt::IRContext* ir_context, SpvOp opcode, - uint32_t use_in_operand_index, - uint32_t type_id_1, uint32_t type_id_2); - private: - // Returns true if the instruction with opcode |opcode| does not change its - // behaviour depending on the signedness of the operand at - // |use_in_operand_index|. - // Assumes that the operand must be the id of an integer scalar or vector. - static bool IsAgnosticToSignednessOfOperand(SpvOp opcode, - uint32_t use_in_operand_index); - protobufs::TransformationReplaceIdWithSynonym message_; }; diff --git a/source/fuzz/transformation_wrap_vector_synonym.cpp b/source/fuzz/transformation_wrap_vector_synonym.cpp index d0eddd44..490bcd78 100644 --- a/source/fuzz/transformation_wrap_vector_synonym.cpp +++ b/source/fuzz/transformation_wrap_vector_synonym.cpp @@ -70,16 +70,29 @@ bool TransformationWrapVectorSynonym::IsApplicable( return false; } - // The 2 vectors must be the same valid vector type. + // The 2 vectors must have compatible vector types. auto vec1_type_id = vec1->type_id(); auto vec2_type_id = vec2->type_id(); - if (vec1_type_id != vec2_type_id) { + for (auto operand_index : {0, 1}) { + if (!fuzzerutil::TypesAreCompatible(ir_context, instruction->opcode(), + operand_index, vec1_type_id, + vec2_type_id)) { + return false; + } + } + + auto vec1_type = ir_context->get_def_use_mgr()->GetDef(vec1_type_id); + if (vec1_type->opcode() != SpvOpTypeVector) { return false; } - if (ir_context->get_def_use_mgr()->GetDef(vec1_type_id)->opcode() != - SpvOpTypeVector) { + // A suitable vector for the result type of the new vector instruction must + // exist in the module. This is a vector of the right length, whose element + // type matches the result type of the scalar instruction. + uint32_t vector_size = vec1_type->GetSingleWordInOperand(1); + if (!fuzzerutil::MaybeGetVectorType(ir_context, instruction->type_id(), + vector_size)) { return false; } @@ -124,9 +137,11 @@ void TransformationWrapVectorSynonym::Apply( // Make a new arithmetic instruction: %fresh_id = OpXX %type_id %result_id1 // %result_id2. - auto vec_type_id = ir_context->get_def_use_mgr() - ->GetDef(message_.vector_operand1()) - ->type_id(); + auto vector_operand_type = ir_context->get_def_use_mgr()->GetDef( + fuzzerutil::GetTypeId(ir_context, message_.vector_operand1())); + uint32_t vector_size = vector_operand_type->GetSingleWordInOperand(1); + auto vec_type_id = fuzzerutil::MaybeGetVectorType( + ir_context, instruction->type_id(), vector_size); auto new_instruction = MakeUnique<opt::Instruction>( ir_context, instruction->opcode(), vec_type_id, message_.fresh_id(), std::move(in_operands)); diff --git a/source/fuzz/transformation_wrap_vector_synonym.h b/source/fuzz/transformation_wrap_vector_synonym.h index 008211a2..94437fe5 100644 --- a/source/fuzz/transformation_wrap_vector_synonym.h +++ b/source/fuzz/transformation_wrap_vector_synonym.h @@ -38,11 +38,14 @@ class TransformationWrapVectorSynonym : public Transformation { // two vector operands. // - |fresh_id| is an unused id that will be used as a result id of the // created instruction. - // - |vector_operand1| and |vector_operand2| must have the same vector type - // that is supported by this transformation. + // - |vector_operand1| and |vector_operand2| must have compatible vector types + // that are supported by this transformation. // - |pos| is an index of the operands of |instruction_id| in the // |vector_operand1| and |vector_operand2|. It must be less than the size // of those vector operands. + // - A vector type with the same width as the types of the vector operands, + // and element type matching the type of |instruction_id|, must exist in the + // module. bool IsApplicable( opt::IRContext* ir_context, const TransformationContext& transformation_context) const override; diff --git a/source/link/linker.cpp b/source/link/linker.cpp index 8da4a98d..c5ca5625 100644 --- a/source/link/linker.cpp +++ b/source/link/linker.cpp @@ -34,6 +34,7 @@ #include "source/opt/pass_manager.h" #include "source/opt/remove_duplicates_pass.h" #include "source/opt/type_manager.h" +#include "source/spirv_constant.h" #include "source/spirv_target_env.h" #include "source/util/make_unique.h" #include "spirv-tools/libspirv.hpp" @@ -207,7 +208,7 @@ spv_result_t GenerateHeader(const MessageConsumer& consumer, header->magic_number = SpvMagicNumber; header->version = version; - header->generator = 17u; + header->generator = SPV_GENERATOR_WORD(SPV_GENERATOR_KHRONOS_LINKER, 0); header->bound = max_id_bound; header->reserved = 0u; diff --git a/source/lint/CMakeLists.txt b/source/lint/CMakeLists.txt index f9cae28a..1feae3f9 100644 --- a/source/lint/CMakeLists.txt +++ b/source/lint/CMakeLists.txt @@ -13,9 +13,11 @@ # limitations under the License. set(SPIRV_TOOLS_LINT_SOURCES divergence_analysis.h + lints.h linter.cpp divergence_analysis.cpp + lint_divergent_derivatives.cpp ) if(MSVC AND (NOT ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang"))) diff --git a/source/lint/lint_divergent_derivatives.cpp b/source/lint/lint_divergent_derivatives.cpp new file mode 100644 index 00000000..512847b0 --- /dev/null +++ b/source/lint/lint_divergent_derivatives.cpp @@ -0,0 +1,169 @@ +// Copyright (c) 2021 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 <cassert> +#include <sstream> +#include <string> + +#include "source/diagnostic.h" +#include "source/lint/divergence_analysis.h" +#include "source/lint/lints.h" +#include "source/opt/basic_block.h" +#include "source/opt/cfg.h" +#include "source/opt/control_dependence.h" +#include "source/opt/def_use_manager.h" +#include "source/opt/dominator_analysis.h" +#include "source/opt/instruction.h" +#include "source/opt/ir_context.h" +#include "spirv-tools/libspirv.h" +#include "spirv/unified1/spirv.h" + +namespace spvtools { +namespace lint { +namespace lints { +namespace { +// Returns the %name[id], where `name` is the first name associated with the +// given id, or just %id if one is not found. +std::string GetFriendlyName(opt::IRContext* context, uint32_t id) { + auto names = context->GetNames(id); + std::stringstream ss; + ss << "%"; + if (names.empty()) { + ss << id; + } else { + opt::Instruction* inst_name = names.begin()->second; + if (inst_name->opcode() == SpvOpName) { + ss << names.begin()->second->GetInOperand(0).AsString(); + ss << "[" << id << "]"; + } else { + ss << id; + } + } + return ss.str(); +} + +bool InstructionHasDerivative(const opt::Instruction& inst) { + static const SpvOp derivative_opcodes[] = { + // Implicit derivatives. + SpvOpImageSampleImplicitLod, + SpvOpImageSampleDrefImplicitLod, + SpvOpImageSampleProjImplicitLod, + SpvOpImageSampleProjDrefImplicitLod, + SpvOpImageSparseSampleImplicitLod, + SpvOpImageSparseSampleDrefImplicitLod, + SpvOpImageSparseSampleProjImplicitLod, + SpvOpImageSparseSampleProjDrefImplicitLod, + // Explicit derivatives. + SpvOpDPdx, + SpvOpDPdy, + SpvOpFwidth, + SpvOpDPdxFine, + SpvOpDPdyFine, + SpvOpFwidthFine, + SpvOpDPdxCoarse, + SpvOpDPdyCoarse, + SpvOpFwidthCoarse, + }; + return std::find(std::begin(derivative_opcodes), std::end(derivative_opcodes), + inst.opcode()) != std::end(derivative_opcodes); +} + +spvtools::DiagnosticStream Warn(opt::IRContext* context, + opt::Instruction* inst) { + if (inst == nullptr) { + return DiagnosticStream({0, 0, 0}, context->consumer(), "", SPV_WARNING); + } else { + // TODO(kuhar): Use line numbers based on debug info. + return DiagnosticStream( + {0, 0, 0}, context->consumer(), + inst->PrettyPrint(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES), + SPV_WARNING); + } +} + +void PrintDivergenceFlow(opt::IRContext* context, DivergenceAnalysis div, + uint32_t id) { + opt::analysis::DefUseManager* def_use = context->get_def_use_mgr(); + opt::CFG* cfg = context->cfg(); + while (id != 0) { + bool is_block = def_use->GetDef(id)->opcode() == SpvOpLabel; + if (is_block) { + Warn(context, nullptr) + << "block " << GetFriendlyName(context, id) << " is divergent"; + uint32_t source = div.GetDivergenceSource(id); + // Skip intermediate blocks. + while (source != 0 && def_use->GetDef(source)->opcode() == SpvOpLabel) { + id = source; + source = div.GetDivergenceSource(id); + } + if (source == 0) break; + spvtools::opt::Instruction* branch = + cfg->block(div.GetDivergenceDependenceSource(id))->terminator(); + Warn(context, branch) + << "because it depends on a conditional branch on divergent value " + << GetFriendlyName(context, source) << ""; + id = source; + } else { + Warn(context, nullptr) + << "value " << GetFriendlyName(context, id) << " is divergent"; + uint32_t source = div.GetDivergenceSource(id); + opt::Instruction* def = def_use->GetDef(id); + opt::Instruction* source_def = + source == 0 ? nullptr : def_use->GetDef(source); + // First print data -> data dependencies. + while (source != 0 && source_def->opcode() != SpvOpLabel) { + Warn(context, def_use->GetDef(id)) + << "because " << GetFriendlyName(context, id) << " uses value " + << GetFriendlyName(context, source) + << "in its definition, which is divergent"; + id = source; + def = source_def; + source = div.GetDivergenceSource(id); + source_def = def_use->GetDef(source); + } + if (source == 0) { + Warn(context, def) << "because it has a divergent definition"; + break; + } + Warn(context, def) << "because it is conditionally set in block " + << GetFriendlyName(context, source); + id = source; + } + } +} +} // namespace + +bool CheckDivergentDerivatives(opt::IRContext* context) { + DivergenceAnalysis div(*context); + for (opt::Function& func : *context->module()) { + div.Run(&func); + for (const opt::BasicBlock& bb : func) { + for (const opt::Instruction& inst : bb) { + if (InstructionHasDerivative(inst) && + div.GetDivergenceLevel(bb.id()) > + DivergenceAnalysis::DivergenceLevel::kPartiallyUniform) { + Warn(context, nullptr) + << "derivative with divergent control flow" + << " located in block " << GetFriendlyName(context, bb.id()); + PrintDivergenceFlow(context, div, bb.id()); + } + } + } + } + return true; +} + +} // namespace lints +} // namespace lint +} // namespace spvtools diff --git a/source/lint/linter.cpp b/source/lint/linter.cpp index 0f847953..e4ed04ea 100644 --- a/source/lint/linter.cpp +++ b/source/lint/linter.cpp @@ -14,6 +14,13 @@ #include "spirv-tools/linter.hpp" +#include "source/lint/lints.h" +#include "source/opt/build_module.h" +#include "source/opt/ir_context.h" +#include "spirv-tools/libspirv.h" +#include "spirv-tools/libspirv.hpp" +#include "spirv/unified1/spirv.h" + namespace spvtools { struct Linter::Impl { @@ -32,20 +39,22 @@ Linter::Linter(spv_target_env env) : impl_(new Impl(env)) {} Linter::~Linter() {} void Linter::SetMessageConsumer(MessageConsumer consumer) { - impl_->message_consumer = consumer; + impl_->message_consumer = std::move(consumer); } -const MessageConsumer& Linter::consumer() const { +const MessageConsumer& Linter::Consumer() const { return impl_->message_consumer; } bool Linter::Run(const uint32_t* binary, size_t binary_size) { - (void)binary; - (void)binary_size; + std::unique_ptr<opt::IRContext> context = + BuildModule(SPV_ENV_VULKAN_1_2, Consumer(), binary, binary_size); + if (context == nullptr) return false; - consumer()(SPV_MSG_INFO, "", {0, 0, 0}, "Hello, world!"); + bool result = true; + result &= lint::lints::CheckDivergentDerivatives(context.get()); - return true; + return result; } } // namespace spvtools diff --git a/source/lint/lints.h b/source/lint/lints.h new file mode 100644 index 00000000..a1995d2f --- /dev/null +++ b/source/lint/lints.h @@ -0,0 +1,34 @@ +// Copyright (c) 2021 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_LINT_LINTS_H_ +#define SOURCE_LINT_LINTS_H_ + +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace lint { + +// All of the functions in this namespace output to the error consumer in the +// |context| argument and return |true| if no errors are found. They do not +// modify the IR. +namespace lints { + +bool CheckDivergentDerivatives(opt::IRContext* context); + +} // namespace lints +} // namespace lint +} // namespace spvtools + +#endif // SOURCE_LINT_LINTS_H_ diff --git a/source/operand.cpp b/source/operand.cpp index bff36a26..6d83e81e 100644 --- a/source/operand.cpp +++ b/source/operand.cpp @@ -579,8 +579,8 @@ std::function<bool(unsigned)> spvOperandCanBeForwardDeclaredFunction( std::function<bool(unsigned)> spvDbgInfoExtOperandCanBeForwardDeclaredFunction( spv_ext_inst_type_t ext_type, uint32_t key) { // The Vulkan debug info extended instruction set is non-semantic so allows no - // forward references ever. - if (ext_type == SPV_EXT_INST_TYPE_NONSEMANTIC_VULKAN_DEBUGINFO_100) { + // forward references ever + if (ext_type == SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100) { return [](unsigned) { return false; }; } diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt index 63af5c1d..7d522fb5 100644 --- a/source/opt/CMakeLists.txt +++ b/source/opt/CMakeLists.txt @@ -39,6 +39,7 @@ set(SPIRV_TOOLS_OPT_SOURCES debug_info_manager.h def_use_manager.h desc_sroa.h + desc_sroa_util.h dominator_analysis.h dominator_tree.h eliminate_dead_constant_pass.h @@ -100,6 +101,7 @@ set(SPIRV_TOOLS_OPT_SOURCES relax_float_ops_pass.h remove_duplicates_pass.h remove_unused_interface_variables_pass.h + replace_desc_array_access_using_var_index.h replace_invalid_opc.h scalar_analysis.h scalar_analysis_nodes.h @@ -148,6 +150,7 @@ set(SPIRV_TOOLS_OPT_SOURCES debug_info_manager.cpp def_use_manager.cpp desc_sroa.cpp + desc_sroa_util.cpp dominator_analysis.cpp dominator_tree.cpp eliminate_dead_constant_pass.cpp @@ -205,6 +208,7 @@ set(SPIRV_TOOLS_OPT_SOURCES relax_float_ops_pass.cpp remove_duplicates_pass.cpp remove_unused_interface_variables_pass.cpp + replace_desc_array_access_using_var_index.cpp replace_invalid_opc.cpp scalar_analysis.cpp scalar_analysis_simplification.cpp diff --git a/source/opt/aggressive_dead_code_elim_pass.cpp b/source/opt/aggressive_dead_code_elim_pass.cpp index 90d30e9a..0b54d5e8 100644 --- a/source/opt/aggressive_dead_code_elim_pass.cpp +++ b/source/opt/aggressive_dead_code_elim_pass.cpp @@ -1,7 +1,7 @@ // Copyright (c) 2017 The Khronos Group Inc. // Copyright (c) 2017 Valve Corporation // Copyright (c) 2017 LunarG Inc. -// Copyright (c) 2018 Google LLC +// Copyright (c) 2018-2021 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ #include "source/cfa.h" #include "source/latest_version_glsl_std_450_header.h" #include "source/opt/eliminate_dead_functions_util.h" +#include "source/opt/ir_builder.h" #include "source/opt/iterator.h" #include "source/opt/reflect.h" #include "source/spirv_constant.h" @@ -35,10 +36,10 @@ 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; const uint32_t kCopyMemoryTargetAddrInIdx = 0; const uint32_t kCopyMemorySourceAddrInIdx = 1; +const uint32_t kLoadSourceAddrInIdx = 0; const uint32_t kDebugDeclareOperandVariableIndex = 5; const uint32_t kGlobalVariableVariableIndex = 12; @@ -96,16 +97,21 @@ bool AggressiveDCEPass::IsVarOfStorage(uint32_t varId, uint32_t storageClass) { storageClass; } -bool AggressiveDCEPass::IsLocalVar(uint32_t varId) { +bool AggressiveDCEPass::IsLocalVar(uint32_t varId, Function* func) { if (IsVarOfStorage(varId, SpvStorageClassFunction)) { return true; } - if (!private_like_local_) { + + if (!IsVarOfStorage(varId, SpvStorageClassPrivate) && + !IsVarOfStorage(varId, SpvStorageClassWorkgroup)) { return false; } - return IsVarOfStorage(varId, SpvStorageClassPrivate) || - IsVarOfStorage(varId, SpvStorageClassWorkgroup); + // For a variable in the Private or WorkGroup storage class, the variable will + // get a new instance for every call to an entry point. If the entry point + // does not have a call, then no other function can read or write to that + // instance of the variable. + return IsEntryPointWithNoCalls(func); } void AggressiveDCEPass::AddStores(Function* func, uint32_t ptrId) { @@ -145,15 +151,19 @@ bool AggressiveDCEPass::AllExtensionsSupported() const { if (extensions_allowlist_.find(extName) == extensions_allowlist_.end()) return false; } - return true; -} - -bool AggressiveDCEPass::IsDead(Instruction* inst) { - if (IsLive(inst)) return false; - if ((inst->IsBranch() || inst->opcode() == SpvOpUnreachable) && - !IsStructuredHeader(context()->get_instr_block(inst), nullptr, nullptr, - nullptr)) - return false; + // Only allow NonSemantic.Shader.DebugInfo.100, we cannot safely optimise + // around unknown extended instruction sets even if they are non-semantic + for (auto& inst : context()->module()->ext_inst_imports()) { + assert(inst.opcode() == SpvOpExtInstImport && + "Expecting an import of an extension's instruction set."); + const char* extension_name = + reinterpret_cast<const char*>(&inst.GetInOperand(0).words[0]); + if (0 == std::strncmp(extension_name, "NonSemantic.", 12) && + 0 != std::strncmp(extension_name, "NonSemantic.Shader.DebugInfo.100", + 32)) { + return false; + } + } return true; } @@ -173,12 +183,12 @@ bool AggressiveDCEPass::IsTargetDead(Instruction* inst) { }); return dead; } - return IsDead(tInst); + return !IsLive(tInst); } void AggressiveDCEPass::ProcessLoad(Function* func, uint32_t varId) { // Only process locals - if (!IsLocalVar(varId)) return; + if (!IsLocalVar(varId, func)) return; // Return if already processed if (live_local_vars_.find(varId) != live_local_vars_.end()) return; // Mark all stores to varId as live @@ -187,66 +197,6 @@ void AggressiveDCEPass::ProcessLoad(Function* func, uint32_t varId) { live_local_vars_.insert(varId); } -bool AggressiveDCEPass::IsStructuredHeader(BasicBlock* bp, - Instruction** mergeInst, - Instruction** branchInst, - uint32_t* mergeBlockId) { - if (!bp) return false; - Instruction* mi = bp->GetMergeInst(); - if (mi == nullptr) return false; - 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<BasicBlock*>& structuredOrder) { - block2headerBranch_.clear(); - header2nextHeaderBranch_.clear(); - branch2merge_.clear(); - structured_order_index_.clear(); - std::stack<Instruction*> 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(); - Instruction* chb = currentHeaderBranch.top(); - if (chb != nullptr) - currentMergeBlockId = branch2merge_[chb]->GetSingleWordInOperand(0); - } - Instruction* mergeInst; - Instruction* branchInst; - uint32_t mergeBlockId; - bool is_header = - IsStructuredHeader(*bi, &mergeInst, &branchInst, &mergeBlockId); - // Map header block to next enclosing header. - if (is_header) header2nextHeaderBranch_[*bi] = currentHeaderBranch.top(); - // If this is a loop header, update state first so the block will map to - // itself. - 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, BasicBlock* bp) { std::unique_ptr<Instruction> newBranch( new Instruction(context(), SpvOpBranch, 0, 0, @@ -262,23 +212,18 @@ void AggressiveDCEPass::AddBreaksAndContinuesToWorklist( mergeInst->opcode() == SpvOpLoopMerge); BasicBlock* header = context()->get_instr_block(mergeInst); - uint32_t headerIndex = structured_order_index_[header]; const uint32_t mergeId = mergeInst->GetSingleWordInOperand(0); - BasicBlock* merge = context()->get_instr_block(mergeId); - uint32_t mergeIndex = structured_order_index_[merge]; - get_def_use_mgr()->ForEachUser( - mergeId, [headerIndex, mergeIndex, this](Instruction* user) { - if (!user->IsBranch()) return; - 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. - Instruction* userMerge = branch2merge_[user]; - if (userMerge != nullptr) AddToWorklist(userMerge); - } - }); + get_def_use_mgr()->ForEachUser(mergeId, [header, this](Instruction* user) { + if (!user->IsBranch()) return; + BasicBlock* block = context()->get_instr_block(user); + if (BlockIsInConstruct(header, block)) { + // This is a break from the loop. + AddToWorklist(user); + // Add branch's merge if there is one. + Instruction* userMerge = GetMergeInstruction(user); + if (userMerge != nullptr) AddToWorklist(userMerge); + } + }); if (mergeInst->opcode() != SpvOpLoopMerge) { return; @@ -292,7 +237,7 @@ void AggressiveDCEPass::AddBreaksAndContinuesToWorklist( 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. - Instruction* hdrMerge = branch2merge_[user]; + Instruction* hdrMerge = GetMergeInstruction(user); if (hdrMerge != nullptr && hdrMerge->opcode() == SpvOpSelectionMerge) { uint32_t hdrMergeId = hdrMerge->GetSingleWordInOperand(kSelectionMergeMergeBlockIdInIdx); @@ -304,9 +249,9 @@ void AggressiveDCEPass::AddBreaksAndContinuesToWorklist( // An unconditional branch can only be a continue if it is not // branching to its own merge block. BasicBlock* blk = context()->get_instr_block(user); - Instruction* hdrBranch = block2headerBranch_[blk]; + Instruction* hdrBranch = GetHeaderBranch(blk); if (hdrBranch == nullptr) return; - Instruction* hdrMerge = branch2merge_[hdrBranch]; + Instruction* hdrMerge = GetMergeInstruction(hdrBranch); if (hdrMerge->opcode() == SpvOpLoopMerge) return; uint32_t hdrMergeId = hdrMerge->GetSingleWordInOperand(kSelectionMergeMergeBlockIdInIdx); @@ -319,253 +264,36 @@ void AggressiveDCEPass::AddBreaksAndContinuesToWorklist( } bool AggressiveDCEPass::AggressiveDCE(Function* func) { - // Mark function parameters as live. - AddToWorklist(&func->DefInst()); - func->ForEachParam( - [this](const Instruction* param) { - AddToWorklist(const_cast<Instruction*>(param)); - }, - false); - - // Compute map from block to controlling conditional branch - std::list<BasicBlock*> 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(); + std::list<BasicBlock*> structured_order; + cfg()->ComputeStructuredOrder(func, &*func->begin(), &structured_order); live_local_vars_.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<bool> assume_branches_live; - std::stack<uint32_t> 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. We cannot call IsLocalVar at this point because - // private_like_local_ has not been set yet. - if (IsVarOfStorage(varId, SpvStorageClassPrivate) || - IsVarOfStorage(varId, SpvStorageClassWorkgroup)) - private_stores_.push_back(&*ii); - else if (!IsVarOfStorage(varId, SpvStorageClassFunction)) - AddToWorklist(&*ii); - } break; - case SpvOpCopyMemory: - case SpvOpCopyMemorySized: { - uint32_t varId; - (void)GetPtr(ii->GetSingleWordInOperand(kCopyMemoryTargetAddrInIdx), - &varId); - if (IsVarOfStorage(varId, SpvStorageClassPrivate) || - IsVarOfStorage(varId, SpvStorageClassWorkgroup)) - 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: - case SpvOpUnreachable: { - 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 (!ii->IsOpcodeSafeToDelete()) { - 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()) { - Instruction* liveInst = worklist_.front(); - // Add all operand instructions if not already live - liveInst->ForEachInId([&liveInst, this](const uint32_t* iid) { - 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. - BasicBlock* blk = context()->get_instr_block(liveInst); - Instruction* branchInst = block2headerBranch_[blk]; - if (branchInst != nullptr) { - AddToWorklist(branchInst); - Instruction* mergeInst = branch2merge_[branchInst]; - AddToWorklist(mergeInst); - } - // If the block is a header, add the next outermost controlling - // conditional branch and its merge. - Instruction* nextBranchInst = header2nextHeaderBranch_[blk]; - if (nextBranchInst != nullptr) { - AddToWorklist(nextBranchInst); - Instruction* mergeInst = branch2merge_[nextBranchInst]; - AddToWorklist(mergeInst); - } - // If local load, add all variable's stores if variable not already live - if (liveInst->opcode() == SpvOpLoad || liveInst->IsAtomicWithLoad()) { - uint32_t varId; - (void)GetPtr(liveInst, &varId); - if (varId != 0) { - ProcessLoad(func, varId); - } - // Process memory copies like loads - } else if (liveInst->opcode() == SpvOpCopyMemory || - liveInst->opcode() == SpvOpCopyMemorySized) { - uint32_t varId; - (void)GetPtr(liveInst->GetSingleWordInOperand(kCopyMemorySourceAddrInIdx), - &varId); - if (varId != 0) { - ProcessLoad(func, varId); - } - // If DebugDeclare, process as load of variable - } else if (liveInst->GetCommonDebugOpcode() == - CommonDebugInfoDebugDeclare) { - uint32_t varId = - liveInst->GetSingleWordOperand(kDebugDeclareOperandVariableIndex); - ProcessLoad(func, varId); - // If DebugValue with Deref, process as load of variable - } else if (liveInst->GetCommonDebugOpcode() == CommonDebugInfoDebugValue) { - uint32_t varId = context() - ->get_debug_info_mgr() - ->GetVariableIdOfDebugValueUsedForDeclare(liveInst); - if (varId != 0) ProcessLoad(func, varId); - // If merge, add other branches that are part of its control structure - } else if (liveInst->opcode() == SpvOpLoopMerge || - liveInst->opcode() == SpvOpSelectionMerge) { - AddBreaksAndContinuesToWorklist(liveInst); - // If function call, treat as if it loads from all pointer arguments - } else if (liveInst->opcode() == SpvOpFunctionCall) { - liveInst->ForEachInId([this, func](const uint32_t* iid) { - // Skip non-ptr args - if (!IsPtr(*iid)) return; - uint32_t varId; - (void)GetPtr(*iid, &varId); - ProcessLoad(func, varId); - }); - // If function parameter, treat as if it's result id is loaded from - } else if (liveInst->opcode() == SpvOpFunctionParameter) { - ProcessLoad(func, 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(func, varId); - } - } - - // Add OpDecorateId instructions that apply to this instruction to the work - // list. We use the decoration manager to look through the group - // decorations to get to the OpDecorate* instructions themselves. - auto decorations = - get_decoration_mgr()->GetDecorationsFor(liveInst->result_id(), false); - for (Instruction* dec : decorations) { - // We only care about OpDecorateId instructions because the are the only - // decorations that will reference an id that will have to be kept live - // because of that use. - if (dec->opcode() != SpvOpDecorateId) { - continue; - } - if (dec->GetSingleWordInOperand(1) == - SpvDecorationHlslCounterBufferGOOGLE) { - // These decorations should not force the use id to be live. It will be - // removed if either the target or the in operand are dead. - continue; - } - AddToWorklist(dec); - } - - // Add DebugScope and DebugInlinedAt for |liveInst| to the work list. - if (liveInst->GetDebugScope().GetLexicalScope() != kNoDebugScope) { - auto* scope = get_def_use_mgr()->GetDef( - liveInst->GetDebugScope().GetLexicalScope()); - AddToWorklist(scope); - } - if (liveInst->GetDebugInlinedAt() != kNoInlinedAt) { - auto* inlined_at = - get_def_use_mgr()->GetDef(liveInst->GetDebugInlinedAt()); - AddToWorklist(inlined_at); - } - worklist_.pop(); - } + InitializeWorkList(func, structured_order); + ProcessWorkList(func); + return KillDeadInstructions(func, structured_order); +} - // Kill dead instructions and remember dead blocks - for (auto bi = structuredOrder.begin(); bi != structuredOrder.end();) { - uint32_t mergeBlockId = 0; - (*bi)->ForEachInst([this, &modified, &mergeBlockId](Instruction* inst) { - if (!IsDead(inst)) return; +bool AggressiveDCEPass::KillDeadInstructions( + const Function* func, std::list<BasicBlock*>& structured_order) { + bool modified = false; + for (auto bi = structured_order.begin(); bi != structured_order.end();) { + uint32_t merge_block_id = 0; + (*bi)->ForEachInst([this, &modified, &merge_block_id](Instruction* inst) { + if (IsLive(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); + merge_block_id = 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) { + if (merge_block_id != 0) { + AddBranch(merge_block_id, *bi); + for (++bi; (*bi)->id() != merge_block_id; ++bi) { } auto merge_terminator = (*bi)->terminator(); @@ -588,13 +316,252 @@ bool AggressiveDCEPass::AggressiveDCE(Function* func) { live_insts_.Set(merge_terminator->unique_id()); } } else { + Instruction* inst = (*bi)->terminator(); + if (!IsLive(inst)) { + // If the terminator is not live, this block has no live instructions, + // and it will be unreachable. + AddUnreachable(*bi); + } ++bi; } } - return modified; } +void AggressiveDCEPass::ProcessWorkList(Function* func) { + while (!worklist_.empty()) { + Instruction* live_inst = worklist_.front(); + worklist_.pop(); + AddOperandsToWorkList(live_inst); + MarkBlockAsLive(live_inst); + MarkLoadedVariablesAsLive(func, live_inst); + AddDecorationsToWorkList(live_inst); + AddDebugInstructionsToWorkList(live_inst); + } +} + +void AggressiveDCEPass::AddDebugInstructionsToWorkList( + const Instruction* inst) { + for (auto& line_inst : inst->dbg_line_insts()) { + if (line_inst.IsDebugLineInst()) { + AddOperandsToWorkList(&line_inst); + } + } + + if (inst->GetDebugScope().GetLexicalScope() != kNoDebugScope) { + auto* scope = + get_def_use_mgr()->GetDef(inst->GetDebugScope().GetLexicalScope()); + AddToWorklist(scope); + } + if (inst->GetDebugInlinedAt() != kNoInlinedAt) { + auto* inlined_at = get_def_use_mgr()->GetDef(inst->GetDebugInlinedAt()); + AddToWorklist(inlined_at); + } +} + +void AggressiveDCEPass::AddDecorationsToWorkList(const Instruction* inst) { + // Add OpDecorateId instructions that apply to this instruction to the work + // list. We use the decoration manager to look through the group + // decorations to get to the OpDecorate* instructions themselves. + auto decorations = + get_decoration_mgr()->GetDecorationsFor(inst->result_id(), false); + for (Instruction* dec : decorations) { + // We only care about OpDecorateId instructions because the are the only + // decorations that will reference an id that will have to be kept live + // because of that use. + if (dec->opcode() != SpvOpDecorateId) { + continue; + } + if (dec->GetSingleWordInOperand(1) == + SpvDecorationHlslCounterBufferGOOGLE) { + // These decorations should not force the use id to be live. It will be + // removed if either the target or the in operand are dead. + continue; + } + AddToWorklist(dec); + } +} + +void AggressiveDCEPass::MarkLoadedVariablesAsLive(Function* func, + Instruction* inst) { + std::vector<uint32_t> live_variables = GetLoadedVariables(inst); + for (uint32_t var_id : live_variables) { + ProcessLoad(func, var_id); + } +} + +std::vector<uint32_t> AggressiveDCEPass::GetLoadedVariables(Instruction* inst) { + if (inst->opcode() == SpvOpFunctionCall) { + return GetLoadedVariablesFromFunctionCall(inst); + } + uint32_t var_id = GetLoadedVariableFromNonFunctionCalls(inst); + if (var_id == 0) { + return {}; + } + return {var_id}; +} + +uint32_t AggressiveDCEPass::GetLoadedVariableFromNonFunctionCalls( + Instruction* inst) { + std::vector<uint32_t> live_variables; + if (inst->IsAtomicWithLoad()) { + return GetVariableId(inst->GetSingleWordInOperand(kLoadSourceAddrInIdx)); + } + + switch (inst->opcode()) { + case SpvOpLoad: + case SpvOpImageTexelPointer: + return GetVariableId(inst->GetSingleWordInOperand(kLoadSourceAddrInIdx)); + case SpvOpCopyMemory: + case SpvOpCopyMemorySized: + return GetVariableId( + inst->GetSingleWordInOperand(kCopyMemorySourceAddrInIdx)); + default: + break; + } + + switch (inst->GetCommonDebugOpcode()) { + case CommonDebugInfoDebugDeclare: + return inst->GetSingleWordOperand(kDebugDeclareOperandVariableIndex); + case CommonDebugInfoDebugValue: { + analysis::DebugInfoManager* debug_info_mgr = + context()->get_debug_info_mgr(); + return debug_info_mgr->GetVariableIdOfDebugValueUsedForDeclare(inst); + } + default: + break; + } + return 0; +} + +std::vector<uint32_t> AggressiveDCEPass::GetLoadedVariablesFromFunctionCall( + const Instruction* inst) { + assert(inst->opcode() == SpvOpFunctionCall); + std::vector<uint32_t> live_variables; + inst->ForEachInId([this, &live_variables](const uint32_t* operand_id) { + if (!IsPtr(*operand_id)) return; + uint32_t var_id = GetVariableId(*operand_id); + live_variables.push_back(var_id); + }); + return live_variables; +} + +uint32_t AggressiveDCEPass::GetVariableId(uint32_t ptr_id) { + assert(IsPtr(ptr_id) && + "Cannot get the variable when input is not a pointer."); + uint32_t varId = 0; + (void)GetPtr(ptr_id, &varId); + return varId; +} + +void AggressiveDCEPass::MarkBlockAsLive(Instruction* inst) { + BasicBlock* basic_block = context()->get_instr_block(inst); + if (basic_block == nullptr) { + return; + } + + // If we intend to keep this instruction, we need the block label and + // block terminator to have a valid block for the instruction. + AddToWorklist(basic_block->GetLabelInst()); + + // We need to mark the successors blocks that follow as live. If this is + // header of the merge construct, the construct may be folded, but we will + // definitely need the merge label. If it is not a construct, the terminator + // must be live, and the successor blocks will be marked as live when + // processing the terminator. + uint32_t merge_id = basic_block->MergeBlockIdIfAny(); + if (merge_id == 0) { + AddToWorklist(basic_block->terminator()); + } else { + AddToWorklist(context()->get_def_use_mgr()->GetDef(merge_id)); + } + + // Mark the structured control flow constructs that contains this block as + // live. If |inst| is an instruction in the loop header, then it is part of + // the loop, so the loop construct must be live. We exclude the label because + // it does not matter how many times it is executed. This could be extended + // to more instructions, but we will need it for now. + if (inst->opcode() != SpvOpLabel) + MarkLoopConstructAsLiveIfLoopHeader(basic_block); + + Instruction* next_branch_inst = GetBranchForNextHeader(basic_block); + if (next_branch_inst != nullptr) { + AddToWorklist(next_branch_inst); + Instruction* mergeInst = GetMergeInstruction(next_branch_inst); + AddToWorklist(mergeInst); + } + + if (inst->opcode() == SpvOpLoopMerge || + inst->opcode() == SpvOpSelectionMerge) { + AddBreaksAndContinuesToWorklist(inst); + } +} +void AggressiveDCEPass::MarkLoopConstructAsLiveIfLoopHeader( + BasicBlock* basic_block) { + // If this is the header for a loop, then loop structure needs to keep as well + // because the loop header is also part of the loop. + Instruction* merge_inst = basic_block->GetLoopMergeInst(); + if (merge_inst != nullptr) { + AddToWorklist(basic_block->terminator()); + AddToWorklist(merge_inst); + } +} + +void AggressiveDCEPass::AddOperandsToWorkList(const Instruction* inst) { + inst->ForEachInId([this](const uint32_t* iid) { + Instruction* inInst = get_def_use_mgr()->GetDef(*iid); + AddToWorklist(inInst); + }); + if (inst->type_id() != 0) { + AddToWorklist(get_def_use_mgr()->GetDef(inst->type_id())); + } +} + +void AggressiveDCEPass::InitializeWorkList( + Function* func, std::list<BasicBlock*>& structured_order) { + AddToWorklist(&func->DefInst()); + MarkFunctionParameterAsLive(func); + MarkFirstBlockAsLive(func); + + // Add instructions with external side effects to the worklist. Also add + // branches that are not attached to a structured construct. + // TODO(s-perron): The handling of branch seems to be adhoc. This needs to be + // cleaned up. + for (auto& bi : structured_order) { + for (auto ii = bi->begin(); ii != bi->end(); ++ii) { + SpvOp op = ii->opcode(); + if (ii->IsBranch()) { + continue; + } + switch (op) { + case SpvOpStore: { + uint32_t var_id = 0; + (void)GetPtr(&*ii, &var_id); + if (!IsLocalVar(var_id, func)) AddToWorklist(&*ii); + } break; + case SpvOpCopyMemory: + case SpvOpCopyMemorySized: { + uint32_t var_id = 0; + uint32_t target_addr_id = + ii->GetSingleWordInOperand(kCopyMemoryTargetAddrInIdx); + (void)GetPtr(target_addr_id, &var_id); + if (!IsLocalVar(var_id, func)) AddToWorklist(&*ii); + } break; + case SpvOpLoopMerge: + case SpvOpSelectionMerge: + case SpvOpUnreachable: + break; + default: { + // Function calls, atomics, function params, function returns, etc. + if (!ii->IsOpcodeSafeToDelete()) { + AddToWorklist(&*ii); + } + } break; + } + } + } +} + void AggressiveDCEPass::InitializeModuleScopeLiveInstructions() { // Keep all execution modes. for (auto& exec : get_module()->execution_modes()) { @@ -602,7 +569,8 @@ void AggressiveDCEPass::InitializeModuleScopeLiveInstructions() { } // Keep all entry points. for (auto& entry : get_module()->entry_points()) { - if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) { + if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4) && + !preserve_interface_) { // In SPIR-V 1.4 and later, entry points must list all global variables // used. DCE can still remove non-input/output variables and update the // interface list. Mark the entry point as live and inputs and outputs as @@ -649,16 +617,25 @@ void AggressiveDCEPass::InitializeModuleScopeLiveInstructions() { } // For each DebugInfo GlobalVariable keep all operands except the Variable. - // Later, if the variable is dead, we will set the operand to DebugInfoNone. + // Later, if the variable is killed with KillInst(), we will set the operand + // to DebugInfoNone. Create and save DebugInfoNone now for this possible + // later use. This is slightly unoptimal, but it avoids generating it during + // instruction killing when the module is not consistent. + bool debug_global_seen = false; for (auto& dbg : get_module()->ext_inst_debuginfo()) { if (dbg.GetCommonDebugOpcode() != CommonDebugInfoDebugGlobalVariable) continue; + debug_global_seen = true; dbg.ForEachInId([this](const uint32_t* iid) { - Instruction* inInst = get_def_use_mgr()->GetDef(*iid); - if (inInst->opcode() == SpvOpVariable) return; - AddToWorklist(inInst); + Instruction* in_inst = get_def_use_mgr()->GetDef(*iid); + if (in_inst->opcode() == SpvOpVariable) return; + AddToWorklist(in_inst); }); } + if (debug_global_seen) { + auto dbg_none = context()->get_debug_info_mgr()->GetDebugInfoNone(); + AddToWorklist(dbg_none); + } } Pass::Status AggressiveDCEPass::ProcessImpl() { @@ -690,7 +667,7 @@ Pass::Status AggressiveDCEPass::ProcessImpl() { // Process all entry point functions. ProcessFunction pfn = [this](Function* fp) { return AggressiveDCE(fp); }; - modified |= context()->ProcessEntryPointCallTree(pfn); + modified |= context()->ProcessReachableCallTree(pfn); // If the decoration manager is kept live then the context will try to keep it // up to date. ADCE deals with group decorations by changing the operands in @@ -717,21 +694,20 @@ Pass::Status AggressiveDCEPass::ProcessImpl() { // Cleanup all CFG including all unreachable blocks. ProcessFunction cleanup = [this](Function* f) { return CFGCleanup(f); }; - modified |= context()->ProcessEntryPointCallTree(cleanup); + modified |= context()->ProcessReachableCallTree(cleanup); 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. + // are dead. std::unordered_set<const Function*> live_function_set; ProcessFunction mark_live = [&live_function_set](Function* fp) { live_function_set.insert(fp); return false; }; - context()->ProcessEntryPointCallTree(mark_live); + context()->ProcessReachableCallTree(mark_live); bool modified = false; for (auto funcIter = get_module()->begin(); @@ -797,7 +773,7 @@ bool AggressiveDCEPass::ProcessGlobalValues() { uint32_t counter_buffer_id = annotation->GetSingleWordInOperand(2); Instruction* counter_buffer_inst = get_def_use_mgr()->GetDef(counter_buffer_id); - if (IsDead(counter_buffer_inst)) { + if (!IsLive(counter_buffer_inst)) { context()->KillInst(annotation); modified = true; } @@ -812,7 +788,7 @@ bool AggressiveDCEPass::ProcessGlobalValues() { for (uint32_t i = 1; i < annotation->NumOperands();) { Instruction* opInst = get_def_use_mgr()->GetDef(annotation->GetSingleWordOperand(i)); - if (IsDead(opInst)) { + if (!IsLive(opInst)) { // Don't increment |i|. annotation->RemoveOperand(i); modified = true; @@ -839,7 +815,7 @@ bool AggressiveDCEPass::ProcessGlobalValues() { for (uint32_t i = 1; i < annotation->NumOperands();) { Instruction* opInst = get_def_use_mgr()->GetDef(annotation->GetSingleWordOperand(i)); - if (IsDead(opInst)) { + if (!IsLive(opInst)) { // Don't increment |i|. annotation->RemoveOperand(i + 1); annotation->RemoveOperand(i); @@ -873,13 +849,13 @@ bool AggressiveDCEPass::ProcessGlobalValues() { } for (auto& dbg : get_module()->ext_inst_debuginfo()) { - if (!IsDead(&dbg)) continue; + if (IsLive(&dbg)) continue; // Save GlobalVariable if its variable is live, otherwise null out variable // index if (dbg.GetCommonDebugOpcode() == CommonDebugInfoDebugGlobalVariable) { auto var_id = dbg.GetSingleWordOperand(kGlobalVariableVariableIndex); Instruction* var_inst = get_def_use_mgr()->GetDef(var_id); - if (!IsDead(var_inst)) continue; + if (IsLive(var_inst)) continue; context()->ForgetUses(&dbg); dbg.SetOperand( kGlobalVariableVariableIndex, @@ -894,7 +870,7 @@ bool AggressiveDCEPass::ProcessGlobalValues() { // 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)) { + if (!IsLive(&val)) { // Save forwarded pointer if pointer is live since closure does not mark // this live as it does not have a result id. This is a little too // conservative since it is not known if the structure type that needed @@ -902,14 +878,15 @@ bool AggressiveDCEPass::ProcessGlobalValues() { if (val.opcode() == SpvOpTypeForwardPointer) { uint32_t ptr_ty_id = val.GetSingleWordInOperand(0); Instruction* ptr_ty_inst = get_def_use_mgr()->GetDef(ptr_ty_id); - if (!IsDead(ptr_ty_inst)) continue; + if (IsLive(ptr_ty_inst)) continue; } to_kill_.push_back(&val); modified = true; } } - if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) { + if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4) && + !preserve_interface_) { // Remove the dead interface variables from the entry point interface list. for (auto& entry : get_module()->entry_points()) { std::vector<Operand> new_operands; @@ -920,7 +897,7 @@ bool AggressiveDCEPass::ProcessGlobalValues() { } else { auto* var = get_def_use_mgr()->GetDef(entry.GetSingleWordInOperand(i)); - if (!IsDead(var)) { + if (IsLive(var)) { new_operands.push_back(entry.GetInOperand(i)); } } @@ -935,8 +912,6 @@ bool AggressiveDCEPass::ProcessGlobalValues() { return modified; } -AggressiveDCEPass::AggressiveDCEPass() = default; - Pass::Status AggressiveDCEPass::Process() { // Initialize extensions allowlist InitExtensions(); @@ -998,8 +973,118 @@ void AggressiveDCEPass::InitExtensions() { "SPV_KHR_subgroup_uniform_control_flow", "SPV_KHR_integer_dot_product", "SPV_EXT_shader_image_int64", + "SPV_KHR_non_semantic_info", }); } +Instruction* AggressiveDCEPass::GetHeaderBranch(BasicBlock* blk) { + if (blk == nullptr) { + return nullptr; + } + BasicBlock* header_block = GetHeaderBlock(blk); + if (header_block == nullptr) { + return nullptr; + } + return header_block->terminator(); +} + +BasicBlock* AggressiveDCEPass::GetHeaderBlock(BasicBlock* blk) const { + if (blk == nullptr) { + return nullptr; + } + + BasicBlock* header_block = nullptr; + if (blk->IsLoopHeader()) { + header_block = blk; + } else { + uint32_t header = + context()->GetStructuredCFGAnalysis()->ContainingConstruct(blk->id()); + header_block = context()->get_instr_block(header); + } + return header_block; +} + +Instruction* AggressiveDCEPass::GetMergeInstruction(Instruction* inst) { + BasicBlock* bb = context()->get_instr_block(inst); + if (bb == nullptr) { + return nullptr; + } + return bb->GetMergeInst(); +} + +Instruction* AggressiveDCEPass::GetBranchForNextHeader(BasicBlock* blk) { + if (blk == nullptr) { + return nullptr; + } + + if (blk->IsLoopHeader()) { + uint32_t header = + context()->GetStructuredCFGAnalysis()->ContainingConstruct(blk->id()); + blk = context()->get_instr_block(header); + } + return GetHeaderBranch(blk); +} + +void AggressiveDCEPass::MarkFunctionParameterAsLive(const Function* func) { + func->ForEachParam( + [this](const Instruction* param) { + AddToWorklist(const_cast<Instruction*>(param)); + }, + false); +} + +bool AggressiveDCEPass::BlockIsInConstruct(BasicBlock* header_block, + BasicBlock* bb) { + if (bb == nullptr || header_block == nullptr) { + return false; + } + + uint32_t current_header = bb->id(); + while (current_header != 0) { + if (current_header == header_block->id()) return true; + current_header = context()->GetStructuredCFGAnalysis()->ContainingConstruct( + current_header); + } + return false; +} + +bool AggressiveDCEPass::IsEntryPointWithNoCalls(Function* func) { + auto cached_result = entry_point_with_no_calls_cache_.find(func->result_id()); + if (cached_result != entry_point_with_no_calls_cache_.end()) { + return cached_result->second; + } + bool result = IsEntryPoint(func) && !HasCall(func); + entry_point_with_no_calls_cache_[func->result_id()] = result; + return result; +} + +bool AggressiveDCEPass::IsEntryPoint(Function* func) { + for (const Instruction& entry_point : get_module()->entry_points()) { + uint32_t entry_point_id = + entry_point.GetSingleWordInOperand(kEntryPointFunctionIdInIdx); + if (entry_point_id == func->result_id()) { + return true; + } + } + return false; +} + +bool AggressiveDCEPass::HasCall(Function* func) { + return !func->WhileEachInst( + [](Instruction* inst) { return inst->opcode() != SpvOpFunctionCall; }); +} + +void AggressiveDCEPass::MarkFirstBlockAsLive(Function* func) { + BasicBlock* first_block = &*func->begin(); + MarkBlockAsLive(first_block->GetLabelInst()); +} + +void AggressiveDCEPass::AddUnreachable(BasicBlock*& block) { + InstructionBuilder builder( + context(), block, + IRContext::kAnalysisInstrToBlockMapping | IRContext::kAnalysisDefUse); + builder.AddUnreachable(); +} + } // namespace opt } // namespace spvtools diff --git a/source/opt/aggressive_dead_code_elim_pass.h b/source/opt/aggressive_dead_code_elim_pass.h index f02e729f..1b3fd1e8 100644 --- a/source/opt/aggressive_dead_code_elim_pass.h +++ b/source/opt/aggressive_dead_code_elim_pass.h @@ -44,7 +44,9 @@ class AggressiveDCEPass : public MemPass { using GetBlocksFunction = std::function<std::vector<BasicBlock*>*(const BasicBlock*)>; - AggressiveDCEPass(); + AggressiveDCEPass(bool preserve_interface = false) + : preserve_interface_(preserve_interface) {} + const char* name() const override { return "eliminate-dead-code-aggressive"; } Status Process() override; @@ -55,23 +57,26 @@ class AggressiveDCEPass : public MemPass { } private: + // Preserve entry point interface if true. All variables in interface + // will be marked live and will not be eliminated. This mode is needed by + // GPU-Assisted Validation instrumentation where a change in the interface + // is not allowed. + bool preserve_interface_; + // 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 the instance of the variable |varId| can only be access in + // |func|. For example, a function scope variable, or a private variable + // where |func| is an entry point with no function calls. + bool IsLocalVar(uint32_t varId, Function* func); // Return true if |inst| is marked live. bool IsLive(const Instruction* inst) const { return live_insts_.Get(inst->unique_id()); } - // Returns true if |inst| is dead. - bool IsDead(Instruction* inst); - // Adds entry points, execution modes and workgroup size decorations to the // worklist for processing with the first function. void InitializeModuleScopeLiveInstructions(); @@ -101,18 +106,6 @@ class AggressiveDCEPass : public MemPass { // If |varId| is local, mark all stores of varId as live. void ProcessLoad(Function* func, 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(BasicBlock* bp, Instruction** mergeInst, - Instruction** branchInst, uint32_t* mergeBlockId); - - // Initialize block2headerBranch_, header2nextHeaderBranch_, and - // branch2merge_ using |structuredOrder| to order blocks. - void ComputeBlock2HeaderMaps(std::list<BasicBlock*>& structuredOrder); - // Add branch to |labelId| to end of block |bp|. void AddBranch(uint32_t labelId, BasicBlock* bp); @@ -140,14 +133,100 @@ class AggressiveDCEPass : public MemPass { Pass::Status ProcessImpl(); - // True if current function has a call instruction contained in it - bool call_in_func_; + // Adds instructions which must be kept because of they have side-effects + // that ADCE cannot model to the work list. + void InitializeWorkList(Function* func, + std::list<BasicBlock*>& structured_order); + + // Process each instruction in the work list by marking any instruction that + // that it depends on as live, and adding it to the work list. The work list + // will be empty at the end. + void ProcessWorkList(Function* func); + + // Kills any instructions in |func| that have not been marked as live. + bool KillDeadInstructions(const Function* func, + std::list<BasicBlock*>& structured_order); + + // Adds the instructions that define the operands of |inst| to the work list. + void AddOperandsToWorkList(const Instruction* inst); + + // Marks all of the labels and branch that inst requires as live. + void MarkBlockAsLive(Instruction* inst); + + // Marks any variables from which |inst| may require data as live. + void MarkLoadedVariablesAsLive(Function* func, Instruction* inst); + + // Returns the id of the variable that |ptr_id| point to. |ptr_id| must be a + // value whose type is a pointer. + uint32_t GetVariableId(uint32_t ptr_id); + + // Returns all of the ids for the variables from which |inst| will load data. + std::vector<uint32_t> GetLoadedVariables(Instruction* inst); + + // Returns all of the ids for the variables from which |inst| will load data. + // The opcode of |inst| must be OpFunctionCall. + std::vector<uint32_t> GetLoadedVariablesFromFunctionCall( + const Instruction* inst); + + // Returns the id of the variable from which |inst| will load data. |inst| + // must not be an OpFunctionCall. Returns 0 if no data is read or the + // variable cannot be determined. Note that in logical addressing mode the + // latter is not possible for function and private storage class because there + // cannot be variable pointers pointing to those storage classes. + uint32_t GetLoadedVariableFromNonFunctionCalls(Instruction* inst); + + // Adds all decorations of |inst| to the work list. + void AddDecorationsToWorkList(const Instruction* inst); + + // Adds all debug instruction associated with |inst| to the work list. + void AddDebugInstructionsToWorkList(const Instruction* inst); - // True if current function is an entry point - bool func_is_entry_point_; + // Marks all of the OpFunctionParameter instructions in |func| as live. + void MarkFunctionParameterAsLive(const Function* func); - // True if current function is entry point and has no function calls. - bool private_like_local_; + // Returns the terminator instruction in the header for the innermost + // construct that contains |blk|. Returns nullptr if no such header exists. + Instruction* GetHeaderBranch(BasicBlock* blk); + + // Returns the header for the innermost construct that contains |blk|. A loop + // header will be its own header. Returns nullptr if no such header exists. + BasicBlock* GetHeaderBlock(BasicBlock* blk) const; + + // Returns the same as |GetHeaderBlock| except if |blk| is a loop header it + // will return the header of the next enclosing construct. Returns nullptr if + // no such header exists. + Instruction* GetBranchForNextHeader(BasicBlock* blk); + + // Returns the merge instruction in the same basic block as |inst|. Returns + // nullptr if one does not exist. + Instruction* GetMergeInstruction(Instruction* inst); + + // Returns true if |bb| is in the construct with header |header_block|. + bool BlockIsInConstruct(BasicBlock* header_block, BasicBlock* bb); + + // Returns true if |func| is an entry point that does not have any function + // calls. + bool IsEntryPointWithNoCalls(Function* func); + + // Returns true if |func| is an entry point. + bool IsEntryPoint(Function* func); + + // Returns true if |func| contains a function call. + bool HasCall(Function* func); + + // Marks the first block, which is the entry block, in |func| as live. + void MarkFirstBlockAsLive(Function* func); + + // Adds an OpUnreachable instruction at the end of |block|. + void AddUnreachable(BasicBlock*& block); + + // Marks the OpLoopMerge and the terminator in |basic_block| as live if + // |basic_block| is a loop header. + void MarkLoopConstructAsLiveIfLoopHeader(BasicBlock* basic_block); + + // The cached results for |IsEntryPointWithNoCalls|. It maps the function's + // result id to the return value. + std::unordered_map<uint32_t, bool> entry_point_with_no_calls_cache_; // Live Instruction Worklist. An instruction is added to this list // if it might have a side effect, either directly or indirectly. @@ -156,27 +235,6 @@ class AggressiveDCEPass : public MemPass { // building up the live instructions set |live_insts_|. std::queue<Instruction*> 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<BasicBlock*, Instruction*> block2headerBranch_; - - // Map from header block to the branch instruction in the header of the - // structured construct enclosing it. - // The liveness algorithm is designed to iteratively mark as live all - // structured constructs enclosing a live instruction. - std::unordered_map<BasicBlock*, Instruction*> header2nextHeaderBranch_; - - // Maps basic block to their index in the structured order traversal. - std::unordered_map<BasicBlock*, uint32_t> structured_order_index_; - - // Map from branch to its associated merge instruction, if any - std::unordered_map<Instruction*, Instruction*> branch2merge_; - - // Store instructions to variables of private storage - std::vector<Instruction*> private_stores_; - // Live Instructions utils::BitVector live_insts_; diff --git a/source/opt/block_merge_pass.cpp b/source/opt/block_merge_pass.cpp index 04e47f1c..ef7f31fe 100644 --- a/source/opt/block_merge_pass.cpp +++ b/source/opt/block_merge_pass.cpp @@ -44,7 +44,7 @@ bool BlockMergePass::MergeBlocks(Function* func) { Pass::Status BlockMergePass::Process() { // Process all entry point functions. ProcessFunction pfn = [this](Function* fp) { return MergeBlocks(fp); }; - bool modified = context()->ProcessEntryPointCallTree(pfn); + bool modified = context()->ProcessReachableCallTree(pfn); return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; } diff --git a/source/opt/block_merge_util.cpp b/source/opt/block_merge_util.cpp index 15e8c6ff..8ae8020a 100644 --- a/source/opt/block_merge_util.cpp +++ b/source/opt/block_merge_util.cpp @@ -171,10 +171,17 @@ void MergeWithSuccessor(IRContext* context, Function* func, // and OpBranchConditional. auto terminator = bi->terminator(); auto& vec = terminator->dbg_line_insts(); - auto& new_vec = merge_inst->dbg_line_insts(); - new_vec.insert(new_vec.end(), vec.begin(), vec.end()); - terminator->clear_dbg_line_insts(); - + if (vec.size() > 0) { + merge_inst->ClearDbgLineInsts(); + auto& new_vec = merge_inst->dbg_line_insts(); + new_vec.insert(new_vec.end(), vec.begin(), vec.end()); + terminator->ClearDbgLineInsts(); + for (auto& l_inst : new_vec) + context->get_def_use_mgr()->AnalyzeInstDefUse(&l_inst); + } + // Clear debug scope of terminator to avoid DebugScope + // emitted between terminator and merge. + terminator->SetDebugScope(DebugScope(kNoDebugScope, kNoInlinedAt)); // Move the merge instruction to just before the terminator. merge_inst->InsertBefore(terminator); } diff --git a/source/opt/ccp_pass.cpp b/source/opt/ccp_pass.cpp index d84f13f5..8b896d50 100644 --- a/source/opt/ccp_pass.cpp +++ b/source/opt/ccp_pass.cpp @@ -291,6 +291,10 @@ bool CCPPass::ReplaceValues() { } bool CCPPass::PropagateConstants(Function* fp) { + if (fp->IsDeclaration()) { + return false; + } + // Mark function parameters as varying. fp->ForEachParam([this](const Instruction* inst) { values_[inst->result_id()] = kVaryingSSAId; diff --git a/source/opt/combine_access_chains.cpp b/source/opt/combine_access_chains.cpp index facfc24b..142897a2 100644 --- a/source/opt/combine_access_chains.cpp +++ b/source/opt/combine_access_chains.cpp @@ -34,6 +34,10 @@ Pass::Status CombineAccessChains::Process() { } bool CombineAccessChains::ProcessFunction(Function& function) { + if (function.IsDeclaration()) { + return false; + } + bool modified = false; cfg()->ForEachBlockInReversePostOrder( diff --git a/source/opt/compact_ids_pass.cpp b/source/opt/compact_ids_pass.cpp index 67091531..8815b8c6 100644 --- a/source/opt/compact_ids_pass.cpp +++ b/source/opt/compact_ids_pass.cpp @@ -86,9 +86,12 @@ Pass::Status CompactIdsPass::Process() { }, true); - if (modified) + if (modified) { context()->module()->SetIdBound( static_cast<uint32_t>(result_id_mapping.size() + 1)); + // There are ids in the feature manager that could now be invalid + context()->ResetFeatureManager(); + } return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; } diff --git a/source/opt/constants.cpp b/source/opt/constants.cpp index a3dac5d7..020e248b 100644 --- a/source/opt/constants.cpp +++ b/source/opt/constants.cpp @@ -217,7 +217,8 @@ Instruction* ConstantManager::BuildInstructionAndAddToModule( auto* new_inst_ptr = new_inst.get(); *pos = pos->InsertBefore(std::move(new_inst)); ++(*pos); - context()->get_def_use_mgr()->AnalyzeInstDefUse(new_inst_ptr); + if (context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse)) + context()->get_def_use_mgr()->AnalyzeInstDefUse(new_inst_ptr); MapConstantToInst(new_const, new_inst_ptr); return new_inst_ptr; } diff --git a/source/opt/convert_to_half_pass.cpp b/source/opt/convert_to_half_pass.cpp index 6b3b540a..b127eabe 100644 --- a/source/opt/convert_to_half_pass.cpp +++ b/source/opt/convert_to_half_pass.cpp @@ -177,18 +177,21 @@ bool ConvertToHalfPass::GenHalfArith(Instruction* inst) { return modified; } -bool ConvertToHalfPass::ProcessPhi(Instruction* inst) { - // Add float16 converts of any float32 operands and change type - // of phi to float16 equivalent. Operand converts need to be added to - // preceeding blocks. +bool ConvertToHalfPass::ProcessPhi(Instruction* inst, uint32_t from_width, + uint32_t to_width) { + // Add converts of any float operands to to_width if they are of from_width. + // If converting to 16, change type of phi to float16 equivalent and remember + // result id. Converts need to be added to preceeding blocks. uint32_t ocnt = 0; uint32_t* prev_idp; - inst->ForEachInId([&ocnt, &prev_idp, this](uint32_t* idp) { + bool modified = false; + inst->ForEachInId([&ocnt, &prev_idp, &from_width, &to_width, &modified, + this](uint32_t* idp) { if (ocnt % 2 == 0) { prev_idp = idp; } else { Instruction* val_inst = get_def_use_mgr()->GetDef(*prev_idp); - if (IsFloat(val_inst, 32)) { + if (IsFloat(val_inst, from_width)) { BasicBlock* bp = context()->get_instr_block(*idp); auto insert_before = bp->tail(); if (insert_before != bp->begin()) { @@ -197,15 +200,19 @@ bool ConvertToHalfPass::ProcessPhi(Instruction* inst) { insert_before->opcode() != SpvOpLoopMerge) ++insert_before; } - GenConvert(prev_idp, 16, &*insert_before); + GenConvert(prev_idp, to_width, &*insert_before); + modified = true; } } ++ocnt; }); - inst->SetResultType(EquivFloatTypeId(inst->type_id(), 16)); - get_def_use_mgr()->AnalyzeInstUse(inst); - converted_ids_.insert(inst->result_id()); - return true; + if (to_width == 16u) { + inst->SetResultType(EquivFloatTypeId(inst->type_id(), 16u)); + converted_ids_.insert(inst->result_id()); + modified = true; + } + if (modified) get_def_use_mgr()->AnalyzeInstUse(inst); + return modified; } bool ConvertToHalfPass::ProcessConvert(Instruction* inst) { @@ -242,9 +249,10 @@ bool ConvertToHalfPass::ProcessImageRef(Instruction* inst) { } bool ConvertToHalfPass::ProcessDefault(Instruction* inst) { - bool modified = false; // If non-relaxed instruction has changed operands, need to convert // them back to float32 + if (inst->opcode() == SpvOpPhi) return ProcessPhi(inst, 16u, 32u); + bool modified = false; inst->ForEachInId([&inst, &modified, this](uint32_t* idp) { if (converted_ids_.count(*idp) == 0) return; uint32_t old_id = *idp; @@ -262,7 +270,7 @@ bool ConvertToHalfPass::GenHalfInst(Instruction* inst) { if (IsArithmetic(inst) && inst_relaxed) modified = GenHalfArith(inst); else if (inst->opcode() == SpvOpPhi && inst_relaxed) - modified = ProcessPhi(inst); + modified = ProcessPhi(inst, 32u, 16u); else if (inst->opcode() == SpvOpFConvert) modified = ProcessConvert(inst); else if (image_ops_.count(inst->opcode()) != 0) @@ -340,7 +348,7 @@ Pass::Status ConvertToHalfPass::ProcessImpl() { Pass::ProcessFunction pfn = [this](Function* fp) { return ProcessFunction(fp); }; - bool modified = context()->ProcessEntryPointCallTree(pfn); + bool modified = context()->ProcessReachableCallTree(pfn); // If modified, make sure module has Float16 capability if (modified) context()->AddCapability(SpvCapabilityFloat16); // Remove all RelaxedPrecision decorations from instructions and globals diff --git a/source/opt/convert_to_half_pass.h b/source/opt/convert_to_half_pass.h index b647dd4a..c6e84d1b 100644 --- a/source/opt/convert_to_half_pass.h +++ b/source/opt/convert_to_half_pass.h @@ -93,7 +93,7 @@ class ConvertToHalfPass : public Pass { bool GenHalfArith(Instruction* inst); // Gen code for relaxed phi |inst| - bool ProcessPhi(Instruction* inst); + bool ProcessPhi(Instruction* inst, uint32_t from_width, uint32_t to_width); // Gen code for relaxed convert |inst| bool ProcessConvert(Instruction* inst); diff --git a/source/opt/copy_prop_arrays.cpp b/source/opt/copy_prop_arrays.cpp index f505d8a5..62ed5e77 100644 --- a/source/opt/copy_prop_arrays.cpp +++ b/source/opt/copy_prop_arrays.cpp @@ -40,6 +40,10 @@ bool IsDebugDeclareOrValue(Instruction* di) { Pass::Status CopyPropagateArrays::Process() { bool modified = false; for (Function& function : *get_module()) { + if (function.IsDeclaration()) { + continue; + } + BasicBlock* entry_bb = &*function.begin(); for (auto var_inst = entry_bb->begin(); var_inst->opcode() == SpvOpVariable; diff --git a/source/opt/dead_branch_elim_pass.cpp b/source/opt/dead_branch_elim_pass.cpp index 0054f576..356dbcb3 100644 --- a/source/opt/dead_branch_elim_pass.cpp +++ b/source/opt/dead_branch_elim_pass.cpp @@ -346,6 +346,7 @@ bool DeadBranchElimPass::FixPhiNodesInLiveBlocks( if (operands.size() == 4) { // First input data operands is at index 2. uint32_t replId = operands[2u].words[0]; + context()->KillNamesAndDecorates(inst->result_id()); context()->ReplaceAllUsesWith(inst->result_id(), replId); iter = context()->KillInst(&*inst); } else { @@ -419,6 +420,10 @@ bool DeadBranchElimPass::EraseDeadBlocks( } bool DeadBranchElimPass::EliminateDeadBranches(Function* func) { + if (func->IsDeclaration()) { + return false; + } + bool modified = false; std::unordered_set<BasicBlock*> live_blocks; modified |= MarkLiveBlocks(func, &live_blocks); diff --git a/source/opt/dead_insert_elim_pass.cpp b/source/opt/dead_insert_elim_pass.cpp index fb5c1634..d877f0f9 100644 --- a/source/opt/dead_insert_elim_pass.cpp +++ b/source/opt/dead_insert_elim_pass.cpp @@ -256,7 +256,7 @@ Pass::Status DeadInsertElimPass::Process() { ProcessFunction pfn = [this](Function* fp) { return EliminateDeadInserts(fp); }; - bool modified = context()->ProcessEntryPointCallTree(pfn); + bool modified = context()->ProcessReachableCallTree(pfn); return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; } diff --git a/source/opt/debug_info_manager.cpp b/source/opt/debug_info_manager.cpp index 2e8e1327..060e0d93 100644 --- a/source/opt/debug_info_manager.cpp +++ b/source/opt/debug_info_manager.cpp @@ -18,7 +18,7 @@ #include "source/opt/ir_context.h" -// Constants for OpenCL.DebugInfo.100 & NonSemantic.Vulkan.DebugInfo.100 +// Constants for OpenCL.DebugInfo.100 & NonSemantic.Shader.DebugInfo.100 // extension instructions. static const uint32_t kOpLineOperandLineIndex = 1; @@ -86,7 +86,7 @@ uint32_t DebugInfoManager::GetDbgSetImportId() { context()->get_feature_mgr()->GetExtInstImportId_OpenCL100DebugInfo(); if (setId == 0) { setId = - context()->get_feature_mgr()->GetExtInstImportId_Vulkan100DebugInfo(); + context()->get_feature_mgr()->GetExtInstImportId_Shader100DebugInfo(); } return setId; } @@ -117,14 +117,14 @@ void DebugInfoManager::RegisterDbgFunction(Instruction* inst) { fn_id_to_dbg_fn_.find(fn_id) == fn_id_to_dbg_fn_.end() && "Register DebugFunction for a function that already has DebugFunction"); fn_id_to_dbg_fn_[fn_id] = inst; - } else if (inst->GetVulkan100DebugOpcode() == - NonSemanticVulkanDebugInfo100DebugFunctionDefinition) { + } else if (inst->GetShader100DebugOpcode() == + NonSemanticShaderDebugInfo100DebugFunctionDefinition) { auto fn_id = inst->GetSingleWordOperand( kDebugFunctionDefinitionOperandOpFunctionIndex); auto fn_inst = GetDbgInst(inst->GetSingleWordOperand( kDebugFunctionDefinitionOperandDebugFunctionIndex)); - assert(fn_inst && fn_inst->GetVulkan100DebugOpcode() == - NonSemanticVulkanDebugInfo100DebugFunction); + assert(fn_inst && fn_inst->GetShader100DebugOpcode() == + NonSemanticShaderDebugInfo100DebugFunction); assert(fn_id_to_dbg_fn_.find(fn_id) == fn_id_to_dbg_fn_.end() && "Register DebugFunctionDefinition for a function that already has " "DebugFunctionDefinition"); @@ -146,6 +146,25 @@ void DebugInfoManager::RegisterDbgDeclare(uint32_t var_id, } } +// Create new constant directly into global value area, bypassing the +// Constant manager. This is used when the DefUse or Constant managers +// are invalid and cannot be regenerated due to the module being in an +// inconsistant state e.g. in the middle of significant modification +// such as inlining. Invalidate Constant and DefUse managers if used. +uint32_t AddNewConstInGlobals(IRContext* context, uint32_t const_value) { + uint32_t id = context->TakeNextId(); + std::unique_ptr<Instruction> new_const(new Instruction( + context, SpvOpConstant, context->get_type_mgr()->GetUIntTypeId(), id, + { + {spv_operand_type_t::SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER, + {const_value}}, + })); + context->module()->AddGlobalValue(std::move(new_const)); + context->InvalidateAnalyses(IRContext::kAnalysisConstants); + context->InvalidateAnalyses(IRContext::kAnalysisDefUse); + return id; +} + uint32_t DebugInfoManager::CreateDebugInlinedAt(const Instruction* line, const DebugScope& scope) { uint32_t setId = GetDbgSetImportId(); @@ -155,10 +174,10 @@ uint32_t DebugInfoManager::CreateDebugInlinedAt(const Instruction* line, spv_operand_type_t line_number_type = spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER; - // In NonSemantic.Vulkan.DebugInfo.100, all constants are IDs of OpConstant, + // In NonSemantic.Shader.DebugInfo.100, all constants are IDs of OpConstant, // not literals. if (setId == - context()->get_feature_mgr()->GetExtInstImportId_Vulkan100DebugInfo()) + context()->get_feature_mgr()->GetExtInstImportId_Shader100DebugInfo()) line_number_type = spv_operand_type_t::SPV_OPERAND_TYPE_ID; uint32_t line_number = 0; @@ -194,10 +213,18 @@ uint32_t DebugInfoManager::CreateDebugInlinedAt(const Instruction* line, line_number = line->GetSingleWordOperand(kOpLineOperandLineIndex); // If we need the line number as an ID, generate that constant now. + // If Constant or DefUse managers are invalid, generate constant + // directly into the global value section of the module; do not + // use Constant manager which may attempt to invoke building of the + // DefUse manager which cannot be done during inlining. The extra + // constants that may be generated here is likely not significant + // and will likely be cleaned up in later passes. if (line_number_type == spv_operand_type_t::SPV_OPERAND_TYPE_ID) { - uint32_t line_id = - context()->get_constant_mgr()->GetUIntConst(line_number); - line_number = line_id; + if (!context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse) || + !context()->AreAnalysesValid(IRContext::Analysis::kAnalysisConstants)) + line_number = AddNewConstInGlobals(context(), line_number); + else + line_number = context()->get_constant_mgr()->GetUIntConst(line_number); } } @@ -307,7 +334,7 @@ Instruction* DebugInfoManager::GetDebugOperationWithDeref() { })); } else { uint32_t deref_id = context()->get_constant_mgr()->GetUIntConst( - NonSemanticVulkanDebugInfo100Deref); + NonSemanticShaderDebugInfo100Deref); deref_operation = std::unique_ptr<Instruction>( new Instruction(context(), SpvOpExtInst, @@ -316,7 +343,7 @@ Instruction* DebugInfoManager::GetDebugOperationWithDeref() { {SPV_OPERAND_TYPE_ID, {GetDbgSetImportId()}}, {SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER, {static_cast<uint32_t>( - NonSemanticVulkanDebugInfo100DebugOperation)}}, + NonSemanticShaderDebugInfo100DebugOperation)}}, {SPV_OPERAND_TYPE_ID, {deref_id}}, })); } @@ -574,8 +601,8 @@ Instruction* DebugInfoManager::AddDebugValueForDecl( } uint32_t DebugInfoManager::GetVulkanDebugOperation(Instruction* inst) { - assert(inst->GetVulkan100DebugOpcode() == - NonSemanticVulkanDebugInfo100DebugOperation && + assert(inst->GetShader100DebugOpcode() == + NonSemanticShaderDebugInfo100DebugOperation && "inst must be Vulkan DebugOperation"); return context() ->get_constant_mgr() @@ -606,7 +633,7 @@ uint32_t DebugInfoManager::GetVariableIdOfDebugValueUsedForDeclare( } } else { uint32_t operation_const = GetVulkanDebugOperation(operation); - if (operation_const != NonSemanticVulkanDebugInfo100Deref) { + if (operation_const != NonSemanticShaderDebugInfo100Deref) { return 0; } } @@ -682,8 +709,8 @@ void DebugInfoManager::AnalyzeDebugInst(Instruction* inst) { RegisterDbgInst(inst); if (inst->GetOpenCL100DebugOpcode() == OpenCLDebugInfo100DebugFunction || - inst->GetVulkan100DebugOpcode() == - NonSemanticVulkanDebugInfo100DebugFunctionDefinition) { + inst->GetShader100DebugOpcode() == + NonSemanticShaderDebugInfo100DebugFunctionDefinition) { RegisterDbgFunction(inst); } @@ -695,10 +722,10 @@ void DebugInfoManager::AnalyzeDebugInst(Instruction* inst) { } if (deref_operation_ == nullptr && - inst->GetVulkan100DebugOpcode() == - NonSemanticVulkanDebugInfo100DebugOperation) { + inst->GetShader100DebugOpcode() == + NonSemanticShaderDebugInfo100DebugOperation) { uint32_t operation_const = GetVulkanDebugOperation(inst); - if (operation_const == NonSemanticVulkanDebugInfo100Deref) { + if (operation_const == NonSemanticShaderDebugInfo100Deref) { deref_operation_ = inst; } } @@ -759,8 +786,11 @@ void DebugInfoManager::ConvertDebugGlobalToLocalVariable( {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {GetEmptyDebugExpression()->result_id()}}, })); - auto* added_dbg_decl = - local_var->NextNode()->InsertBefore(std::move(new_dbg_decl)); + // Must insert after all OpVariables in block + Instruction* insert_before = local_var; + while (insert_before->opcode() == SpvOpVariable) + insert_before = insert_before->NextNode(); + auto* added_dbg_decl = insert_before->InsertBefore(std::move(new_dbg_decl)); if (context()->AreAnalysesValid(IRContext::Analysis::kAnalysisDefUse)) context()->get_def_use_mgr()->AnalyzeInstDefUse(added_dbg_decl); if (context()->AreAnalysesValid( @@ -818,8 +848,8 @@ void DebugInfoManager::ClearDebugInfo(Instruction* instr) { instr->GetSingleWordOperand(kDebugFunctionOperandFunctionIndex); fn_id_to_dbg_fn_.erase(fn_id); } - if (instr->GetVulkan100DebugOpcode() == - NonSemanticVulkanDebugInfo100DebugFunction) { + if (instr->GetShader100DebugOpcode() == + NonSemanticShaderDebugInfo100DebugFunction) { auto fn_id = instr->GetSingleWordOperand( kDebugFunctionDefinitionOperandOpFunctionIndex); fn_id_to_dbg_fn_.erase(fn_id); @@ -851,11 +881,10 @@ void DebugInfoManager::ClearDebugInfo(Instruction* instr) { deref_operation_ = &*dbg_instr_itr; break; } else if (instr != &*dbg_instr_itr && - dbg_instr_itr->GetVulkan100DebugOpcode() == - NonSemanticVulkanDebugInfo100DebugOperation) { + dbg_instr_itr->GetShader100DebugOpcode() == + NonSemanticShaderDebugInfo100DebugOperation) { uint32_t operation_const = GetVulkanDebugOperation(&*dbg_instr_itr); - - if (operation_const == NonSemanticVulkanDebugInfo100Deref) { + if (operation_const == NonSemanticShaderDebugInfo100Deref) { deref_operation_ = &*dbg_instr_itr; break; } diff --git a/source/opt/debug_info_manager.h b/source/opt/debug_info_manager.h index 679ae138..df34b30f 100644 --- a/source/opt/debug_info_manager.h +++ b/source/opt/debug_info_manager.h @@ -68,7 +68,7 @@ class DebugInlinedAtContext { }; // A class for analyzing, managing, and creating OpenCL.DebugInfo.100 and -// NonSemantic.Vulkan.DebugInfo.100 extension instructions. +// NonSemantic.Shader.DebugInfo.100 extension instructions. class DebugInfoManager { public: // Constructs a debug information manager from the given |context|. diff --git a/source/opt/decoration_manager.cpp b/source/opt/decoration_manager.cpp index 4bf026ef..2146c359 100644 --- a/source/opt/decoration_manager.cpp +++ b/source/opt/decoration_manager.cpp @@ -490,6 +490,14 @@ void DecorationManager::ForEachDecoration( }); } +bool DecorationManager::HasDecoration(uint32_t id, uint32_t decoration) { + bool has_decoration = false; + ForEachDecoration(id, decoration, [&has_decoration](const Instruction&) { + has_decoration = true; + }); + return has_decoration; +} + bool DecorationManager::FindDecoration( uint32_t id, uint32_t decoration, std::function<bool(const Instruction&)> f) { diff --git a/source/opt/decoration_manager.h b/source/opt/decoration_manager.h index b753e6be..fe78f2ce 100644 --- a/source/opt/decoration_manager.h +++ b/source/opt/decoration_manager.h @@ -90,6 +90,10 @@ class DecorationManager { bool AreDecorationsTheSame(const Instruction* inst1, const Instruction* inst2, bool ignore_target) const; + // Returns whether a decoration instruction for |id| with decoration + // |decoration| exists or not. + bool HasDecoration(uint32_t id, uint32_t decoration); + // |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. diff --git a/source/opt/def_use_manager.cpp b/source/opt/def_use_manager.cpp index 0ec98cae..394b9fa1 100644 --- a/source/opt/def_use_manager.cpp +++ b/source/opt/def_use_manager.cpp @@ -58,7 +58,7 @@ void DefUseManager::AnalyzeInstUse(Instruction* inst) { case SPV_OPERAND_TYPE_SCOPE_ID: { uint32_t use_id = inst->GetSingleWordOperand(i); Instruction* def = GetDef(use_id); - assert(def && "Definition is not registered."); + if (!def) assert(false && "Definition is not registered."); id_to_users_.insert(UserEntry(def, inst)); used_ids->push_back(use_id); } break; @@ -71,6 +71,9 @@ void DefUseManager::AnalyzeInstUse(Instruction* inst) { void DefUseManager::AnalyzeInstDefUse(Instruction* inst) { AnalyzeInstDef(inst); AnalyzeInstUse(inst); + // Analyze lines last otherwise they will be cleared when inst is + // cleared by preceding two calls + for (auto& l_inst : inst->dbg_line_insts()) AnalyzeInstDefUse(&l_inst); } void DefUseManager::UpdateDefUse(Instruction* inst) { @@ -224,9 +227,11 @@ void DefUseManager::AnalyzeDefUse(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)); + std::bind(&DefUseManager::AnalyzeInstDef, this, std::placeholders::_1), + true); module->ForEachInst( - std::bind(&DefUseManager::AnalyzeInstUse, this, std::placeholders::_1)); + std::bind(&DefUseManager::AnalyzeInstUse, this, std::placeholders::_1), + true); } void DefUseManager::ClearInst(Instruction* inst) { @@ -261,6 +266,16 @@ void DefUseManager::EraseUseRecordsOfOperandIds(const Instruction* inst) { bool operator==(const DefUseManager& lhs, const DefUseManager& rhs) { if (lhs.id_to_def_ != rhs.id_to_def_) { + for (auto p : lhs.id_to_def_) { + if (rhs.id_to_def_.find(p.first) == rhs.id_to_def_.end()) { + return false; + } + } + for (auto p : rhs.id_to_def_) { + if (lhs.id_to_def_.find(p.first) == lhs.id_to_def_.end()) { + return false; + } + } return false; } diff --git a/source/opt/desc_sroa.cpp b/source/opt/desc_sroa.cpp index 5e950069..bcbdde94 100644 --- a/source/opt/desc_sroa.cpp +++ b/source/opt/desc_sroa.cpp @@ -14,10 +14,19 @@ #include "source/opt/desc_sroa.h" +#include "source/opt/desc_sroa_util.h" #include "source/util/string_utils.h" namespace spvtools { namespace opt { +namespace { + +bool IsDecorationBinding(Instruction* inst) { + if (inst->opcode() != SpvOpDecorate) return false; + return inst->GetSingleWordInOperand(1u) == SpvDecorationBinding; +} + +} // namespace Pass::Status DescriptorScalarReplacement::Process() { bool modified = false; @@ -25,7 +34,7 @@ Pass::Status DescriptorScalarReplacement::Process() { std::vector<Instruction*> vars_to_kill; for (Instruction& var : context()->types_values()) { - if (IsCandidate(&var)) { + if (descsroautil::IsDescriptorArray(context(), &var)) { modified = true; if (!ReplaceCandidate(&var)) { return Status::Failure; @@ -41,72 +50,6 @@ Pass::Status DescriptorScalarReplacement::Process() { return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange); } -bool DescriptorScalarReplacement::IsCandidate(Instruction* var) { - if (var->opcode() != SpvOpVariable) { - return false; - } - - uint32_t ptr_type_id = var->type_id(); - Instruction* ptr_type_inst = - context()->get_def_use_mgr()->GetDef(ptr_type_id); - if (ptr_type_inst->opcode() != SpvOpTypePointer) { - return false; - } - - uint32_t var_type_id = ptr_type_inst->GetSingleWordInOperand(1); - Instruction* var_type_inst = - context()->get_def_use_mgr()->GetDef(var_type_id); - if (var_type_inst->opcode() != SpvOpTypeArray && - var_type_inst->opcode() != SpvOpTypeStruct) { - return false; - } - - // All structures with descriptor assignments must be replaced by variables, - // one for each of their members - with the exceptions of buffers. - if (IsTypeOfStructuredBuffer(var_type_inst)) { - return false; - } - - bool has_desc_set_decoration = false; - context()->get_decoration_mgr()->ForEachDecoration( - var->result_id(), SpvDecorationDescriptorSet, - [&has_desc_set_decoration](const Instruction&) { - has_desc_set_decoration = true; - }); - if (!has_desc_set_decoration) { - return false; - } - - bool has_binding_decoration = false; - context()->get_decoration_mgr()->ForEachDecoration( - var->result_id(), SpvDecorationBinding, - [&has_binding_decoration](const Instruction&) { - has_binding_decoration = true; - }); - if (!has_binding_decoration) { - return false; - } - - return true; -} - -bool DescriptorScalarReplacement::IsTypeOfStructuredBuffer( - const Instruction* type) const { - if (type->opcode() != SpvOpTypeStruct) { - return false; - } - - // All buffers have offset decorations for members of their structure types. - // This is how we distinguish it from a structure of descriptors. - bool has_offset_decoration = false; - context()->get_decoration_mgr()->ForEachDecoration( - type->result_id(), SpvDecorationOffset, - [&has_offset_decoration](const Instruction&) { - has_offset_decoration = true; - }); - return has_offset_decoration; -} - bool DescriptorScalarReplacement::ReplaceCandidate(Instruction* var) { std::vector<Instruction*> access_chain_work_list; std::vector<Instruction*> load_work_list; @@ -162,16 +105,15 @@ bool DescriptorScalarReplacement::ReplaceAccessChain(Instruction* var, return false; } - uint32_t idx_id = use->GetSingleWordInOperand(1); - const analysis::Constant* idx_const = - context()->get_constant_mgr()->FindDeclaredConstant(idx_id); - if (idx_const == nullptr) { + const analysis::Constant* const_index = + descsroautil::GetAccessChainIndexAsConst(context(), use); + if (const_index == nullptr) { context()->EmitErrorMessage("Variable cannot be replaced: invalid index", use); return false; } - uint32_t idx = idx_const->GetU32(); + uint32_t idx = const_index->GetU32(); uint32_t replacement_var = GetReplacementVariable(var, idx); if (use->NumInOperands() == 2) { @@ -208,39 +150,12 @@ uint32_t DescriptorScalarReplacement::GetReplacementVariable(Instruction* var, uint32_t idx) { auto replacement_vars = replacement_variables_.find(var); if (replacement_vars == replacement_variables_.end()) { - uint32_t ptr_type_id = var->type_id(); - Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(ptr_type_id); - assert(ptr_type_inst->opcode() == SpvOpTypePointer && - "Variable should be a pointer to an array or structure."); - uint32_t pointee_type_id = ptr_type_inst->GetSingleWordInOperand(1); - Instruction* pointee_type_inst = get_def_use_mgr()->GetDef(pointee_type_id); - const bool is_array = pointee_type_inst->opcode() == SpvOpTypeArray; - const bool is_struct = pointee_type_inst->opcode() == SpvOpTypeStruct; - assert((is_array || is_struct) && - "Variable should be a pointer to an array or structure."); - - // For arrays, each array element should be replaced with a new replacement - // variable - if (is_array) { - uint32_t array_len_id = pointee_type_inst->GetSingleWordInOperand(1); - const analysis::Constant* array_len_const = - context()->get_constant_mgr()->FindDeclaredConstant(array_len_id); - assert(array_len_const != nullptr && "Array length must be a constant."); - uint32_t array_len = array_len_const->GetU32(); - - replacement_vars = replacement_variables_ - .insert({var, std::vector<uint32_t>(array_len, 0)}) - .first; - } - // For structures, each member should be replaced with a new replacement - // variable - if (is_struct) { - const uint32_t num_members = pointee_type_inst->NumInOperands(); - replacement_vars = - replacement_variables_ - .insert({var, std::vector<uint32_t>(num_members, 0)}) - .first; - } + uint32_t number_of_elements = + descsroautil::GetNumberOfElementsForArrayOrStruct(context(), var); + replacement_vars = + replacement_variables_ + .insert({var, std::vector<uint32_t>(number_of_elements, 0)}) + .first; } if (replacement_vars->second[idx] == 0) { @@ -250,6 +165,74 @@ uint32_t DescriptorScalarReplacement::GetReplacementVariable(Instruction* var, return replacement_vars->second[idx]; } +void DescriptorScalarReplacement::CopyDecorationsForNewVariable( + Instruction* old_var, uint32_t index, uint32_t new_var_id, + uint32_t new_var_ptr_type_id, const bool is_old_var_array, + const bool is_old_var_struct, Instruction* old_var_type) { + // Handle OpDecorate instructions. + for (auto old_decoration : + get_decoration_mgr()->GetDecorationsFor(old_var->result_id(), true)) { + uint32_t new_binding = 0; + if (IsDecorationBinding(old_decoration)) { + new_binding = GetNewBindingForElement( + old_decoration->GetSingleWordInOperand(2), index, new_var_ptr_type_id, + is_old_var_array, is_old_var_struct, old_var_type); + } + CreateNewDecorationForNewVariable(old_decoration, new_var_id, new_binding); + } + + // Handle OpMemberDecorate instructions. + for (auto old_decoration : get_decoration_mgr()->GetDecorationsFor( + old_var_type->result_id(), true)) { + assert(old_decoration->opcode() == SpvOpMemberDecorate); + if (old_decoration->GetSingleWordInOperand(1u) != index) continue; + CreateNewDecorationForMemberDecorate(old_decoration, new_var_id); + } +} + +uint32_t DescriptorScalarReplacement::GetNewBindingForElement( + uint32_t old_binding, uint32_t index, uint32_t new_var_ptr_type_id, + const bool is_old_var_array, const bool is_old_var_struct, + Instruction* old_var_type) { + if (is_old_var_array) { + return old_binding + index * GetNumBindingsUsedByType(new_var_ptr_type_id); + } + if (is_old_var_struct) { + // The binding offset that should be added is the sum of binding + // numbers used by previous members of the current struct. + uint32_t new_binding = old_binding; + for (uint32_t i = 0; i < index; ++i) { + new_binding += + GetNumBindingsUsedByType(old_var_type->GetSingleWordInOperand(i)); + } + return new_binding; + } + return old_binding; +} + +void DescriptorScalarReplacement::CreateNewDecorationForNewVariable( + Instruction* old_decoration, uint32_t new_var_id, uint32_t new_binding) { + assert(old_decoration->opcode() == SpvOpDecorate); + std::unique_ptr<Instruction> new_decoration(old_decoration->Clone(context())); + new_decoration->SetInOperand(0, {new_var_id}); + + if (IsDecorationBinding(new_decoration.get())) { + new_decoration->SetInOperand(2, {new_binding}); + } + context()->AddAnnotationInst(std::move(new_decoration)); +} + +void DescriptorScalarReplacement::CreateNewDecorationForMemberDecorate( + Instruction* old_member_decoration, uint32_t new_var_id) { + std::vector<Operand> operands( + {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {new_var_id}}}); + auto new_decorate_operand_begin = old_member_decoration->begin() + 2u; + auto new_decorate_operand_end = old_member_decoration->end(); + operands.insert(operands.end(), new_decorate_operand_begin, + new_decorate_operand_end); + get_decoration_mgr()->AddDecoration(SpvOpDecorate, std::move(operands)); +} + uint32_t DescriptorScalarReplacement::CreateReplacementVariable( Instruction* var, uint32_t idx) { // The storage class for the new variable is the same as the original. @@ -285,33 +268,8 @@ uint32_t DescriptorScalarReplacement::CreateReplacementVariable( {static_cast<uint32_t>(storage_class)}}})); context()->AddGlobalValue(std::move(variable)); - // Copy all of the decorations to the new variable. The only difference is - // the Binding decoration needs to be adjusted. - for (auto old_decoration : - get_decoration_mgr()->GetDecorationsFor(var->result_id(), true)) { - assert(old_decoration->opcode() == SpvOpDecorate); - std::unique_ptr<Instruction> new_decoration( - old_decoration->Clone(context())); - new_decoration->SetInOperand(0, {id}); - - uint32_t decoration = new_decoration->GetSingleWordInOperand(1u); - if (decoration == SpvDecorationBinding) { - uint32_t new_binding = new_decoration->GetSingleWordInOperand(2); - if (is_array) { - new_binding += idx * GetNumBindingsUsedByType(ptr_element_type_id); - } - if (is_struct) { - // The binding offset that should be added is the sum of binding numbers - // used by previous members of the current struct. - for (uint32_t i = 0; i < idx; ++i) { - new_binding += GetNumBindingsUsedByType( - pointee_type_inst->GetSingleWordInOperand(i)); - } - } - new_decoration->SetInOperand(2, {new_binding}); - } - context()->AddAnnotationInst(std::move(new_decoration)); - } + CopyDecorationsForNewVariable(var, idx, id, ptr_element_type_id, is_array, + is_struct, pointee_type_inst); // Create a new OpName for the replacement variable. std::vector<std::unique_ptr<Instruction>> names_to_add; @@ -377,7 +335,7 @@ uint32_t DescriptorScalarReplacement::GetNumBindingsUsedByType( // The number of bindings consumed by a structure is the sum of the bindings // used by its members. if (type_inst->opcode() == SpvOpTypeStruct && - !IsTypeOfStructuredBuffer(type_inst)) { + !descsroautil::IsTypeOfStructuredBuffer(context(), type_inst)) { uint32_t sum = 0; for (uint32_t i = 0; i < type_inst->NumInOperands(); i++) sum += GetNumBindingsUsedByType(type_inst->GetSingleWordInOperand(i)); diff --git a/source/opt/desc_sroa.h b/source/opt/desc_sroa.h index cd72fd30..fea06255 100644 --- a/source/opt/desc_sroa.h +++ b/source/opt/desc_sroa.h @@ -46,10 +46,6 @@ class DescriptorScalarReplacement : public Pass { } private: - // Returns true if |var| is an OpVariable instruction that represents a - // descriptor array. These are the variables that we want to replace. - bool IsCandidate(Instruction* var); - // Replaces all references to |var| by new variables, one for each element of // the array |var|. The binding for the new variables corresponding to // element i will be the binding of |var| plus i. Returns true if successful. @@ -93,10 +89,45 @@ class DescriptorScalarReplacement : public Pass { // bindings used by its members. uint32_t GetNumBindingsUsedByType(uint32_t type_id); - // Returns true if |type| is a type that could be used for a structured buffer - // as opposed to a type that would be used for a structure of resource - // descriptors. - bool IsTypeOfStructuredBuffer(const Instruction* type) const; + // Copy all of the decorations of variable |old_var| and make them as + // decorations for the new variable whose id is |new_var_id|. The new variable + // is supposed to replace |index|th element of |old_var|. + // |new_var_ptr_type_id| is the id of the pointer to the type of the new + // variable. |is_old_var_array| is true if |old_var| has an array type. + // |is_old_var_struct| is true if |old_var| has a structure type. + // |old_var_type| is the pointee type of |old_var|. + void CopyDecorationsForNewVariable(Instruction* old_var, uint32_t index, + uint32_t new_var_id, + uint32_t new_var_ptr_type_id, + const bool is_old_var_array, + const bool is_old_var_struct, + Instruction* old_var_type); + + // Get the new binding number for a new variable that will be replaced with an + // |index|th element of an old variable. The old variable has |old_binding| + // as its binding number. |ptr_elem_type_id| the id of the pointer to the + // element type. |is_old_var_array| is true if the old variable has an array + // type. |is_old_var_struct| is true if the old variable has a structure type. + // |old_var_type| is the pointee type of the old variable. + uint32_t GetNewBindingForElement(uint32_t old_binding, uint32_t index, + uint32_t ptr_elem_type_id, + const bool is_old_var_array, + const bool is_old_var_struct, + Instruction* old_var_type); + + // Create a new OpDecorate instruction by cloning |old_decoration|. The new + // OpDecorate instruction will be used for a variable whose id is + // |new_var_ptr_type_id|. If |old_decoration| is a decoration for a binding, + // the new OpDecorate instruction will have |new_binding| as its binding. + void CreateNewDecorationForNewVariable(Instruction* old_decoration, + uint32_t new_var_id, + uint32_t new_binding); + + // Create a new OpDecorate instruction whose operand is the same as an + // OpMemberDecorate instruction |old_member_decoration| except Target operand. + // The Target operand of the new OpDecorate instruction will be |new_var_id|. + void CreateNewDecorationForMemberDecorate(Instruction* old_decoration, + uint32_t new_var_id); // A map from an OpVariable instruction to the set of variables that will be // used to replace it. The entry |replacement_variables_[var][i]| is the id of diff --git a/source/opt/desc_sroa_util.cpp b/source/opt/desc_sroa_util.cpp new file mode 100644 index 00000000..1954e2cc --- /dev/null +++ b/source/opt/desc_sroa_util.cpp @@ -0,0 +1,117 @@ +// Copyright (c) 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/opt/desc_sroa_util.h" + +namespace spvtools { +namespace opt { +namespace { + +const uint32_t kOpAccessChainInOperandIndexes = 1; + +// Returns the length of array type |type|. +uint32_t GetLengthOfArrayType(IRContext* context, Instruction* type) { + assert(type->opcode() == SpvOpTypeArray && "type must be array"); + uint32_t length_id = type->GetSingleWordInOperand(1); + const analysis::Constant* length_const = + context->get_constant_mgr()->FindDeclaredConstant(length_id); + assert(length_const != nullptr); + return length_const->GetU32(); +} + +} // namespace + +namespace descsroautil { + +bool IsDescriptorArray(IRContext* context, Instruction* var) { + if (var->opcode() != SpvOpVariable) { + return false; + } + + uint32_t ptr_type_id = var->type_id(); + Instruction* ptr_type_inst = context->get_def_use_mgr()->GetDef(ptr_type_id); + if (ptr_type_inst->opcode() != SpvOpTypePointer) { + return false; + } + + uint32_t var_type_id = ptr_type_inst->GetSingleWordInOperand(1); + Instruction* var_type_inst = context->get_def_use_mgr()->GetDef(var_type_id); + if (var_type_inst->opcode() != SpvOpTypeArray && + var_type_inst->opcode() != SpvOpTypeStruct) { + return false; + } + + // All structures with descriptor assignments must be replaced by variables, + // one for each of their members - with the exceptions of buffers. + if (IsTypeOfStructuredBuffer(context, var_type_inst)) { + return false; + } + + if (!context->get_decoration_mgr()->HasDecoration( + var->result_id(), SpvDecorationDescriptorSet)) { + return false; + } + + return context->get_decoration_mgr()->HasDecoration(var->result_id(), + SpvDecorationBinding); +} + +bool IsTypeOfStructuredBuffer(IRContext* context, const Instruction* type) { + if (type->opcode() != SpvOpTypeStruct) { + return false; + } + + // All buffers have offset decorations for members of their structure types. + // This is how we distinguish it from a structure of descriptors. + return context->get_decoration_mgr()->HasDecoration(type->result_id(), + SpvDecorationOffset); +} + +const analysis::Constant* GetAccessChainIndexAsConst( + IRContext* context, Instruction* access_chain) { + if (access_chain->NumInOperands() <= 1) { + return nullptr; + } + uint32_t idx_id = GetFirstIndexOfAccessChain(access_chain); + const analysis::Constant* idx_const = + context->get_constant_mgr()->FindDeclaredConstant(idx_id); + return idx_const; +} + +uint32_t GetFirstIndexOfAccessChain(Instruction* access_chain) { + assert(access_chain->NumInOperands() > 1 && + "OpAccessChain does not have Indexes operand"); + return access_chain->GetSingleWordInOperand(kOpAccessChainInOperandIndexes); +} + +uint32_t GetNumberOfElementsForArrayOrStruct(IRContext* context, + Instruction* var) { + uint32_t ptr_type_id = var->type_id(); + Instruction* ptr_type_inst = context->get_def_use_mgr()->GetDef(ptr_type_id); + assert(ptr_type_inst->opcode() == SpvOpTypePointer && + "Variable should be a pointer to an array or structure."); + uint32_t pointee_type_id = ptr_type_inst->GetSingleWordInOperand(1); + Instruction* pointee_type_inst = + context->get_def_use_mgr()->GetDef(pointee_type_id); + if (pointee_type_inst->opcode() == SpvOpTypeArray) { + return GetLengthOfArrayType(context, pointee_type_inst); + } + assert(pointee_type_inst->opcode() == SpvOpTypeStruct && + "Variable should be a pointer to an array or structure."); + return pointee_type_inst->NumInOperands(); +} + +} // namespace descsroautil +} // namespace opt +} // namespace spvtools diff --git a/source/opt/desc_sroa_util.h b/source/opt/desc_sroa_util.h new file mode 100644 index 00000000..2f45c0c2 --- /dev/null +++ b/source/opt/desc_sroa_util.h @@ -0,0 +1,54 @@ +// Copyright (c) 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef SOURCE_OPT_DESC_SROA_UTIL_H_ +#define SOURCE_OPT_DESC_SROA_UTIL_H_ + +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace opt { + +// Provides functions for the descriptor array SROA. +namespace descsroautil { + +// Returns true if |var| is an OpVariable instruction that represents a +// descriptor array. +bool IsDescriptorArray(IRContext* context, Instruction* var); + +// Returns true if |type| is a type that could be used for a structured buffer +// as opposed to a type that would be used for a structure of resource +// descriptors. +bool IsTypeOfStructuredBuffer(IRContext* context, const Instruction* type); + +// Returns the first index of the OpAccessChain instruction |access_chain| as +// a constant. Returns nullptr if it is not a constant. +const analysis::Constant* GetAccessChainIndexAsConst(IRContext* context, + Instruction* access_chain); + +// Returns the number of elements of an OpVariable instruction |var| whose type +// must be a pointer to an array or a struct. +uint32_t GetNumberOfElementsForArrayOrStruct(IRContext* context, + Instruction* var); + +// Returns the first Indexes operand id of the OpAccessChain or +// OpInBoundsAccessChain instruction |access_chain|. The access chain must have +// at least 1 index. +uint32_t GetFirstIndexOfAccessChain(Instruction* access_chain); + +} // namespace descsroautil +} // namespace opt +} // namespace spvtools + +#endif // SOURCE_OPT_DESC_SROA_UTIL_H_ diff --git a/source/opt/eliminate_dead_members_pass.cpp b/source/opt/eliminate_dead_members_pass.cpp index 173df620..a24ba8f4 100644 --- a/source/opt/eliminate_dead_members_pass.cpp +++ b/source/opt/eliminate_dead_members_pass.cpp @@ -20,6 +20,7 @@ namespace { const uint32_t kRemovedMember = 0xFFFFFFFF; const uint32_t kSpecConstOpOpcodeIdx = 0; +constexpr uint32_t kArrayElementTypeIdx = 0; } // namespace namespace spvtools { @@ -64,6 +65,10 @@ void EliminateDeadMembersPass::FindLiveMembers() { MarkPointeeTypeAsFullUsed(inst.type_id()); break; default: + // Ignore structured buffers as layout(offset) qualifiers cannot be + // applied to structure fields + if (inst.IsVulkanStorageBufferVariable()) + MarkPointeeTypeAsFullUsed(inst.type_id()); break; } } @@ -136,18 +141,22 @@ void EliminateDeadMembersPass::MarkMembersAsLiveForStore( void EliminateDeadMembersPass::MarkTypeAsFullyUsed(uint32_t type_id) { Instruction* type_inst = get_def_use_mgr()->GetDef(type_id); assert(type_inst != nullptr); - if (type_inst->opcode() != SpvOpTypeStruct) { - return; - } - - // Mark every member of the current struct as used. - for (uint32_t i = 0; i < type_inst->NumInOperands(); ++i) { - used_members_[type_id].insert(i); - } - // Mark any sub struct as fully used. - for (uint32_t i = 0; i < type_inst->NumInOperands(); ++i) { - MarkTypeAsFullyUsed(type_inst->GetSingleWordInOperand(i)); + switch (type_inst->opcode()) { + case SpvOpTypeStruct: + // Mark every member and its type as fully used. + for (uint32_t i = 0; i < type_inst->NumInOperands(); ++i) { + used_members_[type_id].insert(i); + MarkTypeAsFullyUsed(type_inst->GetSingleWordInOperand(i)); + } + break; + case SpvOpTypeArray: + case SpvOpTypeRuntimeArray: + MarkTypeAsFullyUsed( + type_inst->GetSingleWordInOperand(kArrayElementTypeIdx)); + break; + default: + break; } } diff --git a/source/opt/feature_manager.cpp b/source/opt/feature_manager.cpp index db3abddf..39a4a348 100644 --- a/source/opt/feature_manager.cpp +++ b/source/opt/feature_manager.cpp @@ -80,8 +80,8 @@ void FeatureManager::AddExtInstImportIds(Module* module) { extinst_importid_GLSLstd450_ = module->GetExtInstImportId("GLSL.std.450"); extinst_importid_OpenCL100DebugInfo_ = module->GetExtInstImportId("OpenCL.DebugInfo.100"); - extinst_importid_Vulkan100DebugInfo_ = - module->GetExtInstImportId("NonSemantic.Vulkan.DebugInfo.100"); + extinst_importid_Shader100DebugInfo_ = + module->GetExtInstImportId("NonSemantic.Shader.DebugInfo.100"); } bool operator==(const FeatureManager& a, const FeatureManager& b) { @@ -109,8 +109,8 @@ bool operator==(const FeatureManager& a, const FeatureManager& b) { return false; } - if (a.extinst_importid_Vulkan100DebugInfo_ != - b.extinst_importid_Vulkan100DebugInfo_) { + if (a.extinst_importid_Shader100DebugInfo_ != + b.extinst_importid_Shader100DebugInfo_) { return false; } diff --git a/source/opt/feature_manager.h b/source/opt/feature_manager.h index 4720c6dc..68c8e9a2 100644 --- a/source/opt/feature_manager.h +++ b/source/opt/feature_manager.h @@ -55,8 +55,8 @@ class FeatureManager { return extinst_importid_OpenCL100DebugInfo_; } - uint32_t GetExtInstImportId_Vulkan100DebugInfo() const { - return extinst_importid_Vulkan100DebugInfo_; + uint32_t GetExtInstImportId_Shader100DebugInfo() const { + return extinst_importid_Shader100DebugInfo_; } friend bool operator==(const FeatureManager& a, const FeatureManager& b); @@ -97,9 +97,9 @@ class FeatureManager { // for performance. uint32_t extinst_importid_OpenCL100DebugInfo_ = 0; - // Common NonSemanticVulkan100DebugInfo external instruction import ids, + // Common NonSemanticShader100DebugInfo external instruction import ids, // cached for performance. - uint32_t extinst_importid_Vulkan100DebugInfo_ = 0; + uint32_t extinst_importid_Shader100DebugInfo_ = 0; }; } // namespace opt diff --git a/source/opt/folding_rules.cpp b/source/opt/folding_rules.cpp index 6ae078fb..4904f186 100644 --- a/source/opt/folding_rules.cpp +++ b/source/opt/folding_rules.cpp @@ -14,6 +14,7 @@ #include "source/opt/folding_rules.h" +#include <climits> #include <limits> #include <memory> #include <utility> @@ -523,7 +524,8 @@ uint32_t PerformFloatingPointOperation(analysis::ConstantManager* const_mgr, float fval = val.getAsFloat(); \ if (!IsValidResult(fval)) return 0; \ words = val.GetWords(); \ - } static_assert(true, "require extra semicolon") + } \ + static_assert(true, "require extra semicolon") switch (opcode) { case SpvOpFMul: FOLD_OP(*); @@ -558,24 +560,19 @@ uint32_t PerformIntegerOperation(analysis::ConstantManager* const_mgr, uint32_t width = type->AsInteger()->width(); assert(width == 32 || width == 64); std::vector<uint32_t> words; -#define FOLD_OP(op) \ - if (width == 64) { \ - if (type->IsSigned()) { \ - int64_t val = input1->GetS64() op input2->GetS64(); \ - words = ExtractInts(static_cast<uint64_t>(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<uint32_t>(val)); \ - } else { \ - uint32_t val = input1->GetU32() op input2->GetU32(); \ - words.push_back(val); \ - } \ - } static_assert(true, "require extra semicalon") + // Regardless of the sign of the constant, folding is performed on an unsigned + // interpretation of the constant data. This avoids signed integer overflow + // while folding, and works because sign is irrelevant for the IAdd, ISub and + // IMul instructions. +#define FOLD_OP(op) \ + if (width == 64) { \ + uint64_t val = input1->GetU64() op input2->GetU64(); \ + words = ExtractInts(val); \ + } else { \ + uint32_t val = input1->GetU32() op input2->GetU32(); \ + words.push_back(val); \ + } \ + static_assert(true, "require extra semicolon") switch (opcode) { case SpvOpIMul: FOLD_OP(*); @@ -971,25 +968,17 @@ FoldingRule MergeDivMulArithmetic() { FoldingRule MergeDivNegateArithmetic() { return [](IRContext* context, Instruction* inst, const std::vector<const analysis::Constant*>& constants) { - assert(inst->opcode() == SpvOpFDiv || inst->opcode() == SpvOpSDiv); + assert(inst->opcode() == SpvOpFDiv); 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; + if (!inst->IsFloatingPointFoldingAllowed()) return false; const analysis::Constant* const_input1 = ConstInput(constants); if (!const_input1) return false; Instruction* other_inst = NonConstInput(context, constants[0], inst); - if (uses_float && !other_inst->IsFloatingPointFoldingAllowed()) - return false; + if (!other_inst->IsFloatingPointFoldingAllowed()) return false; bool first_is_variable = constants[0] == nullptr; - if (other_inst->opcode() == SpvOpFNegate || - other_inst->opcode() == SpvOpSNegate) { + if (other_inst->opcode() == SpvOpFNegate) { uint32_t neg_id = NegateConstant(const_mgr, const_input1); if (first_is_variable) { @@ -1467,90 +1456,121 @@ FoldingRule IntMultipleBy1() { }; } -FoldingRule CompositeConstructFeedingExtract() { - return [](IRContext* context, Instruction* inst, - const std::vector<const analysis::Constant*>&) { - // 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 = context->get_def_use_mgr(); - analysis::TypeManager* type_mgr = context->get_type_mgr(); - - // If there are no index operands, then this rule cannot do anything. - if (inst->NumInOperands() <= 1) { - return false; - } +// Returns the number of elements that the |index|th in operand in |inst| +// contributes to the result of |inst|. |inst| must be an +// OpCompositeConstructInstruction. +uint32_t GetNumOfElementsContributedByOperand(IRContext* context, + const Instruction* inst, + uint32_t index) { + assert(inst->opcode() == SpvOpCompositeConstruct); + analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); + analysis::TypeManager* type_mgr = context->get_type_mgr(); + + analysis::Vector* result_type = + type_mgr->GetType(inst->type_id())->AsVector(); + if (result_type == nullptr) { + // If the result of the OpCompositeConstruct is not a vector then every + // operands corresponds to a single element in the result. + return 1; + } - uint32_t cid = inst->GetSingleWordInOperand(kExtractCompositeIdInIdx); - Instruction* cinst = def_use_mgr->GetDef(cid); + // If the result type is a vector then the operands are either scalars or + // vectors. If it is a scalar, then it corresponds to a single element. If it + // is a vector, then each element in the vector will be an element in the + // result. + uint32_t id = inst->GetSingleWordInOperand(index); + Instruction* def = def_use_mgr->GetDef(id); + analysis::Vector* type = type_mgr->GetType(def->type_id())->AsVector(); + if (type == nullptr) { + return 1; + } + return type->element_count(); +} - if (cinst->opcode() != SpvOpCompositeConstruct) { - return false; - } +// Returns the in-operands for an OpCompositeExtract instruction that are needed +// to extract the |result_index|th element in the result of |inst| without using +// the result of |inst|. Returns the empty vector if |result_index| is +// out-of-bounds. |inst| must be an |OpCompositeConstruct| instruction. +std::vector<Operand> GetExtractOperandsForElementOfCompositeConstruct( + IRContext* context, const Instruction* inst, uint32_t result_index) { + assert(inst->opcode() == SpvOpCompositeConstruct); + analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr(); + analysis::TypeManager* type_mgr = context->get_type_mgr(); - std::vector<Operand> 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)}}); - } + analysis::Type* result_type = type_mgr->GetType(inst->type_id()); + if (result_type->AsVector() == nullptr) { + uint32_t id = inst->GetSingleWordInOperand(result_index); + return {Operand(SPV_OPERAND_TYPE_ID, {id})}; + } - } 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); - 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 the result type is a vector, then vector operands are concatenated. + uint32_t total_element_count = 0; + for (uint32_t idx = 0; idx < inst->NumInOperands(); ++idx) { + uint32_t element_count = + GetNumOfElementsContributedByOperand(context, inst, idx); + total_element_count += element_count; + if (result_index < total_element_count) { + std::vector<Operand> operands; + uint32_t id = inst->GetSingleWordInOperand(idx); + Instruction* operand_def = def_use_mgr->GetDef(id); + analysis::Type* operand_type = type_mgr->GetType(operand_def->type_id()); + + operands.push_back({SPV_OPERAND_TYPE_ID, {id}}); + if (operand_type->AsVector()) { + uint32_t start_index_of_id = total_element_count - element_count; + uint32_t index_into_id = result_index - start_index_of_id; + operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {index_into_id}}); } + return operands; } + } + return {}; +} + +bool CompositeConstructFeedingExtract( + IRContext* context, Instruction* inst, + const std::vector<const analysis::Constant*>&) { + // 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 = context->get_def_use_mgr(); + + // If there are no index operands, then this rule cannot do anything. + if (inst->NumInOperands() <= 1) { + return false; + } + + uint32_t cid = inst->GetSingleWordInOperand(kExtractCompositeIdInIdx); + Instruction* cinst = def_use_mgr->GetDef(cid); + if (cinst->opcode() != SpvOpCompositeConstruct) { + return false; + } + + uint32_t index_into_result = inst->GetSingleWordInOperand(1); + std::vector<Operand> operands = + GetExtractOperandsForElementOfCompositeConstruct(context, cinst, + index_into_result); + + if (operands.empty()) { + return false; + } + + // 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)}}); + } + + if (operands.size() == 1) { // 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); - } + // to extract any more. + inst->SetOpcode(SpvOpCopyObject); + } - inst->SetInOperands(std::move(operands)); - return true; - }; + inst->SetInOperands(std::move(operands)); + return true; } // If the OpCompositeConstruct is simply putting back together elements that @@ -2509,7 +2529,7 @@ void FoldingRules::AddFoldingRules() { rules_[SpvOpCompositeConstruct].push_back(CompositeExtractFeedingConstruct); rules_[SpvOpCompositeExtract].push_back(InsertFeedingExtract()); - rules_[SpvOpCompositeExtract].push_back(CompositeConstructFeedingExtract()); + rules_[SpvOpCompositeExtract].push_back(CompositeConstructFeedingExtract); rules_[SpvOpCompositeExtract].push_back(VectorShuffleFeedingExtract()); rules_[SpvOpCompositeExtract].push_back(FMixFeedingExtract()); @@ -2561,8 +2581,6 @@ void FoldingRules::AddFoldingRules() { 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()); diff --git a/source/opt/function.h b/source/opt/function.h index 9e1c7274..917bf584 100644 --- a/source/opt/function.h +++ b/source/opt/function.h @@ -177,6 +177,9 @@ class Function { // debuggers. void Dump() const; + // Returns true is a function declaration and not a function definition. + bool IsDeclaration() { return begin() == end(); } + private: // The OpFunction instruction that begins the definition of this function. std::unique_ptr<Instruction> def_inst_; diff --git a/source/opt/if_conversion.cpp b/source/opt/if_conversion.cpp index 4284069a..49206617 100644 --- a/source/opt/if_conversion.cpp +++ b/source/opt/if_conversion.cpp @@ -129,6 +129,7 @@ Pass::Status IfConversion::Process() { Instruction* select = builder.AddSelect(phi->type_id(), condition, true_value->result_id(), false_value->result_id()); + context()->get_def_use_mgr()->AnalyzeInstDefUse(select); select->UpdateDebugInfoFrom(phi); context()->ReplaceAllUsesWith(phi->result_id(), select->result_id()); to_kill.push_back(phi); diff --git a/source/opt/inline_exhaustive_pass.cpp b/source/opt/inline_exhaustive_pass.cpp index f24f744d..bef45017 100644 --- a/source/opt/inline_exhaustive_pass.cpp +++ b/source/opt/inline_exhaustive_pass.cpp @@ -65,7 +65,7 @@ Pass::Status InlineExhaustivePass::ProcessImpl() { status = CombineStatus(status, InlineExhaustive(fp)); return false; }; - context()->ProcessEntryPointCallTree(pfn); + context()->ProcessReachableCallTree(pfn); return status; } diff --git a/source/opt/inline_opaque_pass.cpp b/source/opt/inline_opaque_pass.cpp index 6ccaf908..fe9c6799 100644 --- a/source/opt/inline_opaque_pass.cpp +++ b/source/opt/inline_opaque_pass.cpp @@ -105,7 +105,7 @@ Pass::Status InlineOpaquePass::ProcessImpl() { status = CombineStatus(status, InlineOpaque(fp)); return false; }; - context()->ProcessEntryPointCallTree(pfn); + context()->ProcessReachableCallTree(pfn); return status; } diff --git a/source/opt/inline_pass.cpp b/source/opt/inline_pass.cpp index a6bdaaff..2cc31258 100644 --- a/source/opt/inline_pass.cpp +++ b/source/opt/inline_pass.cpp @@ -92,7 +92,7 @@ void InlinePass::AddStore(uint32_t ptr_id, uint32_t val_id, {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {ptr_id}}, {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {val_id}}})); if (line_inst != nullptr) { - newStore->dbg_line_insts().push_back(*line_inst); + newStore->AddDebugLine(line_inst); } newStore->SetDebugScope(dbg_scope); (*block_ptr)->AddInstruction(std::move(newStore)); @@ -106,7 +106,7 @@ void InlinePass::AddLoad(uint32_t type_id, uint32_t resultId, uint32_t ptr_id, new Instruction(context(), SpvOpLoad, type_id, resultId, {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {ptr_id}}})); if (line_inst != nullptr) { - newLoad->dbg_line_insts().push_back(*line_inst); + newLoad->AddDebugLine(line_inst); } newLoad->SetDebugScope(dbg_scope); (*block_ptr)->AddInstruction(std::move(newLoad)); @@ -405,8 +405,8 @@ bool InlinePass::InlineEntryBlock( while (callee_inst_itr != callee_first_block->end()) { // Don't inline function definition links, the calling function is not a // definition. - if (callee_inst_itr->GetVulkan100DebugOpcode() == - NonSemanticVulkanDebugInfo100DebugFunctionDefinition) { + if (callee_inst_itr->GetShader100DebugOpcode() == + NonSemanticShaderDebugInfo100DebugFunctionDefinition) { ++callee_inst_itr; continue; } @@ -441,6 +441,11 @@ std::unique_ptr<BasicBlock> InlinePass::InlineBasicBlocks( auto tail_inst_itr = callee_block_itr->end(); for (auto inst_itr = callee_block_itr->begin(); inst_itr != tail_inst_itr; ++inst_itr) { + // Don't inline function definition links, the calling function is not a + // definition + if (inst_itr->GetShader100DebugOpcode() == + NonSemanticShaderDebugInfo100DebugFunctionDefinition) + continue; if (!InlineSingleInstruction( callee2caller, new_blk_ptr.get(), &*inst_itr, context()->get_debug_info_mgr()->BuildDebugInlinedAtChain( diff --git a/source/opt/inst_buff_addr_check_pass.cpp b/source/opt/inst_buff_addr_check_pass.cpp index 06acc7ea..e2336d36 100644 --- a/source/opt/inst_buff_addr_check_pass.cpp +++ b/source/opt/inst_buff_addr_check_pass.cpp @@ -130,13 +130,48 @@ void InstBuffAddrCheckPass::GenCheckCode( context()->KillInst(ref_inst); } +uint32_t InstBuffAddrCheckPass::GetTypeAlignment(uint32_t type_id) { + Instruction* type_inst = get_def_use_mgr()->GetDef(type_id); + switch (type_inst->opcode()) { + case SpvOpTypeFloat: + case SpvOpTypeInt: + case SpvOpTypeVector: + return GetTypeLength(type_id); + case SpvOpTypeMatrix: + return GetTypeAlignment(type_inst->GetSingleWordInOperand(0)); + case SpvOpTypeArray: + case SpvOpTypeRuntimeArray: + return GetTypeAlignment(type_inst->GetSingleWordInOperand(0)); + case SpvOpTypeStruct: { + uint32_t max = 0; + type_inst->ForEachInId([&max, this](const uint32_t* iid) { + uint32_t alignment = GetTypeAlignment(*iid); + max = (alignment > max) ? alignment : max; + }); + return max; + } + case SpvOpTypePointer: + assert(type_inst->GetSingleWordInOperand(0) == + SpvStorageClassPhysicalStorageBufferEXT && + "unexpected pointer type"); + return 8u; + default: + assert(false && "unexpected type"); + return 0; + } +} + uint32_t InstBuffAddrCheckPass::GetTypeLength(uint32_t type_id) { Instruction* type_inst = get_def_use_mgr()->GetDef(type_id); switch (type_inst->opcode()) { case SpvOpTypeFloat: case SpvOpTypeInt: return type_inst->GetSingleWordInOperand(0) / 8u; - case SpvOpTypeVector: + case SpvOpTypeVector: { + uint32_t raw_cnt = type_inst->GetSingleWordInOperand(1); + uint32_t adj_cnt = (raw_cnt == 3u) ? 4u : raw_cnt; + return adj_cnt * GetTypeLength(type_inst->GetSingleWordInOperand(0)); + } case SpvOpTypeMatrix: return type_inst->GetSingleWordInOperand(1) * GetTypeLength(type_inst->GetSingleWordInOperand(0)); @@ -145,8 +180,29 @@ uint32_t InstBuffAddrCheckPass::GetTypeLength(uint32_t type_id) { SpvStorageClassPhysicalStorageBufferEXT && "unexpected pointer type"); return 8u; + case SpvOpTypeArray: { + uint32_t const_id = type_inst->GetSingleWordInOperand(1); + Instruction* const_inst = get_def_use_mgr()->GetDef(const_id); + uint32_t cnt = const_inst->GetSingleWordInOperand(0); + return cnt * GetTypeLength(type_inst->GetSingleWordInOperand(0)); + } + case SpvOpTypeStruct: { + uint32_t len = 0; + type_inst->ForEachInId([&len, this](const uint32_t* iid) { + // Align struct length + uint32_t alignment = GetTypeAlignment(*iid); + uint32_t mod = len % alignment; + uint32_t diff = (mod != 0) ? alignment - mod : 0; + len += diff; + // Increment struct length by component length + uint32_t comp_len = GetTypeLength(*iid); + len += comp_len; + }); + return len; + } + case SpvOpTypeRuntimeArray: default: - assert(false && "unexpected buffer reference type"); + assert(false && "unexpected type"); return 0; } } diff --git a/source/opt/inst_buff_addr_check_pass.h b/source/opt/inst_buff_addr_check_pass.h index ec7bb684..a8232239 100644 --- a/source/opt/inst_buff_addr_check_pass.h +++ b/source/opt/inst_buff_addr_check_pass.h @@ -28,7 +28,9 @@ namespace opt { // external design of this class may change as the layer evolves. class InstBuffAddrCheckPass : public InstrumentPass { public: - // Preferred interface + // For test harness only + InstBuffAddrCheckPass() : InstrumentPass(7, 23, kInstValidationIdBuffAddr) {} + // For all other interfaces InstBuffAddrCheckPass(uint32_t desc_set, uint32_t shader_id) : InstrumentPass(desc_set, shader_id, kInstValidationIdBuffAddr) {} @@ -40,8 +42,12 @@ class InstBuffAddrCheckPass : public InstrumentPass { const char* name() const override { return "inst-bindless-check-pass"; } private: - // Return byte length of type |type_id|. Must be int, float, vector, matrix - // or physical pointer. + // Return byte alignment of type |type_id|. Must be int, float, vector, + // matrix, struct, array or physical pointer. Uses std430 alignment. + uint32_t GetTypeAlignment(uint32_t type_id); + + // Return byte length of type |type_id|. Must be int, float, vector, matrix, + // struct, array or physical pointer. Uses std430 alignment and sizes. uint32_t GetTypeLength(uint32_t type_id); // Add |type_id| param to |input_func| and add id to |param_vec|. diff --git a/source/opt/instruction.cpp b/source/opt/instruction.cpp index 577c0a67..2461e41e 100644 --- a/source/opt/instruction.cpp +++ b/source/opt/instruction.cpp @@ -16,7 +16,6 @@ #include <initializer_list> -#include "NonSemanticVulkanDebugInfo100.h" #include "OpenCLDebugInfo100.h" #include "source/disassemble.h" #include "source/opt/fold.h" @@ -31,9 +30,10 @@ namespace { const uint32_t kTypeImageDimIndex = 1; const uint32_t kLoadBaseIndex = 0; const uint32_t kPointerTypeStorageClassIndex = 0; +const uint32_t kVariableStorageClassIndex = 0; const uint32_t kTypeImageSampledIndex = 5; -// Constants for OpenCL.DebugInfo.100 / NonSemantic.Vulkan.DebugInfo.100 +// Constants for OpenCL.DebugInfo.100 / NonSemantic.Shader.DebugInfo.100 // extension instructions. const uint32_t kExtInstSetIdInIdx = 0; const uint32_t kExtInstInstructionInIdx = 1; @@ -74,8 +74,6 @@ Instruction::Instruction(IRContext* c, const spv_parsed_instruction_t& inst, unique_id_(c->TakeNextUniqueId()), dbg_line_insts_(std::move(dbg_line)), dbg_scope_(kNoDebugScope, kNoInlinedAt) { - 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<uint32_t> words( @@ -83,6 +81,8 @@ Instruction::Instruction(IRContext* c, const spv_parsed_instruction_t& inst, inst.words + current_payload.offset + current_payload.num_words); operands_.emplace_back(current_payload.type, std::move(words)); } + assert((!IsLineInst() || dbg_line.empty()) && + "Op(No)Line attaching to Op(No)Line found"); } Instruction::Instruction(IRContext* c, const spv_parsed_instruction_t& inst, @@ -159,6 +159,10 @@ Instruction* Instruction::Clone(IRContext* c) const { clone->unique_id_ = c->TakeNextUniqueId(); clone->operands_ = operands_; clone->dbg_line_insts_ = dbg_line_insts_; + for (auto& i : clone->dbg_line_insts_) { + i.unique_id_ = c->TakeNextUniqueId(); + if (i.IsDebugLineInst()) i.SetResultId(c->TakeNextId()); + } clone->dbg_scope_ = dbg_scope_; return clone; } @@ -400,6 +404,21 @@ bool Instruction::IsVulkanStorageBuffer() const { return false; } +bool Instruction::IsVulkanStorageBufferVariable() const { + if (opcode() != SpvOpVariable) { + return false; + } + + uint32_t storage_class = GetSingleWordInOperand(kVariableStorageClassIndex); + if (storage_class == SpvStorageClassStorageBuffer || + storage_class == SpvStorageClassUniform) { + Instruction* var_type = context()->get_def_use_mgr()->GetDef(type_id()); + return var_type != nullptr && var_type->IsVulkanStorageBuffer(); + } + + return false; +} + bool Instruction::IsVulkanUniformBuffer() const { if (opcode() != SpvOpTypePointer) { return false; @@ -512,7 +531,7 @@ void Instruction::UpdateLexicalScope(uint32_t scope) { for (auto& i : dbg_line_insts_) { i.dbg_scope_.SetLexicalScope(scope); } - if (!IsDebugLineInst(opcode()) && + if (!IsLineInst() && context()->AreAnalysesValid(IRContext::kAnalysisDebugInfo)) { context()->get_debug_info_mgr()->AnalyzeDebugInst(this); } @@ -523,24 +542,61 @@ void Instruction::UpdateDebugInlinedAt(uint32_t new_inlined_at) { for (auto& i : dbg_line_insts_) { i.dbg_scope_.SetInlinedAt(new_inlined_at); } - if (!IsDebugLineInst(opcode()) && + if (!IsLineInst() && context()->AreAnalysesValid(IRContext::kAnalysisDebugInfo)) { context()->get_debug_info_mgr()->AnalyzeDebugInst(this); } } +void Instruction::ClearDbgLineInsts() { + if (context()->AreAnalysesValid(IRContext::kAnalysisDefUse)) { + auto def_use_mgr = context()->get_def_use_mgr(); + for (auto& l_inst : dbg_line_insts_) def_use_mgr->ClearInst(&l_inst); + } + clear_dbg_line_insts(); +} + void Instruction::UpdateDebugInfoFrom(const Instruction* from) { if (from == nullptr) return; - clear_dbg_line_insts(); + ClearDbgLineInsts(); if (!from->dbg_line_insts().empty()) - dbg_line_insts().push_back(from->dbg_line_insts().back()); + AddDebugLine(&from->dbg_line_insts().back()); SetDebugScope(from->GetDebugScope()); - if (!IsDebugLineInst(opcode()) && + if (!IsLineInst() && context()->AreAnalysesValid(IRContext::kAnalysisDebugInfo)) { context()->get_debug_info_mgr()->AnalyzeDebugInst(this); } } +void Instruction::AddDebugLine(const Instruction* inst) { + dbg_line_insts_.push_back(*inst); + dbg_line_insts_.back().unique_id_ = context()->TakeNextUniqueId(); + if (inst->IsDebugLineInst()) + dbg_line_insts_.back().SetResultId(context_->TakeNextId()); + if (context()->AreAnalysesValid(IRContext::kAnalysisDefUse)) + context()->get_def_use_mgr()->AnalyzeInstDefUse(&dbg_line_insts_.back()); +} + +bool Instruction::IsDebugLineInst() const { + NonSemanticShaderDebugInfo100Instructions ext_opt = GetShader100DebugOpcode(); + return ((ext_opt == NonSemanticShaderDebugInfo100DebugLine) || + (ext_opt == NonSemanticShaderDebugInfo100DebugNoLine)); +} + +bool Instruction::IsLineInst() const { return IsLine() || IsNoLine(); } + +bool Instruction::IsLine() const { + if (opcode() == SpvOpLine) return true; + NonSemanticShaderDebugInfo100Instructions ext_opt = GetShader100DebugOpcode(); + return ext_opt == NonSemanticShaderDebugInfo100DebugLine; +} + +bool Instruction::IsNoLine() const { + if (opcode() == SpvOpNoLine) return true; + NonSemanticShaderDebugInfo100Instructions ext_opt = GetShader100DebugOpcode(); + return ext_opt == NonSemanticShaderDebugInfo100DebugNoLine; +} + Instruction* Instruction::InsertBefore(std::unique_ptr<Instruction>&& inst) { inst.get()->InsertBefore(this); return inst.release(); @@ -624,22 +680,22 @@ OpenCLDebugInfo100Instructions Instruction::GetOpenCL100DebugOpcode() const { GetSingleWordInOperand(kExtInstInstructionInIdx)); } -NonSemanticVulkanDebugInfo100Instructions Instruction::GetVulkan100DebugOpcode() +NonSemanticShaderDebugInfo100Instructions Instruction::GetShader100DebugOpcode() const { if (opcode() != SpvOpExtInst) { - return NonSemanticVulkanDebugInfo100InstructionsMax; + return NonSemanticShaderDebugInfo100InstructionsMax; } - if (!context()->get_feature_mgr()->GetExtInstImportId_Vulkan100DebugInfo()) { - return NonSemanticVulkanDebugInfo100InstructionsMax; + if (!context()->get_feature_mgr()->GetExtInstImportId_Shader100DebugInfo()) { + return NonSemanticShaderDebugInfo100InstructionsMax; } if (GetSingleWordInOperand(kExtInstSetIdInIdx) != - context()->get_feature_mgr()->GetExtInstImportId_Vulkan100DebugInfo()) { - return NonSemanticVulkanDebugInfo100InstructionsMax; + context()->get_feature_mgr()->GetExtInstImportId_Shader100DebugInfo()) { + return NonSemanticShaderDebugInfo100InstructionsMax; } - return NonSemanticVulkanDebugInfo100Instructions( + return NonSemanticShaderDebugInfo100Instructions( GetSingleWordInOperand(kExtInstInstructionInIdx)); } @@ -650,16 +706,16 @@ CommonDebugInfoInstructions Instruction::GetCommonDebugOpcode() const { const uint32_t opencl_set_id = context()->get_feature_mgr()->GetExtInstImportId_OpenCL100DebugInfo(); - const uint32_t vulkan_set_id = - context()->get_feature_mgr()->GetExtInstImportId_Vulkan100DebugInfo(); + const uint32_t shader_set_id = + context()->get_feature_mgr()->GetExtInstImportId_Shader100DebugInfo(); - if (!opencl_set_id && !vulkan_set_id) { + if (!opencl_set_id && !shader_set_id) { return CommonDebugInfoInstructionsMax; } const uint32_t used_set_id = GetSingleWordInOperand(kExtInstSetIdInIdx); - if (used_set_id != opencl_set_id && used_set_id != vulkan_set_id) { + if (used_set_id != opencl_set_id && used_set_id != shader_set_id) { return CommonDebugInfoInstructionsMax; } diff --git a/source/opt/instruction.h b/source/opt/instruction.h index 7cdfc44d..ce568f66 100644 --- a/source/opt/instruction.h +++ b/source/opt/instruction.h @@ -22,7 +22,7 @@ #include <utility> #include <vector> -#include "NonSemanticVulkanDebugInfo100.h" +#include "NonSemanticShaderDebugInfo100.h" #include "OpenCLDebugInfo100.h" #include "source/common_debug_info.h" #include "source/latest_version_glsl_std_450_header.h" @@ -252,11 +252,6 @@ class Instruction : public utils::IntrusiveNodeBase<Instruction> { // Clear line-related debug instructions attached to this instruction. void clear_dbg_line_insts() { dbg_line_insts_.clear(); } - // Set line-related debug instructions. - void set_dbg_line_insts(const std::vector<Instruction>& lines) { - dbg_line_insts_ = lines; - } - // 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); @@ -306,8 +301,21 @@ class Instruction : public utils::IntrusiveNodeBase<Instruction> { // Sets DebugScope. inline void SetDebugScope(const DebugScope& scope); inline const DebugScope& GetDebugScope() const { return dbg_scope_; } + // Add debug line inst. Renew result id if Debug[No]Line + void AddDebugLine(const Instruction* inst); // Updates DebugInlinedAt of DebugScope and OpLine. void UpdateDebugInlinedAt(uint32_t new_inlined_at); + // Clear line-related debug instructions attached to this instruction + // along with def-use entries. + void ClearDbgLineInsts(); + // Return true if Shader100:Debug[No]Line + bool IsDebugLineInst() const; + // Return true if Op[No]Line or Shader100:Debug[No]Line + bool IsLineInst() const; + // Return true if OpLine or Shader100:DebugLine + bool IsLine() const; + // Return true if OpNoLine or Shader100:DebugNoLine + bool IsNoLine() const; inline uint32_t GetDebugInlinedAt() const { return dbg_scope_.GetInlinedAt(); } @@ -456,6 +464,10 @@ class Instruction : public utils::IntrusiveNodeBase<Instruction> { // storage buffer. bool IsVulkanStorageBuffer() const; + // Returns true if the instruction defines a variable in StorageBuffer or + // Uniform storage class with a pointer type that points to a storage buffer. + bool IsVulkanStorageBufferVariable() const; + // Returns true if the instruction defines a pointer type that points to a // uniform buffer. bool IsVulkanUniformBuffer() const; @@ -552,13 +564,13 @@ class Instruction : public utils::IntrusiveNodeBase<Instruction> { // OpenCLDebugInfo100InstructionsMax. OpenCLDebugInfo100Instructions GetOpenCL100DebugOpcode() const; - // Returns debug opcode of a NonSemantic.Vulkan.DebugInfo.100 instruction. If - // it is not a NonSemantic.Vulkan.DebugInfo.100 instruction, just returns - // NonSemanticVulkanDebugInfo100InstructionsMax. - NonSemanticVulkanDebugInfo100Instructions GetVulkan100DebugOpcode() const; + // Returns debug opcode of an NonSemantic.Shader.DebugInfo.100 instruction. If + // it is not an NonSemantic.Shader.DebugInfo.100 instruction, just return + // NonSemanticShaderDebugInfo100InstructionsMax. + NonSemanticShaderDebugInfo100Instructions GetShader100DebugOpcode() const; // Returns debug opcode of an OpenCL.100.DebugInfo or - // NonSemantic.Vulkan.DebugInfo.100 instruction. Since these overlap, we + // NonSemantic.Shader.DebugInfo.100 instruction. Since these overlap, we // return the OpenCLDebugInfo code CommonDebugInfoInstructions GetCommonDebugOpcode() const; @@ -566,10 +578,11 @@ class Instruction : public utils::IntrusiveNodeBase<Instruction> { bool IsOpenCL100DebugInstr() const { return GetOpenCL100DebugOpcode() != OpenCLDebugInfo100InstructionsMax; } - // Returns true if it is a NonSemantic.Vulkan.DebugInfo.100 instruction. - bool IsVulkan100DebugInstr() const { - return GetVulkan100DebugOpcode() != - NonSemanticVulkanDebugInfo100InstructionsMax; + + // Returns true if it is an NonSemantic.Shader.DebugInfo.100 instruction. + bool IsShader100DebugInstr() const { + return GetShader100DebugOpcode() != + NonSemanticShaderDebugInfo100InstructionsMax; } bool IsCommonDebugInstr() const { return GetCommonDebugOpcode() != CommonDebugInfoInstructionsMax; @@ -608,9 +621,9 @@ class Instruction : public utils::IntrusiveNodeBase<Instruction> { uint32_t unique_id_; // Unique instruction id // All logical operands, including result type id and result id. OperandList operands_; - // Opline and OpNoLine instructions preceding this instruction. Note that for - // Instructions representing OpLine or OpNonLine itself, this field should be - // empty. + // Op[No]Line or Debug[No]Line instructions preceding this instruction. Note + // that for Instructions representing Op[No]Line or Debug[No]Line themselves, + // this field should be empty. std::vector<Instruction> dbg_line_insts_; // DebugScope that wraps this instruction. diff --git a/source/opt/ir_builder.h b/source/opt/ir_builder.h index fe5feff5..4433cf0d 100644 --- a/source/opt/ir_builder.h +++ b/source/opt/ir_builder.h @@ -359,8 +359,9 @@ class InstructionBuilder { return AddInstruction(std::move(select)); } - // Adds a signed int32 constant to the binary. - // The |value| parameter is the constant value to be added. + // Returns a pointer to the definition of a signed 32-bit integer constant + // with the given value. Returns |nullptr| if the constant does not exist and + // cannot be created. Instruction* GetSintConstant(int32_t value) { return GetIntConstant<int32_t>(value, true); } @@ -381,21 +382,24 @@ class InstructionBuilder { 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. + + // Returns a pointer to the definition of an unsigned 32-bit integer constant + // with the given value. Returns |nullptr| if the constant does not exist and + // cannot be created. Instruction* GetUintConstant(uint32_t value) { return GetIntConstant<uint32_t>(value, false); } uint32_t GetUintConstantId(uint32_t value) { Instruction* uint_inst = GetUintConstant(value); - return uint_inst->result_id(); + return (uint_inst != nullptr ? uint_inst->result_id() : 0); } // 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 + // depending 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. + // value must not be a negative number. Returns false if the constant does + // not exists and could be be created. template <typename T> Instruction* GetIntConstant(T value, bool sign) { // Assert that we are not trying to store a negative number in an unsigned @@ -411,6 +415,10 @@ class InstructionBuilder { uint32_t type_id = GetContext()->get_type_mgr()->GetTypeInstruction(&int_type); + if (type_id == 0) { + return nullptr; + } + // Get the memory managed type so that it is safe to be stored by // GetConstant. analysis::Type* rebuilt_type = diff --git a/source/opt/ir_context.cpp b/source/opt/ir_context.cpp index 90873461..612a831a 100644 --- a/source/opt/ir_context.cpp +++ b/source/opt/ir_context.cpp @@ -30,7 +30,7 @@ static const int kSpvDecorateBuiltinInIdx = 2; static const int kEntryPointInterfaceInIdx = 3; static const int kEntryPointFunctionIdInIdx = 1; -// Constants for OpenCL.DebugInfo.100 / NonSemantic.Vulkan.DebugInfo.100 +// Constants for OpenCL.DebugInfo.100 / NonSemantic.Shader.DebugInfo.100 // extension instructions. static const uint32_t kDebugFunctionOperandFunctionIndex = 13; static const uint32_t kDebugGlobalVariableOperandVariableIndex = 11; @@ -171,7 +171,9 @@ Instruction* IRContext::KillInst(Instruction* inst) { KillOperandFromDebugInstructions(inst); if (AreAnalysesValid(kAnalysisDefUse)) { - get_def_use_mgr()->ClearInst(inst); + analysis::DefUseManager* def_use_mgr = get_def_use_mgr(); + def_use_mgr->ClearInst(inst); + for (auto& l_inst : inst->dbg_line_insts()) def_use_mgr->ClearInst(&l_inst); } if (AreAnalysesValid(kAnalysisInstrToBlockMapping)) { instr_to_block_.erase(inst); @@ -218,6 +220,8 @@ Instruction* IRContext::KillInst(Instruction* inst) { void IRContext::CollectNonSemanticTree( Instruction* inst, std::unordered_set<Instruction*>* to_kill) { if (!inst->HasResultId()) return; + // Debug[No]Line result id is not used, so we are done + if (inst->IsDebugLineInst()) return; std::vector<Instruction*> work_list; std::unordered_set<Instruction*> seen; work_list.push_back(inst); @@ -930,7 +934,7 @@ void IRContext::EmitErrorMessage(std::string message, Instruction* inst) { while (line_inst != nullptr) { // Stop at the beginning of the basic block. if (!line_inst->dbg_line_insts().empty()) { line_inst = &line_inst->dbg_line_insts().back(); - if (line_inst->opcode() == SpvOpNoLine) { + if (line_inst->IsNoLine()) { line_inst = nullptr; } break; diff --git a/source/opt/ir_context.h b/source/opt/ir_context.h index 3b89d85c..65853476 100644 --- a/source/opt/ir_context.h +++ b/source/opt/ir_context.h @@ -98,6 +98,7 @@ class IRContext { module_(new Module()), consumer_(std::move(c)), def_use_mgr_(nullptr), + feature_mgr_(nullptr), valid_analyses_(kAnalysisNone), constant_mgr_(nullptr), type_mgr_(nullptr), @@ -116,6 +117,7 @@ class IRContext { module_(std::move(m)), consumer_(std::move(c)), def_use_mgr_(nullptr), + feature_mgr_(nullptr), valid_analyses_(kAnalysisNone), type_mgr_(nullptr), id_to_name_(nullptr), @@ -769,6 +771,8 @@ class IRContext { // The instruction decoration manager for |module_|. std::unique_ptr<analysis::DecorationManager> decoration_mgr_; + + // The feature manager for |module_|. std::unique_ptr<FeatureManager> feature_mgr_; // A map from instructions to the basic block they belong to. This mapping is diff --git a/source/opt/ir_loader.cpp b/source/opt/ir_loader.cpp index bfdd59b3..a82b530e 100644 --- a/source/opt/ir_loader.cpp +++ b/source/opt/ir_loader.cpp @@ -17,9 +17,9 @@ #include <utility> #include "DebugInfo.h" -#include "NonSemanticVulkanDebugInfo100.h" #include "OpenCLDebugInfo100.h" #include "source/ext_inst.h" +#include "source/opt/ir_context.h" #include "source/opt/log.h" #include "source/opt/reflect.h" #include "source/util/make_unique.h" @@ -38,25 +38,37 @@ IrLoader::IrLoader(const MessageConsumer& consumer, Module* m) inst_index_(0), last_dbg_scope_(kNoDebugScope, kNoInlinedAt) {} +bool IsLineInst(const spv_parsed_instruction_t* inst) { + const auto opcode = static_cast<SpvOp>(inst->opcode); + if (IsOpLineInst(opcode)) return true; + if (opcode != SpvOpExtInst) return false; + if (inst->ext_inst_type != SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100) + return false; + const uint32_t ext_inst_index = inst->words[kExtInstSetIndex]; + const NonSemanticShaderDebugInfo100Instructions ext_inst_key = + NonSemanticShaderDebugInfo100Instructions(ext_inst_index); + return ext_inst_key == NonSemanticShaderDebugInfo100DebugLine || + ext_inst_key == NonSemanticShaderDebugInfo100DebugNoLine; +} + bool IrLoader::AddInstruction(const spv_parsed_instruction_t* inst) { ++inst_index_; - const auto opcode = static_cast<SpvOp>(inst->opcode); - if (IsDebugLineInst(opcode)) { + if (IsLineInst(inst)) { module()->SetContainsDebugInfo(); last_line_inst_.reset(); - dbg_line_info_.push_back( - Instruction(module()->context(), *inst, last_dbg_scope_)); + dbg_line_info_.emplace_back(module()->context(), *inst, last_dbg_scope_); return true; } // If it is a DebugScope or DebugNoScope of debug extension, we do not // create a new instruction, but simply keep the information in // struct DebugScope. + const auto opcode = static_cast<SpvOp>(inst->opcode); if (opcode == SpvOpExtInst && spvExtInstIsDebugInfo(inst->ext_inst_type)) { const uint32_t ext_inst_index = inst->words[kExtInstSetIndex]; if (inst->ext_inst_type == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100 || inst->ext_inst_type == - SPV_EXT_INST_TYPE_NONSEMANTIC_VULKAN_DEBUGINFO_100) { + SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100) { const CommonDebugInfoInstructions ext_inst_key = CommonDebugInfoInstructions(ext_inst_index); if (ext_inst_key == CommonDebugInfoDebugScope) { @@ -97,14 +109,20 @@ bool IrLoader::AddInstruction(const spv_parsed_instruction_t* inst) { new Instruction(module()->context(), *inst, std::move(dbg_line_info_))); if (!spv_inst->dbg_line_insts().empty()) { if (extra_line_tracking_ && - (spv_inst->dbg_line_insts().back().opcode() != SpvOpNoLine)) { + (!spv_inst->dbg_line_insts().back().IsNoLine())) { last_line_inst_ = std::unique_ptr<Instruction>( spv_inst->dbg_line_insts().back().Clone(module()->context())); + if (last_line_inst_->IsDebugLineInst()) + last_line_inst_->SetResultId(module()->context()->TakeNextId()); } dbg_line_info_.clear(); } else if (last_line_inst_ != nullptr) { last_line_inst_->SetDebugScope(last_dbg_scope_); spv_inst->dbg_line_insts().push_back(*last_line_inst_); + last_line_inst_ = std::unique_ptr<Instruction>( + spv_inst->dbg_line_insts().back().Clone(module()->context())); + if (last_line_inst_->IsDebugLineInst()) + last_line_inst_->SetResultId(module()->context()->TakeNextId()); } const char* src = source_.c_str(); @@ -243,15 +261,15 @@ bool IrLoader::AddInstruction(const spv_parsed_instruction_t* inst) { } } } else if (inst->ext_inst_type == - SPV_EXT_INST_TYPE_NONSEMANTIC_VULKAN_DEBUGINFO_100) { - const NonSemanticVulkanDebugInfo100Instructions ext_inst_key = - NonSemanticVulkanDebugInfo100Instructions(ext_inst_index); + SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100) { + const NonSemanticShaderDebugInfo100Instructions ext_inst_key = + NonSemanticShaderDebugInfo100Instructions(ext_inst_index); switch (ext_inst_key) { - case NonSemanticVulkanDebugInfo100DebugDeclare: - case NonSemanticVulkanDebugInfo100DebugValue: - case NonSemanticVulkanDebugInfo100DebugScope: - case NonSemanticVulkanDebugInfo100DebugNoScope: - case NonSemanticVulkanDebugInfo100DebugFunctionDefinition: { + case NonSemanticShaderDebugInfo100DebugDeclare: + case NonSemanticShaderDebugInfo100DebugValue: + case NonSemanticShaderDebugInfo100DebugScope: + case NonSemanticShaderDebugInfo100DebugNoScope: + case NonSemanticShaderDebugInfo100DebugFunctionDefinition: { if (block_ == nullptr) { // Inside function but outside blocks Errorf(consumer_, src, loc, "Debug info extension instruction found inside function " diff --git a/source/opt/iterator.h b/source/opt/iterator.h index 2280582d..847e1bbd 100644 --- a/source/opt/iterator.h +++ b/source/opt/iterator.h @@ -142,7 +142,7 @@ inline IteratorRange<IteratorType> make_range(const IteratorType& begin, template <typename IteratorType> inline IteratorRange<IteratorType> make_range(IteratorType&& begin, IteratorType&& end) { - return {std::move(begin), std::move(end)}; + return {std::forward<IteratorType>(begin), std::forward<IteratorType>(end)}; } // Returns a (begin, end) iterator pair for the given container. diff --git a/source/opt/local_access_chain_convert_pass.cpp b/source/opt/local_access_chain_convert_pass.cpp index e8dbdfc4..da9ba8cc 100644 --- a/source/opt/local_access_chain_convert_pass.cpp +++ b/source/opt/local_access_chain_convert_pass.cpp @@ -333,6 +333,20 @@ bool LocalAccessChainConvertPass::AllExtensionsSupported() const { if (extensions_allowlist_.find(extName) == extensions_allowlist_.end()) return false; } + // only allow NonSemantic.Shader.DebugInfo.100, we cannot safely optimise + // around unknown extended + // instruction sets even if they are non-semantic + for (auto& inst : context()->module()->ext_inst_imports()) { + assert(inst.opcode() == SpvOpExtInstImport && + "Expecting an import of an extension's instruction set."); + const char* extension_name = + reinterpret_cast<const char*>(&inst.GetInOperand(0).words[0]); + if (0 == std::strncmp(extension_name, "NonSemantic.", 12) && + 0 != std::strncmp(extension_name, "NonSemantic.Shader.DebugInfo.100", + 32)) { + return false; + } + } return true; } @@ -421,6 +435,7 @@ void LocalAccessChainConvertPass::InitExtensions() { "SPV_KHR_subgroup_uniform_control_flow", "SPV_KHR_integer_dot_product", "SPV_EXT_shader_image_int64", + "SPV_KHR_non_semantic_info", }); } diff --git a/source/opt/local_single_block_elim_pass.cpp b/source/opt/local_single_block_elim_pass.cpp index a742e3b1..5fd4f658 100644 --- a/source/opt/local_single_block_elim_pass.cpp +++ b/source/opt/local_single_block_elim_pass.cpp @@ -188,6 +188,20 @@ bool LocalSingleBlockLoadStoreElimPass::AllExtensionsSupported() const { if (extensions_allowlist_.find(extName) == extensions_allowlist_.end()) return false; } + // only allow NonSemantic.Shader.DebugInfo.100, we cannot safely optimise + // around unknown extended + // instruction sets even if they are non-semantic + for (auto& inst : context()->module()->ext_inst_imports()) { + assert(inst.opcode() == SpvOpExtInstImport && + "Expecting an import of an extension's instruction set."); + const char* extension_name = + reinterpret_cast<const char*>(&inst.GetInOperand(0).words[0]); + if (0 == std::strncmp(extension_name, "NonSemantic.", 12) && + 0 != std::strncmp(extension_name, "NonSemantic.Shader.DebugInfo.100", + 32)) { + return false; + } + } return true; } @@ -209,7 +223,7 @@ Pass::Status LocalSingleBlockLoadStoreElimPass::ProcessImpl() { return LocalSingleBlockLoadStoreElim(fp); }; - bool modified = context()->ProcessEntryPointCallTree(pfn); + bool modified = context()->ProcessReachableCallTree(pfn); return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; } @@ -273,6 +287,7 @@ void LocalSingleBlockLoadStoreElimPass::InitExtensions() { "SPV_KHR_subgroup_uniform_control_flow", "SPV_KHR_integer_dot_product", "SPV_EXT_shader_image_int64", + "SPV_KHR_non_semantic_info", }); } diff --git a/source/opt/local_single_store_elim_pass.cpp b/source/opt/local_single_store_elim_pass.cpp index 9701d10e..051bcada 100644 --- a/source/opt/local_single_store_elim_pass.cpp +++ b/source/opt/local_single_store_elim_pass.cpp @@ -53,6 +53,20 @@ bool LocalSingleStoreElimPass::AllExtensionsSupported() const { if (extensions_allowlist_.find(extName) == extensions_allowlist_.end()) return false; } + // only allow NonSemantic.Shader.DebugInfo.100, we cannot safely optimise + // around unknown extended + // instruction sets even if they are non-semantic + for (auto& inst : context()->module()->ext_inst_imports()) { + assert(inst.opcode() == SpvOpExtInstImport && + "Expecting an import of an extension's instruction set."); + const char* extension_name = + reinterpret_cast<const char*>(&inst.GetInOperand(0).words[0]); + if (0 == std::strncmp(extension_name, "NonSemantic.", 12) && + 0 != std::strncmp(extension_name, "NonSemantic.Shader.DebugInfo.100", + 32)) { + return false; + } + } return true; } @@ -67,7 +81,7 @@ Pass::Status LocalSingleStoreElimPass::ProcessImpl() { ProcessFunction pfn = [this](Function* fp) { return LocalSingleStoreElim(fp); }; - bool modified = context()->ProcessEntryPointCallTree(pfn); + bool modified = context()->ProcessReachableCallTree(pfn); return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; } @@ -126,6 +140,7 @@ void LocalSingleStoreElimPass::InitExtensionAllowList() { "SPV_KHR_subgroup_uniform_control_flow", "SPV_KHR_integer_dot_product", "SPV_EXT_shader_image_int64", + "SPV_KHR_non_semantic_info", }); } bool LocalSingleStoreElimPass::ProcessVariable(Instruction* var_inst) { diff --git a/source/opt/loop_unroller.cpp b/source/opt/loop_unroller.cpp index df68bd20..aff191fe 100644 --- a/source/opt/loop_unroller.cpp +++ b/source/opt/loop_unroller.cpp @@ -760,7 +760,7 @@ void LoopUnrollerUtilsImpl::FoldConditionBlock(BasicBlock* condition_block, IRContext::Analysis::kAnalysisInstrToBlockMapping); Instruction* new_branch = builder.AddBranch(new_target); - new_branch->set_dbg_line_insts(lines); + if (!lines.empty()) new_branch->AddDebugLine(&lines.back()); new_branch->SetDebugScope(scope); } @@ -873,6 +873,10 @@ void LoopUnrollerUtilsImpl::AssignNewResultIds(BasicBlock* basic_block) { def_use_mgr->AnalyzeInstDefUse(basic_block->GetLabelInst()); for (Instruction& inst : *basic_block) { + // Do def/use analysis on new lines + for (auto& line : inst.dbg_line_insts()) + def_use_mgr->AnalyzeInstDefUse(&line); + uint32_t old_id = inst.result_id(); // Ignore stores etc. @@ -1098,6 +1102,10 @@ void LoopUtils::Finalize() { Pass::Status LoopUnroller::Process() { bool changed = false; for (Function& f : *context()->module()) { + if (f.IsDeclaration()) { + continue; + } + LoopDescriptor* LD = context()->GetLoopDescriptor(&f); for (Loop& loop : *LD) { LoopUtils loop_utils{context(), &loop}; diff --git a/source/opt/mem_pass.cpp b/source/opt/mem_pass.cpp index 72442a99..ca4889b7 100644 --- a/source/opt/mem_pass.cpp +++ b/source/opt/mem_pass.cpp @@ -86,8 +86,8 @@ bool MemPass::IsPtr(uint32_t ptrId) { } const SpvOp op = ptrInst->opcode(); if (op == SpvOpVariable || IsNonPtrAccessChain(op)) return true; - if (op != SpvOpFunctionParameter) return false; const uint32_t varTypeId = ptrInst->type_id(); + if (varTypeId == 0) return false; const Instruction* varTypeInst = get_def_use_mgr()->GetDef(varTypeId); return varTypeInst->opcode() == SpvOpTypePointer; } diff --git a/source/opt/merge_return_pass.cpp b/source/opt/merge_return_pass.cpp index f1601049..a962a7cc 100644 --- a/source/opt/merge_return_pass.cpp +++ b/source/opt/merge_return_pass.cpp @@ -111,7 +111,9 @@ bool MergeReturnPass::ProcessStructured( } RecordImmediateDominators(function); - AddSingleCaseSwitchAroundFunction(); + if (!AddSingleCaseSwitchAroundFunction()) { + return false; + } std::list<BasicBlock*> order; cfg()->ComputeStructuredOrder(function, &*function->begin(), &order); @@ -770,7 +772,7 @@ void MergeReturnPass::InsertAfterElement(BasicBlock* element, list->insert(pos, new_element); } -void MergeReturnPass::AddSingleCaseSwitchAroundFunction() { +bool MergeReturnPass::AddSingleCaseSwitchAroundFunction() { CreateReturnBlock(); CreateReturn(final_return_block_); @@ -778,7 +780,10 @@ void MergeReturnPass::AddSingleCaseSwitchAroundFunction() { cfg()->RegisterBlock(final_return_block_); } - CreateSingleCaseSwitch(final_return_block_); + if (!CreateSingleCaseSwitch(final_return_block_)) { + return false; + } + return true; } BasicBlock* MergeReturnPass::CreateContinueTarget(uint32_t header_label_id) { @@ -813,7 +818,7 @@ BasicBlock* MergeReturnPass::CreateContinueTarget(uint32_t header_label_id) { return new_block; } -void MergeReturnPass::CreateSingleCaseSwitch(BasicBlock* merge_target) { +bool MergeReturnPass::CreateSingleCaseSwitch(BasicBlock* merge_target) { // Insert the switch before any code is run. We have to split the entry // block to make sure the OpVariable instructions remain in the entry block. BasicBlock* start_block = &*function_->begin(); @@ -830,13 +835,17 @@ void MergeReturnPass::CreateSingleCaseSwitch(BasicBlock* merge_target) { context(), start_block, IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); - builder.AddSwitch(builder.GetUintConstantId(0u), old_block->id(), {}, - merge_target->id()); + uint32_t const_zero_id = builder.GetUintConstantId(0u); + if (const_zero_id == 0) { + return false; + } + builder.AddSwitch(const_zero_id, old_block->id(), {}, merge_target->id()); if (context()->AreAnalysesValid(IRContext::kAnalysisCFG)) { cfg()->RegisterBlock(old_block); cfg()->AddEdges(start_block); } + return true; } bool MergeReturnPass::HasNontrivialUnreachableBlocks(Function* function) { diff --git a/source/opt/merge_return_pass.h b/source/opt/merge_return_pass.h index 06a3e7b5..4096ce7d 100644 --- a/source/opt/merge_return_pass.h +++ b/source/opt/merge_return_pass.h @@ -277,7 +277,7 @@ class MergeReturnPass : public MemPass { // current function where the switch and case value are both zero and the // default is the merge block. Returns after the switch is executed. Sets // |final_return_block_|. - void AddSingleCaseSwitchAroundFunction(); + bool AddSingleCaseSwitchAroundFunction(); // Creates a new basic block that branches to |header_label_id|. Returns the // new basic block. The block will be the second last basic block in the @@ -286,7 +286,7 @@ class MergeReturnPass : public MemPass { // Creates a one case switch around the executable code of the function with // |merge_target| as the merge node. - void CreateSingleCaseSwitch(BasicBlock* merge_target); + bool CreateSingleCaseSwitch(BasicBlock* merge_target); // Returns true if |function| has an unreachable block that is not a continue // target that simply branches back to the header, or a merge block containing diff --git a/source/opt/module.cpp b/source/opt/module.cpp index 6585012f..f97defbd 100644 --- a/source/opt/module.cpp +++ b/source/opt/module.cpp @@ -151,16 +151,15 @@ void Module::ToBinary(std::vector<uint32_t>* binary, bool skip_nop) const { this](const Instruction* i) { // Skip emitting line instructions between merge and branch instructions. auto opcode = i->opcode(); - if (between_merge_and_branch && - (opcode == SpvOpLine || opcode == SpvOpNoLine)) { + if (between_merge_and_branch && i->IsLineInst()) { return; } between_merge_and_branch = false; if (last_line_inst != nullptr) { - // If the current instruction is OpLine and it is the same with - // the last line instruction that is still effective (can be applied + // If the current instruction is OpLine or DebugLine and it is the same + // as the last line instruction that is still effective (can be applied // to the next instruction), we skip writing the current instruction. - if (opcode == SpvOpLine) { + if (i->IsLine()) { uint32_t operand_index = 0; if (last_line_inst->WhileEachInOperand( [&operand_index, i](const uint32_t* word) { @@ -169,11 +168,22 @@ void Module::ToBinary(std::vector<uint32_t>* binary, bool skip_nop) const { })) { return; } - } else if (opcode != SpvOpNoLine && i->dbg_line_insts().empty()) { + } else if (!i->IsNoLine() && i->dbg_line_insts().empty()) { // If the current instruction does not have the line information, // the last line information is not effective any more. Emit OpNoLine - // to specify it. - binary->push_back((1 << 16) | static_cast<uint16_t>(SpvOpNoLine)); + // or DebugNoLine to specify it. + uint32_t shader_set_id = context() + ->get_feature_mgr() + ->GetExtInstImportId_Shader100DebugInfo(); + if (shader_set_id != 0) { + binary->push_back((5 << 16) | static_cast<uint16_t>(SpvOpExtInst)); + binary->push_back(context()->get_type_mgr()->GetVoidTypeId()); + binary->push_back(context()->TakeNextId()); + binary->push_back(shader_set_id); + binary->push_back(NonSemanticShaderDebugInfo100DebugNoLine); + } else { + binary->push_back((1 << 16) | static_cast<uint16_t>(SpvOpNoLine)); + } last_line_inst = nullptr; } } @@ -181,7 +191,7 @@ void Module::ToBinary(std::vector<uint32_t>* binary, bool skip_nop) const { if (opcode == SpvOpLabel) { between_label_and_phi_var = true; } else if (opcode != SpvOpVariable && opcode != SpvOpPhi && - opcode != SpvOpLine && opcode != SpvOpNoLine) { + !spvtools::opt::IsOpLineInst(opcode)) { between_label_and_phi_var = false; } @@ -190,7 +200,7 @@ void Module::ToBinary(std::vector<uint32_t>* binary, bool skip_nop) const { if (scope != last_scope) { // Can only emit nonsemantic instructions after all phi instructions // in a block so don't emit scope instructions before phi instructions - // for Vulkan.NonSemantic.DebugInfo.100. + // for NonSemantic.Shader.DebugInfo.100. if (!between_label_and_phi_var || context() ->get_feature_mgr() @@ -206,18 +216,19 @@ void Module::ToBinary(std::vector<uint32_t>* binary, bool skip_nop) const { i->ToBinaryWithoutAttachedDebugInsts(binary); } // Update the last line instruction. - if (spvOpcodeIsBlockTerminator(opcode) || opcode == SpvOpNoLine) { + if (spvOpcodeIsBlockTerminator(opcode) || i->IsNoLine()) { last_line_inst = nullptr; } else if (opcode == SpvOpLoopMerge || opcode == SpvOpSelectionMerge) { between_merge_and_branch = true; last_line_inst = nullptr; - } else if (opcode == SpvOpLine) { + } else if (i->IsLine()) { last_line_inst = i; } }; ForEachInst(write_inst, true); - // We create new instructions for DebugScope. The bound must be updated. + // We create new instructions for DebugScope and DebugNoLine. The bound must + // be updated. binary->data()[bound_idx] = header_.bound; } diff --git a/source/opt/module.h b/source/opt/module.h index 43b55ae1..0360b7d5 100644 --- a/source/opt/module.h +++ b/source/opt/module.h @@ -104,7 +104,7 @@ class Module { inline void AddDebug3Inst(std::unique_ptr<Instruction> d); // Appends a debug info extension (OpenCL.DebugInfo.100, - // NonSemantic.Vulkan.DebugInfo.100, or DebugInfo) instruction to this module. + // NonSemantic.Shader.DebugInfo.100, or DebugInfo) instruction to this module. inline void AddExtInstDebugInfo(std::unique_ptr<Instruction> d); // Appends an annotation instruction to this module. diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp index 858c95ee..e74db26f 100644 --- a/source/opt/optimizer.cpp +++ b/source/opt/optimizer.cpp @@ -320,6 +320,8 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) { RegisterPass(CreateCombineAccessChainsPass()); } else if (pass_name == "convert-local-access-chains") { RegisterPass(CreateLocalAccessChainConvertPass()); + } else if (pass_name == "replace-desc-array-access-using-var-index") { + RegisterPass(CreateReplaceDescArrayAccessUsingVarIndexPass()); } else if (pass_name == "descriptor-scalar-replacement") { RegisterPass(CreateDescriptorScalarReplacementPass()); } else if (pass_name == "eliminate-dead-code-aggressive") { @@ -385,7 +387,23 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) { } else if (pass_name == "loop-invariant-code-motion") { RegisterPass(CreateLoopInvariantCodeMotionPass()); } else if (pass_name == "reduce-load-size") { - RegisterPass(CreateReduceLoadSizePass()); + if (pass_args.size() == 0) { + RegisterPass(CreateReduceLoadSizePass()); + } else { + double load_replacement_threshold = 0.9; + if (pass_args.find_first_not_of(".0123456789") == std::string::npos) { + load_replacement_threshold = atof(pass_args.c_str()); + } + + if (load_replacement_threshold >= 0) { + RegisterPass(CreateReduceLoadSizePass(load_replacement_threshold)); + } else { + Error(consumer(), nullptr, {}, + "--reduce-load-size must have no arguments or a non-negative " + "double argument"); + return false; + } + } } else if (pass_name == "redundancy-elimination") { RegisterPass(CreateRedundancyEliminationPass()); } else if (pass_name == "private-to-local") { @@ -401,22 +419,22 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) { RegisterPass(CreateSimplificationPass()); RegisterPass(CreateDeadBranchElimPass()); RegisterPass(CreateBlockMergePass()); - RegisterPass(CreateAggressiveDCEPass()); + RegisterPass(CreateAggressiveDCEPass(true)); } else if (pass_name == "inst-desc-idx-check") { RegisterPass(CreateInstBindlessCheckPass(7, 23, true, true)); RegisterPass(CreateSimplificationPass()); RegisterPass(CreateDeadBranchElimPass()); RegisterPass(CreateBlockMergePass()); - RegisterPass(CreateAggressiveDCEPass()); + RegisterPass(CreateAggressiveDCEPass(true)); } else if (pass_name == "inst-buff-oob-check") { RegisterPass(CreateInstBindlessCheckPass(7, 23, false, false, true, true)); RegisterPass(CreateSimplificationPass()); RegisterPass(CreateDeadBranchElimPass()); RegisterPass(CreateBlockMergePass()); - RegisterPass(CreateAggressiveDCEPass()); + RegisterPass(CreateAggressiveDCEPass(true)); } else if (pass_name == "inst-buff-addr-check") { RegisterPass(CreateInstBuffAddrCheckPass(7, 23)); - RegisterPass(CreateAggressiveDCEPass()); + RegisterPass(CreateAggressiveDCEPass(true)); } else if (pass_name == "convert-relaxed-to-half") { RegisterPass(CreateConvertRelaxedToHalfPass()); } else if (pass_name == "relax-float-ops") { @@ -746,9 +764,9 @@ Optimizer::PassToken CreateLocalMultiStoreElimPass() { MakeUnique<opt::SSARewritePass>()); } -Optimizer::PassToken CreateAggressiveDCEPass() { +Optimizer::PassToken CreateAggressiveDCEPass(bool preserve_interface) { return MakeUnique<Optimizer::PassToken::Impl>( - MakeUnique<opt::AggressiveDCEPass>()); + MakeUnique<opt::AggressiveDCEPass>(preserve_interface)); } Optimizer::PassToken CreateRemoveUnusedInterfaceVariablesPass() { @@ -879,9 +897,10 @@ Optimizer::PassToken CreateVectorDCEPass() { return MakeUnique<Optimizer::PassToken::Impl>(MakeUnique<opt::VectorDCE>()); } -Optimizer::PassToken CreateReduceLoadSizePass() { +Optimizer::PassToken CreateReduceLoadSizePass( + double load_replacement_threshold) { return MakeUnique<Optimizer::PassToken::Impl>( - MakeUnique<opt::ReduceLoadSize>()); + MakeUnique<opt::ReduceLoadSize>(load_replacement_threshold)); } Optimizer::PassToken CreateCombineAccessChainsPass() { @@ -941,6 +960,11 @@ Optimizer::PassToken CreateGraphicsRobustAccessPass() { MakeUnique<opt::GraphicsRobustAccessPass>()); } +Optimizer::PassToken CreateReplaceDescArrayAccessUsingVarIndexPass() { + return MakeUnique<Optimizer::PassToken::Impl>( + MakeUnique<opt::ReplaceDescArrayAccessUsingVarIndex>()); +} + Optimizer::PassToken CreateDescriptorScalarReplacementPass() { return MakeUnique<Optimizer::PassToken::Impl>( MakeUnique<opt::DescriptorScalarReplacement>()); diff --git a/source/opt/pass.cpp b/source/opt/pass.cpp index 09b78af9..017aad10 100644 --- a/source/opt/pass.cpp +++ b/source/opt/pass.cpp @@ -43,8 +43,8 @@ Pass::Status Pass::Run(IRContext* ctx) { if (status == Status::SuccessWithChange) { ctx->InvalidateAnalysesExceptFor(GetPreservedAnalyses()); } - assert((status == Status::Failure || ctx->IsConsistent()) && - "An analysis in the context is out of date."); + if (!(status == Status::Failure || ctx->IsConsistent())) + assert(false && "An analysis in the context is out of date."); return status; } diff --git a/source/opt/passes.h b/source/opt/passes.h index da837f2a..f3c30d57 100644 --- a/source/opt/passes.h +++ b/source/opt/passes.h @@ -66,6 +66,7 @@ #include "source/opt/relax_float_ops_pass.h" #include "source/opt/remove_duplicates_pass.h" #include "source/opt/remove_unused_interface_variables_pass.h" +#include "source/opt/replace_desc_array_access_using_var_index.h" #include "source/opt/replace_invalid_opc.h" #include "source/opt/scalar_replacement_pass.h" #include "source/opt/set_spec_constant_default_value_pass.h" diff --git a/source/opt/reduce_load_size.cpp b/source/opt/reduce_load_size.cpp index 87d2c4c0..e9b80874 100644 --- a/source/opt/reduce_load_size.cpp +++ b/source/opt/reduce_load_size.cpp @@ -27,7 +27,6 @@ namespace { const uint32_t kExtractCompositeIdInIdx = 0; const uint32_t kVariableStorageClassInIdx = 0; const uint32_t kLoadPointerInIdx = 0; -const double kThreshold = 0.9; } // namespace @@ -151,6 +150,8 @@ bool ReduceLoadSize::ShouldReplaceExtract(Instruction* inst) { bool should_replace = false; if (all_elements_used) { should_replace = false; + } else if (1.0 <= replacement_threshold_) { + should_replace = true; } else { analysis::ConstantManager* const_mgr = context()->get_constant_mgr(); analysis::TypeManager* type_mgr = context()->get_type_mgr(); @@ -172,7 +173,7 @@ bool ReduceLoadSize::ShouldReplaceExtract(Instruction* inst) { } double percent_used = static_cast<double>(elements_used.size()) / static_cast<double>(total_size); - should_replace = (percent_used < kThreshold); + should_replace = (percent_used < replacement_threshold_); } should_replace_cache_[op_inst->result_id()] = should_replace; diff --git a/source/opt/reduce_load_size.h b/source/opt/reduce_load_size.h index ccac49be..b3238453 100644 --- a/source/opt/reduce_load_size.h +++ b/source/opt/reduce_load_size.h @@ -27,6 +27,9 @@ namespace opt { // See optimizer.hpp for documentation. class ReduceLoadSize : public Pass { public: + explicit ReduceLoadSize(double replacement_threshold) + : replacement_threshold_(replacement_threshold) {} + const char* name() const override { return "reduce-load-size"; } Status Process() override; @@ -54,6 +57,11 @@ class ReduceLoadSize : public Pass { // on the load feeding |inst|. bool ShouldReplaceExtract(Instruction* inst); + // Threshold to determine whether we have to replace the load or not. If the + // ratio of the used components of the load is less than the threshold, we + // replace the load. + double replacement_threshold_; + // Maps the result id of an OpLoad instruction to the result of whether or // not the OpCompositeExtract that use the id should be replaced. std::unordered_map<uint32_t, bool> should_replace_cache_; diff --git a/source/opt/redundancy_elimination.cpp b/source/opt/redundancy_elimination.cpp index 362e54dc..398225bb 100644 --- a/source/opt/redundancy_elimination.cpp +++ b/source/opt/redundancy_elimination.cpp @@ -24,6 +24,10 @@ Pass::Status RedundancyEliminationPass::Process() { ValueNumberTable vnTable(context()); for (auto& func : *get_module()) { + if (func.IsDeclaration()) { + continue; + } + // Build the dominator tree for this function. It is how the code is // traversed. DominatorTree& dom_tree = diff --git a/source/opt/reflect.h b/source/opt/reflect.h index c7d46df5..c2ffb0be 100644 --- a/source/opt/reflect.h +++ b/source/opt/reflect.h @@ -34,7 +34,7 @@ inline bool IsDebug2Inst(SpvOp opcode) { inline bool IsDebug3Inst(SpvOp opcode) { return opcode == SpvOpModuleProcessed; } -inline bool IsDebugLineInst(SpvOp opcode) { +inline bool IsOpLineInst(SpvOp opcode) { return opcode == SpvOpLine || opcode == SpvOpNoLine; } inline bool IsAnnotationInst(SpvOp opcode) { @@ -51,7 +51,8 @@ inline bool IsTypeInst(SpvOp opcode) { opcode == SpvOpTypeCooperativeMatrixNV; } inline bool IsConstantInst(SpvOp opcode) { - return opcode >= SpvOpConstantTrue && opcode <= SpvOpSpecConstantOp; + return (opcode >= SpvOpConstantTrue && opcode <= SpvOpSpecConstantOp) || + opcode == SpvOpConstantFunctionPointerINTEL; } inline bool IsCompileTimeConstantInst(SpvOp opcode) { return opcode >= SpvOpConstantTrue && opcode <= SpvOpConstantNull; diff --git a/source/opt/relax_float_ops_pass.cpp b/source/opt/relax_float_ops_pass.cpp index 73f16ddf..3fcf8795 100644 --- a/source/opt/relax_float_ops_pass.cpp +++ b/source/opt/relax_float_ops_pass.cpp @@ -76,7 +76,7 @@ Pass::Status RelaxFloatOpsPass::ProcessImpl() { Pass::ProcessFunction pfn = [this](Function* fp) { return ProcessFunction(fp); }; - bool modified = context()->ProcessEntryPointCallTree(pfn); + bool modified = context()->ProcessReachableCallTree(pfn); return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; } diff --git a/source/opt/replace_desc_array_access_using_var_index.cpp b/source/opt/replace_desc_array_access_using_var_index.cpp new file mode 100644 index 00000000..1082e679 --- /dev/null +++ b/source/opt/replace_desc_array_access_using_var_index.cpp @@ -0,0 +1,423 @@ +// Copyright (c) 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/opt/replace_desc_array_access_using_var_index.h" + +#include "source/opt/desc_sroa_util.h" +#include "source/opt/ir_builder.h" +#include "source/util/string_utils.h" + +namespace spvtools { +namespace opt { +namespace { + +const uint32_t kOpAccessChainInOperandIndexes = 1; +const uint32_t kOpTypePointerInOperandType = 1; +const uint32_t kOpTypeArrayInOperandType = 0; +const uint32_t kOpTypeStructInOperandMember = 0; +IRContext::Analysis kAnalysisDefUseAndInstrToBlockMapping = + IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping; + +uint32_t GetValueWithKeyExistenceCheck( + uint32_t key, const std::unordered_map<uint32_t, uint32_t>& map) { + auto itr = map.find(key); + assert(itr != map.end() && "Key does not exist"); + return itr->second; +} + +} // namespace + +Pass::Status ReplaceDescArrayAccessUsingVarIndex::Process() { + Status status = Status::SuccessWithoutChange; + for (Instruction& var : context()->types_values()) { + if (descsroautil::IsDescriptorArray(context(), &var)) { + if (ReplaceVariableAccessesWithConstantElements(&var)) + status = Status::SuccessWithChange; + } + } + return status; +} + +bool ReplaceDescArrayAccessUsingVarIndex:: + ReplaceVariableAccessesWithConstantElements(Instruction* var) const { + std::vector<Instruction*> work_list; + get_def_use_mgr()->ForEachUser(var, [&work_list](Instruction* use) { + switch (use->opcode()) { + case SpvOpAccessChain: + case SpvOpInBoundsAccessChain: + work_list.push_back(use); + break; + default: + break; + } + }); + + bool updated = false; + for (Instruction* access_chain : work_list) { + if (descsroautil::GetAccessChainIndexAsConst(context(), access_chain) == + nullptr) { + ReplaceAccessChain(var, access_chain); + updated = true; + } + } + // Note that we do not consider OpLoad and OpCompositeExtract because + // OpCompositeExtract always has constant literals for indices. + return updated; +} + +void ReplaceDescArrayAccessUsingVarIndex::ReplaceAccessChain( + Instruction* var, Instruction* access_chain) const { + uint32_t number_of_elements = + descsroautil::GetNumberOfElementsForArrayOrStruct(context(), var); + assert(number_of_elements != 0 && "Number of element is 0"); + if (number_of_elements == 1) { + UseConstIndexForAccessChain(access_chain, 0); + get_def_use_mgr()->AnalyzeInstUse(access_chain); + return; + } + ReplaceUsersOfAccessChain(access_chain, number_of_elements); +} + +void ReplaceDescArrayAccessUsingVarIndex::ReplaceUsersOfAccessChain( + Instruction* access_chain, uint32_t number_of_elements) const { + std::vector<Instruction*> final_users; + CollectRecursiveUsersWithConcreteType(access_chain, &final_users); + for (auto* inst : final_users) { + std::deque<Instruction*> insts_to_be_cloned = + CollectRequiredImageInsts(inst); + ReplaceNonUniformAccessWithSwitchCase( + inst, access_chain, number_of_elements, insts_to_be_cloned); + } +} + +void ReplaceDescArrayAccessUsingVarIndex::CollectRecursiveUsersWithConcreteType( + Instruction* access_chain, std::vector<Instruction*>* final_users) const { + std::queue<Instruction*> work_list; + work_list.push(access_chain); + while (!work_list.empty()) { + auto* inst_from_work_list = work_list.front(); + work_list.pop(); + get_def_use_mgr()->ForEachUser( + inst_from_work_list, [this, final_users, &work_list](Instruction* use) { + // TODO: Support Boolean type as well. + if (!use->HasResultId() || IsConcreteType(use->type_id())) { + final_users->push_back(use); + } else { + work_list.push(use); + } + }); + } +} + +std::deque<Instruction*> +ReplaceDescArrayAccessUsingVarIndex::CollectRequiredImageInsts( + Instruction* user_of_image_insts) const { + std::unordered_set<uint32_t> seen_inst_ids; + std::queue<Instruction*> work_list; + + auto decision_to_include_operand = [this, &seen_inst_ids, + &work_list](uint32_t* idp) { + if (!seen_inst_ids.insert(*idp).second) return; + Instruction* operand = get_def_use_mgr()->GetDef(*idp); + if (context()->get_instr_block(operand) != nullptr && + HasImageOrImagePtrType(operand)) { + work_list.push(operand); + } + }; + + std::deque<Instruction*> required_image_insts; + required_image_insts.push_front(user_of_image_insts); + user_of_image_insts->ForEachInId(decision_to_include_operand); + while (!work_list.empty()) { + auto* inst_from_work_list = work_list.front(); + work_list.pop(); + required_image_insts.push_front(inst_from_work_list); + inst_from_work_list->ForEachInId(decision_to_include_operand); + } + return required_image_insts; +} + +bool ReplaceDescArrayAccessUsingVarIndex::HasImageOrImagePtrType( + const Instruction* inst) const { + assert(inst != nullptr && inst->type_id() != 0 && "Invalid instruction"); + return IsImageOrImagePtrType(get_def_use_mgr()->GetDef(inst->type_id())); +} + +bool ReplaceDescArrayAccessUsingVarIndex::IsImageOrImagePtrType( + const Instruction* type_inst) const { + if (type_inst->opcode() == SpvOpTypeImage || + type_inst->opcode() == SpvOpTypeSampler || + type_inst->opcode() == SpvOpTypeSampledImage) { + return true; + } + if (type_inst->opcode() == SpvOpTypePointer) { + Instruction* pointee_type_inst = get_def_use_mgr()->GetDef( + type_inst->GetSingleWordInOperand(kOpTypePointerInOperandType)); + return IsImageOrImagePtrType(pointee_type_inst); + } + if (type_inst->opcode() == SpvOpTypeArray) { + Instruction* element_type_inst = get_def_use_mgr()->GetDef( + type_inst->GetSingleWordInOperand(kOpTypeArrayInOperandType)); + return IsImageOrImagePtrType(element_type_inst); + } + if (type_inst->opcode() != SpvOpTypeStruct) return false; + for (uint32_t in_operand_idx = kOpTypeStructInOperandMember; + in_operand_idx < type_inst->NumInOperands(); ++in_operand_idx) { + Instruction* member_type_inst = get_def_use_mgr()->GetDef( + type_inst->GetSingleWordInOperand(kOpTypeStructInOperandMember)); + if (IsImageOrImagePtrType(member_type_inst)) return true; + } + return false; +} + +bool ReplaceDescArrayAccessUsingVarIndex::IsConcreteType( + uint32_t type_id) const { + Instruction* type_inst = get_def_use_mgr()->GetDef(type_id); + if (type_inst->opcode() == SpvOpTypeInt || + type_inst->opcode() == SpvOpTypeFloat) { + return true; + } + if (type_inst->opcode() == SpvOpTypeVector || + type_inst->opcode() == SpvOpTypeMatrix || + type_inst->opcode() == SpvOpTypeArray) { + return IsConcreteType(type_inst->GetSingleWordInOperand(0)); + } + if (type_inst->opcode() == SpvOpTypeStruct) { + for (uint32_t i = 0; i < type_inst->NumInOperands(); ++i) { + if (!IsConcreteType(type_inst->GetSingleWordInOperand(i))) return false; + } + return true; + } + return false; +} + +BasicBlock* ReplaceDescArrayAccessUsingVarIndex::CreateCaseBlock( + Instruction* access_chain, uint32_t element_index, + const std::deque<Instruction*>& insts_to_be_cloned, + uint32_t branch_target_id, + std::unordered_map<uint32_t, uint32_t>* old_ids_to_new_ids) const { + auto* case_block = CreateNewBlock(); + AddConstElementAccessToCaseBlock(case_block, access_chain, element_index, + old_ids_to_new_ids); + CloneInstsToBlock(case_block, access_chain, insts_to_be_cloned, + old_ids_to_new_ids); + AddBranchToBlock(case_block, branch_target_id); + UseNewIdsInBlock(case_block, *old_ids_to_new_ids); + return case_block; +} + +void ReplaceDescArrayAccessUsingVarIndex::CloneInstsToBlock( + BasicBlock* block, Instruction* inst_to_skip_cloning, + const std::deque<Instruction*>& insts_to_be_cloned, + std::unordered_map<uint32_t, uint32_t>* old_ids_to_new_ids) const { + for (auto* inst_to_be_cloned : insts_to_be_cloned) { + if (inst_to_be_cloned == inst_to_skip_cloning) continue; + std::unique_ptr<Instruction> clone(inst_to_be_cloned->Clone(context())); + if (inst_to_be_cloned->HasResultId()) { + uint32_t new_id = context()->TakeNextId(); + clone->SetResultId(new_id); + (*old_ids_to_new_ids)[inst_to_be_cloned->result_id()] = new_id; + } + get_def_use_mgr()->AnalyzeInstDefUse(clone.get()); + context()->set_instr_block(clone.get(), block); + block->AddInstruction(std::move(clone)); + } +} + +void ReplaceDescArrayAccessUsingVarIndex::UseNewIdsInBlock( + BasicBlock* block, + const std::unordered_map<uint32_t, uint32_t>& old_ids_to_new_ids) const { + for (auto block_itr = block->begin(); block_itr != block->end(); + ++block_itr) { + (&*block_itr)->ForEachInId([&old_ids_to_new_ids](uint32_t* idp) { + auto old_ids_to_new_ids_itr = old_ids_to_new_ids.find(*idp); + if (old_ids_to_new_ids_itr == old_ids_to_new_ids.end()) return; + *idp = old_ids_to_new_ids_itr->second; + }); + get_def_use_mgr()->AnalyzeInstUse(&*block_itr); + } +} + +void ReplaceDescArrayAccessUsingVarIndex::ReplaceNonUniformAccessWithSwitchCase( + Instruction* access_chain_final_user, Instruction* access_chain, + uint32_t number_of_elements, + const std::deque<Instruction*>& insts_to_be_cloned) const { + // Create merge block and add terminator + auto* block = context()->get_instr_block(access_chain_final_user); + auto* merge_block = SeparateInstructionsIntoNewBlock( + block, access_chain_final_user->NextNode()); + + auto* function = block->GetParent(); + + // Add case blocks + std::vector<uint32_t> phi_operands; + std::vector<uint32_t> case_block_ids; + for (uint32_t idx = 0; idx < number_of_elements; ++idx) { + std::unordered_map<uint32_t, uint32_t> old_ids_to_new_ids_for_cloned_insts; + std::unique_ptr<BasicBlock> case_block(CreateCaseBlock( + access_chain, idx, insts_to_be_cloned, merge_block->id(), + &old_ids_to_new_ids_for_cloned_insts)); + case_block_ids.push_back(case_block->id()); + function->InsertBasicBlockBefore(std::move(case_block), merge_block); + + // Keep the operand for OpPhi + if (!access_chain_final_user->HasResultId()) continue; + uint32_t phi_operand = + GetValueWithKeyExistenceCheck(access_chain_final_user->result_id(), + old_ids_to_new_ids_for_cloned_insts); + phi_operands.push_back(phi_operand); + } + + // Create default block + std::unique_ptr<BasicBlock> default_block( + CreateDefaultBlock(access_chain_final_user->HasResultId(), &phi_operands, + merge_block->id())); + uint32_t default_block_id = default_block->id(); + function->InsertBasicBlockBefore(std::move(default_block), merge_block); + + // Create OpSwitch + uint32_t access_chain_index_var_id = + descsroautil::GetFirstIndexOfAccessChain(access_chain); + AddSwitchForAccessChain(block, access_chain_index_var_id, default_block_id, + merge_block->id(), case_block_ids); + + // Create phi instructions + if (!phi_operands.empty()) { + uint32_t phi_id = CreatePhiInstruction(merge_block, phi_operands, + case_block_ids, default_block_id); + context()->ReplaceAllUsesWith(access_chain_final_user->result_id(), phi_id); + } + + // Replace OpPhi incoming block operand that uses |block| with |merge_block| + ReplacePhiIncomingBlock(block->id(), merge_block->id()); +} + +BasicBlock* +ReplaceDescArrayAccessUsingVarIndex::SeparateInstructionsIntoNewBlock( + BasicBlock* block, Instruction* separation_begin_inst) const { + auto separation_begin = block->begin(); + while (separation_begin != block->end() && + &*separation_begin != separation_begin_inst) { + ++separation_begin; + } + return block->SplitBasicBlock(context(), context()->TakeNextId(), + separation_begin); +} + +BasicBlock* ReplaceDescArrayAccessUsingVarIndex::CreateNewBlock() const { + auto* new_block = new BasicBlock(std::unique_ptr<Instruction>( + new Instruction(context(), SpvOpLabel, 0, context()->TakeNextId(), {}))); + get_def_use_mgr()->AnalyzeInstDefUse(new_block->GetLabelInst()); + context()->set_instr_block(new_block->GetLabelInst(), new_block); + return new_block; +} + +void ReplaceDescArrayAccessUsingVarIndex::UseConstIndexForAccessChain( + Instruction* access_chain, uint32_t const_element_idx) const { + uint32_t const_element_idx_id = + context()->get_constant_mgr()->GetUIntConst(const_element_idx); + access_chain->SetInOperand(kOpAccessChainInOperandIndexes, + {const_element_idx_id}); +} + +void ReplaceDescArrayAccessUsingVarIndex::AddConstElementAccessToCaseBlock( + BasicBlock* case_block, Instruction* access_chain, + uint32_t const_element_idx, + std::unordered_map<uint32_t, uint32_t>* old_ids_to_new_ids) const { + std::unique_ptr<Instruction> access_clone(access_chain->Clone(context())); + UseConstIndexForAccessChain(access_clone.get(), const_element_idx); + + uint32_t new_access_id = context()->TakeNextId(); + (*old_ids_to_new_ids)[access_clone->result_id()] = new_access_id; + access_clone->SetResultId(new_access_id); + get_def_use_mgr()->AnalyzeInstDefUse(access_clone.get()); + + context()->set_instr_block(access_clone.get(), case_block); + case_block->AddInstruction(std::move(access_clone)); +} + +void ReplaceDescArrayAccessUsingVarIndex::AddBranchToBlock( + BasicBlock* parent_block, uint32_t branch_destination) const { + InstructionBuilder builder{context(), parent_block, + kAnalysisDefUseAndInstrToBlockMapping}; + builder.AddBranch(branch_destination); +} + +BasicBlock* ReplaceDescArrayAccessUsingVarIndex::CreateDefaultBlock( + bool null_const_for_phi_is_needed, std::vector<uint32_t>* phi_operands, + uint32_t merge_block_id) const { + auto* default_block = CreateNewBlock(); + AddBranchToBlock(default_block, merge_block_id); + if (!null_const_for_phi_is_needed) return default_block; + + // Create null value for OpPhi + Instruction* inst = context()->get_def_use_mgr()->GetDef((*phi_operands)[0]); + auto* null_const_inst = GetConstNull(inst->type_id()); + phi_operands->push_back(null_const_inst->result_id()); + return default_block; +} + +Instruction* ReplaceDescArrayAccessUsingVarIndex::GetConstNull( + uint32_t type_id) const { + assert(type_id != 0 && "Result type is expected"); + auto* type = context()->get_type_mgr()->GetType(type_id); + auto* null_const = context()->get_constant_mgr()->GetConstant(type, {}); + return context()->get_constant_mgr()->GetDefiningInstruction(null_const); +} + +void ReplaceDescArrayAccessUsingVarIndex::AddSwitchForAccessChain( + BasicBlock* parent_block, uint32_t access_chain_index_var_id, + uint32_t default_id, uint32_t merge_id, + const std::vector<uint32_t>& case_block_ids) const { + InstructionBuilder builder{context(), parent_block, + kAnalysisDefUseAndInstrToBlockMapping}; + std::vector<std::pair<Operand::OperandData, uint32_t>> cases; + for (uint32_t i = 0; i < static_cast<uint32_t>(case_block_ids.size()); ++i) { + cases.emplace_back(Operand::OperandData{i}, case_block_ids[i]); + } + builder.AddSwitch(access_chain_index_var_id, default_id, cases, merge_id); +} + +uint32_t ReplaceDescArrayAccessUsingVarIndex::CreatePhiInstruction( + BasicBlock* parent_block, const std::vector<uint32_t>& phi_operands, + const std::vector<uint32_t>& case_block_ids, + uint32_t default_block_id) const { + std::vector<uint32_t> incomings; + assert(case_block_ids.size() + 1 == phi_operands.size() && + "Number of Phi operands must be exactly 1 bigger than the one of case " + "blocks"); + for (size_t i = 0; i < case_block_ids.size(); ++i) { + incomings.push_back(phi_operands[i]); + incomings.push_back(case_block_ids[i]); + } + incomings.push_back(phi_operands.back()); + incomings.push_back(default_block_id); + + InstructionBuilder builder{context(), &*parent_block->begin(), + kAnalysisDefUseAndInstrToBlockMapping}; + uint32_t phi_result_type_id = + context()->get_def_use_mgr()->GetDef(phi_operands[0])->type_id(); + auto* phi = builder.AddPhi(phi_result_type_id, incomings); + return phi->result_id(); +} + +void ReplaceDescArrayAccessUsingVarIndex::ReplacePhiIncomingBlock( + uint32_t old_incoming_block_id, uint32_t new_incoming_block_id) const { + context()->ReplaceAllUsesWithPredicate( + old_incoming_block_id, new_incoming_block_id, + [](Instruction* use) { return use->opcode() == SpvOpPhi; }); +} + +} // namespace opt +} // namespace spvtools diff --git a/source/opt/replace_desc_array_access_using_var_index.h b/source/opt/replace_desc_array_access_using_var_index.h new file mode 100644 index 00000000..e18222c8 --- /dev/null +++ b/source/opt/replace_desc_array_access_using_var_index.h @@ -0,0 +1,204 @@ +// Copyright (c) 2021 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_REPLACE_DESC_VAR_INDEX_ACCESS_H_ +#define SOURCE_OPT_REPLACE_DESC_VAR_INDEX_ACCESS_H_ + +#include <cstdio> +#include <memory> +#include <queue> +#include <unordered_map> +#include <unordered_set> +#include <vector> + +#include "source/opt/function.h" +#include "source/opt/pass.h" +#include "source/opt/type_manager.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class ReplaceDescArrayAccessUsingVarIndex : public Pass { + public: + ReplaceDescArrayAccessUsingVarIndex() {} + + const char* name() const override { + return "replace-desc-array-access-using-var-index"; + } + + Status Process() override; + + IRContext::Analysis GetPreservedAnalyses() override { + return IRContext::kAnalysisDefUse | + IRContext::kAnalysisInstrToBlockMapping | + IRContext::kAnalysisConstants | IRContext::kAnalysisTypes; + } + + private: + // Replaces all acceses to |var| using variable indices with constant + // elements of the array |var|. Creates switch-case statements to determine + // the value of the variable index for all the possible cases. Returns + // whether replacement is done or not. + bool ReplaceVariableAccessesWithConstantElements(Instruction* var) const; + + // Replaces the OpAccessChain or OpInBoundsAccessChain instruction |use| that + // uses the descriptor variable |var| with the OpAccessChain or + // OpInBoundsAccessChain instruction with a constant Indexes operand. + void ReplaceAccessChain(Instruction* var, Instruction* use) const; + + // Updates the first Indexes operand of the OpAccessChain or + // OpInBoundsAccessChain instruction |access_chain| to let it use a constant + // index |const_element_idx|. + void UseConstIndexForAccessChain(Instruction* access_chain, + uint32_t const_element_idx) const; + + // Replaces users of the OpAccessChain or OpInBoundsAccessChain instruction + // |access_chain| that accesses an array descriptor variable using variable + // indices with constant elements. |number_of_elements| is the number + // of array elements. + void ReplaceUsersOfAccessChain(Instruction* access_chain, + uint32_t number_of_elements) const; + + // Puts all the recursive users of |access_chain| with concrete result types + // or the ones without result it in |final_users|. + void CollectRecursiveUsersWithConcreteType( + Instruction* access_chain, std::vector<Instruction*>* final_users) const; + + // Recursively collects the operands of |user_of_image_insts| (and operands + // of the operands) whose result types are images/samplers or pointers/array/ + // struct of them and returns them. + std::deque<Instruction*> CollectRequiredImageInsts( + Instruction* user_of_image_insts) const; + + // Returns whether result type of |inst| is an image/sampler/pointer of image + // or sampler or not. + bool HasImageOrImagePtrType(const Instruction* inst) const; + + // Returns whether |type_inst| is an image/sampler or pointer/array/struct of + // image or sampler or not. + bool IsImageOrImagePtrType(const Instruction* type_inst) const; + + // Returns whether the type with |type_id| is a concrete type or not. + bool IsConcreteType(uint32_t type_id) const; + + // Replaces the non-uniform access to a descriptor variable + // |access_chain_final_user| with OpSwitch instruction and case blocks. Each + // case block will contain a clone of |access_chain| and clones of + // |non_uniform_accesses_to_clone| that are recursively used by + // |access_chain_final_user|. The clone of |access_chain| (or + // OpInBoundsAccessChain) will have a constant index for its first index. The + // OpSwitch instruction will have the cases for the variable index of + // |access_chain| from 0 to |number_of_elements| - 1. + void ReplaceNonUniformAccessWithSwitchCase( + Instruction* access_chain_final_user, Instruction* access_chain, + uint32_t number_of_elements, + const std::deque<Instruction*>& non_uniform_accesses_to_clone) const; + + // Creates and returns a new basic block that contains all instructions of + // |block| after |separation_begin_inst|. The new basic block is added to the + // function in this method. + BasicBlock* SeparateInstructionsIntoNewBlock( + BasicBlock* block, Instruction* separation_begin_inst) const; + + // Creates and returns a new block. + BasicBlock* CreateNewBlock() const; + + // Returns the first operand id of the OpAccessChain or OpInBoundsAccessChain + // instruction |access_chain|. + uint32_t GetFirstIndexOfAccessChain(Instruction* access_chain) const; + + // Adds a clone of the OpAccessChain or OpInBoundsAccessChain instruction + // |access_chain| to |case_block|. The clone of |access_chain| will use + // |const_element_idx| for its first index. |old_ids_to_new_ids| keeps the + // mapping from the result id of |access_chain| to the result of its clone. + void AddConstElementAccessToCaseBlock( + BasicBlock* case_block, Instruction* access_chain, + uint32_t const_element_idx, + std::unordered_map<uint32_t, uint32_t>* old_ids_to_new_ids) const; + + // Clones all instructions in |insts_to_be_cloned| and put them to |block|. + // |old_ids_to_new_ids| keeps the mapping from the result id of each + // instruction of |insts_to_be_cloned| to the result of their clones. + void CloneInstsToBlock( + BasicBlock* block, Instruction* inst_to_skip_cloning, + const std::deque<Instruction*>& insts_to_be_cloned, + std::unordered_map<uint32_t, uint32_t>* old_ids_to_new_ids) const; + + // Adds OpBranch to |branch_destination| at the end of |parent_block|. + void AddBranchToBlock(BasicBlock* parent_block, + uint32_t branch_destination) const; + + // Replaces in-operands of all instructions in the basic block |block| using + // |old_ids_to_new_ids|. It conducts the replacement only if the in-operand + // id is a key of |old_ids_to_new_ids|. + void UseNewIdsInBlock( + BasicBlock* block, + const std::unordered_map<uint32_t, uint32_t>& old_ids_to_new_ids) const; + + // Creates a case block for |element_index| case. It adds clones of + // |insts_to_be_cloned| and a clone of |access_chain| with |element_index| as + // its first index. The termination instruction of the created case block will + // be a branch to |branch_target_id|. Puts old ids to new ids map for the + // cloned instructions in |old_ids_to_new_ids|. + BasicBlock* CreateCaseBlock( + Instruction* access_chain, uint32_t element_index, + const std::deque<Instruction*>& insts_to_be_cloned, + uint32_t branch_target_id, + std::unordered_map<uint32_t, uint32_t>* old_ids_to_new_ids) const; + + // Creates a default block for switch-case statement that has only a single + // instruction OpBranch whose target is a basic block with |merge_block_id|. + // If |null_const_for_phi_is_needed| is true, gets or creates a default null + // constant value for a phi instruction whose operands are |phi_operands| and + // puts it in |phi_operands|. + BasicBlock* CreateDefaultBlock(bool null_const_for_phi_is_needed, + std::vector<uint32_t>* phi_operands, + uint32_t merge_block_id) const; + + // Creates and adds an OpSwitch used for the selection of OpAccessChain whose + // first Indexes operand is |access_chain_index_var_id|. The OpSwitch will be + // added at the end of |parent_block|. It will jump to |default_id| for the + // default case and jumps to one of case blocks whoes ids are |case_block_ids| + // if |access_chain_index_var_id| matches the case number. |merge_id| is the + // merge block id. + void AddSwitchForAccessChain( + BasicBlock* parent_block, uint32_t access_chain_index_var_id, + uint32_t default_id, uint32_t merge_id, + const std::vector<uint32_t>& case_block_ids) const; + + // Creates a phi instruction with |phi_operands| as values and + // |case_block_ids| and |default_block_id| as incoming blocks. The size of + // |phi_operands| must be exactly 1 larger than the size of |case_block_ids|. + // The last element of |phi_operands| will be used for |default_block_id|. It + // adds the phi instruction to the beginning of |parent_block|. + uint32_t CreatePhiInstruction(BasicBlock* parent_block, + const std::vector<uint32_t>& phi_operands, + const std::vector<uint32_t>& case_block_ids, + uint32_t default_block_id) const; + + // Replaces the incoming block operand of OpPhi instructions with + // |new_incoming_block_id| if the incoming block operand is + // |old_incoming_block_id|. + void ReplacePhiIncomingBlock(uint32_t old_incoming_block_id, + uint32_t new_incoming_block_id) const; + + // Create an OpConstantNull instruction whose result type id is |type_id|. + Instruction* GetConstNull(uint32_t type_id) const; +}; + +} // namespace opt +} // namespace spvtools + +#endif // SOURCE_OPT_REPLACE_DESC_VAR_INDEX_ACCESS_H_ diff --git a/source/opt/replace_invalid_opc.cpp b/source/opt/replace_invalid_opc.cpp index 38b7539b..e3b9d3e4 100644 --- a/source/opt/replace_invalid_opc.cpp +++ b/source/opt/replace_invalid_opc.cpp @@ -71,10 +71,10 @@ bool ReplaceInvalidOpcodePass::RewriteFunction(Function* function, function->ForEachInst( [model, &modified, &last_line_dbg_inst, this](Instruction* inst) { // Track the debug information so we can have a meaningful message. - if (inst->opcode() == SpvOpLabel || inst->opcode() == SpvOpNoLine) { + if (inst->opcode() == SpvOpLabel || inst->IsNoLine()) { last_line_dbg_inst = nullptr; return; - } else if (inst->opcode() == SpvOpLine) { + } else if (inst->IsLine()) { last_line_dbg_inst = inst; return; } @@ -100,8 +100,18 @@ bool ReplaceInvalidOpcodePass::RewriteFunction(Function* function, ReplaceInstruction(inst, nullptr, 0, 0); } else { // Get the name of the source file. - Instruction* file_name = context()->get_def_use_mgr()->GetDef( - last_line_dbg_inst->GetSingleWordInOperand(0)); + uint32_t file_name_id = 0; + if (last_line_dbg_inst->opcode() == SpvOpLine) { + file_name_id = last_line_dbg_inst->GetSingleWordInOperand(0); + } else { // Shader100::DebugLine + uint32_t debug_source_id = + last_line_dbg_inst->GetSingleWordInOperand(2); + Instruction* debug_source_inst = + context()->get_def_use_mgr()->GetDef(debug_source_id); + file_name_id = debug_source_inst->GetSingleWordInOperand(2); + } + Instruction* file_name = + context()->get_def_use_mgr()->GetDef(file_name_id); const char* source = reinterpret_cast<const char*>( &file_name->GetInOperand(0).words[0]); diff --git a/source/opt/scalar_replacement_pass.cpp b/source/opt/scalar_replacement_pass.cpp index 8adca7b9..4d6a7aad 100644 --- a/source/opt/scalar_replacement_pass.cpp +++ b/source/opt/scalar_replacement_pass.cpp @@ -27,6 +27,7 @@ static const uint32_t kDebugValueOperandValueIndex = 5; static const uint32_t kDebugValueOperandExpressionIndex = 6; +static const uint32_t kDebugDeclareOperandVariableIndex = 5; namespace spvtools { namespace opt { @@ -34,6 +35,10 @@ namespace opt { Pass::Status ScalarReplacementPass::Process() { Status status = Status::SuccessWithoutChange; for (auto& f : *get_module()) { + if (f.IsDeclaration()) { + continue; + } + Status functionStatus = ProcessFunction(&f); if (functionStatus == Status::Failure) return functionStatus; @@ -172,10 +177,14 @@ bool ScalarReplacementPass::ReplaceWholeDebugDeclare( // Add DebugValue instruction with Indexes operand and Deref operation. int32_t idx = 0; for (const auto* var : replacements) { + Instruction* insert_before = var->NextNode(); + while (insert_before->opcode() == SpvOpVariable) + insert_before = insert_before->NextNode(); + assert(insert_before != nullptr && "unexpected end of list"); Instruction* added_dbg_value = context()->get_debug_info_mgr()->AddDebugValueForDecl( dbg_decl, /*value_id=*/var->result_id(), - /*insert_before=*/var->NextNode(), /*scope_and_line=*/dbg_decl); + /*insert_before=*/insert_before, /*scope_and_line=*/dbg_decl); if (added_dbg_value == nullptr) return false; added_dbg_value->AddOperand( @@ -864,6 +873,11 @@ bool ScalarReplacementPass::CheckUsesRelaxed(const Instruction* inst) const { case SpvOpImageTexelPointer: if (!CheckImageTexelPointer(index)) ok = false; break; + case SpvOpExtInst: + if (user->GetCommonDebugOpcode() != CommonDebugInfoDebugDeclare || + !CheckDebugDeclare(index)) + ok = false; + break; default: ok = false; break; @@ -894,6 +908,12 @@ bool ScalarReplacementPass::CheckStore(const Instruction* inst, return false; return true; } + +bool ScalarReplacementPass::CheckDebugDeclare(uint32_t index) const { + if (index != kDebugDeclareOperandVariableIndex) return false; + return true; +} + bool ScalarReplacementPass::IsLargerThanSizeLimit(uint64_t length) const { if (max_num_elements_ == 0) { return false; diff --git a/source/opt/scalar_replacement_pass.h b/source/opt/scalar_replacement_pass.h index 9e9f0739..0928830c 100644 --- a/source/opt/scalar_replacement_pass.h +++ b/source/opt/scalar_replacement_pass.h @@ -142,6 +142,9 @@ class ScalarReplacementPass : public Pass { // of |inst| and the store is not to volatile memory. bool CheckStore(const Instruction* inst, uint32_t index) const; + // Returns true if the DebugDeclare can be scalarized at |index|. + bool CheckDebugDeclare(uint32_t index) const; + // Returns true if |index| is the pointer operand of an OpImageTexelPointer // instruction. bool CheckImageTexelPointer(uint32_t index) const; diff --git a/source/opt/set_spec_constant_default_value_pass.cpp b/source/opt/set_spec_constant_default_value_pass.cpp index 4c8d116f..4def2b09 100644 --- a/source/opt/set_spec_constant_default_value_pass.cpp +++ b/source/opt/set_spec_constant_default_value_pass.cpp @@ -85,6 +85,10 @@ std::vector<uint32_t> ParseDefaultValueStr(const char* text, // 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'. +// For integer and floating point types narrower than 32 bits, the upper bits +// in the input bit pattern are ignored. Instead the upper bits are set +// according to SPIR-V literal requirements: sign extend a signed integer, and +// otherwise set the upper bits to zero. std::vector<uint32_t> ParseDefaultValueBitPattern( const std::vector<uint32_t>& input_bit_pattern, const analysis::Type* type) { @@ -98,12 +102,33 @@ std::vector<uint32_t> ParseDefaultValueBitPattern( } return result; } else if (const auto* IT = type->AsInteger()) { - if (IT->width() == input_bit_pattern.size() * sizeof(uint32_t) * 8) { - return std::vector<uint32_t>(input_bit_pattern); + const auto width = IT->width(); + assert(width > 0); + const auto adjusted_width = std::max(32u, width); + if (adjusted_width == input_bit_pattern.size() * sizeof(uint32_t) * 8) { + result = std::vector<uint32_t>(input_bit_pattern); + if (width < 32) { + const uint32_t high_active_bit = (1u << width) >> 1; + if (IT->IsSigned() && (high_active_bit & result[0])) { + // Sign extend. This overwrites the sign bit again, but that's ok. + result[0] = result[0] | ~(high_active_bit - 1); + } else { + // Upper bits must be zero. + result[0] = result[0] & ((1u << width) - 1); + } + } + return result; } } else if (const auto* FT = type->AsFloat()) { - if (FT->width() == input_bit_pattern.size() * sizeof(uint32_t) * 8) { - return std::vector<uint32_t>(input_bit_pattern); + const auto width = FT->width(); + const auto adjusted_width = std::max(32u, width); + if (adjusted_width == input_bit_pattern.size() * sizeof(uint32_t) * 8) { + result = std::vector<uint32_t>(input_bit_pattern); + if (width < 32) { + // Upper bits must be zero. + result[0] = result[0] & ((1u << width) - 1); + } + return result; } } result.clear(); diff --git a/source/opt/simplification_pass.cpp b/source/opt/simplification_pass.cpp index 319ceecf..43ec15f8 100644 --- a/source/opt/simplification_pass.cpp +++ b/source/opt/simplification_pass.cpp @@ -45,6 +45,10 @@ void SimplificationPass::AddNewOperands( } bool SimplificationPass::SimplifyFunction(Function* function) { + if (function->IsDeclaration()) { + return false; + } + bool modified = false; // Phase 1: Traverse all instructions in dominance order. // The second phase will only be on the instructions whose inputs have changed diff --git a/source/opt/ssa_rewrite_pass.cpp b/source/opt/ssa_rewrite_pass.cpp index 81770d77..29ab6123 100644 --- a/source/opt/ssa_rewrite_pass.cpp +++ b/source/opt/ssa_rewrite_pass.cpp @@ -753,6 +753,9 @@ Pass::Status SSARewriter::RewriteFunctionIntoSSA(Function* fp) { Pass::Status SSARewritePass::Process() { Status status = Status::SuccessWithoutChange; for (auto& fn : *get_module()) { + if (fn.IsDeclaration()) { + continue; + } status = CombineStatus(status, SSARewriter(this).RewriteFunctionIntoSSA(&fn)); // Kill DebugDeclares for target variables. diff --git a/source/opt/vector_dce.cpp b/source/opt/vector_dce.cpp index 3c9f9446..28d94a07 100644 --- a/source/opt/vector_dce.cpp +++ b/source/opt/vector_dce.cpp @@ -110,7 +110,11 @@ void VectorDCE::MarkExtractUseAsLive(const Instruction* current_inst, if (current_inst->NumInOperands() < 2) { new_item.components = live_elements; } else { - new_item.components.Set(current_inst->GetSingleWordInOperand(1)); + uint32_t element_index = current_inst->GetSingleWordInOperand(1); + uint32_t item_size = GetVectorComponentCount(operand_inst->type_id()); + if (element_index < item_size) { + new_item.components.Set(element_index); + } } AddItemToWorkListIfNeeded(new_item, live_components, work_list); } @@ -176,10 +180,10 @@ void VectorDCE::MarkVectorShuffleUsesAsLive( second_operand.instruction = def_use_mgr->GetDef(current_item.instruction->GetSingleWordInOperand(1)); - analysis::TypeManager* type_mgr = context()->get_type_mgr(); - analysis::Vector* first_type = - type_mgr->GetType(first_operand.instruction->type_id())->AsVector(); - uint32_t size_of_first_operand = first_type->element_count(); + uint32_t size_of_first_operand = + GetVectorComponentCount(first_operand.instruction->type_id()); + uint32_t size_of_second_operand = + GetVectorComponentCount(second_operand.instruction->type_id()); for (uint32_t in_op = 2; in_op < current_item.instruction->NumInOperands(); ++in_op) { @@ -187,7 +191,7 @@ void VectorDCE::MarkVectorShuffleUsesAsLive( if (current_item.components.Get(in_op - 2)) { if (index < size_of_first_operand) { first_operand.components.Set(index); - } else { + } else if (index - size_of_first_operand < size_of_second_operand) { second_operand.components.Set(index - size_of_first_operand); } } @@ -202,7 +206,6 @@ void VectorDCE::MarkCompositeContructUsesAsLive( VectorDCE::LiveComponentMap* live_components, std::vector<VectorDCE::WorkListItem>* work_list) { analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); - analysis::TypeManager* type_mgr = context()->get_type_mgr(); uint32_t current_component = 0; Instruction* current_inst = work_item.instruction; @@ -223,8 +226,7 @@ void VectorDCE::MarkCompositeContructUsesAsLive( assert(HasVectorResult(op_inst)); WorkListItem new_work_item; new_work_item.instruction = op_inst; - uint32_t op_vector_size = - type_mgr->GetType(op_inst->type_id())->AsVector()->element_count(); + uint32_t op_vector_size = GetVectorComponentCount(op_inst->type_id()); for (uint32_t op_vector_idx = 0; op_vector_idx < op_vector_size; op_vector_idx++, current_component++) { @@ -297,6 +299,18 @@ bool VectorDCE::HasScalarResult(const Instruction* inst) const { } } +uint32_t VectorDCE::GetVectorComponentCount(uint32_t type_id) { + assert(type_id != 0 && + "Trying to get the vector element count, but the type id is 0"); + analysis::TypeManager* type_mgr = context()->get_type_mgr(); + const analysis::Type* type = type_mgr->GetType(type_id); + const analysis::Vector* vector_type = type->AsVector(); + assert( + vector_type && + "Trying to get the vector element count, but the type is not a vector"); + return vector_type->element_count(); +} + bool VectorDCE::RewriteInstructions( Function* function, const VectorDCE::LiveComponentMap& live_components) { bool modified = false; diff --git a/source/opt/vector_dce.h b/source/opt/vector_dce.h index 0df9aee1..4d30b926 100644 --- a/source/opt/vector_dce.h +++ b/source/opt/vector_dce.h @@ -94,12 +94,15 @@ class VectorDCE : public MemPass { // Returns true if the result of |inst| is a vector or a scalar. bool HasVectorOrScalarResult(const Instruction* inst) const; - // Returns true if the result of |inst| is a scalar. + // Returns true if the result of |inst| is a vector. bool HasVectorResult(const Instruction* inst) const; - // Returns true if the result of |inst| is a vector. + // Returns true if the result of |inst| is a scalar. bool HasScalarResult(const Instruction* inst) const; + // Returns the number of elements in the vector type with id |type_id|. + uint32_t GetVectorComponentCount(uint32_t type_id); + // Adds |work_item| to |work_list| if it is not already live according to // |live_components|. |live_components| is updated to indicate that // |work_item| is now live. diff --git a/source/spirv_constant.h b/source/spirv_constant.h index 39771ccb..8636806c 100644 --- a/source/spirv_constant.h +++ b/source/spirv_constant.h @@ -84,6 +84,7 @@ typedef enum spv_generator_t { SPV_GENERATOR_KHRONOS_LLVM_TRANSLATOR = 6, SPV_GENERATOR_KHRONOS_ASSEMBLER = 7, SPV_GENERATOR_KHRONOS_GLSLANG = 8, + SPV_GENERATOR_KHRONOS_LINKER = 17, SPV_GENERATOR_NUM_ENTRIES, SPV_FORCE_16_BIT_ENUM(spv_generator_t) } spv_generator_t; diff --git a/source/spirv_target_env.cpp b/source/spirv_target_env.cpp index f20ebb4f..187ab61e 100644 --- a/source/spirv_target_env.cpp +++ b/source/spirv_target_env.cpp @@ -62,7 +62,7 @@ const char* spvTargetEnvDescription(spv_target_env env) { case SPV_ENV_VULKAN_1_1: return "SPIR-V 1.3 (under Vulkan 1.1 semantics)"; case SPV_ENV_WEBGPU_0: - assert(false); + assert(false && "Deprecated target environment value."); break; case SPV_ENV_UNIVERSAL_1_4: return "SPIR-V 1.4"; @@ -72,6 +72,9 @@ const char* spvTargetEnvDescription(spv_target_env env) { return "SPIR-V 1.5"; case SPV_ENV_VULKAN_1_2: return "SPIR-V 1.5 (under Vulkan 1.2 semantics)"; + case SPV_ENV_MAX: + assert(false && "Invalid target environment value."); + break; } return ""; } @@ -102,7 +105,7 @@ uint32_t spvVersionForTargetEnv(spv_target_env env) { case SPV_ENV_VULKAN_1_1: return SPV_SPIRV_VERSION_WORD(1, 3); case SPV_ENV_WEBGPU_0: - assert(false); + assert(false && "Deprecated target environment value."); break; case SPV_ENV_UNIVERSAL_1_4: case SPV_ENV_VULKAN_1_1_SPIRV_1_4: @@ -110,6 +113,9 @@ uint32_t spvVersionForTargetEnv(spv_target_env env) { case SPV_ENV_UNIVERSAL_1_5: case SPV_ENV_VULKAN_1_2: return SPV_SPIRV_VERSION_WORD(1, 5); + case SPV_ENV_MAX: + assert(false && "Invalid target environment value."); + break; } return SPV_SPIRV_VERSION_WORD(0, 0); } @@ -212,7 +218,10 @@ bool spvIsVulkanEnv(spv_target_env env) { case SPV_ENV_VULKAN_1_2: return true; case SPV_ENV_WEBGPU_0: - assert(false); + assert(false && "Deprecated target environment value."); + break; + case SPV_ENV_MAX: + assert(false && "Invalid target environment value."); break; } return false; @@ -246,7 +255,10 @@ bool spvIsOpenCLEnv(spv_target_env env) { case SPV_ENV_OPENCL_2_2: return true; case SPV_ENV_WEBGPU_0: - assert(false); + assert(false && "Deprecated target environment value."); + break; + case SPV_ENV_MAX: + assert(false && "Invalid target environment value."); break; } return false; @@ -280,7 +292,43 @@ bool spvIsOpenGLEnv(spv_target_env env) { case SPV_ENV_OPENGL_4_5: return true; case SPV_ENV_WEBGPU_0: - assert(false); + assert(false && "Deprecated target environment value."); + break; + case SPV_ENV_MAX: + assert(false && "Invalid target environment value."); + break; + } + return false; +} + +bool spvIsValidEnv(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_UNIVERSAL_1_2: + case SPV_ENV_UNIVERSAL_1_3: + case SPV_ENV_VULKAN_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_EMBEDDED_2_1: + case SPV_ENV_OPENCL_EMBEDDED_2_2: + case SPV_ENV_OPENCL_2_1: + case SPV_ENV_OPENCL_2_2: + case SPV_ENV_UNIVERSAL_1_4: + case SPV_ENV_VULKAN_1_1_SPIRV_1_4: + case SPV_ENV_UNIVERSAL_1_5: + case SPV_ENV_VULKAN_1_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: + return true; + case SPV_ENV_WEBGPU_0: + case SPV_ENV_MAX: break; } return false; @@ -320,7 +368,10 @@ std::string spvLogStringForEnv(spv_target_env env) { return "Universal"; } case SPV_ENV_WEBGPU_0: - assert(false); + assert(false && "Deprecated target environment value."); + break; + case SPV_ENV_MAX: + assert(false && "Invalid target environment value."); break; } return "Unknown"; diff --git a/source/spirv_target_env.h b/source/spirv_target_env.h index a804d615..cc06deca 100644 --- a/source/spirv_target_env.h +++ b/source/spirv_target_env.h @@ -28,6 +28,9 @@ bool spvIsOpenCLEnv(spv_target_env env); // Returns true if |env| is an OPENGL environment, false otherwise. bool spvIsOpenGLEnv(spv_target_env env); +// Returns true if |env| is an implemented/valid environment, false otherwise. +bool spvIsValidEnv(spv_target_env env); + // Returns the version number for the given SPIR-V target environment. uint32_t spvVersionForTargetEnv(spv_target_env env); diff --git a/source/spirv_validator_options.cpp b/source/spirv_validator_options.cpp index 2716cca9..e5b1eece 100644 --- a/source/spirv_validator_options.cpp +++ b/source/spirv_validator_options.cpp @@ -120,3 +120,8 @@ void spvValidatorOptionsSetSkipBlockLayout(spv_validator_options options, bool val) { options->skip_block_layout = val; } + +void spvValidatorOptionsSetAllowLocalSizeId(spv_validator_options options, + bool val) { + options->allow_localsizeid = val; +} diff --git a/source/spirv_validator_options.h b/source/spirv_validator_options.h index baaa5359..a357c031 100644 --- a/source/spirv_validator_options.h +++ b/source/spirv_validator_options.h @@ -47,6 +47,7 @@ struct spv_validator_options_t { scalar_block_layout(false), workgroup_scalar_block_layout(false), skip_block_layout(false), + allow_localsizeid(false), before_hlsl_legalization(false) {} validator_universal_limits_t universal_limits_; @@ -57,6 +58,7 @@ struct spv_validator_options_t { bool scalar_block_layout; bool workgroup_scalar_block_layout; bool skip_block_layout; + bool allow_localsizeid; bool before_hlsl_legalization; }; diff --git a/source/text_handler.cpp b/source/text_handler.cpp index c31f34a6..46b98456 100644 --- a/source/text_handler.cpp +++ b/source/text_handler.cpp @@ -120,7 +120,8 @@ spv_result_t getWord(spv_text text, spv_position position, std::string* word) { case '\n': case '\r': if (escaping || quoting) break; - // Fall through. + word->assign(text->str + start_index, text->str + position->index); + return SPV_SUCCESS; case '\0': { // NOTE: End of word found! word->assign(text->str + start_index, text->str + position->index); return SPV_SUCCESS; diff --git a/source/util/hex_float.h b/source/util/hex_float.h index cfc40fa6..be28eae3 100644 --- a/source/util/hex_float.h +++ b/source/util/hex_float.h @@ -1005,6 +1005,9 @@ std::istream& operator>>(std::istream& is, HexFloat<T, Traits>& value) { is.get(); next_char = is.peek(); } + + // Finished reading the part preceding any '.' or 'p'. + bits_written = false; while (seen_dot && !seen_p) { // Handle only fractional parts now. @@ -1037,11 +1040,16 @@ std::istream& operator>>(std::istream& is, HexFloat<T, Traits>& value) { next_char = is.peek(); } + // Finished reading the part preceding 'p'. + // In hex floats syntax, the binary exponent is required. + bool seen_sign = false; int8_t exponent_sign = 1; + bool seen_written_exponent_digits = false; int_type written_exponent = 0; while (true) { - if ((next_char == '-' || next_char == '+')) { + if (!seen_written_exponent_digits && + (next_char == '-' || next_char == '+')) { if (seen_sign) { is.setstate(std::ios::failbit); return is; @@ -1049,6 +1057,7 @@ std::istream& operator>>(std::istream& is, HexFloat<T, Traits>& value) { seen_sign = true; exponent_sign = (next_char == '-') ? -1 : 1; } else if (::isdigit(next_char)) { + seen_written_exponent_digits = true; // Hex-floats express their exponent as decimal. written_exponent = static_cast<int_type>(written_exponent * 10); written_exponent = @@ -1059,6 +1068,11 @@ std::istream& operator>>(std::istream& is, HexFloat<T, Traits>& value) { is.get(); next_char = is.peek(); } + if (!seen_written_exponent_digits) { + // Binary exponent had no digits. + is.setstate(std::ios::failbit); + return is; + } written_exponent = static_cast<int_type>(written_exponent * exponent_sign); exponent = static_cast<int_type>(exponent + written_exponent); diff --git a/source/val/construct.cpp b/source/val/construct.cpp index 53008690..251e2bba 100644 --- a/source/val/construct.cpp +++ b/source/val/construct.cpp @@ -181,8 +181,9 @@ bool Construct::IsStructuredExit(ValidationState_t& _, BasicBlock* dest) const { for (auto& use : block->label()->uses()) { if ((use.first->opcode() == SpvOpLoopMerge || use.first->opcode() == SpvOpSelectionMerge) && - use.second == 1) + use.second == 1 && use.first->block()->dominates(*block)) { return use.first->block(); + } } return block->immediate_dominator(); }; diff --git a/source/val/function.cpp b/source/val/function.cpp index 249c8664..9ad68e86 100644 --- a/source/val/function.cpp +++ b/source/val/function.cpp @@ -308,6 +308,9 @@ int Function::GetBlockDepth(BasicBlock* bb) { if (block_depth_.find(bb) != block_depth_.end()) { return block_depth_[bb]; } + // Avoid recursion. Something is wrong if the same block is encountered + // multiple times. + block_depth_[bb] = 0; BasicBlock* bb_dom = bb->immediate_dominator(); if (!bb_dom || bb == bb_dom) { diff --git a/source/val/validate_adjacency.cpp b/source/val/validate_adjacency.cpp index 13720f0e..8e6c373e 100644 --- a/source/val/validate_adjacency.cpp +++ b/source/val/validate_adjacency.cpp @@ -63,7 +63,7 @@ spv_result_t ValidateAdjacency(ValidationState_t& _) { // NOTE: This does not apply to the non-semantic vulkan debug info. if (!spvExtInstIsDebugInfo(inst.ext_inst_type()) || inst.ext_inst_type() == - SPV_EXT_INST_TYPE_NONSEMANTIC_VULKAN_DEBUGINFO_100) { + SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100) { adjacency_status = PHI_AND_VAR_INVALID; } break; diff --git a/source/val/validate_annotation.cpp b/source/val/validate_annotation.cpp index 85d2b751..3a77552e 100644 --- a/source/val/validate_annotation.cpp +++ b/source/val/validate_annotation.cpp @@ -138,14 +138,14 @@ std::string LogStringForDecoration(uint32_t decoration) { return "PerTaskNV"; case SpvDecorationPerVertexNV: return "PerVertexNV"; - case SpvDecorationNonUniformEXT: - return "NonUniformEXT"; - case SpvDecorationRestrictPointerEXT: - return "RestrictPointerEXT"; - case SpvDecorationAliasedPointerEXT: - return "AliasedPointerEXT"; - case SpvDecorationHlslCounterBufferGOOGLE: - return "HlslCounterBufferGOOGLE"; + case SpvDecorationNonUniform: + return "NonUniform"; + case SpvDecorationRestrictPointer: + return "RestrictPointer"; + case SpvDecorationAliasedPointer: + return "AliasedPointer"; + case SpvDecorationCounterBuffer: + return "CounterBuffer"; case SpvDecorationHlslSemanticGOOGLE: return "HlslSemanticGOOGLE"; default: @@ -156,8 +156,8 @@ std::string LogStringForDecoration(uint32_t decoration) { // Returns true if the decoration takes ID parameters. // TODO(dneto): This can be generated from the grammar. -bool DecorationTakesIdParameters(uint32_t type) { - switch (static_cast<SpvDecoration>(type)) { +bool DecorationTakesIdParameters(SpvDecoration type) { + switch (type) { case SpvDecorationUniformId: case SpvDecorationAlignmentId: case SpvDecorationMaxByteOffsetId: @@ -169,17 +169,213 @@ bool DecorationTakesIdParameters(uint32_t type) { return false; } -spv_result_t ValidateDecorate(ValidationState_t& _, const Instruction* inst) { - const auto decoration = inst->GetOperandAs<uint32_t>(1); - if (decoration == SpvDecorationSpecId) { - const auto target_id = inst->GetOperandAs<uint32_t>(0); - const auto target = _.FindDef(target_id); - if (!target || !spvOpcodeIsScalarSpecConstant(target->opcode())) { - return _.diag(SPV_ERROR_INVALID_ID, inst) - << "OpDecorate SpecId decoration target <id> '" - << _.getIdName(target_id) - << "' is not a scalar specialization constant."; +bool IsMemberDecorationOnly(SpvDecoration dec) { + switch (dec) { + case SpvDecorationRowMajor: + case SpvDecorationColMajor: + case SpvDecorationMatrixStride: + // SPIR-V spec bug? Offset is generated on variables when dealing with + // transform feedback. + // case SpvDecorationOffset: + return true; + default: + break; + } + return false; +} + +bool IsNotMemberDecoration(SpvDecoration dec) { + switch (dec) { + case SpvDecorationSpecId: + case SpvDecorationBlock: + case SpvDecorationBufferBlock: + case SpvDecorationArrayStride: + case SpvDecorationGLSLShared: + case SpvDecorationGLSLPacked: + case SpvDecorationCPacked: + // TODO: https://github.com/KhronosGroup/glslang/issues/703: + // glslang applies Restrict to structure members. + // case SpvDecorationRestrict: + case SpvDecorationAliased: + case SpvDecorationConstant: + case SpvDecorationUniform: + case SpvDecorationUniformId: + case SpvDecorationSaturatedConversion: + case SpvDecorationIndex: + case SpvDecorationBinding: + case SpvDecorationDescriptorSet: + case SpvDecorationFuncParamAttr: + case SpvDecorationFPRoundingMode: + case SpvDecorationFPFastMathMode: + case SpvDecorationLinkageAttributes: + case SpvDecorationNoContraction: + case SpvDecorationInputAttachmentIndex: + case SpvDecorationAlignment: + case SpvDecorationMaxByteOffset: + case SpvDecorationAlignmentId: + case SpvDecorationMaxByteOffsetId: + case SpvDecorationNoSignedWrap: + case SpvDecorationNoUnsignedWrap: + case SpvDecorationNonUniform: + case SpvDecorationRestrictPointer: + case SpvDecorationAliasedPointer: + case SpvDecorationCounterBuffer: + return true; + default: + break; + } + return false; +} + +spv_result_t ValidateDecorationTarget(ValidationState_t& _, SpvDecoration dec, + const Instruction* inst, + const Instruction* target) { + auto fail = [&_, dec, inst, target](uint32_t vuid = 0) -> DiagnosticStream { + DiagnosticStream ds = std::move( + _.diag(SPV_ERROR_INVALID_ID, inst) + << _.VkErrorID(vuid) << LogStringForDecoration(dec) + << " decoration on target <id> '" << _.getIdName(target->id()) << "' "); + return ds; + }; + switch (dec) { + case SpvDecorationSpecId: + if (!spvOpcodeIsScalarSpecConstant(target->opcode())) { + return fail() << "must be a scalar specialization constant"; + } + break; + case SpvDecorationBlock: + case SpvDecorationBufferBlock: + case SpvDecorationGLSLShared: + case SpvDecorationGLSLPacked: + case SpvDecorationCPacked: + if (target->opcode() != SpvOpTypeStruct) { + return fail() << "must be a structure type"; + } + break; + case SpvDecorationArrayStride: + if (target->opcode() != SpvOpTypeArray && + target->opcode() != SpvOpTypeRuntimeArray && + target->opcode() != SpvOpTypePointer) { + return fail() << "must be an array or pointer type"; + } + break; + case SpvDecorationBuiltIn: + if (target->opcode() != SpvOpVariable && + !spvOpcodeIsConstant(target->opcode())) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "BuiltIns can only target variables, structure members or " + "constants"; + } + if (_.HasCapability(SpvCapabilityShader) && + inst->GetOperandAs<SpvBuiltIn>(2) == SpvBuiltInWorkgroupSize) { + if (!spvOpcodeIsConstant(target->opcode())) { + return fail() << "must be a constant for WorkgroupSize"; + } + } else if (target->opcode() != SpvOpVariable) { + return fail() << "must be a variable"; + } + break; + case SpvDecorationNoPerspective: + case SpvDecorationFlat: + case SpvDecorationPatch: + case SpvDecorationCentroid: + case SpvDecorationSample: + case SpvDecorationRestrict: + case SpvDecorationAliased: + case SpvDecorationVolatile: + case SpvDecorationCoherent: + case SpvDecorationNonWritable: + case SpvDecorationNonReadable: + case SpvDecorationXfbBuffer: + case SpvDecorationXfbStride: + case SpvDecorationComponent: + case SpvDecorationStream: + case SpvDecorationRestrictPointer: + case SpvDecorationAliasedPointer: + if (target->opcode() != SpvOpVariable && + target->opcode() != SpvOpFunctionParameter) { + return fail() << "must be a memory object declaration"; + } + if (_.GetIdOpcode(target->type_id()) != SpvOpTypePointer) { + return fail() << "must be a pointer type"; + } + break; + case SpvDecorationInvariant: + case SpvDecorationConstant: + case SpvDecorationLocation: + case SpvDecorationIndex: + case SpvDecorationBinding: + case SpvDecorationDescriptorSet: + case SpvDecorationInputAttachmentIndex: + if (target->opcode() != SpvOpVariable) { + return fail() << "must be a variable"; + } + break; + default: + break; + } + + if (spvIsVulkanEnv(_.context()->target_env)) { + // The following were all checked as pointer types above. + SpvStorageClass sc = SpvStorageClassUniform; + const auto type = _.FindDef(target->type_id()); + if (type && type->operands().size() > 2) { + sc = type->GetOperandAs<SpvStorageClass>(1); } + switch (dec) { + case SpvDecorationLocation: + case SpvDecorationComponent: + // Location is used for input, output and ray tracing stages. + if (sc == SpvStorageClassStorageBuffer || + sc == SpvStorageClassUniform || + sc == SpvStorageClassUniformConstant || + sc == SpvStorageClassWorkgroup || sc == SpvStorageClassPrivate || + sc == SpvStorageClassFunction) { + return _.diag(SPV_ERROR_INVALID_ID, target) + << LogStringForDecoration(dec) + << " decoration must not be applied to this storage class"; + } + break; + case SpvDecorationIndex: + if (sc != SpvStorageClassOutput) { + return fail() << "must be in the Output storage class"; + } + break; + case SpvDecorationBinding: + case SpvDecorationDescriptorSet: + if (sc != SpvStorageClassStorageBuffer && + sc != SpvStorageClassUniform && + sc != SpvStorageClassUniformConstant) { + return fail() << "must be in the StorageBuffer, Uniform, or " + "UniformConstant storage class"; + } + break; + case SpvDecorationInputAttachmentIndex: + if (sc != SpvStorageClassUniformConstant) { + return fail() << "must be in the UniformConstant storage class"; + } + break; + case SpvDecorationFlat: + case SpvDecorationNoPerspective: + case SpvDecorationCentroid: + case SpvDecorationSample: + if (sc != SpvStorageClassInput && sc != SpvStorageClassOutput) { + return fail(4670) << "storage class must be Input or Output"; + } + break; + default: + break; + } + } + return SPV_SUCCESS; +} + +spv_result_t ValidateDecorate(ValidationState_t& _, const Instruction* inst) { + const auto decoration = inst->GetOperandAs<SpvDecoration>(1); + const auto target_id = inst->GetOperandAs<uint32_t>(0); + const auto target = _.FindDef(target_id); + if (!target) { + return _.diag(SPV_ERROR_INVALID_ID, inst) << "target is not defined"; } if (spvIsVulkanEnv(_.context()->target_env)) { @@ -197,17 +393,34 @@ spv_result_t ValidateDecorate(ValidationState_t& _, const Instruction* inst) { << "Decorations taking ID parameters may not be used with " "OpDecorateId"; } + + if (target->opcode() != SpvOpDecorationGroup) { + if (IsMemberDecorationOnly(decoration)) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << LogStringForDecoration(decoration) + << " can only be applied to structure members"; + } + + if (auto error = ValidateDecorationTarget(_, decoration, inst, target)) { + return error; + } + } + // TODO: Add validations for all decorations. return SPV_SUCCESS; } spv_result_t ValidateDecorateId(ValidationState_t& _, const Instruction* inst) { - const auto decoration = inst->GetOperandAs<uint32_t>(1); + const auto decoration = inst->GetOperandAs<SpvDecoration>(1); if (!DecorationTakesIdParameters(decoration)) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "Decorations that don't take ID parameters may not be used with " "OpDecorateId"; } + + // No member decorations take id parameters, so we don't bother checking if + // we are using a member only decoration here. + // TODO: Add validations for these decorations. // UniformId is covered elsewhere. return SPV_SUCCESS; @@ -234,6 +447,13 @@ spv_result_t ValidateMemberDecorate(ValidationState_t& _, << " members. Largest valid index is " << member_count - 1 << "."; } + const auto decoration = inst->GetOperandAs<SpvDecoration>(2); + if (IsNotMemberDecoration(decoration)) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << LogStringForDecoration(decoration) + << " cannot be applied to structure members"; + } + return SPV_SUCCESS; } diff --git a/source/val/validate_atomics.cpp b/source/val/validate_atomics.cpp index da023b80..cfa15d9f 100644 --- a/source/val/validate_atomics.cpp +++ b/source/val/validate_atomics.cpp @@ -184,7 +184,7 @@ spv_result_t AtomicsPass(ValidationState_t& _, const Instruction* inst) { } // Can't use result_type because OpAtomicStore doesn't have a result - if (_.GetBitWidth(data_type) == 64 && _.IsIntScalarType(data_type) && + if ( _.IsIntScalarType(data_type) &&_.GetBitWidth(data_type) == 64 && !_.HasCapability(SpvCapabilityInt64Atomics)) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << spvOpcodeString(opcode) diff --git a/source/val/validate_builtins.cpp b/source/val/validate_builtins.cpp index a6e624f7..57dde8ad 100644 --- a/source/val/validate_builtins.cpp +++ b/source/val/validate_builtins.cpp @@ -3993,14 +3993,6 @@ spv_result_t BuiltInsValidator::ValidateSingleBuiltInAtDefinition( const Decoration& decoration, const Instruction& inst) { const SpvBuiltIn label = SpvBuiltIn(decoration.params()[0]); - // Builtins can only be applied to variables, structures or constants. - auto target_opcode = inst.opcode(); - if (target_opcode != SpvOpTypeStruct && target_opcode != SpvOpVariable && - !spvOpcodeIsConstant(target_opcode)) { - return _.diag(SPV_ERROR_INVALID_DATA, &inst) - << "BuiltIns can only target variables, structs or constants"; - } - if (!spvIsVulkanEnv(_.context()->target_env)) { // Early return. All currently implemented rules are based on Vulkan spec. // diff --git a/source/val/validate_cfg.cpp b/source/val/validate_cfg.cpp index 36f632a8..7842e56d 100644 --- a/source/val/validate_cfg.cpp +++ b/source/val/validate_cfg.cpp @@ -199,6 +199,18 @@ spv_result_t ValidateSwitch(ValidationState_t& _, const Instruction* inst) { // At least two operands (selector, default), any more than that are // literal/target. + const auto sel_type_id = _.GetOperandTypeId(inst, 0); + if (!_.IsIntScalarType(sel_type_id)) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Selector type must be OpTypeInt"; + } + + const auto default_label = _.FindDef(inst->GetOperandAs<uint32_t>(1)); + if (default_label->opcode() != SpvOpLabel) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Default must be an OpLabel instruction"; + } + // target operands must be OpLabel for (size_t i = 2; i < num_operands; i += 2) { // literal, id @@ -647,9 +659,9 @@ spv_result_t ValidateStructuredSelections( // Mark the upcoming blocks as seen now, but only error out if this block // was missing a merge instruction and both labels hadn't been seen // previously. - const bool both_unseen = - seen.insert(true_label).second && seen.insert(false_label).second; - if (!merge && both_unseen) { + const bool true_label_unseen = seen.insert(true_label).second; + const bool false_label_unseen = seen.insert(false_label).second; + if (!merge && true_label_unseen && false_label_unseen) { return _.diag(SPV_ERROR_INVALID_CFG, terminator) << "Selection must be structured"; } diff --git a/source/val/validate_decorations.cpp b/source/val/validate_decorations.cpp index c483635b..3cdb471c 100644 --- a/source/val/validate_decorations.cpp +++ b/source/val/validate_decorations.cpp @@ -129,18 +129,30 @@ std::vector<uint32_t> getStructMembers(uint32_t struct_id, SpvOp type, // Returns whether the given structure is missing Offset decoration for any // member. Handles also nested structures. bool isMissingOffsetInStruct(uint32_t struct_id, ValidationState_t& vstate) { - std::vector<bool> hasOffset(getStructMembers(struct_id, vstate).size(), - false); - // Check offsets of member decorations - for (auto& decoration : vstate.id_decorations(struct_id)) { - if (SpvDecorationOffset == decoration.dec_type() && - Decoration::kInvalidMember != decoration.struct_member_index()) { - hasOffset[decoration.struct_member_index()] = true; + const auto* inst = vstate.FindDef(struct_id); + std::vector<bool> hasOffset; + std::vector<uint32_t> struct_members; + if (inst->opcode() == SpvOpTypeStruct) { + // Check offsets of member decorations. + struct_members = getStructMembers(struct_id, vstate); + hasOffset.resize(struct_members.size(), false); + + for (auto& decoration : vstate.id_decorations(struct_id)) { + if (SpvDecorationOffset == decoration.dec_type() && + Decoration::kInvalidMember != decoration.struct_member_index()) { + // Offset 0xffffffff is not valid so ignore it for simplicity's sake. + if (decoration.params()[0] == 0xffffffff) return true; + hasOffset[decoration.struct_member_index()] = true; + } } + } else if (inst->opcode() == SpvOpTypeArray || + inst->opcode() == SpvOpTypeRuntimeArray) { + hasOffset.resize(1, true); + struct_members.push_back(inst->GetOperandAs<uint32_t>(1u)); } - // Check also nested structures + // Look through nested structs (which may be in an array). bool nestedStructsMissingOffset = false; - for (auto id : getStructMembers(struct_id, SpvOpTypeStruct, vstate)) { + for (auto id : struct_members) { if (isMissingOffsetInStruct(id, vstate)) { nestedStructsMissingOffset = true; break; @@ -985,7 +997,9 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) { const bool phys_storage_buffer = storageClass == SpvStorageClassPhysicalStorageBufferEXT; - const bool workgroup = storageClass == SpvStorageClassWorkgroup; + const bool workgroup = + storageClass == SpvStorageClassWorkgroup && + vstate.HasCapability(SpvCapabilityWorkgroupMemoryExplicitLayoutKHR); if (uniform || push_constant || storage_buffer || phys_storage_buffer || workgroup) { const auto ptrInst = vstate.FindDef(words[1]); diff --git a/source/val/validate_derivatives.cpp b/source/val/validate_derivatives.cpp index 067cc964..25b941ab 100644 --- a/source/val/validate_derivatives.cpp +++ b/source/val/validate_derivatives.cpp @@ -79,11 +79,13 @@ spv_result_t DerivativesPass(ValidationState_t& _, const Instruction* inst) { std::string* message) { const auto* models = state.GetExecutionModels(entry_point->id()); const auto* modes = state.GetExecutionModes(entry_point->id()); - if (models->find(SpvExecutionModelGLCompute) != models->end() && - modes->find(SpvExecutionModeDerivativeGroupLinearNV) == - modes->end() && - modes->find(SpvExecutionModeDerivativeGroupQuadsNV) == - modes->end()) { + if (models && + models->find(SpvExecutionModelGLCompute) != models->end() && + (!modes || + (modes->find(SpvExecutionModeDerivativeGroupLinearNV) == + modes->end() && + modes->find(SpvExecutionModeDerivativeGroupQuadsNV) == + modes->end()))) { if (message) { *message = std::string( "Derivative instructions require " diff --git a/source/val/validate_extensions.cpp b/source/val/validate_extensions.cpp index 5ea23e1f..dccbe149 100644 --- a/source/val/validate_extensions.cpp +++ b/source/val/validate_extensions.cpp @@ -20,7 +20,7 @@ #include "spirv/unified1/NonSemanticClspvReflection.h" -#include "NonSemanticVulkanDebugInfo100.h" +#include "NonSemanticShaderDebugInfo100.h" #include "OpenCLDebugInfo100.h" #include "source/common_debug_info.h" #include "source/diagnostic.h" @@ -98,7 +98,7 @@ spv_result_t ValidateOperandForDebugInfo( return SPV_SUCCESS; } -// For NonSemantic.Vulkan.DebugInfo.100 check that the operand of a debug info +// For NonSemantic.Shader.DebugInfo.100 check that the operand of a debug info // instruction |inst| at |word_index| is a result id of a 32-bit integer // OpConstant instruction. For OpenCL.DebugInfo.100 the parameter is a literal // word so cannot be validated. @@ -140,7 +140,7 @@ bool DoesDebugInfoOperandMatchExpectation( if (debug_inst->opcode() != SpvOpExtInst || (debug_inst->ext_inst_type() != SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100 && debug_inst->ext_inst_type() != - SPV_EXT_INST_TYPE_NONSEMANTIC_VULKAN_DEBUGINFO_100) || + SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100) || !expectation(CommonDebugInfoInstructions(debug_inst->word(4)))) { return false; } @@ -706,7 +706,7 @@ bool IsDebugVariableWithIntScalarType(ValidationState_t& _, const spv_ext_inst_type_t ext_inst_type = spv_ext_inst_type_t(inst->ext_inst_type()); const bool vulkanDebugInfo = - ext_inst_type == SPV_EXT_INST_TYPE_NONSEMANTIC_VULKAN_DEBUGINFO_100; + ext_inst_type == SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100; uint32_t encoding = dbg_type->word(7); if (!vulkanDebugInfo || IsUint32Constant(_, encoding)) { auto ocl_encoding = OpenCLDebugInfo100DebugBaseTypeAttributeEncoding( @@ -2707,7 +2707,7 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) { } } else if (ext_inst_type == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100 || ext_inst_type == - SPV_EXT_INST_TYPE_NONSEMANTIC_VULKAN_DEBUGINFO_100) { + SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100) { if (!_.IsVoidType(result_type)) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << ext_inst_name() << ": " @@ -2716,7 +2716,7 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) { } const bool vulkanDebugInfo = - ext_inst_type == SPV_EXT_INST_TYPE_NONSEMANTIC_VULKAN_DEBUGINFO_100; + ext_inst_type == SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100; auto num_words = inst->words().size(); @@ -2965,7 +2965,7 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) { CHECK_DEBUG_OPERAND("Source", CommonDebugInfoDebugSource, 7); CHECK_CONST_UINT_OPERAND("Line", 8); CHECK_CONST_UINT_OPERAND("Column", 9); - // NonSemantic.Vulkan.DebugInfo doesn't have the Parent operand + // NonSemantic.Shader.DebugInfo doesn't have the Parent operand if (vulkanDebugInfo) { CHECK_OPERAND("Offset", SpvOpConstant, 10); CHECK_OPERAND("Size", SpvOpConstant, 11); @@ -3023,7 +3023,7 @@ spv_result_t ValidateExtInst(ValidationState_t& _, const Instruction* inst) { CHECK_OPERAND("Linkage Name", SpvOpString, 11); CHECK_CONST_UINT_OPERAND("Flags", 12); CHECK_CONST_UINT_OPERAND("Scope Line", 13); - // NonSemantic.Vulkan.DebugInfo.100 doesn't include a reference to the + // NonSemantic.Shader.DebugInfo.100 doesn't include a reference to the // OpFunction if (vulkanDebugInfo) { if (num_words == 15) { diff --git a/source/val/validate_image.cpp b/source/val/validate_image.cpp index e5968d06..64f6ba7b 100644 --- a/source/val/validate_image.cpp +++ b/source/val/validate_image.cpp @@ -66,6 +66,11 @@ bool CheckAllImageOperandsHandled() { case SpvImageOperandsVolatileTexelKHRMask: case SpvImageOperandsSignExtendMask: case SpvImageOperandsZeroExtendMask: + // TODO(jaebaek): Move this line properly after handling image offsets + // operand. This line temporarily fixes CI failure that + // blocks other PRs. + // https://github.com/KhronosGroup/SPIRV-Tools/issues/4565 + case SpvImageOperandsOffsetsMask: return true; } return false; @@ -281,13 +286,14 @@ spv_result_t ValidateImageOperands(ValidationState_t& _, // the module to be invalid. if (mask == 0) return SPV_SUCCESS; - if (spvtools::utils::CountSetBits( - mask & (SpvImageOperandsOffsetMask | SpvImageOperandsConstOffsetMask | - SpvImageOperandsConstOffsetsMask)) > 1) { + if (spvtools::utils::CountSetBits(mask & (SpvImageOperandsOffsetMask | + SpvImageOperandsConstOffsetMask | + SpvImageOperandsConstOffsetsMask | + SpvImageOperandsOffsetsMask)) > 1) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << _.VkErrorID(4662) - << "Image Operands Offset, ConstOffset, ConstOffsets cannot be used " - << "together"; + << "Image Operands Offset, ConstOffset, ConstOffsets, Offsets " + "cannot be used together"; } const bool is_implicit_lod = IsImplicitLod(opcode); @@ -620,6 +626,10 @@ spv_result_t ValidateImageOperands(ValidationState_t& _, // setup. } + if (mask & SpvImageOperandsOffsetsMask) { + // TODO: add validation + } + return SPV_SUCCESS; } @@ -2058,11 +2068,13 @@ spv_result_t ImagePass(ValidationState_t& _, const Instruction* inst) { std::string* message) { const auto* models = state.GetExecutionModels(entry_point->id()); const auto* modes = state.GetExecutionModes(entry_point->id()); - if (models->find(SpvExecutionModelGLCompute) != models->end() && - modes->find(SpvExecutionModeDerivativeGroupLinearNV) == - modes->end() && - modes->find(SpvExecutionModeDerivativeGroupQuadsNV) == - modes->end()) { + if (models && + models->find(SpvExecutionModelGLCompute) != models->end() && + (!modes || + (modes->find(SpvExecutionModeDerivativeGroupLinearNV) == + modes->end() && + modes->find(SpvExecutionModeDerivativeGroupQuadsNV) == + modes->end()))) { if (message) { *message = std::string( diff --git a/source/val/validate_instruction.cpp b/source/val/validate_instruction.cpp index 9d395fb4..dad98673 100644 --- a/source/val/validate_instruction.cpp +++ b/source/val/validate_instruction.cpp @@ -318,10 +318,9 @@ spv_result_t VersionCheck(ValidationState_t& _, const Instruction* inst) { if (module_version < min_version) { return _.diag(SPV_ERROR_WRONG_VERSION, inst) - << spvOpcodeString(opcode) << " requires " - << spvTargetEnvDescription( - static_cast<spv_target_env>(min_version)) - << " at minimum."; + << spvOpcodeString(opcode) << " requires SPIR-V version " + << SPV_SPIRV_VERSION_MAJOR_PART(min_version) << "." + << SPV_SPIRV_VERSION_MINOR_PART(min_version) << " at minimum."; } } else if (!_.HasAnyOfExtensions(exts)) { // Otherwise, we only error out when no enabling extensions are diff --git a/source/val/validate_interfaces.cpp b/source/val/validate_interfaces.cpp index d3ef5386..7ccb6371 100644 --- a/source/val/validate_interfaces.cpp +++ b/source/val/validate_interfaces.cpp @@ -199,6 +199,10 @@ uint32_t NumConsumedComponents(ValidationState_t& _, const Instruction* type) { NumConsumedComponents(_, _.FindDef(type->GetOperandAs<uint32_t>(1))); num_components *= type->GetOperandAs<uint32_t>(2); break; + case SpvOpTypeArray: + // Skip the array. + return NumConsumedComponents(_, + _.FindDef(type->GetOperandAs<uint32_t>(1))); default: // This is an error that is validated elsewhere. break; @@ -430,17 +434,36 @@ spv_result_t GetLocationsForVariable( continue; } - uint32_t end = (location + num_locations) * 4; - if (num_components != 0) { - start += component; - end = location * 4 + component + num_components; - } - for (uint32_t l = start; l < end; ++l) { - if (!locations->insert(l).second) { - return _.diag(SPV_ERROR_INVALID_DATA, entry_point) - << "Entry-point has conflicting " << storage_class - << " location assignment at location " << l / 4 - << ", component " << l % 4; + if (member->opcode() == SpvOpTypeArray && num_components >= 1 && + num_components < 4) { + // When an array has an element that takes less than a location in + // size, calculate the used locations in a strided manner. + for (uint32_t l = location; l < num_locations + location; ++l) { + for (uint32_t c = component; c < component + num_components; ++c) { + uint32_t check = 4 * l + c; + if (!locations->insert(check).second) { + return _.diag(SPV_ERROR_INVALID_DATA, entry_point) + << "Entry-point has conflicting " << storage_class + << " location assignment at location " << l + << ", component " << c; + } + } + } + } else { + // TODO: There is a hole here is the member is an array of 3- or + // 4-element vectors of 64-bit types. + uint32_t end = (location + num_locations) * 4; + if (num_components != 0) { + start += component; + end = location * 4 + component + num_components; + } + for (uint32_t l = start; l < end; ++l) { + if (!locations->insert(l).second) { + return _.diag(SPV_ERROR_INVALID_DATA, entry_point) + << "Entry-point has conflicting " << storage_class + << " location assignment at location " << l / 4 + << ", component " << l % 4; + } } } } diff --git a/source/val/validate_layout.cpp b/source/val/validate_layout.cpp index e6f4fcad..d5823219 100644 --- a/source/val/validate_layout.cpp +++ b/source/val/validate_layout.cpp @@ -17,7 +17,7 @@ #include <cassert> #include "DebugInfo.h" -#include "NonSemanticVulkanDebugInfo100.h" +#include "NonSemanticShaderDebugInfo100.h" #include "OpenCLDebugInfo100.h" #include "source/diagnostic.h" #include "source/opcode.h" @@ -51,15 +51,17 @@ spv_result_t ModuleScopedInstructions(ValidationState_t& _, local_debug_info = true; } } else if (inst->ext_inst_type() == - SPV_EXT_INST_TYPE_NONSEMANTIC_VULKAN_DEBUGINFO_100) { - const NonSemanticVulkanDebugInfo100Instructions ext_inst_key = - NonSemanticVulkanDebugInfo100Instructions(ext_inst_index); - if (ext_inst_key == NonSemanticVulkanDebugInfo100DebugScope || - ext_inst_key == NonSemanticVulkanDebugInfo100DebugNoScope || - ext_inst_key == NonSemanticVulkanDebugInfo100DebugDeclare || - ext_inst_key == NonSemanticVulkanDebugInfo100DebugValue || + SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100) { + const NonSemanticShaderDebugInfo100Instructions ext_inst_key = + NonSemanticShaderDebugInfo100Instructions(ext_inst_index); + if (ext_inst_key == NonSemanticShaderDebugInfo100DebugScope || + ext_inst_key == NonSemanticShaderDebugInfo100DebugNoScope || + ext_inst_key == NonSemanticShaderDebugInfo100DebugDeclare || + ext_inst_key == NonSemanticShaderDebugInfo100DebugValue || + ext_inst_key == NonSemanticShaderDebugInfo100DebugLine || + ext_inst_key == NonSemanticShaderDebugInfo100DebugNoLine || ext_inst_key == - NonSemanticVulkanDebugInfo100DebugFunctionDefinition) { + NonSemanticShaderDebugInfo100DebugFunctionDefinition) { local_debug_info = true; } } else { @@ -256,15 +258,17 @@ spv_result_t FunctionScopedInstructions(ValidationState_t& _, local_debug_info = true; } } else if (inst->ext_inst_type() == - SPV_EXT_INST_TYPE_NONSEMANTIC_VULKAN_DEBUGINFO_100) { - const NonSemanticVulkanDebugInfo100Instructions ext_inst_key = - NonSemanticVulkanDebugInfo100Instructions(ext_inst_index); - if (ext_inst_key == NonSemanticVulkanDebugInfo100DebugScope || - ext_inst_key == NonSemanticVulkanDebugInfo100DebugNoScope || - ext_inst_key == NonSemanticVulkanDebugInfo100DebugDeclare || - ext_inst_key == NonSemanticVulkanDebugInfo100DebugValue || + SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100) { + const NonSemanticShaderDebugInfo100Instructions ext_inst_key = + NonSemanticShaderDebugInfo100Instructions(ext_inst_index); + if (ext_inst_key == NonSemanticShaderDebugInfo100DebugScope || + ext_inst_key == NonSemanticShaderDebugInfo100DebugNoScope || + ext_inst_key == NonSemanticShaderDebugInfo100DebugDeclare || + ext_inst_key == NonSemanticShaderDebugInfo100DebugValue || + ext_inst_key == NonSemanticShaderDebugInfo100DebugLine || + ext_inst_key == NonSemanticShaderDebugInfo100DebugNoLine || ext_inst_key == - NonSemanticVulkanDebugInfo100DebugFunctionDefinition) { + NonSemanticShaderDebugInfo100DebugFunctionDefinition) { local_debug_info = true; } } else { diff --git a/source/val/validate_mode_setting.cpp b/source/val/validate_mode_setting.cpp index 79f82d8d..96352687 100644 --- a/source/val/validate_mode_setting.cpp +++ b/source/val/validate_mode_setting.cpp @@ -225,14 +225,21 @@ spv_result_t ValidateEntryPoint(ValidationState_t& _, const Instruction* inst) { } } } + if (i.opcode() == SpvOpExecutionModeId) { + const auto mode = i.GetOperandAs<SpvExecutionMode>(1); + if (mode == SpvExecutionModeLocalSizeId) { + ok = true; + break; + } + } } if (!ok) { return _.diag(SPV_ERROR_INVALID_DATA, inst) - << _.VkErrorID(4683) + << _.VkErrorID(6426) << "In the Vulkan environment, GLCompute execution model " - "entry points require either the LocalSize execution " - "mode or an object decorated with WorkgroupSize must be " - "specified."; + "entry points require either the LocalSize or " + "LocalSizeId execution mode or an object decorated with " + "WorkgroupSize must be specified."; } } break; @@ -429,6 +436,10 @@ spv_result_t ValidateExecutionMode(ValidationState_t& _, break; case SpvExecutionModeLocalSize: case SpvExecutionModeLocalSizeId: + if (mode == SpvExecutionModeLocalSizeId && !_.IsLocalSizeIdAllowed()) + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "LocalSizeId mode is not allowed by the current environment."; + if (!std::all_of(models->begin(), models->end(), [&_](const SpvExecutionModel& model) { switch (model) { diff --git a/source/val/validate_type.cpp b/source/val/validate_type.cpp index 612fc5c2..4376b52c 100644 --- a/source/val/validate_type.cpp +++ b/source/val/validate_type.cpp @@ -596,7 +596,7 @@ spv_result_t ValidateTypeCooperativeMatrixNV(ValidationState_t& _, if (!cols || !_.IsIntScalarType(cols->type_id()) || !spvOpcodeIsConstant(cols->opcode())) { return _.diag(SPV_ERROR_INVALID_ID, inst) - << "OpTypeCooperativeMatrixNV Cols <id> '" << _.getIdName(rows_id) + << "OpTypeCooperativeMatrixNV Cols <id> '" << _.getIdName(cols_id) << "' is not a constant instruction with scalar integer type."; } diff --git a/source/val/validation_state.cpp b/source/val/validation_state.cpp index c9ac3ae7..8d1a0d3f 100644 --- a/source/val/validation_state.cpp +++ b/source/val/validation_state.cpp @@ -175,6 +175,9 @@ ValidationState_t::ValidationState_t(const spv_const_context ctx, } } + // LocalSizeId is always allowed in non-Vulkan environments. + features_.env_allow_localsizeid = !spvIsVulkanEnv(env); + // Only attempt to count if we have words, otherwise let the other validation // fail and generate an error. if (num_words > 0) { @@ -729,19 +732,19 @@ uint32_t ValidationState_t::GetBitWidth(uint32_t id) const { bool ValidationState_t::IsVoidType(uint32_t id) const { const Instruction* inst = FindDef(id); - assert(inst); - return inst->opcode() == SpvOpTypeVoid; + return inst && inst->opcode() == SpvOpTypeVoid; } bool ValidationState_t::IsFloatScalarType(uint32_t id) const { const Instruction* inst = FindDef(id); - assert(inst); - return inst->opcode() == SpvOpTypeFloat; + return inst && inst->opcode() == SpvOpTypeFloat; } bool ValidationState_t::IsFloatVectorType(uint32_t id) const { const Instruction* inst = FindDef(id); - assert(inst); + if (!inst) { + return false; + } if (inst->opcode() == SpvOpTypeVector) { return IsFloatScalarType(GetComponentType(id)); @@ -752,7 +755,9 @@ bool ValidationState_t::IsFloatVectorType(uint32_t id) const { bool ValidationState_t::IsFloatScalarOrVectorType(uint32_t id) const { const Instruction* inst = FindDef(id); - assert(inst); + if (!inst) { + return false; + } if (inst->opcode() == SpvOpTypeFloat) { return true; @@ -767,13 +772,14 @@ bool ValidationState_t::IsFloatScalarOrVectorType(uint32_t id) const { bool ValidationState_t::IsIntScalarType(uint32_t id) const { const Instruction* inst = FindDef(id); - assert(inst); - return inst->opcode() == SpvOpTypeInt; + return inst && inst->opcode() == SpvOpTypeInt; } bool ValidationState_t::IsIntVectorType(uint32_t id) const { const Instruction* inst = FindDef(id); - assert(inst); + if (!inst) { + return false; + } if (inst->opcode() == SpvOpTypeVector) { return IsIntScalarType(GetComponentType(id)); @@ -784,7 +790,9 @@ bool ValidationState_t::IsIntVectorType(uint32_t id) const { bool ValidationState_t::IsIntScalarOrVectorType(uint32_t id) const { const Instruction* inst = FindDef(id); - assert(inst); + if (!inst) { + return false; + } if (inst->opcode() == SpvOpTypeInt) { return true; @@ -799,13 +807,14 @@ bool ValidationState_t::IsIntScalarOrVectorType(uint32_t id) const { bool ValidationState_t::IsUnsignedIntScalarType(uint32_t id) const { const Instruction* inst = FindDef(id); - assert(inst); - return inst->opcode() == SpvOpTypeInt && inst->word(3) == 0; + return inst && inst->opcode() == SpvOpTypeInt && inst->word(3) == 0; } bool ValidationState_t::IsUnsignedIntVectorType(uint32_t id) const { const Instruction* inst = FindDef(id); - assert(inst); + if (!inst) { + return false; + } if (inst->opcode() == SpvOpTypeVector) { return IsUnsignedIntScalarType(GetComponentType(id)); @@ -816,13 +825,14 @@ bool ValidationState_t::IsUnsignedIntVectorType(uint32_t id) const { bool ValidationState_t::IsSignedIntScalarType(uint32_t id) const { const Instruction* inst = FindDef(id); - assert(inst); - return inst->opcode() == SpvOpTypeInt && inst->word(3) == 1; + return inst && inst->opcode() == SpvOpTypeInt && inst->word(3) == 1; } bool ValidationState_t::IsSignedIntVectorType(uint32_t id) const { const Instruction* inst = FindDef(id); - assert(inst); + if (!inst) { + return false; + } if (inst->opcode() == SpvOpTypeVector) { return IsSignedIntScalarType(GetComponentType(id)); @@ -833,13 +843,14 @@ bool ValidationState_t::IsSignedIntVectorType(uint32_t id) const { bool ValidationState_t::IsBoolScalarType(uint32_t id) const { const Instruction* inst = FindDef(id); - assert(inst); - return inst->opcode() == SpvOpTypeBool; + return inst && inst->opcode() == SpvOpTypeBool; } bool ValidationState_t::IsBoolVectorType(uint32_t id) const { const Instruction* inst = FindDef(id); - assert(inst); + if (!inst) { + return false; + } if (inst->opcode() == SpvOpTypeVector) { return IsBoolScalarType(GetComponentType(id)); @@ -850,7 +861,9 @@ bool ValidationState_t::IsBoolVectorType(uint32_t id) const { bool ValidationState_t::IsBoolScalarOrVectorType(uint32_t id) const { const Instruction* inst = FindDef(id); - assert(inst); + if (!inst) { + return false; + } if (inst->opcode() == SpvOpTypeBool) { return true; @@ -865,7 +878,9 @@ bool ValidationState_t::IsBoolScalarOrVectorType(uint32_t id) const { bool ValidationState_t::IsFloatMatrixType(uint32_t id) const { const Instruction* inst = FindDef(id); - assert(inst); + if (!inst) { + return false; + } if (inst->opcode() == SpvOpTypeMatrix) { return IsFloatScalarType(GetComponentType(id)); @@ -920,8 +935,7 @@ bool ValidationState_t::GetStructMemberTypes( bool ValidationState_t::IsPointerType(uint32_t id) const { const Instruction* inst = FindDef(id); - assert(inst); - return inst->opcode() == SpvOpTypePointer; + return inst && inst->opcode() == SpvOpTypePointer; } bool ValidationState_t::GetPointerTypeInfo(uint32_t id, uint32_t* data_type, @@ -939,8 +953,7 @@ bool ValidationState_t::GetPointerTypeInfo(uint32_t id, uint32_t* data_type, bool ValidationState_t::IsCooperativeMatrixType(uint32_t id) const { const Instruction* inst = FindDef(id); - assert(inst); - return inst->opcode() == SpvOpTypeCooperativeMatrixNV; + return inst && inst->opcode() == SpvOpTypeCooperativeMatrixNV; } bool ValidationState_t::IsFloatCooperativeMatrixType(uint32_t id) const { @@ -1826,14 +1839,16 @@ std::string ValidationState_t::VkErrorID(uint32_t id, return VUID_WRAP(VUID-StandaloneSpirv-None-04667); case 4669: return VUID_WRAP(VUID-StandaloneSpirv-GLSLShared-04669); + case 4670: + return VUID_WRAP(VUID-StandaloneSpirv-Flat-04670); case 4675: return VUID_WRAP(VUID-StandaloneSpirv-FPRoundingMode-04675); case 4677: return VUID_WRAP(VUID-StandaloneSpirv-Invariant-04677); case 4682: return VUID_WRAP(VUID-StandaloneSpirv-OpControlBarrier-04682); - case 4683: - return VUID_WRAP(VUID-StandaloneSpirv-LocalSize-04683); + case 6426: + return VUID_WRAP(VUID-StandaloneSpirv-LocalSize-06426); // formally 04683 case 4685: return VUID_WRAP(VUID-StandaloneSpirv-OpGroupNonUniformBallotBitCount-04685); case 4686: diff --git a/source/val/validation_state.h b/source/val/validation_state.h index 2fe96621..2ddfa4a9 100644 --- a/source/val/validation_state.h +++ b/source/val/validation_state.h @@ -90,23 +90,6 @@ class ValidationState_t { // conversion opcodes bool use_int8_type = false; - // Use scalar block layout. See VK_EXT_scalar_block_layout: - // Defines scalar alignment: - // - scalar alignment equals the scalar size in bytes - // - array alignment is same as its element alignment - // - array alignment is max alignment of any of its members - // - vector alignment is same as component alignment - // - matrix alignment is same as component alignment - // For struct in Uniform, StorageBuffer, PushConstant: - // - Offset of a member is multiple of scalar alignment of that member - // - ArrayStride and MatrixStride are multiples of scalar alignment - // Members need not be listed in offset order - bool scalar_block_layout = false; - - // Use scalar block layout (as defined above) for Workgroup block - // variables. See VK_KHR_workgroup_memory_explicit_layout. - bool workgroup_scalar_block_layout = false; - // SPIR-V 1.4 allows us to select between any two composite values // of the same type. bool select_between_composites = false; @@ -121,6 +104,9 @@ class ValidationState_t { // SPIR-V 1.4 allows Function and Private variables to be NonWritable bool nonwritable_var_in_function_or_private = false; + + // Whether LocalSizeId execution mode is allowed by the environment. + bool env_allow_localsizeid = false; }; ValidationState_t(const spv_const_context context, @@ -493,6 +479,12 @@ class ValidationState_t { return features_.env_relaxed_block_layout || options()->relax_block_layout; } + // Returns true if allowing localsizeid, either because the environment always + // allows it, or because it is enabled from the command-line. + bool IsLocalSizeIdAllowed() const { + return features_.env_allow_localsizeid || options()->allow_localsizeid; + } + /// 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; diff --git a/source/wasm/README.md b/source/wasm/README.md new file mode 100644 index 00000000..aca0f70c --- /dev/null +++ b/source/wasm/README.md @@ -0,0 +1,43 @@ +# SPIRV-Tools + +Wasm (WebAssembly) build of https://github.com/KhronosGroup/SPIRV-Tools + +## Usage + +```js +const spirvTools = require("spirv-tools"); + +const test = async () => { + // Load the library + const spv = await spirvTools(); + + // assemble + const source = ` + OpCapability Linkage + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpSource GLSL 450 + OpDecorate %spec SpecId 1 + %int = OpTypeInt 32 1 + %spec = OpSpecConstant %int 0 + %const = OpConstant %int 42`; + const asResult = spv.as( + source, + spv.SPV_ENV_UNIVERSAL_1_3, + spv.SPV_TEXT_TO_BINARY_OPTION_NONE + ); + console.log(`as returned ${asResult.byteLength} bytes`); + + // re-disassemble + const disResult = spv.dis( + asResult, + spv.SPV_ENV_UNIVERSAL_1_3, + spv.SPV_BINARY_TO_TEXT_OPTION_INDENT | + spv.SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES | + spv.SPV_BINARY_TO_TEXT_OPTION_COLOR + ); + console.log("dis:\n", disResult); +}; + +test(); +``` diff --git a/source/wasm/build.sh b/source/wasm/build.sh new file mode 100755 index 00000000..f02ae525 --- /dev/null +++ b/source/wasm/build.sh @@ -0,0 +1,78 @@ +#!/bin/bash + +# Copyright (c) 2020 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 -e + +NUM_CORES=$(nproc) +echo "Detected $NUM_CORES cores for building" + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +VERSION=$(sed -n '0,/^v20/ s/^v\(20[0-9.]*\).*/\1/p' $DIR/../../CHANGES).${GITHUB_RUN_NUMBER:-0} +echo "Version: $VERSION" + +build() { + type=$1 + shift + args=$@ + mkdir -p build/$type + pushd build/$type + echo $args + emcmake cmake \ + -DCMAKE_BUILD_TYPE=Release \ + $args \ + ../.. + emmake make -j $(( $NUM_CORES )) SPIRV-Tools-static + + echo Building js interface + emcc \ + --bind \ + -I../../include \ + -std=c++11 \ + ../../source/wasm/spirv-tools.cpp \ + source/libSPIRV-Tools.a \ + -o spirv-tools.js \ + -s MODULARIZE \ + -Oz + + popd + mkdir -p out/$type + + # copy other js files + cp source/wasm/spirv-tools.d.ts out/$type/ + sed -e 's/\("version"\s*:\s*\).*/\1"'$VERSION'",/' source/wasm/package.json > out/$type/package.json + cp source/wasm/README.md out/$type/ + cp LICENSE out/$type/ + + cp build/$type/spirv-tools.js out/$type/ + gzip -9 -k -f out/$type/spirv-tools.js + if [ -e build/$type/spirv-tools.wasm ] ; then + cp build/$type/spirv-tools.wasm out/$type/ + gzip -9 -k -f out/$type/spirv-tools.wasm + fi +} + +if [ ! -d external/spirv-headers ] ; then + echo "Fetching SPIRV-headers" + git clone https://github.com/KhronosGroup/SPIRV-Headers.git external/spirv-headers +fi + +echo Building ${BASH_REMATCH[1]} +build web\ + -DSPIRV_COLOR_TERMINAL=OFF\ + -DSPIRV_SKIP_TESTS=ON\ + -DSPIRV_SKIP_EXECUTABLES=ON + +wc -c out/*/* diff --git a/source/wasm/package.json b/source/wasm/package.json new file mode 100644 index 00000000..78273538 --- /dev/null +++ b/source/wasm/package.json @@ -0,0 +1,17 @@ +{ + "name": "spirv-tools", + "version": "VERSION", + "license": "Apache-2.0", + "main": "spirv-tools", + "types": "spirv-tools.d.ts", + "files": [ + "*.wasm", + "*.js", + "*.d.ts" + ], + "repository": { + "type": "git", + "url": "https://github.com/KhronosGroup/SPIRV-Tools" + }, + "homepage": "https://github.com/KhronosGroup/SPIRV-Tools" +} diff --git a/source/wasm/spirv-tools.cpp b/source/wasm/spirv-tools.cpp new file mode 100644 index 00000000..90407f32 --- /dev/null +++ b/source/wasm/spirv-tools.cpp @@ -0,0 +1,93 @@ +// Copyright (c) 2020 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-tools/libspirv.hpp" + +#include <iostream> +#include <string> +#include <vector> + +#include <emscripten/bind.h> +#include <emscripten/val.h> +using namespace emscripten; + +void print_msg_to_stderr (spv_message_level_t, const char*, + const spv_position_t&, const char* m) { + std::cerr << "error: " << m << std::endl; +}; + +std::string dis(std::string const& buffer, uint32_t env, uint32_t options) { + spvtools::SpirvTools core(static_cast<spv_target_env>(env)); + core.SetMessageConsumer(print_msg_to_stderr); + + std::vector<uint32_t> spirv; + const uint32_t* ptr = reinterpret_cast<const uint32_t*>(buffer.data()); + spirv.assign(ptr, ptr + buffer.size() / 4); + std::string disassembly; + if (!core.Disassemble(spirv, &disassembly, options)) return "Error"; + return disassembly; +} + +emscripten::val as(std::string const& source, uint32_t env, uint32_t options) { + spvtools::SpirvTools core(static_cast<spv_target_env>(env)); + core.SetMessageConsumer(print_msg_to_stderr); + + std::vector<uint32_t> spirv; + if (!core.Assemble(source, &spirv, options)) spirv.clear(); + const uint8_t* ptr = reinterpret_cast<const uint8_t*>(spirv.data()); + return emscripten::val(emscripten::typed_memory_view(spirv.size() * 4, + ptr)); +} + +EMSCRIPTEN_BINDINGS(my_module) { + function("dis", &dis); + function("as", &as); + + constant("SPV_ENV_UNIVERSAL_1_0", static_cast<uint32_t>(SPV_ENV_UNIVERSAL_1_0)); + constant("SPV_ENV_VULKAN_1_0", static_cast<uint32_t>(SPV_ENV_VULKAN_1_0)); + constant("SPV_ENV_UNIVERSAL_1_1", static_cast<uint32_t>(SPV_ENV_UNIVERSAL_1_1)); + constant("SPV_ENV_OPENCL_2_1", static_cast<uint32_t>(SPV_ENV_OPENCL_2_1)); + constant("SPV_ENV_OPENCL_2_2", static_cast<uint32_t>(SPV_ENV_OPENCL_2_2)); + constant("SPV_ENV_OPENGL_4_0", static_cast<uint32_t>(SPV_ENV_OPENGL_4_0)); + constant("SPV_ENV_OPENGL_4_1", static_cast<uint32_t>(SPV_ENV_OPENGL_4_1)); + constant("SPV_ENV_OPENGL_4_2", static_cast<uint32_t>(SPV_ENV_OPENGL_4_2)); + constant("SPV_ENV_OPENGL_4_3", static_cast<uint32_t>(SPV_ENV_OPENGL_4_3)); + constant("SPV_ENV_OPENGL_4_5", static_cast<uint32_t>(SPV_ENV_OPENGL_4_5)); + constant("SPV_ENV_UNIVERSAL_1_2", static_cast<uint32_t>(SPV_ENV_UNIVERSAL_1_2)); + constant("SPV_ENV_OPENCL_1_2", static_cast<uint32_t>(SPV_ENV_OPENCL_1_2)); + constant("SPV_ENV_OPENCL_EMBEDDED_1_2", static_cast<uint32_t>(SPV_ENV_OPENCL_EMBEDDED_1_2)); + constant("SPV_ENV_OPENCL_2_0", static_cast<uint32_t>(SPV_ENV_OPENCL_2_0)); + constant("SPV_ENV_OPENCL_EMBEDDED_2_0", static_cast<uint32_t>(SPV_ENV_OPENCL_EMBEDDED_2_0)); + constant("SPV_ENV_OPENCL_EMBEDDED_2_1", static_cast<uint32_t>(SPV_ENV_OPENCL_EMBEDDED_2_1)); + constant("SPV_ENV_OPENCL_EMBEDDED_2_2", static_cast<uint32_t>(SPV_ENV_OPENCL_EMBEDDED_2_2)); + constant("SPV_ENV_UNIVERSAL_1_3", static_cast<uint32_t>(SPV_ENV_UNIVERSAL_1_3)); + constant("SPV_ENV_VULKAN_1_1", static_cast<uint32_t>(SPV_ENV_VULKAN_1_1)); + constant("SPV_ENV_WEBGPU_0", static_cast<uint32_t>(SPV_ENV_WEBGPU_0)); + constant("SPV_ENV_UNIVERSAL_1_4", static_cast<uint32_t>(SPV_ENV_UNIVERSAL_1_4)); + constant("SPV_ENV_VULKAN_1_1_SPIRV_1_4", static_cast<uint32_t>(SPV_ENV_VULKAN_1_1_SPIRV_1_4)); + constant("SPV_ENV_UNIVERSAL_1_5", static_cast<uint32_t>(SPV_ENV_UNIVERSAL_1_5)); + constant("SPV_ENV_VULKAN_1_2", static_cast<uint32_t>(SPV_ENV_VULKAN_1_2)); + + + constant("SPV_BINARY_TO_TEXT_OPTION_NONE", static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_NONE)); + constant("SPV_BINARY_TO_TEXT_OPTION_PRINT", static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_PRINT)); + constant("SPV_BINARY_TO_TEXT_OPTION_COLOR", static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_COLOR)); + constant("SPV_BINARY_TO_TEXT_OPTION_INDENT", static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_INDENT)); + constant("SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET", static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET)); + constant("SPV_BINARY_TO_TEXT_OPTION_NO_HEADER", static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER)); + constant("SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES", static_cast<uint32_t>(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES)); + + constant("SPV_TEXT_TO_BINARY_OPTION_NONE", static_cast<uint32_t>(SPV_TEXT_TO_BINARY_OPTION_NONE)); + constant("SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS", static_cast<uint32_t>(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS)); +}
\ No newline at end of file diff --git a/source/wasm/spirv-tools.d.ts b/source/wasm/spirv-tools.d.ts new file mode 100644 index 00000000..9c197973 --- /dev/null +++ b/source/wasm/spirv-tools.d.ts @@ -0,0 +1,56 @@ +// Copyright (c) 2020 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. + +declare interface SpirvTools { + as(input: string, env: number, options: number): Uint8Array; + dis(input: Uint8Array, env: number, options: number): string; + + SPV_ENV_UNIVERSAL_1_0: number; + SPV_ENV_VULKAN_1_0: number; + SPV_ENV_UNIVERSAL_1_1: number; + SPV_ENV_OPENCL_2_1: number; + SPV_ENV_OPENCL_2_2: number; + SPV_ENV_OPENGL_4_0: number; + SPV_ENV_OPENGL_4_1: number; + SPV_ENV_OPENGL_4_2: number; + SPV_ENV_OPENGL_4_3: number; + SPV_ENV_OPENGL_4_5: number; + SPV_ENV_UNIVERSAL_1_2: number; + SPV_ENV_OPENCL_1_2: number; + SPV_ENV_OPENCL_EMBEDDED_1_2: number; + SPV_ENV_OPENCL_2_0: number; + SPV_ENV_OPENCL_EMBEDDED_2_0: number; + SPV_ENV_OPENCL_EMBEDDED_2_1: number; + SPV_ENV_OPENCL_EMBEDDED_2_2: number; + SPV_ENV_UNIVERSAL_1_3: number; + SPV_ENV_VULKAN_1_1: number; + SPV_ENV_WEBGPU_0: number; + SPV_ENV_UNIVERSAL_1_4: number; + SPV_ENV_VULKAN_1_1_SPIRV_1_4: number; + SPV_ENV_UNIVERSAL_1_5: number; + SPV_ENV_VULKAN_1_2: number; + + SPV_TEXT_TO_BINARY_OPTION_NONE: number; + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS: number; + + SPV_BINARY_TO_TEXT_OPTION_NONE: number; + SPV_BINARY_TO_TEXT_OPTION_PRINT: number; + SPV_BINARY_TO_TEXT_OPTION_COLOR: number; + SPV_BINARY_TO_TEXT_OPTION_INDENT: number; + SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET: number; + SPV_BINARY_TO_TEXT_OPTION_NO_HEADER: number; + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES: number; +} + +export default function (): Promise<SpirvTools>; diff --git a/test/fuzz/fuzzerutil_test.cpp b/test/fuzz/fuzzerutil_test.cpp index 6771c404..0ad3e742 100644 --- a/test/fuzz/fuzzerutil_test.cpp +++ b/test/fuzz/fuzzerutil_test.cpp @@ -1549,6 +1549,259 @@ TEST(FuzzerutilTest, FuzzerUtilMaybeGetZeroConstantTest) { float_ids.end()); } +TEST(FuzzerutilTest, TypesAreCompatible) { + const std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 320 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %9 = OpTypeInt 32 0 + %8 = OpTypeStruct %6 + %10 = OpTypePointer StorageBuffer %8 + %11 = OpVariable %10 StorageBuffer + %86 = OpTypeStruct %9 + %87 = OpTypePointer Workgroup %86 + %88 = OpVariable %87 Workgroup + %89 = OpTypePointer Workgroup %9 + %19 = OpConstant %9 0 + %18 = OpConstant %9 1 + %12 = OpConstant %6 0 + %13 = OpTypePointer StorageBuffer %6 + %15 = OpConstant %6 2 + %16 = OpConstant %6 7 + %20 = OpConstant %9 64 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %14 = OpAccessChain %13 %11 %12 + %90 = OpAccessChain %89 %88 %19 + %21 = OpAtomicLoad %6 %14 %15 %20 + %22 = OpAtomicExchange %6 %14 %15 %20 %16 + %23 = OpAtomicCompareExchange %6 %14 %15 %20 %12 %16 %15 + %24 = OpAtomicIIncrement %6 %14 %15 %20 + %25 = OpAtomicIDecrement %6 %14 %15 %20 + %26 = OpAtomicIAdd %6 %14 %15 %20 %16 + %27 = OpAtomicISub %6 %14 %15 %20 %16 + %28 = OpAtomicSMin %6 %14 %15 %20 %16 + %29 = OpAtomicUMin %9 %90 %15 %20 %18 + %30 = OpAtomicSMax %6 %14 %15 %20 %15 + %31 = OpAtomicUMax %9 %90 %15 %20 %18 + %32 = OpAtomicAnd %6 %14 %15 %20 %16 + %33 = OpAtomicOr %6 %14 %15 %20 %16 + %34 = OpAtomicXor %6 %14 %15 %20 %16 + OpAtomicStore %14 %15 %20 %16 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + spvtools::ValidatorOptions validator_options; + ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, + kConsoleMessageConsumer)); + + const uint32_t int_type = 6; // The id of OpTypeInt 32 1 + const uint32_t uint_type = 9; // The id of OpTypeInt 32 0 + + // OpAtomicLoad +#ifndef NDEBUG + ASSERT_DEATH(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicLoad, 0, + int_type, uint_type), + "Signedness check should not occur on a pointer operand."); +#endif + ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicLoad, 1, + int_type, uint_type)); + ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicLoad, 2, + int_type, uint_type)); + + // OpAtomicExchange +#ifndef NDEBUG + ASSERT_DEATH(fuzzerutil::TypesAreCompatible( + context.get(), SpvOpAtomicExchange, 0, int_type, uint_type), + "Signedness check should not occur on a pointer operand."); +#endif + ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicExchange, + 1, int_type, uint_type)); + ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicExchange, + 2, int_type, uint_type)); + ASSERT_FALSE(fuzzerutil::TypesAreCompatible( + context.get(), SpvOpAtomicExchange, 3, int_type, uint_type)); + + // OpAtomicStore +#ifndef NDEBUG + ASSERT_DEATH(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicStore, + 0, int_type, uint_type), + "Signedness check should not occur on a pointer operand."); +#endif + ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicStore, 1, + int_type, uint_type)); + ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicStore, 2, + int_type, uint_type)); + ASSERT_FALSE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicStore, + 3, int_type, uint_type)); + + // OpAtomicCompareExchange +#ifndef NDEBUG + ASSERT_DEATH( + fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicCompareExchange, + 0, int_type, uint_type), + "Signedness check should not occur on a pointer operand."); +#endif + ASSERT_TRUE(fuzzerutil::TypesAreCompatible( + context.get(), SpvOpAtomicCompareExchange, 1, int_type, uint_type)); + ASSERT_TRUE(fuzzerutil::TypesAreCompatible( + context.get(), SpvOpAtomicCompareExchange, 2, int_type, uint_type)); + ASSERT_TRUE(fuzzerutil::TypesAreCompatible( + context.get(), SpvOpAtomicCompareExchange, 3, int_type, uint_type)); + ASSERT_FALSE(fuzzerutil::TypesAreCompatible( + context.get(), SpvOpAtomicCompareExchange, 4, int_type, uint_type)); + + // OpAtomicIIncrement +#ifndef NDEBUG + ASSERT_DEATH( + fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicIIncrement, 0, + int_type, uint_type), + "Signedness check should not occur on a pointer operand."); +#endif + ASSERT_TRUE(fuzzerutil::TypesAreCompatible( + context.get(), SpvOpAtomicIIncrement, 1, int_type, uint_type)); + ASSERT_TRUE(fuzzerutil::TypesAreCompatible( + context.get(), SpvOpAtomicIIncrement, 2, int_type, uint_type)); + +// OpAtomicIDecrement +#ifndef NDEBUG + ASSERT_DEATH(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicStore, + 0, int_type, uint_type), + "Signedness check should not occur on a pointer operand."); +#endif + ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicStore, 1, + int_type, uint_type)); + ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicStore, 2, + int_type, uint_type)); + +// OpAtomicIAdd +#ifndef NDEBUG + ASSERT_DEATH(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicIAdd, 0, + int_type, uint_type), + "Signedness check should not occur on a pointer operand."); +#endif + ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicIAdd, 1, + int_type, uint_type)); + ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicIAdd, 2, + int_type, uint_type)); + ASSERT_FALSE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicIAdd, 3, + int_type, uint_type)); + +// OpAtomicISub +#ifndef NDEBUG + ASSERT_DEATH(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicISub, 0, + int_type, uint_type), + "Signedness check should not occur on a pointer operand."); +#endif + ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicISub, 1, + int_type, uint_type)); + ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicISub, 2, + int_type, uint_type)); + ASSERT_FALSE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicISub, 3, + int_type, uint_type)); + +// OpAtomicSMin +#ifndef NDEBUG + ASSERT_DEATH(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicSMin, 0, + int_type, uint_type), + "Signedness check should not occur on a pointer operand."); +#endif + ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicSMin, 1, + int_type, uint_type)); + ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicSMin, 2, + int_type, uint_type)); + ASSERT_FALSE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicSMin, 3, + int_type, uint_type)); + +// OpAtomicUMin +#ifndef NDEBUG + ASSERT_DEATH(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicUMin, 0, + int_type, uint_type), + "Signedness check should not occur on a pointer operand."); +#endif + ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicUMin, 1, + int_type, uint_type)); + ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicUMin, 2, + int_type, uint_type)); + ASSERT_FALSE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicUMin, 3, + int_type, uint_type)); + +// OpAtomicSMax +#ifndef NDEBUG + ASSERT_DEATH(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicSMax, 0, + int_type, uint_type), + "Signedness check should not occur on a pointer operand."); +#endif + ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicSMax, 1, + int_type, uint_type)); + ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicSMax, 2, + int_type, uint_type)); + ASSERT_FALSE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicSMax, 3, + int_type, uint_type)); + +// OpAtomicUMax +#ifndef NDEBUG + ASSERT_DEATH(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicUMax, 0, + int_type, uint_type), + "Signedness check should not occur on a pointer operand."); +#endif + ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicUMax, 1, + int_type, uint_type)); + ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicUMax, 2, + int_type, uint_type)); + ASSERT_FALSE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicUMax, 3, + int_type, uint_type)); + +// OpAtomicAnd +#ifndef NDEBUG + ASSERT_DEATH(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicAnd, 0, + int_type, uint_type), + "Signedness check should not occur on a pointer operand."); +#endif + ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicAnd, 1, + int_type, uint_type)); + ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicAnd, 2, + int_type, uint_type)); + ASSERT_FALSE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicAnd, 3, + int_type, uint_type)); + +// OpAtomicOr +#ifndef NDEBUG + ASSERT_DEATH(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicOr, 0, + int_type, uint_type), + "Signedness check should not occur on a pointer operand."); +#endif + ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicOr, 1, + int_type, uint_type)); + ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicOr, 2, + int_type, uint_type)); + ASSERT_FALSE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicOr, 3, + int_type, uint_type)); + +// OpAtomicXor +#ifndef NDEBUG + ASSERT_DEATH(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicXor, 0, + int_type, uint_type), + "Signedness check should not occur on a pointer operand."); +#endif + ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicXor, 1, + int_type, uint_type)); + ASSERT_TRUE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicXor, 2, + int_type, uint_type)); + ASSERT_FALSE(fuzzerutil::TypesAreCompatible(context.get(), SpvOpAtomicXor, 3, + int_type, uint_type)); +} + } // namespace } // namespace fuzz } // namespace spvtools diff --git a/test/fuzz/transformation_replace_id_with_synonym_test.cpp b/test/fuzz/transformation_replace_id_with_synonym_test.cpp index cebce8a0..b33dd48c 100644 --- a/test/fuzz/transformation_replace_id_with_synonym_test.cpp +++ b/test/fuzz/transformation_replace_id_with_synonym_test.cpp @@ -2173,262 +2173,6 @@ TEST(TransformationReplaceIdWithSynonymTest, ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); } -// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/4345): Improve this -// test so that it covers more atomic operations, and enable the test once the -// issue is fixed. -TEST(TransformationReplaceIdWithSynonymTest, TypesAreCompatible) { - const std::string shader = R"( - OpCapability Shader - %1 = OpExtInstImport "GLSL.std.450" - OpMemoryModel Logical GLSL450 - OpEntryPoint Fragment %4 "main" - OpExecutionMode %4 OriginUpperLeft - OpSource ESSL 320 - %2 = OpTypeVoid - %3 = OpTypeFunction %2 - %6 = OpTypeInt 32 1 - %9 = OpTypeInt 32 0 - %8 = OpTypeStruct %6 - %10 = OpTypePointer StorageBuffer %8 - %11 = OpVariable %10 StorageBuffer - %86 = OpTypeStruct %9 - %87 = OpTypePointer Workgroup %86 - %88 = OpVariable %87 Workgroup - %89 = OpTypePointer Workgroup %9 - %19 = OpConstant %9 0 - %18 = OpConstant %9 1 - %12 = OpConstant %6 0 - %13 = OpTypePointer StorageBuffer %6 - %15 = OpConstant %6 2 - %16 = OpConstant %6 7 - %20 = OpConstant %9 64 - %4 = OpFunction %2 None %3 - %5 = OpLabel - %14 = OpAccessChain %13 %11 %12 - %90 = OpAccessChain %89 %88 %19 - %21 = OpAtomicLoad %6 %14 %15 %20 - %22 = OpAtomicExchange %6 %14 %15 %20 %16 - %23 = OpAtomicCompareExchange %6 %14 %15 %20 %12 %16 %15 - %24 = OpAtomicIIncrement %6 %14 %15 %20 - %25 = OpAtomicIDecrement %6 %14 %15 %20 - %26 = OpAtomicIAdd %6 %14 %15 %20 %16 - %27 = OpAtomicISub %6 %14 %15 %20 %16 - %28 = OpAtomicSMin %6 %14 %15 %20 %16 - %29 = OpAtomicUMin %9 %90 %15 %20 %18 - %30 = OpAtomicSMax %6 %14 %15 %20 %15 - %31 = OpAtomicUMax %9 %90 %15 %20 %18 - %32 = OpAtomicAnd %6 %14 %15 %20 %16 - %33 = OpAtomicOr %6 %14 %15 %20 %16 - %34 = OpAtomicXor %6 %14 %15 %20 %16 - OpAtomicStore %14 %15 %20 %16 - OpReturn - OpFunctionEnd - )"; - - const auto env = SPV_ENV_UNIVERSAL_1_3; - const auto consumer = nullptr; - const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); - spvtools::ValidatorOptions validator_options; - ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, - kConsoleMessageConsumer)); - - const uint32_t int_type = 6; // The id of OpTypeInt 32 1 - const uint32_t uint_type = 9; // The id of OpTypeInt 32 0 - - // OpAtomicLoad -#ifndef NDEBUG - ASSERT_DEATH(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicLoad, 0, int_type, uint_type), - "Signedness check should not occur on a pointer operand."); -#endif - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicLoad, 1, int_type, uint_type)); - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicLoad, 2, int_type, uint_type)); - - // OpAtomicExchange -#ifndef NDEBUG - ASSERT_DEATH(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicExchange, 0, int_type, uint_type), - "Signedness check should not occur on a pointer operand."); -#endif - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicExchange, 1, int_type, uint_type)); - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicExchange, 2, int_type, uint_type)); - ASSERT_FALSE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicExchange, 3, int_type, uint_type)); - - // OpAtomicStore -#ifndef NDEBUG - ASSERT_DEATH(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicStore, 0, int_type, uint_type), - "Signedness check should not occur on a pointer operand."); -#endif - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicStore, 1, int_type, uint_type)); - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicStore, 2, int_type, uint_type)); - ASSERT_FALSE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicStore, 3, int_type, uint_type)); - - // OpAtomicCompareExchange -#ifndef NDEBUG - ASSERT_DEATH( - TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicCompareExchange, 0, int_type, uint_type), - "Signedness check should not occur on a pointer operand."); -#endif - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicCompareExchange, 1, int_type, uint_type)); - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicCompareExchange, 2, int_type, uint_type)); - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicCompareExchange, 3, int_type, uint_type)); - ASSERT_FALSE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicCompareExchange, 4, int_type, uint_type)); - - // OpAtomicIIncrement -#ifndef NDEBUG - ASSERT_DEATH( - TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicIIncrement, 0, int_type, uint_type), - "Signedness check should not occur on a pointer operand."); -#endif - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicIIncrement, 1, int_type, uint_type)); - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicIIncrement, 2, int_type, uint_type)); - -// OpAtomicIDecrement -#ifndef NDEBUG - ASSERT_DEATH(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicStore, 0, int_type, uint_type), - "Signedness check should not occur on a pointer operand."); -#endif - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicStore, 1, int_type, uint_type)); - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicStore, 2, int_type, uint_type)); - -// OpAtomicIAdd -#ifndef NDEBUG - ASSERT_DEATH(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicIAdd, 0, int_type, uint_type), - "Signedness check should not occur on a pointer operand."); -#endif - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicIAdd, 1, int_type, uint_type)); - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicIAdd, 2, int_type, uint_type)); - ASSERT_FALSE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicIAdd, 3, int_type, uint_type)); - -// OpAtomicISub -#ifndef NDEBUG - ASSERT_DEATH(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicISub, 0, int_type, uint_type), - "Signedness check should not occur on a pointer operand."); -#endif - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicISub, 1, int_type, uint_type)); - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicISub, 2, int_type, uint_type)); - ASSERT_FALSE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicISub, 3, int_type, uint_type)); - -// OpAtomicSMin -#ifndef NDEBUG - ASSERT_DEATH(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicSMin, 0, int_type, uint_type), - "Signedness check should not occur on a pointer operand."); -#endif - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicSMin, 1, int_type, uint_type)); - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicSMin, 2, int_type, uint_type)); - ASSERT_FALSE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicSMin, 3, int_type, uint_type)); - -// OpAtomicUMin -#ifndef NDEBUG - ASSERT_DEATH(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicUMin, 0, int_type, uint_type), - "Signedness check should not occur on a pointer operand."); -#endif - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicUMin, 1, int_type, uint_type)); - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicUMin, 2, int_type, uint_type)); - ASSERT_FALSE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicUMin, 3, int_type, uint_type)); - -// OpAtomicSMax -#ifndef NDEBUG - ASSERT_DEATH(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicSMax, 0, int_type, uint_type), - "Signedness check should not occur on a pointer operand."); -#endif - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicSMax, 1, int_type, uint_type)); - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicSMax, 2, int_type, uint_type)); - ASSERT_FALSE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicSMax, 3, int_type, uint_type)); - -// OpAtomicUMax -#ifndef NDEBUG - ASSERT_DEATH(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicUMax, 0, int_type, uint_type), - "Signedness check should not occur on a pointer operand."); -#endif - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicUMax, 1, int_type, uint_type)); - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicUMax, 2, int_type, uint_type)); - ASSERT_FALSE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicUMax, 3, int_type, uint_type)); - -// OpAtomicAnd -#ifndef NDEBUG - ASSERT_DEATH(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicAnd, 0, int_type, uint_type), - "Signedness check should not occur on a pointer operand."); -#endif - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicAnd, 1, int_type, uint_type)); - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicAnd, 2, int_type, uint_type)); - ASSERT_FALSE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicAnd, 3, int_type, uint_type)); - -// OpAtomicOr -#ifndef NDEBUG - ASSERT_DEATH(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicOr, 0, int_type, uint_type), - "Signedness check should not occur on a pointer operand."); -#endif - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicOr, 1, int_type, uint_type)); - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicOr, 2, int_type, uint_type)); - ASSERT_FALSE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicOr, 3, int_type, uint_type)); - -// OpAtomicXor -#ifndef NDEBUG - ASSERT_DEATH(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicXor, 0, int_type, uint_type), - "Signedness check should not occur on a pointer operand."); -#endif - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicXor, 1, int_type, uint_type)); - ASSERT_TRUE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicXor, 2, int_type, uint_type)); - ASSERT_FALSE(TransformationReplaceIdWithSynonym::TypesAreCompatible( - context.get(), SpvOpAtomicXor, 3, int_type, uint_type)); -} - } // namespace } // namespace fuzz } // namespace spvtools diff --git a/test/fuzz/transformation_wrap_vector_synonym_test.cpp b/test/fuzz/transformation_wrap_vector_synonym_test.cpp index dae78a5d..0d1009b3 100644 --- a/test/fuzz/transformation_wrap_vector_synonym_test.cpp +++ b/test/fuzz/transformation_wrap_vector_synonym_test.cpp @@ -1389,6 +1389,165 @@ TEST(TransformationWrapVectorSynonym, AdditionalWidthSupportTest) { ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); } +TEST(TransformationWrapVectorSynonym, DifferentVectorSignedness) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 320 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypeVector %6 2 + %8 = OpTypePointer Function %7 + %10 = OpConstant %6 1 + %11 = OpConstant %6 0 + %12 = OpConstantComposite %7 %10 %11 + %14 = OpTypeInt 32 0 + %15 = OpTypeVector %14 2 + %18 = OpConstant %14 3 + %19 = OpConstant %14 0 + %20 = OpConstantComposite %15 %18 %19 + %21 = OpConstantComposite %15 %19 %18 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %100 = OpIAdd %14 %10 %18 + %101 = OpIAdd %6 %10 %18 + %102 = OpIAdd %6 %18 %19 + OpReturn + OpFunctionEnd + )"; + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + spvtools::ValidatorOptions validator_options; + + // Check context validity. + ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, + kConsoleMessageConsumer)); + + TransformationContext transformation_context( + MakeUnique<FactManager>(context.get()), validator_options); + + transformation_context.GetFactManager()->AddFactDataSynonym( + MakeDataDescriptor(10, {}), MakeDataDescriptor(12, {0})); + transformation_context.GetFactManager()->AddFactDataSynonym( + MakeDataDescriptor(18, {}), MakeDataDescriptor(20, {0})); + transformation_context.GetFactManager()->AddFactDataSynonym( + MakeDataDescriptor(19, {}), MakeDataDescriptor(21, {0})); + + { + TransformationWrapVectorSynonym transformation1(100, 12, 20, 200, 0); + ASSERT_TRUE( + transformation1.IsApplicable(context.get(), transformation_context)); + ApplyAndCheckFreshIds(transformation1, context.get(), + &transformation_context); + ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous( + MakeDataDescriptor(200, {0}), MakeDataDescriptor(100, {}))); + } + + { + TransformationWrapVectorSynonym transformation2(101, 12, 20, 201, 0); + ASSERT_TRUE( + transformation2.IsApplicable(context.get(), transformation_context)); + ApplyAndCheckFreshIds(transformation2, context.get(), + &transformation_context); + ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous( + MakeDataDescriptor(201, {0}), MakeDataDescriptor(101, {}))); + } + + { + TransformationWrapVectorSynonym transformation3(102, 20, 21, 202, 0); + ASSERT_TRUE( + transformation3.IsApplicable(context.get(), transformation_context)); + ApplyAndCheckFreshIds(transformation3, context.get(), + &transformation_context); + ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous( + MakeDataDescriptor(202, {0}), MakeDataDescriptor(102, {}))); + } + + std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 320 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypeVector %6 2 + %8 = OpTypePointer Function %7 + %10 = OpConstant %6 1 + %11 = OpConstant %6 0 + %12 = OpConstantComposite %7 %10 %11 + %14 = OpTypeInt 32 0 + %15 = OpTypeVector %14 2 + %18 = OpConstant %14 3 + %19 = OpConstant %14 0 + %20 = OpConstantComposite %15 %18 %19 + %21 = OpConstantComposite %15 %19 %18 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %200 = OpIAdd %15 %12 %20 + %100 = OpIAdd %14 %10 %18 + %201 = OpIAdd %7 %12 %20 + %101 = OpIAdd %6 %10 %18 + %202 = OpIAdd %7 %20 %21 + %102 = OpIAdd %6 %18 %19 + OpReturn + OpFunctionEnd + )"; + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +TEST(TransformationWrapVectorSynonym, SignednessDoesNotMatchResultType) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 320 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypeVector %6 2 + %8 = OpTypePointer Function %7 + %10 = OpConstant %6 1 + %11 = OpConstant %6 0 + %12 = OpConstantComposite %7 %10 %11 + %13 = OpConstantComposite %7 %11 %10 + %14 = OpTypeInt 32 0 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %100 = OpIAdd %14 %10 %11 + OpReturn + OpFunctionEnd + )"; + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + spvtools::ValidatorOptions validator_options; + + // Check context validity. + ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, + kConsoleMessageConsumer)); + + TransformationContext transformation_context( + MakeUnique<FactManager>(context.get()), validator_options); + + transformation_context.GetFactManager()->AddFactDataSynonym( + MakeDataDescriptor(10, {}), MakeDataDescriptor(12, {0})); + transformation_context.GetFactManager()->AddFactDataSynonym( + MakeDataDescriptor(11, {}), MakeDataDescriptor(13, {0})); + + ASSERT_FALSE(TransformationWrapVectorSynonym(100, 12, 13, 200, 0) + .IsApplicable(context.get(), transformation_context)); +} + } // namespace } // namespace fuzz } // namespace spvtools diff --git a/test/fuzzers/BUILD.gn b/test/fuzzers/BUILD.gn index ec09b2b0..2aef4e8f 100644 --- a/test/fuzzers/BUILD.gn +++ b/test/fuzzers/BUILD.gn @@ -48,6 +48,7 @@ template("spvtools_fuzzer") { source_set(target_name) { testonly = true sources = invoker.sources + sources += [ "random_generator.cpp" ] deps = [ "../..:spvtools", "../..:spvtools_opt", diff --git a/test/fuzzers/CMakeLists.txt b/test/fuzzers/CMakeLists.txt index 6b0926ea..50c45895 100644 --- a/test/fuzzers/CMakeLists.txt +++ b/test/fuzzers/CMakeLists.txt @@ -26,19 +26,31 @@ function(add_spvtools_libfuzzer_target) ${spirv-tools_BINARY_DIR} ) set_property(TARGET ${ARG_TARGET} PROPERTY FOLDER "SPIRV-Tools libFuzzer targets") - target_compile_options(${ARG_TARGET} PRIVATE "-fsanitize=fuzzer") - target_link_options(${ARG_TARGET} PRIVATE "-fsanitize=fuzzer") + if(NOT ${SPIRV_LIB_FUZZING_ENGINE_LINK_OPTIONS} STREQUAL "") + # This is set when the fuzzers are being built by OSS-Fuzz. In this case the + # variable provides the necessary linker flags, and OSS-Fuzz will take care + # of passing suitable compiler flags. + target_link_options(${ARG_TARGET} PRIVATE ${SPIRV_LIB_FUZZING_ENGINE_LINK_OPTIONS}) + else() + # When the fuzzers are being built outside of OSS-Fuzz, standard libFuzzer + # arguments to enable fuzzing are used. + target_compile_options(${ARG_TARGET} PRIVATE "-fsanitize=fuzzer") + target_link_options(${ARG_TARGET} PRIVATE "-fsanitize=fuzzer") + endif() endfunction() if (${SPIRV_BUILD_LIBFUZZER_TARGETS}) if(NOT "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") message(FATAL_ERROR "The libFuzzer targets are only supported with the Clang compiler. Compiler '${CMAKE_CXX_COMPILER_ID}' is not supported!") endif() - add_spvtools_libfuzzer_target(TARGET spvtools_as_fuzzer SRCS spvtools_as_fuzzer.cpp LIBS ${SPIRV_TOOLS_FULL_VISIBILITY}) - add_spvtools_libfuzzer_target(TARGET spvtools_binary_parser_fuzzer SRCS spvtools_binary_parser_fuzzer.cpp LIBS ${SPIRV_TOOLS_FULL_VISIBILITY}) - add_spvtools_libfuzzer_target(TARGET spvtools_dis_fuzzer SRCS spvtools_dis_fuzzer.cpp LIBS ${SPIRV_TOOLS_FULL_VISIBILITY}) - add_spvtools_libfuzzer_target(TARGET spvtools_opt_legalization_fuzzer SRCS spvtools_opt_legalization_fuzzer.cpp LIBS SPIRV-Tools-opt ${SPIRV_TOOLS_FULL_VISIBILITY}) - add_spvtools_libfuzzer_target(TARGET spvtools_opt_performance_fuzzer SRCS spvtools_opt_performance_fuzzer.cpp LIBS SPIRV-Tools-opt ${SPIRV_TOOLS_FULL_VISIBILITY}) - add_spvtools_libfuzzer_target(TARGET spvtools_opt_size_fuzzer SRCS spvtools_opt_size_fuzzer.cpp LIBS SPIRV-Tools-opt ${SPIRV_TOOLS_FULL_VISIBILITY}) - add_spvtools_libfuzzer_target(TARGET spvtools_val_fuzzer SRCS spvtools_val_fuzzer.cpp LIBS ${SPIRV_TOOLS_FULL_VISIBILITY}) + add_spvtools_libfuzzer_target(TARGET spvtools_as_fuzzer SRCS spvtools_as_fuzzer.cpp random_generator.cpp LIBS ${SPIRV_TOOLS_FULL_VISIBILITY}) + add_spvtools_libfuzzer_target(TARGET spvtools_binary_parser_fuzzer SRCS spvtools_binary_parser_fuzzer.cpp random_generator.cpp LIBS ${SPIRV_TOOLS_FULL_VISIBILITY}) + add_spvtools_libfuzzer_target(TARGET spvtools_dis_fuzzer SRCS spvtools_dis_fuzzer.cpp random_generator.cpp LIBS ${SPIRV_TOOLS_FULL_VISIBILITY}) + add_spvtools_libfuzzer_target(TARGET spvtools_opt_legalization_fuzzer SRCS spvtools_opt_legalization_fuzzer.cpp random_generator.cpp LIBS SPIRV-Tools-opt ${SPIRV_TOOLS_FULL_VISIBILITY}) + add_spvtools_libfuzzer_target(TARGET spvtools_opt_performance_fuzzer SRCS spvtools_opt_performance_fuzzer.cpp random_generator.cpp LIBS SPIRV-Tools-opt ${SPIRV_TOOLS_FULL_VISIBILITY}) + add_spvtools_libfuzzer_target(TARGET spvtools_opt_size_fuzzer SRCS spvtools_opt_size_fuzzer.cpp random_generator.cpp LIBS SPIRV-Tools-opt ${SPIRV_TOOLS_FULL_VISIBILITY}) + add_spvtools_libfuzzer_target(TARGET spvtools_val_fuzzer SRCS spvtools_val_fuzzer.cpp random_generator.cpp LIBS ${SPIRV_TOOLS_FULL_VISIBILITY}) + if (${SPIRV_BUILD_FUZZER}) + add_spvtools_libfuzzer_target(TARGET spvtools_fuzz_fuzzer SRCS spvtools_fuzz_fuzzer.cpp random_generator.cpp LIBS SPIRV-Tools-fuzz ${SPIRV_TOOLS_FULL_VISIBILITY}) + endif() endif() diff --git a/test/fuzzers/random_generator.cpp b/test/fuzzers/random_generator.cpp new file mode 100644 index 00000000..801a9ffe --- /dev/null +++ b/test/fuzzers/random_generator.cpp @@ -0,0 +1,135 @@ +// Copyright (c) 2021 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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/fuzzers/random_generator.h" + +#include <algorithm> +#include <array> +#include <cassert> + +namespace spvtools { +namespace fuzzers { + +namespace { +/// Generate integer from uniform distribution +/// @tparam I - integer type +/// @param engine - random number engine to use +/// @param lower - Lower bound of integer generated +/// @param upper - Upper bound of integer generated +/// @returns i, where lower <= i < upper +template <typename I> +I RandomUInt(std::mt19937_64* engine, I lower, I upper) { + assert(lower < upper && "|lower| must be stictly less than |upper|"); + return std::uniform_int_distribution<I>(lower, upper - 1)(*engine); +} + +/// Helper for obtaining a seed bias value for HashCombine with a bit-width +/// dependent on the size of size_t. +template <int SIZE_OF_SIZE_T> +struct HashCombineOffset {}; +/// Specialization of HashCombineOffset for size_t == 4. +template <> +struct HashCombineOffset<4> { + /// @returns the seed bias value for HashCombine() + static constexpr inline uint32_t value() { + return 0x9e3779b9; // Fractional portion of Golden Ratio, suggested by + // Linux Kernel and Knuth's Art of Computer Programming + } +}; +/// Specialization of HashCombineOffset for size_t == 8. +template <> +struct HashCombineOffset<8> { + /// @returns the seed bias value for HashCombine() + static constexpr inline uint64_t value() { + return 0x9e3779b97f4a7c16; // Fractional portion of Golden Ratio, suggested + // by Linux Kernel and Knuth's Art of Computer + // Programming + } +}; + +/// HashCombine "hashes" together an existing hash and hashable values. +template <typename T> +void HashCombine(size_t* hash, const T& value) { + constexpr size_t offset = HashCombineOffset<sizeof(size_t)>::value(); + *hash ^= std::hash<T>()(value) + offset + (*hash << 6) + (*hash >> 2); +} + +/// Calculate the hash for the contents of a C-style data buffer +/// @param data - pointer to buffer to be hashed +/// @param size - number of elements in buffer +/// @returns hash of the data in the buffer +size_t HashBuffer(const uint8_t* data, const size_t size) { + size_t hash = + static_cast<size_t>(0xCA8945571519E991); // seed with an arbitrary prime + HashCombine(&hash, size); + for (size_t i = 0; i < size; i++) { + HashCombine(&hash, data[i]); + } + return hash; +} + +} // namespace + +RandomGenerator::RandomGenerator(uint64_t seed) : engine_(seed) {} + +RandomGenerator::RandomGenerator(const uint8_t* data, size_t size) { + RandomGenerator(RandomGenerator::CalculateSeed(data, size)); +} + +spv_target_env RandomGenerator::GetTargetEnv() { + spv_target_env result; + + // Need to check that the generated value isn't for a deprecated target env. + do { + result = static_cast<spv_target_env>( + RandomUInt(&engine_, 0u, static_cast<unsigned int>(SPV_ENV_MAX))); + } while (!spvIsValidEnv(result)); + + return result; +} + +uint32_t RandomGenerator::GetUInt32(uint32_t lower, uint32_t upper) { + return RandomUInt(&engine_, lower, upper); +} + +uint32_t RandomGenerator::GetUInt32(uint32_t bound) { + assert(bound > 0 && "|bound| must be greater than 0"); + return RandomUInt(&engine_, 0u, bound); +} + +uint64_t RandomGenerator::CalculateSeed(const uint8_t* data, size_t size) { + assert(data != nullptr && "|data| must be !nullptr"); + + // Number of bytes we want to skip at the start of data for the hash. + // Fewer bytes may be skipped when `size` is small. + // Has lower precedence than kHashDesiredMinBytes. + static const int64_t kHashDesiredLeadingSkipBytes = 5; + // Minimum number of bytes we want to use in the hash. + // Used for short buffers. + static const int64_t kHashDesiredMinBytes = 4; + // Maximum number of bytes we want to use in the hash. + static const int64_t kHashDesiredMaxBytes = 32; + int64_t size_i64 = static_cast<int64_t>(size); + int64_t hash_begin_i64 = + std::min(kHashDesiredLeadingSkipBytes, + std::max<int64_t>(size_i64 - kHashDesiredMinBytes, 0)); + int64_t hash_end_i64 = + std::min(hash_begin_i64 + kHashDesiredMaxBytes, size_i64); + size_t hash_begin = static_cast<size_t>(hash_begin_i64); + size_t hash_size = static_cast<size_t>(hash_end_i64) - hash_begin; + return HashBuffer(data + hash_begin, hash_size); +} + +} // namespace fuzzers +} // namespace spvtools diff --git a/test/fuzzers/random_generator.h b/test/fuzzers/random_generator.h new file mode 100644 index 00000000..b121fe8c --- /dev/null +++ b/test/fuzzers/random_generator.h @@ -0,0 +1,68 @@ +// Copyright (c) 2021 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 TEST_FUZZERS_RANDOM_GENERATOR_H_ +#define TEST_FUZZERS_RANDOM_GENERATOR_H_ + +#include <cstdint> +#include <random> + +#include "source/spirv_target_env.h" + +namespace spvtools { +namespace fuzzers { + +/// Pseudo random generator utility class for fuzzing +class RandomGenerator { + public: + /// @brief Initializes the internal engine + /// @param seed - seed value passed to engine + explicit RandomGenerator(uint64_t seed); + + /// @brief Initializes the internal engine + /// @param data - data to calculate the seed from + /// @param size - size of the data + explicit RandomGenerator(const uint8_t* data, size_t size); + + ~RandomGenerator() {} + + /// Calculate a seed value based on a blob of data. + /// Currently hashes bytes near the front of the buffer, after skipping N + /// bytes. + /// @param data - pointer to data to base calculation off of, must be !nullptr + /// @param size - number of elements in |data|, must be > 0 + static uint64_t CalculateSeed(const uint8_t* data, size_t size); + + /// Get random valid target env. + spv_target_env GetTargetEnv(); + + /// Get uint32_t value from uniform distribution. + /// @param lower - lower bound of integer generated + /// @param upper - upper bound of integer generated + /// @returns i, where lower <= i < upper + uint32_t GetUInt32(uint32_t lower, uint32_t upper); + + /// Get uint32_t value from uniform distribution. + /// @param bound - Upper bound of integer generated + /// @returns i, where 0 <= i < bound + uint32_t GetUInt32(uint32_t bound); + + private: + std::mt19937_64 engine_; +}; // class RandomGenerator + +} // namespace fuzzers +} // namespace spvtools + +#endif // TEST_FUZZERS_RANDOM_GENERATOR_UTILS_H_ diff --git a/test/fuzzers/spvtools_as_fuzzer.cpp b/test/fuzzers/spvtools_as_fuzzer.cpp index 8cecb05f..ba3f5b97 100644 --- a/test/fuzzers/spvtools_as_fuzzer.cpp +++ b/test/fuzzers/spvtools_as_fuzzer.cpp @@ -18,18 +18,27 @@ #include "source/spirv_target_env.h" #include "spirv-tools/libspirv.hpp" +#include "test/fuzzers/random_generator.h" extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - if (size < sizeof(spv_target_env) + 1) return 0; - - const spv_context context = - spvContextCreate(*reinterpret_cast<const spv_target_env*>(data)); - if (context == nullptr) return 0; + spv_target_env target_env = SPV_ENV_UNIVERSAL_1_0; + if (size > 0) { + spvtools::fuzzers::RandomGenerator random_gen(data, size); + target_env = random_gen.GetTargetEnv(); + } - data += sizeof(spv_target_env); - size -= sizeof(spv_target_env); + const spv_context context = spvContextCreate(target_env); + if (context == nullptr) { + return 0; + } std::vector<uint32_t> input; + input.resize(size >> 2); + size_t count = 0; + for (size_t i = 0; (i + 3) < size; i += 4) { + input[count++] = data[i] | (data[i + 1] << 8) | (data[i + 2] << 16) | + (data[i + 3]) << 24; + } std::vector<char> input_str; size_t char_count = input.size() * sizeof(uint32_t) / sizeof(char); diff --git a/test/fuzzers/spvtools_binary_parser_fuzzer.cpp b/test/fuzzers/spvtools_binary_parser_fuzzer.cpp index 76ba4d9e..3a97db41 100644 --- a/test/fuzzers/spvtools_binary_parser_fuzzer.cpp +++ b/test/fuzzers/spvtools_binary_parser_fuzzer.cpp @@ -16,16 +16,18 @@ #include <vector> #include "spirv-tools/libspirv.hpp" +#include "test/fuzzers/random_generator.h" extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - if (size < sizeof(spv_target_env) + 1) return 0; - - const spv_context context = - spvContextCreate(*reinterpret_cast<const spv_target_env*>(data)); - if (context == nullptr) return 0; + if (size < 1) { + return 0; + } - data += sizeof(spv_target_env); - size -= sizeof(spv_target_env); + spvtools::fuzzers::RandomGenerator random_gen(data, size); + const spv_context context = spvContextCreate(random_gen.GetTargetEnv()); + if (context == nullptr) { + return 0; + } std::vector<uint32_t> input; input.resize(size >> 2); diff --git a/test/fuzzers/spvtools_dis_fuzzer.cpp b/test/fuzzers/spvtools_dis_fuzzer.cpp index ca9a52d8..0cb0eff5 100644 --- a/test/fuzzers/spvtools_dis_fuzzer.cpp +++ b/test/fuzzers/spvtools_dis_fuzzer.cpp @@ -18,16 +18,20 @@ #include "source/spirv_target_env.h" #include "spirv-tools/libspirv.hpp" +#include "test/fuzzers/random_generator.h" extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - if (size < sizeof(spv_target_env) + 1) return 0; - - const spv_context context = - spvContextCreate(*reinterpret_cast<const spv_target_env*>(data)); - if (context == nullptr) return 0; + if (size < 4) { + // There are not enough bytes to constitute a binary that can be + // disassembled. + return 0; + } - data += sizeof(spv_target_env); - size -= sizeof(spv_target_env); + spvtools::fuzzers::RandomGenerator random_gen(data, size); + const spv_context context = spvContextCreate(random_gen.GetTargetEnv()); + if (context == nullptr) { + return 0; + } std::vector<uint32_t> input; input.resize(size >> 2); diff --git a/test/fuzzers/spvtools_fuzz_fuzzer.cpp b/test/fuzzers/spvtools_fuzz_fuzzer.cpp new file mode 100644 index 00000000..d43920cf --- /dev/null +++ b/test/fuzzers/spvtools_fuzz_fuzzer.cpp @@ -0,0 +1,80 @@ +// Copyright (c) 2021 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 <cstdint> +#include <vector> + +#include "source/fuzz/fuzzer.h" +#include "source/fuzz/pseudo_random_generator.h" +#include "spirv-tools/libspirv.hpp" +#include "test/fuzzers/random_generator.h" + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + if (size == 0 || (size % sizeof(uint32_t)) != 0) { + // An empty binary, or a binary whose size is not a multiple of word-size, + // cannot be valid, so can be rejected immediately. + return 0; + } + + std::vector<uint32_t> initial_binary(size / sizeof(uint32_t)); + memcpy(initial_binary.data(), data, size); + + spvtools::ValidatorOptions validator_options; + + spvtools::MessageConsumer message_consumer = + [](spv_message_level_t, const char*, const spv_position_t&, const char*) { + }; + + spvtools::fuzzers::RandomGenerator random_gen(data, size); + auto target_env = random_gen.GetTargetEnv(); + std::unique_ptr<spvtools::opt::IRContext> ir_context; + if (!spvtools::fuzz::fuzzerutil::BuildIRContext( + target_env, message_consumer, initial_binary, validator_options, + &ir_context)) { + // The input is invalid - give up. + return 0; + } + + std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier> donor_suppliers = { + [&initial_binary, message_consumer, target_env, + &validator_options]() -> std::unique_ptr<spvtools::opt::IRContext> { + std::unique_ptr<spvtools::opt::IRContext> result; + if (!spvtools::fuzz::fuzzerutil::BuildIRContext( + target_env, message_consumer, initial_binary, validator_options, + &result)) { + // The input was successfully parsed and validated first time around, + // so something is wrong if it is now invalid. + abort(); + } + return result; + }}; + + uint32_t seed = random_gen.GetUInt32(std::numeric_limits<uint32_t>::max()); + auto fuzzer_context = spvtools::MakeUnique<spvtools::fuzz::FuzzerContext>( + spvtools::MakeUnique<spvtools::fuzz::PseudoRandomGenerator>(seed), + spvtools::fuzz::FuzzerContext::GetMinFreshId(ir_context.get()), false); + + auto transformation_context = + spvtools::MakeUnique<spvtools::fuzz::TransformationContext>( + spvtools::MakeUnique<spvtools::fuzz::FactManager>(ir_context.get()), + validator_options); + + spvtools::fuzz::Fuzzer fuzzer( + std::move(ir_context), std::move(transformation_context), + std::move(fuzzer_context), message_consumer, donor_suppliers, false, + spvtools::fuzz::RepeatedPassStrategy::kLoopedWithRecommendations, true, + validator_options); + fuzzer.Run(0); + return 0; +} diff --git a/test/fuzzers/spvtools_opt_legalization_fuzzer.cpp b/test/fuzzers/spvtools_opt_legalization_fuzzer.cpp index b45a98c3..6f4d7e8b 100644 --- a/test/fuzzers/spvtools_opt_legalization_fuzzer.cpp +++ b/test/fuzzers/spvtools_opt_legalization_fuzzer.cpp @@ -16,9 +16,15 @@ #include <vector> #include "spirv-tools/optimizer.hpp" +#include "test/fuzzers/random_generator.h" extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - spvtools::Optimizer optimizer(SPV_ENV_UNIVERSAL_1_3); + if (size < 1) { + return 0; + } + + spvtools::fuzzers::RandomGenerator random_gen(data, size); + spvtools::Optimizer optimizer(random_gen.GetTargetEnv()); optimizer.SetMessageConsumer([](spv_message_level_t, const char*, const spv_position_t&, const char*) {}); diff --git a/test/fuzzers/spvtools_opt_performance_fuzzer.cpp b/test/fuzzers/spvtools_opt_performance_fuzzer.cpp index 6c3bd6ab..9c47d7d7 100644 --- a/test/fuzzers/spvtools_opt_performance_fuzzer.cpp +++ b/test/fuzzers/spvtools_opt_performance_fuzzer.cpp @@ -16,9 +16,15 @@ #include <vector> #include "spirv-tools/optimizer.hpp" +#include "test/fuzzers/random_generator.h" extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - spvtools::Optimizer optimizer(SPV_ENV_UNIVERSAL_1_3); + if (size < 1) { + return 0; + } + + spvtools::fuzzers::RandomGenerator random_gen(data, size); + spvtools::Optimizer optimizer(random_gen.GetTargetEnv()); optimizer.SetMessageConsumer([](spv_message_level_t, const char*, const spv_position_t&, const char*) {}); diff --git a/test/fuzzers/spvtools_opt_size_fuzzer.cpp b/test/fuzzers/spvtools_opt_size_fuzzer.cpp index 68c79747..10fac42a 100644 --- a/test/fuzzers/spvtools_opt_size_fuzzer.cpp +++ b/test/fuzzers/spvtools_opt_size_fuzzer.cpp @@ -16,9 +16,15 @@ #include <vector> #include "spirv-tools/optimizer.hpp" +#include "test/fuzzers/random_generator.h" extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - spvtools::Optimizer optimizer(SPV_ENV_UNIVERSAL_1_3); + if (size < 1) { + return 0; + } + + spvtools::fuzzers::RandomGenerator random_gen(data, size); + spvtools::Optimizer optimizer(random_gen.GetTargetEnv()); optimizer.SetMessageConsumer([](spv_message_level_t, const char*, const spv_position_t&, const char*) {}); diff --git a/test/fuzzers/spvtools_val_fuzzer.cpp b/test/fuzzers/spvtools_val_fuzzer.cpp index 5dc4303b..fd6396cd 100644 --- a/test/fuzzers/spvtools_val_fuzzer.cpp +++ b/test/fuzzers/spvtools_val_fuzzer.cpp @@ -16,9 +16,15 @@ #include <vector> #include "spirv-tools/libspirv.hpp" +#include "test/fuzzers/random_generator.h" extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_3); + if (size < 1) { + return 0; + } + + spvtools::fuzzers::RandomGenerator random_gen(data, size); + spvtools::SpirvTools tools(random_gen.GetTargetEnv()); tools.SetMessageConsumer([](spv_message_level_t, const char*, const spv_position_t&, const char*) {}); diff --git a/test/hex_float_test.cpp b/test/hex_float_test.cpp index c422f756..ffdb8bda 100644 --- a/test/hex_float_test.cpp +++ b/test/hex_float_test.cpp @@ -1325,6 +1325,76 @@ TEST(FloatProxy, Lowest) { Eq(std::numeric_limits<double>::lowest())); } +template <typename T> +struct StreamParseCase { + StreamParseCase(const std::string& lit, bool succ, const std::string& suffix, + T value) + : literal(lit), + expect_success(succ), + expected_suffix(suffix), + expected_value(HexFloat<FloatProxy<T>>(value)) {} + + std::string literal; + bool expect_success; + std::string expected_suffix; + HexFloat<FloatProxy<T>> expected_value; +}; + +template <typename T> +std::ostream& operator<<(std::ostream& os, const StreamParseCase<T>& fspc) { + os << "StreamParseCase(" << fspc.literal + << ", expect_succes:" << int(fspc.expect_success) << "," + << fspc.expected_suffix << "," << fspc.expected_value << ")"; + return os; +} + +using FloatStreamParseTest = ::testing::TestWithParam<StreamParseCase<float>>; + +TEST_P(FloatStreamParseTest, Samples) { + std::stringstream input(GetParam().literal); + HexFloat<FloatProxy<float>> parsed_value(0.0f); + // Hex floats must be read with the stream input operator. + input >> parsed_value; + if (GetParam().expect_success) { + EXPECT_FALSE(input.fail()); + std::string suffix; + input >> suffix; + // EXPECT_EQ(suffix, GetParam().expected_suffix); + EXPECT_EQ(parsed_value.value().getAsFloat(), + GetParam().expected_value.value().getAsFloat()); + } else { + EXPECT_TRUE(input.fail()); + } +} + +INSTANTIATE_TEST_SUITE_P( + HexFloatExponentMissingDigits, FloatStreamParseTest, + ::testing::ValuesIn(std::vector<StreamParseCase<float>>{ + {"0x1.0p1", true, "", 2.0f}, + {"0x1.0p1a", true, "a", 2.0f}, + {"-0x1.0p1f", true, "f", -2.0f}, + {"0x1.0p", false, "", 0.0f}, + {"0x1.0pa", false, "", 0.0f}, + {"0x1.0p!", false, "", 0.0f}, + {"0x1.0p+", false, "", 0.0f}, + {"0x1.0p+a", false, "", 0.0f}, + {"0x1.0p+!", false, "", 0.0f}, + {"0x1.0p-", false, "", 0.0f}, + {"0x1.0p-a", false, "", 0.0f}, + {"0x1.0p-!", false, "", 0.0f}, + {"0x1.0p++", false, "", 0.0f}, + {"0x1.0p+-", false, "", 0.0f}, + {"0x1.0p-+", false, "", 0.0f}, + {"0x1.0p--", false, "", 0.0f}})); + +INSTANTIATE_TEST_SUITE_P( + HexFloatExponentTrailingSign, FloatStreamParseTest, + ::testing::ValuesIn(std::vector<StreamParseCase<float>>{ + // Don't consume a sign after the binary exponent digits. + {"0x1.0p1", true, "", 2.0f}, + {"0x1.0p1+", true, "+", 2.0f}, + {"0x1.0p1-", true, "-", 2.0f}})); + // TODO(awoloszyn): Add fp16 tests and HexFloatTraits. } // namespace } // namespace utils diff --git a/test/operand_capabilities_test.cpp b/test/operand_capabilities_test.cpp index 6f83dfeb..bc0ee055 100644 --- a/test/operand_capabilities_test.cpp +++ b/test/operand_capabilities_test.cpp @@ -368,19 +368,6 @@ INSTANTIATE_TEST_SUITE_P( // clang-format on }))); -// See SPIR-V Section 3.15 FP Fast Math Mode -INSTANTIATE_TEST_SUITE_P( - FPFastMathMode, EnumCapabilityTest, - Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1), - ValuesIn(std::vector<EnumCapabilityCase>{ - 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_SUITE_P( LinkageType, EnumCapabilityTest, diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt index 11299755..bc44e8d6 100644 --- a/test/opt/CMakeLists.txt +++ b/test/opt/CMakeLists.txt @@ -85,6 +85,7 @@ add_spvtools_unittest(TARGET opt remove_unused_interface_variables_test.cpp register_liveness.cpp relax_float_ops_test.cpp + replace_desc_array_access_using_var_index_test.cpp replace_invalid_opc_test.cpp scalar_analysis.cpp scalar_replacement_test.cpp diff --git a/test/opt/aggressive_dead_code_elim_test.cpp b/test/opt/aggressive_dead_code_elim_test.cpp index 5b4291da..f228c8cb 100644 --- a/test/opt/aggressive_dead_code_elim_test.cpp +++ b/test/opt/aggressive_dead_code_elim_test.cpp @@ -7063,6 +7063,287 @@ TEST_F(AggressiveDCETest, DebugInfoKeepInFunctionElimStoreVar) { SinglePassRunAndMatch<AggressiveDCEPass>(text, true); } +TEST_F(AggressiveDCETest, ShaderDebugInfoKeepInFunctionElimStoreVar) { + // Verify that dead local variable tc and store eliminated but all + // in-function NonSemantic Shader debuginfo kept. + + const std::string text = R"( + OpCapability Shader + OpExtension "SPV_KHR_non_semantic_info" + %1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %MainPs "MainPs" %g_tColor %g_sAniso %in_var_TEXCOORD2 %out_var_SV_Target0 + OpExecutionMode %MainPs OriginUpperLeft + %7 = OpString "foo.frag" + %8 = OpString "PS_OUTPUT" + %9 = OpString "float" + %10 = OpString "vColor" + %11 = OpString "PS_INPUT" + %12 = OpString "vTextureCoords" + %13 = OpString "@type.2d.image" + %14 = OpString "type.2d.image" + %15 = OpString "Texture2D.TemplateParam" + %16 = OpString "src.MainPs" + %17 = OpString "tc" + %18 = OpString "ps_output" + %19 = OpString "i" + %20 = OpString "@type.sampler" + %21 = OpString "type.sampler" + %22 = OpString "g_sAniso" + %23 = OpString "g_tColor" + OpName %type_2d_image "type.2d.image" + OpName %g_tColor "g_tColor" + OpName %type_sampler "type.sampler" + OpName %g_sAniso "g_sAniso" + OpName %in_var_TEXCOORD2 "in.var.TEXCOORD2" + OpName %out_var_SV_Target0 "out.var.SV_Target0" + OpName %MainPs "MainPs" + OpName %PS_INPUT "PS_INPUT" + OpMemberName %PS_INPUT 0 "vTextureCoords" + OpName %param_var_i "param.var.i" + OpName %PS_OUTPUT "PS_OUTPUT" + OpMemberName %PS_OUTPUT 0 "vColor" + OpName %type_sampled_image "type.sampled.image" + OpDecorate %in_var_TEXCOORD2 Location 0 + OpDecorate %out_var_SV_Target0 Location 0 + OpDecorate %g_tColor DescriptorSet 0 + OpDecorate %g_tColor Binding 0 + OpDecorate %g_sAniso DescriptorSet 0 + OpDecorate %g_sAniso Binding 1 + %int = OpTypeInt 32 1 + %int_0 = OpConstant %int 0 + %uint = OpTypeInt 32 0 + %uint_32 = OpConstant %uint 32 + %float = OpTypeFloat 32 +%type_2d_image = OpTypeImage %float 2D 2 0 0 1 Unknown +%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image +%type_sampler = OpTypeSampler +%_ptr_UniformConstant_type_sampler = OpTypePointer UniformConstant %type_sampler + %v2float = OpTypeVector %float 2 +%_ptr_Input_v2float = OpTypePointer Input %v2float + %v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %void = OpTypeVoid + %uint_128 = OpConstant %uint 128 + %uint_0 = OpConstant %uint 0 + %uint_1 = OpConstant %uint 1 + %uint_2 = OpConstant %uint 2 + %uint_3 = OpConstant %uint 3 + %uint_4 = OpConstant %uint 4 + %uint_5 = OpConstant %uint 5 + %uint_7 = OpConstant %uint 7 + %uint_8 = OpConstant %uint 8 + %uint_10 = OpConstant %uint 10 + %uint_11 = OpConstant %uint 11 + %uint_12 = OpConstant %uint 12 + %uint_14 = OpConstant %uint 14 + %uint_15 = OpConstant %uint 15 + %uint_16 = OpConstant %uint 16 + %uint_17 = OpConstant %uint 17 + %uint_19 = OpConstant %uint 19 + %uint_20 = OpConstant %uint 20 + %uint_21 = OpConstant %uint 21 + %uint_25 = OpConstant %uint 25 + %uint_29 = OpConstant %uint 29 + %uint_30 = OpConstant %uint 30 + %uint_35 = OpConstant %uint 35 + %uint_41 = OpConstant %uint 41 + %uint_48 = OpConstant %uint 48 + %uint_53 = OpConstant %uint 53 + %uint_64 = OpConstant %uint 64 + %45 = OpTypeFunction %void + %PS_INPUT = OpTypeStruct %v2float +%_ptr_Function_PS_INPUT = OpTypePointer Function %PS_INPUT + %PS_OUTPUT = OpTypeStruct %v4float + %47 = OpTypeFunction %PS_OUTPUT %_ptr_Function_PS_INPUT +%_ptr_Function_PS_OUTPUT = OpTypePointer Function %PS_OUTPUT +%_ptr_Function_v2float = OpTypePointer Function %v2float +%type_sampled_image = OpTypeSampledImage %type_2d_image +%_ptr_Function_v4float = OpTypePointer Function %v4float + %g_tColor = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant + %g_sAniso = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant +%in_var_TEXCOORD2 = OpVariable %_ptr_Input_v2float Input +%out_var_SV_Target0 = OpVariable %_ptr_Output_v4float Output + %51 = OpExtInst %void %1 DebugInfoNone + %52 = OpExtInst %void %1 DebugExpression + %53 = OpExtInst %void %1 DebugOperation %uint_0 + %54 = OpExtInst %void %1 DebugExpression %53 + %55 = OpExtInst %void %1 DebugSource %7 + %56 = OpExtInst %void %1 DebugCompilationUnit %uint_1 %uint_4 %55 %uint_5 + %59 = OpExtInst %void %1 DebugTypeBasic %9 %uint_32 %uint_3 %uint_0 + %60 = OpExtInst %void %1 DebugTypeVector %59 %uint_4 + %58 = OpExtInst %void %1 DebugTypeMember %10 %60 %55 %uint_12 %uint_5 %uint_0 %uint_128 %uint_3 + %57 = OpExtInst %void %1 DebugTypeComposite %8 %uint_1 %55 %uint_10 %uint_1 %56 %8 %uint_128 %uint_3 %58 + %63 = OpExtInst %void %1 DebugTypeVector %59 %uint_2 + %62 = OpExtInst %void %1 DebugTypeMember %12 %63 %55 %uint_7 %uint_5 %uint_0 %uint_64 %uint_3 + %61 = OpExtInst %void %1 DebugTypeComposite %11 %uint_1 %55 %uint_5 %uint_1 %56 %11 %uint_64 %uint_3 %62 + %64 = OpExtInst %void %1 DebugTypeComposite %13 %uint_0 %55 %uint_0 %uint_0 %56 %14 %51 %uint_3 + %67 = OpExtInst %void %1 DebugTypeFunction %uint_3 %57 %61 + %68 = OpExtInst %void %1 DebugFunction %16 %67 %55 %uint_15 %uint_1 %56 %16 %uint_3 %uint_16 + %69 = OpExtInst %void %1 DebugLexicalBlock %55 %uint_16 %uint_1 %68 + %70 = OpExtInst %void %1 DebugLocalVariable %17 %63 %55 %uint_19 %uint_12 %69 %uint_4 + %71 = OpExtInst %void %1 DebugLocalVariable %18 %57 %55 %uint_17 %uint_15 %69 %uint_4 + %72 = OpExtInst %void %1 DebugLocalVariable %19 %61 %55 %uint_15 %uint_29 %68 %uint_4 %uint_1 + %73 = OpExtInst %void %1 DebugTypeComposite %20 %uint_1 %55 %uint_0 %uint_0 %56 %21 %51 %uint_3 + %74 = OpExtInst %void %1 DebugGlobalVariable %22 %73 %55 %uint_3 %uint_14 %56 %22 %g_sAniso %uint_8 + %75 = OpExtInst %void %1 DebugGlobalVariable %23 %64 %55 %uint_1 %uint_11 %56 %23 %g_tColor %uint_8 + %MainPs = OpFunction %void None %45 + %76 = OpLabel + %78 = OpVariable %_ptr_Function_PS_OUTPUT Function + %79 = OpVariable %_ptr_Function_v2float Function + %81 = OpVariable %_ptr_Function_PS_OUTPUT Function +%param_var_i = OpVariable %_ptr_Function_PS_INPUT Function + %82 = OpLoad %v2float %in_var_TEXCOORD2 + %83 = OpCompositeConstruct %PS_INPUT %82 + OpStore %param_var_i %83 + %112 = OpExtInst %void %1 DebugFunctionDefinition %68 %MainPs + %109 = OpExtInst %void %1 DebugScope %68 + %85 = OpExtInst %void %1 DebugDeclare %72 %param_var_i %52 + %110 = OpExtInst %void %1 DebugScope %69 + %87 = OpExtInst %void %1 DebugDeclare %71 %78 %52 +;CHECK: {{%\w+}} = OpExtInst %void %1 DebugFunctionDefinition %68 %MainPs +;CHECK: {{%\w+}} = OpExtInst %void %1 DebugScope %68 +;CHECK: {{%\w+}} = OpExtInst %void %1 DebugDeclare %72 %param_var_i %52 +;CHECK: {{%\w+}} = OpExtInst %void %1 DebugScope %69 +;CHECK: {{%\w+}} = OpExtInst %void %1 DebugDeclare %71 %78 %52 + %300 = OpExtInst %void %1 DebugLine %55 %uint_19 %uint_19 %uint_17 %uint_30 +;CHECK: {{%\w+}} = OpExtInst %void %1 DebugLine %55 %uint_19 %uint_19 %uint_17 %uint_30 + %88 = OpAccessChain %_ptr_Function_v2float %param_var_i %int_0 + %89 = OpLoad %v2float %88 + %301 = OpExtInst %void %1 DebugLine %55 %uint_19 %uint_19 %uint_12 %uint_35 + OpStore %79 %89 +;CHECK-NOT: OpStore %79 %89 + %302 = OpExtInst %void %1 DebugLine %55 %uint_19 %uint_19 %uint_12 %uint_35 +;CHECK: {{%\w+}} = OpExtInst %void %1 DebugLine %55 %uint_19 %uint_19 %uint_12 %uint_35 + %106 = OpExtInst %void %1 DebugValue %70 %89 %52 +;CHECK: {{%\w+}} = OpExtInst %void %1 DebugValue %70 %89 %52 + %303 = OpExtInst %void %1 DebugLine %55 %uint_20 %uint_20 %uint_25 %uint_32 + %91 = OpLoad %type_2d_image %g_tColor + %304 = OpExtInst %void %1 DebugLine %55 %uint_20 %uint_20 %uint_41 %uint_48 + %92 = OpLoad %type_sampler %g_sAniso + %305 = OpExtInst %void %1 DebugLine %55 %uint_20 %uint_20 %uint_25 %uint_53 + %94 = OpSampledImage %type_sampled_image %91 %92 + %95 = OpImageSampleImplicitLod %v4float %94 %89 None + %306 = OpExtInst %void %1 DebugLine %55 %uint_20 %uint_20 %uint_5 %uint_53 + %96 = OpAccessChain %_ptr_Function_v4float %78 %int_0 + OpStore %96 %95 + %307 = OpExtInst %void %1 DebugLine %55 %uint_21 %uint_21 %uint_12 %uint_20 + %97 = OpLoad %PS_OUTPUT %78 + %308 = OpExtInst %void %1 DebugLine %55 %uint_21 %uint_21 %uint_5 %uint_20 + OpStore %81 %97 + %309 = OpExtInst %void %1 DebugNoLine +;CHECK: {{%\w+}} = OpExtInst %void %1 DebugNoLine + %111 = OpExtInst %void %1 DebugNoScope +;CHECK: {{%\w+}} = OpExtInst %void %1 DebugNoScope + %100 = OpCompositeExtract %v4float %97 0 + OpStore %out_var_SV_Target0 %100 + OpReturn + OpFunctionEnd +)"; + + SetTargetEnv(SPV_ENV_VULKAN_1_2); + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndMatch<AggressiveDCEPass>(text, true); +} + +TEST_F(AggressiveDCETest, ShaderDebugInfoGlobalDCE) { + // Verify that DebugGlobalVariable for eliminated private variable has + // variable operand replaced with DebugInfoNone. + + const std::string text = R"(OpCapability Shader +OpExtension "SPV_KHR_non_semantic_info" +%1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %MainPs "MainPs" %out_var_SV_Target0 %a +OpExecutionMode %MainPs OriginUpperLeft +%5 = OpString "source2.hlsl" +%24 = OpString "float" +%29 = OpString "vColor" +%33 = OpString "PS_OUTPUT" +%37 = OpString "MainPs" +%38 = OpString "" +%42 = OpString "ps_output" +%46 = OpString "a" +OpName %a "a" +OpName %out_var_SV_Target0 "out.var.SV_Target0" +OpName %MainPs "MainPs" +OpName %PS_OUTPUT "PS_OUTPUT" +OpMemberName %PS_OUTPUT 0 "vColor" +OpDecorate %out_var_SV_Target0 Location 0 +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%8 = OpConstantNull %v4float +%float_0 = OpConstant %float 0 +%10 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%uint = OpTypeInt 32 0 +%uint_32 = OpConstant %uint 32 +%_ptr_Private_v4float = OpTypePointer Private %v4float +%_ptr_Output_v4float = OpTypePointer Output %v4float +%void = OpTypeVoid +%uint_1 = OpConstant %uint 1 +%uint_4 = OpConstant %uint 4 +%uint_5 = OpConstant %uint 5 +%uint_3 = OpConstant %uint 3 +%uint_0 = OpConstant %uint 0 +%uint_128 = OpConstant %uint 128 +%uint_12 = OpConstant %uint 12 +%uint_8 = OpConstant %uint 8 +%uint_9 = OpConstant %uint 9 +%uint_10 = OpConstant %uint 10 +%uint_15 = OpConstant %uint 15 +%48 = OpTypeFunction %void +%PS_OUTPUT = OpTypeStruct %v4float +%54 = OpTypeFunction %PS_OUTPUT +%_ptr_Function_PS_OUTPUT = OpTypePointer Function %PS_OUTPUT +%_ptr_Function_v4float = OpTypePointer Function %v4float +%a = OpVariable %_ptr_Private_v4float Private +;CHECK-NOT: %a = OpVariable %_ptr_Private_v4float Private +%out_var_SV_Target0 = OpVariable %_ptr_Output_v4float Output +;CHECK: [[dbg_none:%\w+]] = OpExtInst %void %1 DebugInfoNone +%18 = OpExtInst %void %1 DebugExpression +%19 = OpExtInst %void %1 DebugSource %5 +%20 = OpExtInst %void %1 DebugCompilationUnit %uint_1 %uint_4 %19 %uint_5 +%25 = OpExtInst %void %1 DebugTypeBasic %24 %uint_32 %uint_3 %uint_0 +%28 = OpExtInst %void %1 DebugTypeVector %25 %uint_4 +%31 = OpExtInst %void %1 DebugTypeMember %29 %28 %19 %uint_5 %uint_12 %uint_0 %uint_128 %uint_3 +%34 = OpExtInst %void %1 DebugTypeComposite %33 %uint_1 %19 %uint_3 %uint_8 %20 %33 %uint_128 %uint_3 %31 +%36 = OpExtInst %void %1 DebugTypeFunction %uint_3 %34 +%39 = OpExtInst %void %1 DebugFunction %37 %36 %19 %uint_8 %uint_1 %20 %38 %uint_3 %uint_9 +%41 = OpExtInst %void %1 DebugLexicalBlock %19 %uint_9 %uint_1 %39 +%43 = OpExtInst %void %1 DebugLocalVariable %42 %34 %19 %uint_10 %uint_15 %41 %uint_4 +%47 = OpExtInst %void %1 DebugGlobalVariable %46 %28 %19 %uint_1 %uint_15 %20 %46 %a %uint_8 +;CHECK: %47 = OpExtInst %void %1 DebugGlobalVariable %46 %28 %19 %uint_1 %uint_15 %20 %46 [[dbg_none]] %uint_8 +%MainPs = OpFunction %void None %48 +%49 = OpLabel +%65 = OpVariable %_ptr_Function_PS_OUTPUT Function +%66 = OpVariable %_ptr_Function_PS_OUTPUT Function +OpStore %a %8 +%72 = OpExtInst %void %1 DebugScope %41 +%69 = OpExtInst %void %1 DebugDeclare %43 %65 %18 +OpLine %5 11 5 +%70 = OpAccessChain %_ptr_Function_v4float %65 %int_0 +OpStore %70 %10 +OpLine %5 12 12 +%71 = OpLoad %PS_OUTPUT %65 +OpLine %5 12 5 +OpStore %66 %71 +%73 = OpExtInst %void %1 DebugNoLine +%74 = OpExtInst %void %1 DebugNoScope +%51 = OpLoad %PS_OUTPUT %66 +%53 = OpCompositeExtract %v4float %51 0 +OpStore %out_var_SV_Target0 %53 +OpLine %5 13 1 +OpReturn +OpFunctionEnd +)"; + + SetTargetEnv(SPV_ENV_VULKAN_1_2); + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndMatch<AggressiveDCEPass>(text, true); +} + TEST_F(AggressiveDCETest, DebugInfoDeclareKeepsStore) { // Verify that local variable tc and its store are kept by DebugDeclare. // @@ -7584,11 +7865,178 @@ TEST_F(AggressiveDCETest, KeepDebugScopeParent) { SinglePassRunAndMatch<AggressiveDCEPass>(text, true); } -// TODO(greg-lunarg): Add tests to verify handling of these cases: -// -// Check that logical addressing required -// Check that function calls inhibit optimization -// Others? +TEST_F(AggressiveDCETest, KeepExportFunctions) { + // All functions are reachable. In particular, ExportedFunc and Constant are + // reachable because ExportedFunc is exported. Nothing should be removed. + const std::vector<const char*> 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<AggressiveDCEPass>( + assembly, /* skip_nop = */ true, /* do_validation = */ false); + EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result)); + EXPECT_EQ(assembly, std::get<0>(result)); +} + +TEST_F(AggressiveDCETest, KeepPrivateVarInExportFunctions) { + // The loads and stores from the private variable should not be removed + // because the functions are exported and could be called. + const std::string text = R"(OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpSource HLSL 630 +OpName %privateVar "privateVar" +OpName %ReadPrivate "ReadPrivate" +OpName %WritePrivate "WritePrivate" +OpName %value "value" +OpDecorate %ReadPrivate LinkageAttributes "ReadPrivate" Export +OpDecorate %WritePrivate LinkageAttributes "WritePrivate" Export +%int = OpTypeInt 32 1 +%_ptr_Private_int = OpTypePointer Private %int +%6 = OpTypeFunction %int +%void = OpTypeVoid +%_ptr_Function_int = OpTypePointer Function %int +%10 = OpTypeFunction %void %_ptr_Function_int +%privateVar = OpVariable %_ptr_Private_int Private +%ReadPrivate = OpFunction %int None %6 +%12 = OpLabel +%8 = OpLoad %int %privateVar +OpReturnValue %8 +OpFunctionEnd +%WritePrivate = OpFunction %void None %10 +%value = OpFunctionParameter %_ptr_Function_int +%13 = OpLabel +%14 = OpLoad %int %value +OpStore %privateVar %14 +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + auto result = SinglePassRunAndDisassemble<AggressiveDCEPass>( + text, /* skip_nop = */ true, /* do_validation = */ false); + EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result)); + EXPECT_EQ(text, std::get<0>(result)); +} + +TEST_F(AggressiveDCETest, KeepLableNames) { + const std::string text = R"(OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpSource HLSL 630 +OpName %WritePrivate "WritePrivate" +OpName %entry "entry" +OpName %target "target" +OpDecorate %WritePrivate LinkageAttributes "WritePrivate" Export +%void = OpTypeVoid +%3 = OpTypeFunction %void +%WritePrivate = OpFunction %void None %3 +%entry = OpLabel +OpBranch %target +%target = OpLabel +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + auto result = SinglePassRunAndDisassemble<AggressiveDCEPass>( + text, /* skip_nop = */ true, /* do_validation = */ false); + EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result)); + EXPECT_EQ(text, std::get<0>(result)); +} + +TEST_F(AggressiveDCETest, PreserveInterface) { + // Set preserve_interface to true. Verify that unused uniform + // constant in entry point interface is not eliminated. + const std::string text = R"(OpCapability RayTracingKHR +OpExtension "SPV_KHR_ray_tracing" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint RayGenerationNV %2 "main" %3 %4 +OpDecorate %3 Location 0 +OpDecorate %4 DescriptorSet 2 +OpDecorate %4 Binding 0 +%void = OpTypeVoid +%6 = OpTypeFunction %void +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%float = OpTypeFloat 32 +%_ptr_CallableDataNV_float = OpTypePointer CallableDataNV %float +%3 = OpVariable %_ptr_CallableDataNV_float CallableDataNV +%13 = OpTypeAccelerationStructureKHR +%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13 +%4 = OpVariable %_ptr_UniformConstant_13 UniformConstant +%2 = OpFunction %void None %6 +%15 = OpLabel +OpExecuteCallableKHR %uint_0 %3 +OpReturn +OpFunctionEnd +)"; + + SetTargetEnv(SPV_ENV_VULKAN_1_2); + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + auto result = SinglePassRunAndDisassemble<AggressiveDCEPass>( + text, /* skip_nop = */ true, /* do_validation = */ false, + /* preserve_interface */ true); + EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result)); + EXPECT_EQ(text, std::get<0>(result)); +} + +TEST_F(AggressiveDCETest, EmptyContinueWithConditionalBranch) { + const std::string text = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" +OpExecutionMode %2 OriginUpperLeft +%void = OpTypeVoid +%4 = OpTypeFunction %void +%bool = OpTypeBool +%false = OpConstantFalse %bool +%2 = OpFunction %void None %4 +%9 = OpLabel +OpBranch %10 +%10 = OpLabel +OpLoopMerge %11 %12 None +OpBranch %13 +%13 = OpLabel +OpKill +%12 = OpLabel +OpBranchConditional %false %10 %10 +%11 = OpLabel +OpUnreachable +OpFunctionEnd +)"; + + SetTargetEnv(SPV_ENV_VULKAN_1_2); + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck<AggressiveDCEPass>(text, text, false); +} } // namespace } // namespace opt diff --git a/test/opt/block_merge_test.cpp b/test/opt/block_merge_test.cpp index 140a5c09..6903c4e7 100644 --- a/test/opt/block_merge_test.cpp +++ b/test/opt/block_merge_test.cpp @@ -90,6 +90,63 @@ OpFunctionEnd true); } +TEST_F(BlockMergeTest, BlockMergeForLinkage) { + const std::string before = + R"(OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpSource HLSL 630 +OpName %main "main" +OpName %BaseColor "BaseColor" +OpName %bb_entry "bb.entry" +OpName %v "v" +OpDecorate %main LinkageAttributes "main" Export +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%8 = OpTypeFunction %v4float %_ptr_Function_v4float +%main = OpFunction %v4float None %8 +%BaseColor = OpFunctionParameter %_ptr_Function_v4float +%bb_entry = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%9 = OpLoad %v4float %BaseColor +OpStore %v %9 +OpBranch %10 +%10 = OpLabel +%11 = OpLoad %v4float %v +OpBranch %12 +%12 = OpLabel +OpReturnValue %11 +OpFunctionEnd +)"; + + const std::string after = + R"(OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpSource HLSL 630 +OpName %main "main" +OpName %BaseColor "BaseColor" +OpName %bb_entry "bb.entry" +OpName %v "v" +OpDecorate %main LinkageAttributes "main" Export +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%8 = OpTypeFunction %v4float %_ptr_Function_v4float +%main = OpFunction %v4float None %8 +%BaseColor = OpFunctionParameter %_ptr_Function_v4float +%bb_entry = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%9 = OpLoad %v4float %BaseColor +OpStore %v %9 +%11 = OpLoad %v4float %v +OpReturnValue %11 +OpFunctionEnd +)"; + SinglePassRunAndCheck<BlockMergePass>(before, after, true, true); +} + TEST_F(BlockMergeTest, EmptyBlock) { // Note: SPIR-V hand edited to insert empty block // after two statements in main. @@ -1038,6 +1095,112 @@ OpFunctionEnd SinglePassRunAndCheck<BlockMergePass>(spirv, spirv, true, true); } +TEST_F(BlockMergeTest, DebugMerge) { + // Verify merge can be done completely, cleanly and validly in presence of + // NonSemantic.Shader.DebugInfo.100 instructions + const std::string text = R"( +; CHECK: OpLoopMerge +; CHECK-NEXT: OpBranch +; CHECK-NOT: OpBranch +OpCapability Shader +OpExtension "SPV_KHR_non_semantic_info" +%1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %in_var_COLOR %out_var_SV_TARGET +OpExecutionMode %main OriginUpperLeft +%5 = OpString "lexblock.hlsl" +%20 = OpString "float" +%32 = OpString "main" +%33 = OpString "" +%46 = OpString "b" +%49 = OpString "a" +%58 = OpString "c" +%63 = OpString "color" +OpName %in_var_COLOR "in.var.COLOR" +OpName %out_var_SV_TARGET "out.var.SV_TARGET" +OpName %main "main" +OpDecorate %in_var_COLOR Location 0 +OpDecorate %out_var_SV_TARGET Location 0 +%float = OpTypeFloat 32 +%float_0 = OpConstant %float 0 +%v4float = OpTypeVector %float 4 +%9 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0 +%float_1 = OpConstant %float 1 +%13 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +%uint = OpTypeInt 32 0 +%uint_32 = OpConstant %uint 32 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%_ptr_Output_v4float = OpTypePointer Output %v4float +%void = OpTypeVoid +%uint_3 = OpConstant %uint 3 +%uint_0 = OpConstant %uint 0 +%uint_4 = OpConstant %uint 4 +%uint_1 = OpConstant %uint 1 +%uint_5 = OpConstant %uint 5 +%uint_12 = OpConstant %uint 12 +%uint_13 = OpConstant %uint 13 +%uint_20 = OpConstant %uint 20 +%uint_15 = OpConstant %uint 15 +%uint_17 = OpConstant %uint 17 +%uint_16 = OpConstant %uint 16 +%uint_14 = OpConstant %uint 14 +%uint_10 = OpConstant %uint 10 +%65 = OpTypeFunction %void +%in_var_COLOR = OpVariable %_ptr_Input_v4float Input +%out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output +%62 = OpExtInst %void %1 DebugExpression +%22 = OpExtInst %void %1 DebugTypeBasic %20 %uint_32 %uint_3 %uint_0 +%25 = OpExtInst %void %1 DebugTypeVector %22 %uint_4 +%27 = OpExtInst %void %1 DebugTypeFunction %uint_3 %25 %25 +%28 = OpExtInst %void %1 DebugSource %5 +%29 = OpExtInst %void %1 DebugCompilationUnit %uint_1 %uint_4 %28 %uint_5 +%34 = OpExtInst %void %1 DebugFunction %32 %27 %28 %uint_12 %uint_1 %29 %33 %uint_3 %uint_13 +%37 = OpExtInst %void %1 DebugLexicalBlock %28 %uint_13 %uint_1 %34 +%52 = OpExtInst %void %1 DebugLexicalBlock %28 %uint_15 %uint_12 %37 +%54 = OpExtInst %void %1 DebugLocalVariable %46 %25 %28 %uint_17 %uint_12 %52 %uint_4 +%56 = OpExtInst %void %1 DebugLocalVariable %49 %25 %28 %uint_16 %uint_12 %52 %uint_4 +%59 = OpExtInst %void %1 DebugLocalVariable %58 %25 %28 %uint_14 %uint_10 %37 %uint_4 +%64 = OpExtInst %void %1 DebugLocalVariable %63 %25 %28 %uint_12 %uint_20 %34 %uint_4 %uint_1 +%main = OpFunction %void None %65 +%66 = OpLabel +%69 = OpLoad %v4float %in_var_COLOR +%168 = OpExtInst %void %1 DebugValue %64 %69 %62 +%169 = OpExtInst %void %1 DebugScope %37 +OpLine %5 14 10 +%164 = OpExtInst %void %1 DebugValue %59 %9 %62 +OpLine %5 15 3 +OpBranch %150 +%150 = OpLabel +%165 = OpPhi %v4float %9 %66 %158 %159 +%167 = OpExtInst %void %1 DebugValue %59 %165 %62 +%170 = OpExtInst %void %1 DebugScope %37 +OpLine %5 15 12 +%171 = OpExtInst %void %1 DebugNoScope +OpLoopMerge %160 %159 None +OpBranch %151 +%151 = OpLabel +OpLine %5 16 12 +%162 = OpExtInst %void %1 DebugValue %56 %9 %62 +OpLine %5 17 12 +%163 = OpExtInst %void %1 DebugValue %54 %13 %62 +OpLine %5 18 15 +%158 = OpFAdd %v4float %165 %13 +OpLine %5 18 5 +%166 = OpExtInst %void %1 DebugValue %59 %158 %62 +%172 = OpExtInst %void %1 DebugScope %37 +OpLine %5 19 3 +OpBranch %159 +%159 = OpLabel +OpLine %5 19 3 +OpBranch %150 +%160 = OpLabel +OpUnreachable +OpFunctionEnd +)"; + + SinglePassRunAndMatch<BlockMergePass>(text, true); +} + // TODO(greg-lunarg): Add tests to verify handling of these cases: // // More complex control flow diff --git a/test/opt/ccp_test.cpp b/test/opt/ccp_test.cpp index ef734353..212e0514 100644 --- a/test/opt/ccp_test.cpp +++ b/test/opt/ccp_test.cpp @@ -1208,6 +1208,32 @@ TEST_F(CCPTest, CCPNoChangeFailureWithUnfoldableInstr) { auto result = SinglePassRunAndMatch<CCPPass>(text, true); EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange); } + +TEST_F(CCPTest, FunctionDeclaration) { + // Make sure the pass works with a function declaration that is called. + const std::string text = R"(OpCapability Addresses +OpCapability Linkage +OpCapability Kernel +OpCapability Int8 +%1 = OpExtInstImport "OpenCL.std" +OpMemoryModel Physical64 OpenCL +OpEntryPoint Kernel %2 "_Z23julia__1166_kernel_77094Bool" +OpExecutionMode %2 ContractionOff +OpSource Unknown 0 +OpDecorate %3 LinkageAttributes "julia_error_7712" Import +%void = OpTypeVoid +%5 = OpTypeFunction %void +%3 = OpFunction %void None %5 +OpFunctionEnd +%2 = OpFunction %void None %5 +%6 = OpLabel +%7 = OpFunctionCall %void %3 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck<CCPPass>(text, text, false); +} } // namespace } // namespace opt } // namespace spvtools diff --git a/test/opt/combine_access_chains_test.cpp b/test/opt/combine_access_chains_test.cpp index aed14c9d..5be3ba63 100644 --- a/test/opt/combine_access_chains_test.cpp +++ b/test/opt/combine_access_chains_test.cpp @@ -768,6 +768,32 @@ OpFunctionEnd SinglePassRunAndMatch<CombineAccessChains>(text, true); } +TEST_F(CombineAccessChainsTest, FunctionDeclaration) { + // Make sure the pass works with a function declaration that is called. + const std::string text = R"(OpCapability Addresses +OpCapability Linkage +OpCapability Kernel +OpCapability Int8 +%1 = OpExtInstImport "OpenCL.std" +OpMemoryModel Physical64 OpenCL +OpEntryPoint Kernel %2 "_Z23julia__1166_kernel_77094Bool" +OpExecutionMode %2 ContractionOff +OpSource Unknown 0 +OpDecorate %3 LinkageAttributes "julia_error_7712" Import +%void = OpTypeVoid +%5 = OpTypeFunction %void +%3 = OpFunction %void None %5 +OpFunctionEnd +%2 = OpFunction %void None %5 +%6 = OpLabel +%7 = OpFunctionCall %void %3 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck<CombineAccessChains>(text, text, false); +} + } // namespace } // namespace opt } // namespace spvtools diff --git a/test/opt/convert_relaxed_to_half_test.cpp b/test/opt/convert_relaxed_to_half_test.cpp index ca6ee583..6a06de84 100644 --- a/test/opt/convert_relaxed_to_half_test.cpp +++ b/test/opt/convert_relaxed_to_half_test.cpp @@ -204,6 +204,98 @@ OpFunctionEnd defs_after + func_after, true, true); } +TEST_F(ConvertToHalfTest, ConvertToHalfForLinkage) { + const std::string before = + R"(OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpSource HLSL 630 +OpName %type_cbuff "type.cbuff" +OpMemberName %type_cbuff 0 "c" +OpName %cbuff "cbuff" +OpName %main "main" +OpName %BaseColor "BaseColor" +OpName %bb_entry "bb.entry" +OpName %v "v" +OpDecorate %main LinkageAttributes "main" Export +OpDecorate %cbuff DescriptorSet 0 +OpDecorate %cbuff Binding 0 +OpMemberDecorate %type_cbuff 0 Offset 0 +OpDecorate %type_cbuff Block +OpDecorate %18 RelaxedPrecision +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%float = OpTypeFloat 32 +%type_cbuff = OpTypeStruct %float +%_ptr_Uniform_type_cbuff = OpTypePointer Uniform %type_cbuff +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%9 = OpTypeFunction %v4float %_ptr_Function_v4float +%_ptr_Uniform_float = OpTypePointer Uniform %float +%cbuff = OpVariable %_ptr_Uniform_type_cbuff Uniform +%main = OpFunction %v4float None %9 +%BaseColor = OpFunctionParameter %_ptr_Function_v4float +%bb_entry = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%14 = OpLoad %v4float %BaseColor +%16 = OpAccessChain %_ptr_Uniform_float %cbuff %int_0 +%17 = OpLoad %float %16 +%18 = OpVectorTimesScalar %v4float %14 %17 +OpStore %v %18 +%19 = OpLoad %v4float %v +OpReturnValue %19 +OpFunctionEnd +)"; + + const std::string after = + R"(OpCapability Shader +OpCapability Linkage +OpCapability Float16 +OpMemoryModel Logical GLSL450 +OpSource HLSL 630 +OpName %type_cbuff "type.cbuff" +OpMemberName %type_cbuff 0 "c" +OpName %cbuff "cbuff" +OpName %main "main" +OpName %BaseColor "BaseColor" +OpName %bb_entry "bb.entry" +OpName %v "v" +OpDecorate %main LinkageAttributes "main" Export +OpDecorate %cbuff DescriptorSet 0 +OpDecorate %cbuff Binding 0 +OpMemberDecorate %type_cbuff 0 Offset 0 +OpDecorate %type_cbuff Block +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%float = OpTypeFloat 32 +%type_cbuff = OpTypeStruct %float +%_ptr_Uniform_type_cbuff = OpTypePointer Uniform %type_cbuff +%v4float = OpTypeVector %float 4 +%_ptr_Function_v4float = OpTypePointer Function %v4float +%14 = OpTypeFunction %v4float %_ptr_Function_v4float +%_ptr_Uniform_float = OpTypePointer Uniform %float +%cbuff = OpVariable %_ptr_Uniform_type_cbuff Uniform +%half = OpTypeFloat 16 +%v4half = OpTypeVector %half 4 +%main = OpFunction %v4float None %14 +%BaseColor = OpFunctionParameter %_ptr_Function_v4float +%bb_entry = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%16 = OpLoad %v4float %BaseColor +%17 = OpAccessChain %_ptr_Uniform_float %cbuff %int_0 +%18 = OpLoad %float %17 +%22 = OpFConvert %v4half %16 +%23 = OpFConvert %half %18 +%7 = OpVectorTimesScalar %v4half %22 %23 +%24 = OpFConvert %v4float %7 +OpStore %v %24 +%19 = OpLoad %v4float %v +OpReturnValue %19 +OpFunctionEnd +)"; + + SinglePassRunAndCheck<ConvertToHalfPass>(before, after, true, true); +} TEST_F(ConvertToHalfTest, ConvertToHalfWithDrefSample) { // The resulting SPIR-V was processed with --relax-float-ops. // @@ -1397,6 +1489,87 @@ TEST_F(ConvertToHalfTest, RemoveRelaxDec) { EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result)); } +TEST_F(ConvertToHalfTest, HandleNonRelaxedPhi) { + // See https://github.com/KhronosGroup/SPIRV-Tools/issues/4452 + + // This test is a case with a non-relaxed phi with a relaxed operand. + // A convert must be inserted at the end of the block associated with + // the operand. + const std::string test = + R"( +; CHECK: [[fcvt:%\w+]] = OpFConvert %v3float {{%\w+}} +; CHECK-NEXT: OpSelectionMerge {{%\w+}} None +; CHECK: {{%\w+}} = OpPhi %v3float [[fcvt]] {{%\w+}} {{%\w+}} {{%\w+}} + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %output_color + OpExecutionMode %main OriginUpperLeft + OpSource GLSL 450 + OpName %main "main" + OpName %MaterialParams "MaterialParams" + OpMemberName %MaterialParams 0 "foo" + OpName %materialParams "materialParams" + OpName %output_color "output_color" + OpMemberDecorate %MaterialParams 0 Offset 0 + OpDecorate %MaterialParams Block + OpDecorate %materialParams DescriptorSet 0 + OpDecorate %materialParams Binding 5 + OpDecorate %output_color Location 0 + OpDecorate %57 RelaxedPrecision + %void = OpTypeVoid + %3 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v3float = OpTypeVector %float 3 +%MaterialParams = OpTypeStruct %float +%_ptr_Uniform_MaterialParams = OpTypePointer Uniform %MaterialParams +%materialParams = OpVariable %_ptr_Uniform_MaterialParams Uniform + %int = OpTypeInt 32 1 + %int_0 = OpConstant %int 0 +%_ptr_Uniform_float = OpTypePointer Uniform %float + %float_0 = OpConstant %float 0 + %bool = OpTypeBool + %v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%output_color = OpVariable %_ptr_Output_v4float Output + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 +%_ptr_Output_float = OpTypePointer Output %float + %uint_1 = OpConstant %uint 1 + %uint_2 = OpConstant %uint 2 + %float_0_5 = OpConstant %float 0.5 + %61 = OpConstantComposite %v3float %float_0_5 %float_0_5 %float_0_5 + %main = OpFunction %void None %3 + %5 = OpLabel + %55 = OpAccessChain %_ptr_Uniform_float %materialParams %int_0 + %56 = OpLoad %float %55 + %57 = OpCompositeConstruct %v3float %56 %56 %56 + %31 = OpFOrdGreaterThan %bool %56 %float_0 + OpSelectionMerge %33 None + OpBranchConditional %31 %32 %33 + %32 = OpLabel + %37 = OpFMul %v3float %57 %61 + OpBranch %33 + %33 = OpLabel + %58 = OpPhi %v3float %57 %5 %37 %32 + %45 = OpAccessChain %_ptr_Output_float %output_color %uint_0 + %46 = OpCompositeExtract %float %58 0 + OpStore %45 %46 + %48 = OpAccessChain %_ptr_Output_float %output_color %uint_1 + %49 = OpCompositeExtract %float %58 1 + OpStore %48 %49 + %51 = OpAccessChain %_ptr_Output_float %output_color %uint_2 + %52 = OpCompositeExtract %float %58 2 + OpStore %51 %52 + OpReturn + OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + auto result = SinglePassRunAndMatch<ConvertToHalfPass>(test, true); + EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result)); +} + } // namespace } // namespace opt } // namespace spvtools diff --git a/test/opt/copy_prop_array_test.cpp b/test/opt/copy_prop_array_test.cpp index 72bc7f6f..a4599f0f 100644 --- a/test/opt/copy_prop_array_test.cpp +++ b/test/opt/copy_prop_array_test.cpp @@ -1814,6 +1814,31 @@ OpFunctionEnd SinglePassRunAndMatch<CopyPropagateArrays>(before, false); } +TEST_F(CopyPropArrayPassTest, FunctionDeclaration) { + // Make sure the pass works with a function declaration that is called. + const std::string text = R"(OpCapability Addresses +OpCapability Linkage +OpCapability Kernel +OpCapability Int8 +%1 = OpExtInstImport "OpenCL.std" +OpMemoryModel Physical64 OpenCL +OpEntryPoint Kernel %2 "_Z23julia__1166_kernel_77094Bool" +OpExecutionMode %2 ContractionOff +OpSource Unknown 0 +OpDecorate %3 LinkageAttributes "julia_error_7712" Import +%void = OpTypeVoid +%5 = OpTypeFunction %void +%3 = OpFunction %void None %5 +OpFunctionEnd +%2 = OpFunction %void None %5 +%6 = OpLabel +%7 = OpFunctionCall %void %3 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck<CopyPropagateArrays>(text, text, false); +} } // namespace } // namespace opt } // namespace spvtools diff --git a/test/opt/dead_branch_elim_test.cpp b/test/opt/dead_branch_elim_test.cpp index f89befb4..9c1ef2a3 100644 --- a/test/opt/dead_branch_elim_test.cpp +++ b/test/opt/dead_branch_elim_test.cpp @@ -3403,6 +3403,71 @@ OpFunctionEnd SinglePassRunAndMatch<DeadBranchElimPass>(text, true); } +TEST_F(DeadBranchElimTest, DontTransferDecorations) { + // When replacing %4 with %14, we don't want %14 to inherit %4's decorations. + const std::string text = R"( +; CHECK-NOT: OpDecorate {{%\w+}} RelaxedPrecision +; CHECK: [[div:%\w+]] = OpFDiv +; CHECK: {{%\w+}} = OpCopyObject %float [[div]] + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + %3 = OpString "STEVEN" + OpDecorate %4 RelaxedPrecision + %float = OpTypeFloat 32 + %uint = OpTypeInt 32 0 + %void = OpTypeVoid + %float_1 = OpConstant %float 1 + %uint_0 = OpConstant %uint 0 + %10 = OpTypeFunction %void + %2 = OpFunction %void None %10 + %11 = OpLabel + OpSelectionMerge %12 None + OpSwitch %uint_0 %13 + %13 = OpLabel + %14 = OpFDiv %float %float_1 %float_1 + OpLine %3 0 0 + OpBranch %12 + %15 = OpLabel + OpBranch %12 + %12 = OpLabel + %4 = OpPhi %float %float_1 %15 %14 %13 + %16 = OpCopyObject %float %4 + OpReturn + OpFunctionEnd +)"; + + SinglePassRunAndMatch<DeadBranchElimPass>(text, true); +} + +TEST_F(DeadBranchElimTest, FunctionDeclaration) { + // Make sure the pass works with a function declaration that is called. + const std::string text = R"(OpCapability Addresses +OpCapability Linkage +OpCapability Kernel +OpCapability Int8 +%1 = OpExtInstImport "OpenCL.std" +OpMemoryModel Physical64 OpenCL +OpEntryPoint Kernel %2 "_Z23julia__1166_kernel_77094Bool" +OpExecutionMode %2 ContractionOff +OpSource Unknown 0 +OpDecorate %3 LinkageAttributes "julia_error_7712" Import +%void = OpTypeVoid +%5 = OpTypeFunction %void +%3 = OpFunction %void None %5 +OpFunctionEnd +%2 = OpFunction %void None %5 +%6 = OpLabel +%7 = OpFunctionCall %void %3 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck<DeadBranchElimPass>(text, text, false); +} + // TODO(greg-lunarg): Add tests to verify handling of these cases: // // More complex control flow diff --git a/test/opt/dead_insert_elim_test.cpp b/test/opt/dead_insert_elim_test.cpp index 9ea948a4..268e6590 100644 --- a/test/opt/dead_insert_elim_test.cpp +++ b/test/opt/dead_insert_elim_test.cpp @@ -170,6 +170,72 @@ OpFunctionEnd after_predefs + after, true, true); } +TEST_F(DeadInsertElimTest, DeadInsertForLinkage) { + const std::string before = + R"(OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpSource HLSL 630 +OpName %main "main" +OpName %BaseColor "BaseColor" +OpName %bb_entry "bb.entry" +OpName %v "v" +OpDecorate %main LinkageAttributes "main" Export +%int = OpTypeInt 32 1 +%int_1 = OpConstant %int 1 +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%int_0 = OpConstant %int 0 +%float = OpTypeFloat 32 +%float_0 = OpConstant %float 0 +%v2float = OpTypeVector %float 2 +%_ptr_Function_v2float = OpTypePointer Function %v2float +%14 = OpTypeFunction %v2float %_ptr_Function_v2float +%_ptr_Function_float = OpTypePointer Function %float +%main = OpFunction %v2float None %14 +%BaseColor = OpFunctionParameter %_ptr_Function_v2float +%bb_entry = OpLabel +%v = OpVariable %_ptr_Function_v2float Function +%16 = OpLoad %v2float %v +%17 = OpAccessChain %_ptr_Function_float %BaseColor %int_1 +%18 = OpLoad %float %17 +%19 = OpCompositeInsert %v2float %18 %16 0 +%20 = OpCompositeInsert %v2float %float_0 %19 0 +OpReturnValue %20 +OpFunctionEnd +)"; + const std::string after = + R"(OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpSource HLSL 630 +OpName %main "main" +OpName %BaseColor "BaseColor" +OpName %bb_entry "bb.entry" +OpName %v "v" +OpDecorate %main LinkageAttributes "main" Export +%int = OpTypeInt 32 1 +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%int_0 = OpConstant %int 0 +%float = OpTypeFloat 32 +%float_0 = OpConstant %float 0 +%v2float = OpTypeVector %float 2 +%_ptr_Function_v2float = OpTypePointer Function %v2float +%14 = OpTypeFunction %v2float %_ptr_Function_v2float +%_ptr_Function_float = OpTypePointer Function %float +%main = OpFunction %v2float None %14 +%BaseColor = OpFunctionParameter %_ptr_Function_v2float +%bb_entry = OpLabel +%v = OpVariable %_ptr_Function_v2float Function +%16 = OpLoad %v2float %v +%20 = OpCompositeInsert %v2float %float_0 %16 0 +OpReturnValue %20 +OpFunctionEnd +)"; + SinglePassRunAndCheck<DeadInsertElimPass>(before, after, true, true); +} + TEST_F(DeadInsertElimTest, DeadInsertInChainWithPhi) { // Dead insert eliminated with phi in insertion chain. // diff --git a/test/opt/debug_info_manager_test.cpp b/test/opt/debug_info_manager_test.cpp index 49407fd5..e87d0bea 100644 --- a/test/opt/debug_info_manager_test.cpp +++ b/test/opt/debug_info_manager_test.cpp @@ -185,6 +185,122 @@ void main(float in_var_color : COLOR) { 100U); } +TEST(DebugInfoManager, CreateDebugInlinedAtWithConstantManager) { + // Show that CreateDebugInlinedAt will use the Constant manager to generate + // its line operand if the Constant and DefUse managers are valid. This is + // proven by checking that the id for the line operand 7 is the same as the + // existing constant 7. + // + // int function1() { + // return 1; + // } + // + // void main() { + // function1(); + // } + const std::string text = R"(OpCapability Shader +OpExtension "SPV_KHR_non_semantic_info" +%1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +%3 = OpString "parent3.hlsl" +%8 = OpString "int" +%19 = OpString "function1" +%20 = OpString "" +%26 = OpString "main" +OpName %main "main" +OpName %src_main "src.main" +OpName %bb_entry "bb.entry" +OpName %function1 "function1" +OpName %bb_entry_0 "bb.entry" +%int = OpTypeInt 32 1 +%int_1 = OpConstant %int 1 +%uint = OpTypeInt 32 0 +%uint_32 = OpConstant %uint 32 +%void = OpTypeVoid +%uint_4 = OpConstant %uint 4 +%uint_0 = OpConstant %uint 0 +%uint_3 = OpConstant %uint 3 +%uint_1 = OpConstant %uint 1 +%uint_5 = OpConstant %uint 5 +%uint_2 = OpConstant %uint 2 +%uint_17 = OpConstant %uint 17 +%uint_6 = OpConstant %uint 6 +%uint_13 = OpConstant %uint 13 +%100 = OpConstant %uint 7 +%31 = OpTypeFunction %void +%42 = OpTypeFunction %int +%10 = OpExtInst %void %1 DebugTypeBasic %8 %uint_32 %uint_4 %uint_0 +%13 = OpExtInst %void %1 DebugTypeFunction %uint_3 %10 +%15 = OpExtInst %void %1 DebugSource %3 +%16 = OpExtInst %void %1 DebugCompilationUnit %uint_1 %uint_4 %15 %uint_5 +%21 = OpExtInst %void %1 DebugFunction %19 %13 %15 %uint_2 %uint_1 %16 %20 %uint_3 %uint_2 +%23 = OpExtInst %void %1 DebugLexicalBlock %15 %uint_2 %uint_17 %21 +%25 = OpExtInst %void %1 DebugTypeFunction %uint_3 %void +%27 = OpExtInst %void %1 DebugFunction %26 %25 %15 %uint_6 %uint_1 %16 %20 %uint_3 %uint_6 +%29 = OpExtInst %void %1 DebugLexicalBlock %15 %uint_6 %uint_13 %27 +%main = OpFunction %void None %31 +%32 = OpLabel +%33 = OpFunctionCall %void %src_main +OpLine %3 8 1 +OpReturn +OpFunctionEnd +OpLine %3 6 1 +%src_main = OpFunction %void None %31 +OpNoLine +%bb_entry = OpLabel +%47 = OpExtInst %void %1 DebugScope %27 +%37 = OpExtInst %void %1 DebugFunctionDefinition %27 %src_main +%48 = OpExtInst %void %1 DebugScope %29 +OpLine %3 7 3 +%39 = OpFunctionCall %int %function1 +%49 = OpExtInst %void %1 DebugScope %27 +OpLine %3 8 1 +OpReturn +%50 = OpExtInst %void %1 DebugNoScope +OpFunctionEnd +OpLine %3 2 1 +%function1 = OpFunction %int None %42 +OpNoLine +%bb_entry_0 = OpLabel +%51 = OpExtInst %void %1 DebugScope %21 +%45 = OpExtInst %void %1 DebugFunctionDefinition %21 %function1 +%52 = OpExtInst %void %1 DebugScope %23 +OpLine %3 3 3 +OpReturnValue %int_1 +%53 = OpExtInst %void %1 DebugNoScope +OpFunctionEnd + )"; + + std::unique_ptr<IRContext> context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + + const uint32_t line_number = 7U; + Instruction line(context.get(), SpvOpLine); + line.SetInOperands({ + {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {5U}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {line_number}}, + {spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER, {0U}}, + }); + + DebugScope scope(29U, 0U); + + auto db_manager = context.get()->get_debug_info_mgr(); + auto du_manager = context.get()->get_def_use_mgr(); + auto c_manager = context.get()->get_constant_mgr(); + + (void)du_manager; + (void)c_manager; + + uint32_t inlined_at_id = db_manager->CreateDebugInlinedAt(&line, scope); + auto* inlined_at = db_manager->GetDebugInlinedAt(inlined_at_id); + EXPECT_NE(inlined_at, nullptr); + EXPECT_EQ(inlined_at->GetSingleWordOperand(kDebugInlinedAtOperandLineIndex), + 100); +} + TEST(DebugInfoManager, GetDebugInfoNone) { const std::string text = R"( OpCapability Shader diff --git a/test/opt/desc_sroa_test.cpp b/test/opt/desc_sroa_test.cpp index b35ad474..dcb625d6 100644 --- a/test/opt/desc_sroa_test.cpp +++ b/test/opt/desc_sroa_test.cpp @@ -770,6 +770,69 @@ TEST_F(DescriptorScalarReplacementTest, BindingForResourceArrayOfStructs) { SinglePassRunAndMatch<DescriptorScalarReplacement>(shader, true); } +TEST_F(DescriptorScalarReplacementTest, MemberDecorationForResourceStruct) { + // Check that an OpMemberDecorate instruction is correctly converted to a + // OpDecorate instruction. + + const std::string shader = R"( +; CHECK: OpDecorate [[t:%\w+]] DescriptorSet 0 +; CHECK: OpDecorate [[t]] Binding 0 +; CHECK: OpDecorate [[t]] RelaxedPrecision +; CHECK: OpDecorate [[s:%\w+]] DescriptorSet 0 +; CHECK: OpDecorate [[s]] Binding 1 + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %PSMain "PSMain" %in_var_TEXCOORD %out_var_SV_Target + OpExecutionMode %PSMain OriginUpperLeft + OpSource HLSL 600 + OpName %sampler2D_h "sampler2D_h" + OpMemberName %sampler2D_h 0 "t" + OpMemberName %sampler2D_h 1 "s" + OpName %type_2d_image "type.2d.image" + OpName %type_sampler "type.sampler" + OpName %_MainTex "_MainTex" + OpName %in_var_TEXCOORD "in.var.TEXCOORD" + OpName %out_var_SV_Target "out.var.SV_Target" + OpName %PSMain "PSMain" + OpName %type_sampled_image "type.sampled.image" + OpDecorate %in_var_TEXCOORD Location 0 + OpDecorate %out_var_SV_Target Location 0 + OpDecorate %_MainTex DescriptorSet 0 + OpDecorate %_MainTex Binding 0 + OpMemberDecorate %sampler2D_h 0 RelaxedPrecision + OpDecorate %out_var_SV_Target RelaxedPrecision + OpDecorate %69 RelaxedPrecision + %float = OpTypeFloat 32 +%type_2d_image = OpTypeImage %float 2D 2 0 0 1 Unknown +%type_sampler = OpTypeSampler +%sampler2D_h = OpTypeStruct %type_2d_image %type_sampler +%_ptr_UniformConstant_sampler2D_h = OpTypePointer UniformConstant %sampler2D_h + %v2float = OpTypeVector %float 2 +%_ptr_Input_v2float = OpTypePointer Input %v2float + %v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %void = OpTypeVoid + %35 = OpTypeFunction %void +%type_sampled_image = OpTypeSampledImage %type_2d_image + %_MainTex = OpVariable %_ptr_UniformConstant_sampler2D_h UniformConstant +%in_var_TEXCOORD = OpVariable %_ptr_Input_v2float Input +%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output + %PSMain = OpFunction %void None %35 + %43 = OpLabel + %44 = OpLoad %v2float %in_var_TEXCOORD + %57 = OpLoad %sampler2D_h %_MainTex + %72 = OpCompositeExtract %type_2d_image %57 0 + %73 = OpCompositeExtract %type_sampler %57 1 + %68 = OpSampledImage %type_sampled_image %72 %73 + %69 = OpImageSampleImplicitLod %v4float %68 %44 None + OpStore %out_var_SV_Target %69 + OpReturn + OpFunctionEnd +)"; + + SinglePassRunAndMatch<DescriptorScalarReplacement>(shader, true); +} + } // namespace } // namespace opt } // namespace spvtools diff --git a/test/opt/eliminate_dead_member_test.cpp b/test/opt/eliminate_dead_member_test.cpp index 78874330..e277999e 100644 --- a/test/opt/eliminate_dead_member_test.cpp +++ b/test/opt/eliminate_dead_member_test.cpp @@ -626,6 +626,67 @@ TEST_F(EliminateDeadMemberTest, KeepMembersOpStore) { EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); } +TEST_F(EliminateDeadMemberTest, KeepStorageBufferMembers) { + // Test that all members of the storage buffer struct %S are kept. + // No change expected. + const std::string text = R"( + OpCapability Shader + OpExtension "SPV_GOOGLE_hlsl_functionality1" + OpExtension "SPV_GOOGLE_user_type" + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %PSMain "PSMain" %out_var_SV_TARGET + OpExecutionMode %PSMain OriginUpperLeft + OpSource HLSL 600 + OpName %type_StructuredBuffer_S "type.StructuredBuffer.S" + OpName %S "S" + OpMemberName %S 0 "A" + OpMemberName %S 1 "B" + OpName %Buf "Buf" + OpName %out_var_SV_TARGET "out.var.SV_TARGET" + OpName %PSMain "PSMain" + OpDecorateString %out_var_SV_TARGET UserSemantic "SV_TARGET" + OpDecorate %out_var_SV_TARGET Location 0 + OpDecorate %Buf DescriptorSet 0 + OpDecorate %Buf Binding 0 + OpMemberDecorate %S 0 Offset 0 + OpMemberDecorate %S 1 Offset 16 + OpDecorate %_runtimearr_S ArrayStride 32 + OpMemberDecorate %type_StructuredBuffer_S 0 Offset 0 + OpMemberDecorate %type_StructuredBuffer_S 0 NonWritable + OpDecorate %type_StructuredBuffer_S BufferBlock + OpDecorateString %Buf UserTypeGOOGLE "structuredbuffer" + %int = OpTypeInt 32 1 + %int_0 = OpConstant %int 0 + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %int_1 = OpConstant %int 1 + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 + %S = OpTypeStruct %v4float %v4float +%_runtimearr_S = OpTypeRuntimeArray %S +%type_StructuredBuffer_S = OpTypeStruct %_runtimearr_S +%_ptr_Uniform_type_StructuredBuffer_S = OpTypePointer Uniform %type_StructuredBuffer_S +%_ptr_Output_v4float = OpTypePointer Output %v4float + %void = OpTypeVoid + %18 = OpTypeFunction %void +%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float + %Buf = OpVariable %_ptr_Uniform_type_StructuredBuffer_S Uniform +%out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output + %PSMain = OpFunction %void None %18 + %20 = OpLabel + %21 = OpAccessChain %_ptr_Uniform_v4float %Buf %int_0 %uint_0 %int_1 + %22 = OpLoad %v4float %21 + OpStore %out_var_SV_TARGET %22 + OpReturn + OpFunctionEnd +)"; + + auto result = SinglePassRunAndDisassemble<opt::EliminateDeadMembersPass>( + text, /* skip_nop = */ true, /* do_validation = */ true); + EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + TEST_F(EliminateDeadMemberTest, KeepMembersOpCopyMemory) { // Test that all members are kept because of an OpCopyMemory. // No change expected. diff --git a/test/opt/fold_test.cpp b/test/opt/fold_test.cpp index da5b017d..0487e78c 100644 --- a/test/opt/fold_test.cpp +++ b/test/opt/fold_test.cpp @@ -137,6 +137,7 @@ OpName %main "main" %int = OpTypeInt 32 1 %long = OpTypeInt 64 1 %uint = OpTypeInt 32 0 +%ulong = OpTypeInt 64 0 %v2int = OpTypeVector %int 2 %v4int = OpTypeVector %int 4 %v4float = OpTypeVector %float 4 @@ -154,6 +155,7 @@ OpName %main "main" %_ptr_double = OpTypePointer Function %double %_ptr_half = OpTypePointer Function %half %_ptr_long = OpTypePointer Function %long +%_ptr_ulong = OpTypePointer Function %ulong %_ptr_v2int = OpTypePointer Function %v2int %_ptr_v4int = OpTypePointer Function %v4int %_ptr_v4float = OpTypePointer Function %v4float @@ -171,12 +173,23 @@ OpName %main "main" %int_2 = OpConstant %int 2 %int_3 = OpConstant %int 3 %int_4 = OpConstant %int 4 +%int_10 = OpConstant %int 10 +%int_1073741824 = OpConstant %int 1073741824 +%int_n1 = OpConstant %int -1 %int_n24 = OpConstant %int -24 +%int_n858993459 = OpConstant %int -858993459 %int_min = OpConstant %int -2147483648 %int_max = OpConstant %int 2147483647 %long_0 = OpConstant %long 0 +%long_1 = OpConstant %long 1 %long_2 = OpConstant %long 2 %long_3 = OpConstant %long 3 +%long_10 = OpConstant %long 10 +%long_4611686018427387904 = OpConstant %long 4611686018427387904 +%long_n1 = OpConstant %long -1 +%long_n3689348814741910323 = OpConstant %long -3689348814741910323 +%long_min = OpConstant %long -9223372036854775808 +%long_max = OpConstant %long 9223372036854775807 %uint_0 = OpConstant %uint 0 %uint_1 = OpConstant %uint 1 %uint_2 = OpConstant %uint 2 @@ -184,7 +197,13 @@ OpName %main "main" %uint_4 = OpConstant %uint 4 %uint_32 = OpConstant %uint 32 %uint_42 = OpConstant %uint 42 +%uint_2147483649 = OpConstant %uint 2147483649 %uint_max = OpConstant %uint 4294967295 +%ulong_0 = OpConstant %ulong 0 +%ulong_1 = OpConstant %ulong 1 +%ulong_2 = OpConstant %ulong 2 +%ulong_9223372036854775809 = OpConstant %ulong 9223372036854775809 +%ulong_max = OpConstant %ulong 18446744073709551615 %v2int_undef = OpUndef %v2int %v2int_0_0 = OpConstantComposite %v2int %int_0 %int_0 %v2int_1_0 = OpConstantComposite %v2int %int_1 %int_0 @@ -3589,7 +3608,19 @@ INSTANTIATE_TEST_SUITE_P(CompositeExtractFoldingTest, GeneralInstructionFoldingT "%4 = OpCompositeExtract %int %3 2\n" + "OpReturn\n" + "OpFunctionEnd", - 4, INT_0_ID) + 4, INT_0_ID), + // Test case 15: + // Don't fold extract fed by construct with vector result if the index is + // past the last element. + InstructionFoldingCase<uint32_t>( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpCompositeConstruct %v2int %int_0 %int_0\n" + + "%3 = OpCompositeConstruct %v4int %2 %100 %int_0\n" + + "%4 = OpCompositeExtract %int %3 4\n" + + "OpReturn\n" + + "OpFunctionEnd", + 4, 0) )); INSTANTIATE_TEST_SUITE_P(CompositeConstructFoldingTest, GeneralInstructionFoldingTest, @@ -5572,7 +5603,109 @@ INSTANTIATE_TEST_SUITE_P(MergeMulTest, MatchingInstructionFoldingTest, "%5 = OpFMul %float %4 %2\n" + "OpReturn\n" + "OpFunctionEnd\n", - 5, true) + 5, true), + // Test case 25: fold overflowing signed 32 bit imuls + // (x * 1073741824) * 2 = x * int_min + InstructionFoldingCase<bool>( + Header() + + "; CHECK: [[int:%\\w+]] = OpTypeInt 32\n" + + "; CHECK: [[int_min:%\\w+]] = OpConstant [[int]] -2147483648\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[int]]\n" + + "; CHECK: %4 = OpIMul [[int]] [[ld]] [[int_min]]\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_1073741824\n" + + "%4 = OpIMul %int %3 %int_2\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 26: fold overflowing signed 64 bit imuls + // (x * 4611686018427387904) * 2 = x * long_min + InstructionFoldingCase<bool>( + Header() + + "; CHECK: [[long:%\\w+]] = OpTypeInt 64\n" + + "; CHECK: [[long_min:%\\w+]] = OpConstant [[long]] -9223372036854775808\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[long]]\n" + + "; CHECK: %4 = OpIMul [[long]] [[ld]] [[long_min]]\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_4611686018427387904\n" + + "%4 = OpIMul %long %3 %long_2\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 27: fold overflowing 32 bit unsigned imuls + // (x * 2147483649) * 2 = x * 2 + InstructionFoldingCase<bool>( + Header() + + "; CHECK: [[uint:%\\w+]] = OpTypeInt 32 0\n" + + "; CHECK: [[uint_2:%\\w+]] = OpConstant [[uint]] 2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[uint]]\n" + + "; CHECK: %4 = OpIMul [[uint]] [[ld]] [[uint_2]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_uint Function\n" + + "%2 = OpLoad %uint %var\n" + + "%3 = OpIMul %uint %2 %uint_2147483649\n" + + "%4 = OpIMul %uint %3 %uint_2\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 28: fold overflowing 64 bit unsigned imuls + // (x * 9223372036854775809) * 2 = x * 2 + InstructionFoldingCase<bool>( + Header() + + "; CHECK: [[ulong:%\\w+]] = OpTypeInt 64 0\n" + + "; CHECK: [[ulong_2:%\\w+]] = OpConstant [[ulong]] 2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[ulong]]\n" + + "; CHECK: %4 = OpIMul [[ulong]] [[ld]] [[ulong_2]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_ulong Function\n" + + "%2 = OpLoad %ulong %var\n" + + "%3 = OpIMul %ulong %2 %ulong_9223372036854775809\n" + + "%4 = OpIMul %ulong %3 %ulong_2\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 29: fold underflowing signed 32 bit imuls + // (x * (-858993459)) * 10 = x * 2 + InstructionFoldingCase<bool>( + Header() + + "; CHECK: [[int:%\\w+]] = OpTypeInt 32\n" + + "; CHECK: [[int_2:%\\w+]] = OpConstant [[int]] 2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[int]]\n" + + "; CHECK: %4 = OpIMul [[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 = OpIMul %int %2 %int_n858993459\n" + + "%4 = OpIMul %int %3 %int_10\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 30: fold underflowing signed 64 bit imuls + // (x * (-3689348814741910323)) * 10 = x * 2 + InstructionFoldingCase<bool>( + Header() + + "; CHECK: [[long:%\\w+]] = OpTypeInt 64\n" + + "; CHECK: [[long_2:%\\w+]] = OpConstant [[long]] 2\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[long]]\n" + + "; CHECK: %4 = OpIMul [[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 = OpIMul %long %2 %long_n3689348814741910323\n" + + "%4 = OpIMul %long %3 %long_10\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true) )); INSTANTIATE_TEST_SUITE_P(MergeDivTest, MatchingInstructionFoldingTest, @@ -5732,15 +5865,11 @@ INSTANTIATE_TEST_SUITE_P(MergeDivTest, MatchingInstructionFoldingTest, "OpReturn\n" + "OpFunctionEnd\n", 4, false), - // Test case 11: merge sdiv of snegate - // (-x) / 2 = x / -2 + // Test case 11: Do not merge sdiv of snegate. If %2 is INT_MIN, then the + // sign of %3 will be the same as %2. This cannot be accounted for in OpSDiv. + // Specifically, (-INT_MIN) / 2 != INT_MIN / -2. InstructionFoldingCase<bool>( Header() + - "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" + - "; CHECK: OpConstant [[int]] -2147483648\n" + - "; CHECK: [[int_n2:%\\w+]] = OpConstant [[int]] -2{{[[:space:]]}}\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" + @@ -5749,16 +5878,12 @@ INSTANTIATE_TEST_SUITE_P(MergeDivTest, MatchingInstructionFoldingTest, "%4 = OpSDiv %int %3 %int_2\n" + "OpReturn\n" + "OpFunctionEnd\n", - 4, true), - // Test case 12: merge sdiv of snegate - // 2 / (-x) = -2 / x + 4, false), + // Test case 12: Do not merge sdiv of snegate. If %2 is INT_MIN, then the + // sign of %3 will be the same as %2. This cannot be accounted for in OpSDiv. + // Specifically, 2 / (-INT_MIN) != -2 / INT_MIN. InstructionFoldingCase<bool>( Header() + - "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" + - "; CHECK: OpConstant [[int]] -2147483648\n" + - "; CHECK: [[int_n2:%\\w+]] = OpConstant [[int]] -2{{[[:space:]]}}\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" + @@ -5767,7 +5892,7 @@ INSTANTIATE_TEST_SUITE_P(MergeDivTest, MatchingInstructionFoldingTest, "%4 = OpSDiv %int %int_2 %3\n" + "OpReturn\n" + "OpFunctionEnd\n", - 4, true), + 4, false), // Test case 13: Don't merge // (x / {null}) / {null} InstructionFoldingCase<bool>( @@ -6052,6 +6177,108 @@ INSTANTIATE_TEST_SUITE_P(MergeAddTest, MatchingInstructionFoldingTest, "%4 = OpFAdd %float %float_2 %3\n" + "OpReturn\n" + "OpFunctionEnd\n", + 4, true), + // Test case 12: fold overflowing signed 32 bit iadds + // (x + int_max) + 1 = x + int_min + InstructionFoldingCase<bool>( + Header() + + "; CHECK: [[int:%\\w+]] = OpTypeInt 32\n" + + "; CHECK: [[int_min:%\\w+]] = OpConstant [[int]] -2147483648\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[int]]\n" + + "; CHECK: %4 = OpIAdd [[int]] [[ld]] [[int_min]]\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_max\n" + + "%4 = OpIAdd %int %3 %int_1\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 13: fold overflowing signed 64 bit iadds + // (x + long_max) + 1 = x + long_min + InstructionFoldingCase<bool>( + Header() + + "; CHECK: [[long:%\\w+]] = OpTypeInt 64\n" + + "; CHECK: [[long_min:%\\w+]] = OpConstant [[long]] -9223372036854775808\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[long]]\n" + + "; CHECK: %4 = OpIAdd [[long]] [[ld]] [[long_min]]\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_max\n" + + "%4 = OpIAdd %long %3 %long_1\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 14: fold overflowing 32 bit unsigned iadds + // (x + uint_max) + 2 = x + 1 + InstructionFoldingCase<bool>( + Header() + + "; CHECK: [[uint:%\\w+]] = OpTypeInt 32 0\n" + + "; CHECK: [[uint_1:%\\w+]] = OpConstant [[uint]] 1\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[uint]]\n" + + "; CHECK: %4 = OpIAdd [[uint]] [[ld]] [[uint_1]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_uint Function\n" + + "%2 = OpLoad %uint %var\n" + + "%3 = OpIAdd %uint %2 %uint_max\n" + + "%4 = OpIAdd %uint %3 %uint_2\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 15: fold overflowing 64 bit unsigned iadds + // (x + ulong_max) + 2 = x + 1 + InstructionFoldingCase<bool>( + Header() + + "; CHECK: [[ulong:%\\w+]] = OpTypeInt 64 0\n" + + "; CHECK: [[ulong_1:%\\w+]] = OpConstant [[ulong]] 1\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[ulong]]\n" + + "; CHECK: %4 = OpIAdd [[ulong]] [[ld]] [[ulong_1]]\n" + + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%var = OpVariable %_ptr_ulong Function\n" + + "%2 = OpLoad %ulong %var\n" + + "%3 = OpIAdd %ulong %2 %ulong_max\n" + + "%4 = OpIAdd %ulong %3 %ulong_2\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 16: fold underflowing signed 32 bit iadds + // (x + int_min) + (-1) = x + int_max + InstructionFoldingCase<bool>( + Header() + + "; CHECK: [[int:%\\w+]] = OpTypeInt 32\n" + + "; CHECK: [[int_max:%\\w+]] = OpConstant [[int]] 2147483647\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[int]]\n" + + "; CHECK: %4 = OpIAdd [[int]] [[ld]] [[int_max]]\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_min\n" + + "%4 = OpIAdd %int %3 %int_n1\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 17: fold underflowing signed 64 bit iadds + // (x + long_min) + (-1) = x + long_max + InstructionFoldingCase<bool>( + Header() + + "; CHECK: [[long:%\\w+]] = OpTypeInt 64\n" + + "; CHECK: [[long_max:%\\w+]] = OpConstant [[long]] 9223372036854775807\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[long]]\n" + + "; CHECK: %4 = OpIAdd [[long]] [[ld]] [[long_max]]\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_min\n" + + "%4 = OpIAdd %long %3 %long_n1\n" + + "OpReturn\n" + + "OpFunctionEnd\n", 4, true) )); @@ -6420,6 +6647,40 @@ INSTANTIATE_TEST_SUITE_P(MergeSubTest, MatchingInstructionFoldingTest, "%4 = OpISub %int %int_2 %3\n" + "OpReturn\n" + "OpFunctionEnd\n", + 4, true), + // Test case 14: fold overflowing signed 32 bit isubs + // (x - int_max) - 1 = x - int_min + InstructionFoldingCase<bool>( + Header() + + "; CHECK: [[int:%\\w+]] = OpTypeInt 32\n" + + "; CHECK: [[int_min:%\\w+]] = OpConstant [[int]] -2147483648\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[int]]\n" + + "; CHECK: %4 = OpISub [[int]] [[ld]] [[int_min]]\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_max\n" + + "%4 = OpISub %int %3 %int_1\n" + + "OpReturn\n" + + "OpFunctionEnd\n", + 4, true), + // Test case 15: fold overflowing signed 64 bit isubs + // (x - long_max) - 1 = x - long_min + InstructionFoldingCase<bool>( + Header() + + "; CHECK: [[long:%\\w+]] = OpTypeInt 64\n" + + "; CHECK: [[long_min:%\\w+]] = OpConstant [[long]] -9223372036854775808\n" + + "; CHECK: [[ld:%\\w+]] = OpLoad [[long]]\n" + + "; CHECK: %4 = OpISub [[long]] [[ld]] [[long_min]]\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_max\n" + + "%4 = OpISub %long %3 %long_1\n" + + "OpReturn\n" + + "OpFunctionEnd\n", 4, true) )); diff --git a/test/opt/inline_opaque_test.cpp b/test/opt/inline_opaque_test.cpp index 8cb8925c..e4db4325 100644 --- a/test/opt/inline_opaque_test.cpp +++ b/test/opt/inline_opaque_test.cpp @@ -226,6 +226,115 @@ OpFunctionEnd predefs + before + post_defs, predefs + after + post_defs, true, true); } +TEST_F(InlineOpaqueTest, InlineOpaqueForLinkage) { + const std::string predefs_1 = + R"(OpCapability Shader +OpCapability Linkage +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpSource HLSL 630 +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 %main LinkageAttributes "main" Export +)"; + + const std::string name = R"(OpName %return_value "return_value" +)"; + + const std::string predefs_2 = R"(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"(%main = 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 +%return_value = OpFunctionCall %void %foo_struct_S_t_vf2_vf21_ %param +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(%main = 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 +%42 = OpAccessChain %_ptr_Function_19 %param %int_2 +%43 = OpLoad %19 %42 +%44 = OpAccessChain %_ptr_Function_v2float %param %int_0 +%45 = OpLoad %v2float %44 +%46 = OpImageSampleImplicitLod %v4float %43 %45 +OpStore %outColor %46 +OpReturn +OpFunctionEnd +)"; + + const std::string post_defs = + R"(%foo_struct_S_t_vf2_vf21_ = OpFunction %void None %21 +%s = OpFunctionParameter %_ptr_Function_S_t +%35 = OpLabel +%36 = OpAccessChain %_ptr_Function_19 %s %int_2 +%37 = OpLoad %19 %36 +%38 = OpAccessChain %_ptr_Function_v2float %s %int_0 +%39 = OpLoad %v2float %38 +%40 = OpImageSampleImplicitLod %v4float %37 %39 +OpStore %outColor %40 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck<InlineOpaquePass>( + predefs_1 + name + predefs_2 + before + post_defs, + predefs_1 + predefs_2 + 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 diff --git a/test/opt/inline_test.cpp b/test/opt/inline_test.cpp index 29399013..d22f027c 100644 --- a/test/opt/inline_test.cpp +++ b/test/opt/inline_test.cpp @@ -2300,7 +2300,6 @@ TEST_F(InlineTest, DontInlineDirectlyRecursiveFunc) { OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %1 "main" OpExecutionMode %1 OriginUpperLeft -OpDecorate %2 DescriptorSet 439418829 %void = OpTypeVoid %4 = OpTypeFunction %void %float = OpTypeFloat 32 @@ -2330,7 +2329,6 @@ TEST_F(InlineTest, DontInlineInDirectlyRecursiveFunc) { OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %1 "main" OpExecutionMode %1 OriginUpperLeft -OpDecorate %2 DescriptorSet 439418829 %void = OpTypeVoid %4 = OpTypeFunction %void %float = OpTypeFloat 32 @@ -2581,6 +2579,132 @@ OpFunctionEnd SinglePassRunAndCheck<InlineExhaustivePass>(before, after, false, true); } +TEST_F(InlineTest, InlineForLinkage) { + const std::string before = + R"(OpCapability SampledBuffer +OpCapability ImageBuffer +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpSource HLSL 630 +OpName %type_buffer_image "type.buffer.image" +OpName %output "output" +OpName %main "main" +OpName %color "color" +OpName %bb_entry "bb.entry" +OpName %param_var_color "param.var.color" +OpName %fn "fn" +OpName %color_0 "color" +OpName %bb_entry_0 "bb.entry" +OpName %v "v" +OpDecorate %main LinkageAttributes "main" Export +OpDecorate %output DescriptorSet 0 +OpDecorate %output Binding 1 +%float = OpTypeFloat 32 +%float_0_200000003 = OpConstant %float 0.200000003 +%v4float = OpTypeVector %float 4 +%6 = OpConstantComposite %v4float %float_0_200000003 %float_0_200000003 %float_0_200000003 %float_0_200000003 +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%type_buffer_image = OpTypeImage %float Buffer 2 0 0 2 Rgba32f +%_ptr_UniformConstant_type_buffer_image = OpTypePointer UniformConstant %type_buffer_image +%_ptr_Function_v4float = OpTypePointer Function %v4float +%11 = OpTypeFunction %float %_ptr_Function_v4float +%_ptr_Function_float = OpTypePointer Function %float +%output = OpVariable %_ptr_UniformConstant_type_buffer_image UniformConstant +%main = OpFunction %float None %11 +%color = OpFunctionParameter %_ptr_Function_v4float +%bb_entry = OpLabel +%param_var_color = OpVariable %_ptr_Function_v4float Function +%16 = OpLoad %v4float %color +OpStore %param_var_color %16 +%17 = OpFunctionCall %float %fn %param_var_color +OpReturnValue %17 +OpFunctionEnd +%fn = OpFunction %float None %11 +%color_0 = OpFunctionParameter %_ptr_Function_v4float +%bb_entry_0 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%22 = OpLoad %v4float %color_0 +OpStore %v %22 +%23 = OpLoad %v4float %v +%24 = OpFMul %v4float %23 %6 +OpStore %v %24 +%26 = OpAccessChain %_ptr_Function_float %v %int_0 +%27 = OpLoad %float %26 +OpReturnValue %27 +OpFunctionEnd + )"; + + const std::string after = + R"(OpCapability SampledBuffer +OpCapability ImageBuffer +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpSource HLSL 630 +OpName %type_buffer_image "type.buffer.image" +OpName %output "output" +OpName %main "main" +OpName %color "color" +OpName %bb_entry "bb.entry" +OpName %param_var_color "param.var.color" +OpName %fn "fn" +OpName %color_0 "color" +OpName %bb_entry_0 "bb.entry" +OpName %v "v" +OpDecorate %main LinkageAttributes "main" Export +OpDecorate %output DescriptorSet 0 +OpDecorate %output Binding 1 +%float = OpTypeFloat 32 +%float_0_200000003 = OpConstant %float 0.200000003 +%v4float = OpTypeVector %float 4 +%6 = OpConstantComposite %v4float %float_0_200000003 %float_0_200000003 %float_0_200000003 %float_0_200000003 +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%type_buffer_image = OpTypeImage %float Buffer 2 0 0 2 Rgba32f +%_ptr_UniformConstant_type_buffer_image = OpTypePointer UniformConstant %type_buffer_image +%_ptr_Function_v4float = OpTypePointer Function %v4float +%11 = OpTypeFunction %float %_ptr_Function_v4float +%_ptr_Function_float = OpTypePointer Function %float +%output = OpVariable %_ptr_UniformConstant_type_buffer_image UniformConstant +%main = OpFunction %float None %11 +%color = OpFunctionParameter %_ptr_Function_v4float +%bb_entry = OpLabel +%28 = OpVariable %_ptr_Function_v4float Function +%29 = OpVariable %_ptr_Function_float Function +%param_var_color = OpVariable %_ptr_Function_v4float Function +%16 = OpLoad %v4float %color +OpStore %param_var_color %16 +%31 = OpLoad %v4float %param_var_color +OpStore %28 %31 +%32 = OpLoad %v4float %28 +%33 = OpFMul %v4float %32 %6 +OpStore %28 %33 +%34 = OpAccessChain %_ptr_Function_float %28 %int_0 +%35 = OpLoad %float %34 +OpStore %29 %35 +%17 = OpLoad %float %29 +OpReturnValue %17 +OpFunctionEnd +%fn = OpFunction %float None %11 +%color_0 = OpFunctionParameter %_ptr_Function_v4float +%bb_entry_0 = OpLabel +%v = OpVariable %_ptr_Function_v4float Function +%22 = OpLoad %v4float %color_0 +OpStore %v %22 +%23 = OpLoad %v4float %v +%24 = OpFMul %v4float %23 %6 +OpStore %v %24 +%26 = OpAccessChain %_ptr_Function_float %v %int_0 +%27 = OpLoad %float %26 +OpReturnValue %27 +OpFunctionEnd +)"; + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck<InlineExhaustivePass>(before, after, false, true); +} + TEST_F(InlineTest, InlineFuncWithOpTerminateRayNotInContinue) { const std::string text = R"( @@ -3259,6 +3383,82 @@ TEST_F(InlineTest, DebugSimple) { SinglePassRunAndMatch<InlineExhaustivePass>(text, true); } +TEST_F(InlineTest, ShaderDebugSimple) { + // Same as DebugSimple but for NonSemantic.Shader.DebugInfo.100. + const std::string text = R"( +; CHECK: [[main_name:%\d+]] = OpString "main" +; CHECK: [[foo_name:%\d+]] = OpString "foo" +; CHECK: [[dbg_main:%\d+]] = OpExtInst %void {{%\d+}} DebugFunction [[main_name]] {{%\d+}} {{%\d+}} %uint_4 %uint_1 {{%\d+}} [[main_name]] %uint_3 %uint_4 +; CHECK: [[dbg_foo:%\d+]] = OpExtInst %void {{%\d+}} DebugFunction [[foo_name]] {{%\d+}} {{%\d+}} %uint_1 %uint_1 {{%\d+}} [[foo_name]] %uint_3 %uint_1 +; CHECK: [[foo_bb:%\d+]] = OpExtInst %void {{%\d+}} DebugLexicalBlock {{%\d+}} %uint_1 %uint_14 [[dbg_foo]] +; CHECK: [[inlined_at:%\d+]] = OpExtInst %void {{%\d+}} DebugInlinedAt %uint_4 [[dbg_main]] +; CHECK: [[main:%\d+]] = OpFunction %void None +; CHECK: {{%\d+}} = OpExtInst %void {{%\d+}} DebugScope [[foo_bb]] [[inlined_at]] +; CHECK: [[foo:%\d+]] = OpFunction %v4float None + OpCapability Shader + OpExtension "SPV_KHR_non_semantic_info" + %1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %3 %4 + OpExecutionMode %main OriginUpperLeft + %5 = OpString "ps.hlsl" + OpSource HLSL 600 %5 + %6 = OpString "float" + %main_name = OpString "main" + %foo_name = OpString "foo" + OpDecorate %3 Location 0 + OpDecorate %4 Location 0 + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %uint_1 = OpConstant %uint 1 + %uint_2 = OpConstant %uint 2 + %uint_3 = OpConstant %uint 3 + %uint_4 = OpConstant %uint 4 + %uint_5 = OpConstant %uint 5 + %uint_14 = OpConstant %uint 14 + %uint_32 = OpConstant %uint 32 + %float = OpTypeFloat 32 + %float_1 = OpConstant %float 1 + %v4float = OpTypeVector %float 4 + %14 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%_ptr_Output_v4float = OpTypePointer Output %v4float + %void = OpTypeVoid + %18 = OpTypeFunction %void + %19 = OpTypeFunction %v4float + %3 = OpVariable %_ptr_Input_v4float Input + %4 = OpVariable %_ptr_Output_v4float Output + %20 = OpExtInst %void %1 DebugSource %5 + %21 = OpExtInst %void %1 DebugCompilationUnit %uint_1 %uint_4 %20 %uint_5 + %22 = OpExtInst %void %1 DebugTypeBasic %6 %uint_32 %uint_3 %uint_0 + %23 = OpExtInst %void %1 DebugTypeVector %22 %uint_4 + %24 = OpExtInst %void %1 DebugTypeFunction %uint_3 %23 %23 + %25 = OpExtInst %void %1 DebugTypeFunction %uint_3 %23 + %dbg_main = OpExtInst %void %1 DebugFunction %main_name %24 %20 %uint_4 %uint_1 %21 %main_name %uint_3 %uint_4 + %dbg_foo = OpExtInst %void %1 DebugFunction %foo_name %25 %20 %uint_1 %uint_1 %21 %foo_name %uint_3 %uint_1 + %29 = OpExtInst %void %1 DebugLexicalBlock %20 %uint_1 %uint_14 %dbg_foo + %main = OpFunction %void None %18 + %30 = OpLabel +%dbg_main_def = OpExtInst %void %1 DebugFunctionDefinition %dbg_main %main + %31 = OpExtInst %void %1 DebugScope %dbg_main + %32 = OpFunctionCall %v4float %foo + %33 = OpLoad %v4float %3 + %34 = OpFAdd %v4float %32 %33 + OpStore %4 %34 + OpReturn + OpFunctionEnd + %foo = OpFunction %v4float None %19 + %36 = OpLabel +%dbg_foo_def = OpExtInst %void %1 DebugFunctionDefinition %dbg_foo %foo + %35 = OpExtInst %void %1 DebugScope %dbg_foo + %37 = OpExtInst %void %1 DebugScope %29 + OpReturnValue %14 + OpFunctionEnd +)"; + + SinglePassRunAndMatch<InlineExhaustivePass>(text, true); +} + TEST_F(InlineTest, DebugNested) { // When function main() calls function zoo() and function zoo() calls // function bar() and function bar() calls function foo(), check that @@ -3462,6 +3662,103 @@ float4 main(float4 color : COLOR) : SV_TARGET { SinglePassRunAndMatch<InlineExhaustivePass>(text, true); } +TEST_F(InlineTest, ShaderDebugSimpleHLSLPixelShader) { + // Same as DebugSimpleHLSLPixelShader but for + // NonSemantic.Shader.DebugInfo.100. + const std::string text = R"( +; CHECK: [[dbg_main:%\d+]] = OpExtInst %void [[ext:%\d+]] DebugFunction {{%\d+}} {{%\d+}} {{%\d+}} %uint_1 %uint_1 {{%\d+}} {{%\d+}} %uint_3 %uint_1 +; CHECK: [[lex_blk:%\d+]] = OpExtInst %void [[ext]] DebugLexicalBlock {{%\d+}} %uint_1 %uint_47 [[dbg_main]] +; CHECK: %main = OpFunction %void None +; CHECK: {{%\d+}} = OpExtInst %void [[ext]] DebugScope [[dbg_main]] +; CHECK: {{%\d+}} = OpExtInst %void [[ext]] DebugDeclare {{%\d+}} %param_var_color +; CHECK: {{%\d+}} = OpExtInst %void [[ext]] DebugScope [[lex_blk]] +; CHECK: {{%\d+}} = OpExtInst %void %1 DebugLine {{%\d+}} %uint_2 %uint_2 %uint_10 %uint_10 +; CHECK: {{%\d+}} = OpLoad %v4float %param_var_color +; CHECK: {{%\d+}} = OpExtInst %void %1 DebugLine {{%\d+}} %uint_2 %uint_2 %uint_3 %uint_3 +; CHECK: OpFunctionEnd +; CHECK: %src_main = OpFunction %v4float None + OpCapability Shader + OpExtension "SPV_KHR_non_semantic_info" + %1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %in_var_COLOR %out_var_SV_TARGET + OpExecutionMode %main OriginUpperLeft + %5 = OpString "ps.hlsl" + OpSource HLSL 600 %5 + %14 = OpString "#line 1 \"ps.hlsl\" +float4 main(float4 color : COLOR) : SV_TARGET { + return color; +} +" + %17 = OpString "float" + %21 = OpString "src.main" + %24 = OpString "color" + OpName %in_var_COLOR "in.var.COLOR" + OpName %out_var_SV_TARGET "out.var.SV_TARGET" + OpName %main "main" + OpName %param_var_color "param.var.color" + OpName %src_main "src.main" + OpName %color "color" + OpName %bb_entry "bb.entry" + OpDecorate %in_var_COLOR Location 0 + OpDecorate %out_var_SV_TARGET Location 0 + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %uint_1 = OpConstant %uint 1 + %uint_2 = OpConstant %uint 2 + %uint_3 = OpConstant %uint 3 + %uint_4 = OpConstant %uint 4 + %uint_5 = OpConstant %uint 5 + %uint_10 = OpConstant %uint 10 + %uint_20 = OpConstant %uint 20 + %uint_32 = OpConstant %uint 32 + %uint_47 = OpConstant %uint 47 + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%_ptr_Output_v4float = OpTypePointer Output %v4float + %void = OpTypeVoid + %27 = OpTypeFunction %void +%_ptr_Function_v4float = OpTypePointer Function %v4float + %33 = OpTypeFunction %v4float %_ptr_Function_v4float +%in_var_COLOR = OpVariable %_ptr_Input_v4float Input +%out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output + %13 = OpExtInst %void %1 DebugExpression + %15 = OpExtInst %void %1 DebugSource %5 %14 + %16 = OpExtInst %void %1 DebugCompilationUnit %uint_1 %uint_4 %15 %uint_5 + %18 = OpExtInst %void %1 DebugTypeBasic %17 %uint_32 %uint_3 %uint_0 + %19 = OpExtInst %void %1 DebugTypeVector %18 %uint_4 + %20 = OpExtInst %void %1 DebugTypeFunction %uint_3 %19 %19 + %22 = OpExtInst %void %1 DebugFunction %21 %20 %15 %uint_1 %uint_1 %16 %21 %uint_3 %uint_1 + %25 = OpExtInst %void %1 DebugLocalVariable %24 %19 %15 %uint_1 %uint_20 %22 %uint_4 %uint_0 + %26 = OpExtInst %void %1 DebugLexicalBlock %15 %uint_1 %uint_47 %22 + %main = OpFunction %void None %27 + %28 = OpLabel +%param_var_color = OpVariable %_ptr_Function_v4float Function + %31 = OpLoad %v4float %in_var_COLOR + OpStore %param_var_color %31 + %32 = OpFunctionCall %v4float %src_main %param_var_color + OpStore %out_var_SV_TARGET %32 + OpReturn + OpFunctionEnd + %src_main = OpFunction %v4float None %33 + %color = OpFunctionParameter %_ptr_Function_v4float + %bb_entry = OpLabel + %140 = OpExtInst %void %1 DebugFunctionDefinition %22 %src_main + %141 = OpExtInst %void %1 DebugLine %5 %uint_1 %uint_1 %uint_1 %uint_1 + %34 = OpExtInst %void %1 DebugScope %22 + %36 = OpExtInst %void %1 DebugDeclare %25 %color %13 + %38 = OpExtInst %void %1 DebugScope %26 + %142 = OpExtInst %void %1 DebugLine %5 %uint_2 %uint_2 %uint_10 %uint_10 + %39 = OpLoad %v4float %color + %143 = OpExtInst %void %1 DebugLine %5 %uint_2 %uint_2 %uint_3 %uint_3 + OpReturnValue %39 + OpFunctionEnd +)"; + + SinglePassRunAndMatch<InlineExhaustivePass>(text, true); +} + TEST_F(InlineTest, DebugDeclareForCalleeFunctionParam) { // Check that InlinePass correctly generates DebugDeclare instructions // for callee function's parameters and maps them to corresponding @@ -3937,6 +4234,105 @@ OpFunctionEnd SinglePassRunAndMatch<InlineExhaustivePass>(text, true); } +TEST_F(InlineTest, CreateConstantForInlinedAt) { + // This shader causes CreateDebugInlinedAt to generate a constant. + // Using the Constant manager would attempt to build the invalidated + // DefUse manager during inlining which could cause an assert because + // the function is in an inconsistant state. This test verifies that + // CreateDebugInlinedAt detects that the DefUse manager is disabled + // and creates a duplicate constant safely without the Constant manager. + // + // int function1() { + // return 1; + // } + // + // void main() { + // function1(); + // } + + const std::string text = R"(OpCapability Shader +; CHECK: %uint_7 = OpConstant %uint 7 +; CHECK: %uint_7_0 = OpConstant %uint 7 +; CHECK: OpExtInst %void %1 DebugInlinedAt %uint_7_0 +OpExtension "SPV_KHR_non_semantic_info" +%1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +%3 = OpString "parent3.hlsl" +%8 = OpString "int" +%19 = OpString "function1" +%20 = OpString "" +%26 = OpString "main" +OpName %main "main" +OpName %src_main "src.main" +OpName %bb_entry "bb.entry" +OpName %function1 "function1" +OpName %bb_entry_0 "bb.entry" +%int = OpTypeInt 32 1 +%int_1 = OpConstant %int 1 +%uint = OpTypeInt 32 0 +%uint_32 = OpConstant %uint 32 +%void = OpTypeVoid +%uint_4 = OpConstant %uint 4 +%uint_0 = OpConstant %uint 0 +%uint_3 = OpConstant %uint 3 +%uint_1 = OpConstant %uint 1 +%uint_5 = OpConstant %uint 5 +%uint_2 = OpConstant %uint 2 +%uint_17 = OpConstant %uint 17 +%uint_6 = OpConstant %uint 6 +%uint_13 = OpConstant %uint 13 +%uint_7 = OpConstant %uint 7 +%31 = OpTypeFunction %void +%42 = OpTypeFunction %int +%10 = OpExtInst %void %1 DebugTypeBasic %8 %uint_32 %uint_4 %uint_0 +%13 = OpExtInst %void %1 DebugTypeFunction %uint_3 %10 +%15 = OpExtInst %void %1 DebugSource %3 +%16 = OpExtInst %void %1 DebugCompilationUnit %uint_1 %uint_4 %15 %uint_5 +%21 = OpExtInst %void %1 DebugFunction %19 %13 %15 %uint_2 %uint_1 %16 %20 %uint_3 %uint_2 +%23 = OpExtInst %void %1 DebugLexicalBlock %15 %uint_2 %uint_17 %21 +%25 = OpExtInst %void %1 DebugTypeFunction %uint_3 %void +%27 = OpExtInst %void %1 DebugFunction %26 %25 %15 %uint_6 %uint_1 %16 %20 %uint_3 %uint_6 +%29 = OpExtInst %void %1 DebugLexicalBlock %15 %uint_6 %uint_13 %27 +%main = OpFunction %void None %31 +%32 = OpLabel +%33 = OpFunctionCall %void %src_main +OpLine %3 8 1 +OpReturn +OpFunctionEnd +OpLine %3 6 1 +%src_main = OpFunction %void None %31 +OpNoLine +%bb_entry = OpLabel +%47 = OpExtInst %void %1 DebugScope %27 +%37 = OpExtInst %void %1 DebugFunctionDefinition %27 %src_main +%48 = OpExtInst %void %1 DebugScope %29 +OpLine %3 7 3 +%39 = OpFunctionCall %int %function1 +%49 = OpExtInst %void %1 DebugScope %27 +OpLine %3 8 1 +OpReturn +%50 = OpExtInst %void %1 DebugNoScope +OpFunctionEnd +OpLine %3 2 1 +%function1 = OpFunction %int None %42 +OpNoLine +%bb_entry_0 = OpLabel +%51 = OpExtInst %void %1 DebugScope %21 +%45 = OpExtInst %void %1 DebugFunctionDefinition %21 %function1 +%52 = OpExtInst %void %1 DebugScope %23 +OpLine %3 3 3 +OpReturnValue %int_1 +%53 = OpExtInst %void %1 DebugNoScope +OpFunctionEnd +)"; + + SetTargetEnv(SPV_ENV_VULKAN_1_2); + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndMatch<InlineExhaustivePass>(text, true); +} + // TODO(greg-lunarg): Add tests to verify handling of these cases: // // Empty modules diff --git a/test/opt/inst_buff_addr_check_test.cpp b/test/opt/inst_buff_addr_check_test.cpp index 41ead67a..95114b23 100644 --- a/test/opt/inst_buff_addr_check_test.cpp +++ b/test/opt/inst_buff_addr_check_test.cpp @@ -615,6 +615,212 @@ OpFunctionEnd true, 7u, 23u); } +TEST_F(InstBuffAddrTest, StructLoad) { + // #version 450 + // #extension GL_EXT_buffer_reference : enable + // #extension GL_ARB_gpu_shader_int64 : enable + // struct Test { + // float a; + // }; + // + // layout(buffer_reference, std430, buffer_reference_align = 16) buffer + // TestBuffer { Test test; }; + // + // Test GetTest(uint64_t ptr) { + // return TestBuffer(ptr).test; + // } + // + // void main() { + // GetTest(0xe0000000); + // } + + const std::string defs = + R"( +OpCapability Shader +OpCapability Int64 +OpCapability PhysicalStorageBufferAddresses +; CHECK: OpExtension "SPV_KHR_storage_buffer_storage_class" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel PhysicalStorageBuffer64 GLSL450 +OpEntryPoint Fragment %main "main" +; CHECK: OpEntryPoint Fragment %main "main" %60 %99 %gl_FragCoord +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpSourceExtension "GL_ARB_gpu_shader_int64" +OpSourceExtension "GL_EXT_buffer_reference" +OpName %main "main" +OpName %Test "Test" +OpMemberName %Test 0 "a" +OpName %Test_0 "Test" +OpMemberName %Test_0 0 "a" +OpName %TestBuffer "TestBuffer" +OpMemberName %TestBuffer 0 "test" +)"; + + const std::string decorates = + R"( +OpMemberDecorate %Test_0 0 Offset 0 +OpMemberDecorate %TestBuffer 0 Offset 0 +OpDecorate %TestBuffer Block +; CHECK: OpDecorate %_runtimearr_ulong ArrayStride 8 +; CHECK: OpDecorate %_struct_58 Block +; CHECK: OpMemberDecorate %_struct_58 0 Offset 0 +; CHECK: OpDecorate %60 DescriptorSet 7 +; CHECK: OpDecorate %60 Binding 2 +; CHECK: OpDecorate %_runtimearr_uint ArrayStride 4 +; CHECK: OpDecorate %_struct_97 Block +; CHECK: OpMemberDecorate %_struct_97 0 Offset 0 +; CHECK: OpMemberDecorate %_struct_97 1 Offset 4 +; CHECK: OpDecorate %99 DescriptorSet 7 +; CHECK: OpDecorate %99 Binding 0 +; CHECK: OpDecorate %gl_FragCoord BuiltIn FragCoord +)"; + + const std::string globals = + R"( +%void = OpTypeVoid +%3 = OpTypeFunction %void +%ulong = OpTypeInt 64 0 +%float = OpTypeFloat 32 +%Test = OpTypeStruct %float +OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_TestBuffer PhysicalStorageBuffer +%Test_0 = OpTypeStruct %float +%TestBuffer = OpTypeStruct %Test_0 +%_ptr_PhysicalStorageBuffer_TestBuffer = OpTypePointer PhysicalStorageBuffer %TestBuffer +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%_ptr_PhysicalStorageBuffer_Test_0 = OpTypePointer PhysicalStorageBuffer %Test_0 +%ulong_18446744073172680704 = OpConstant %ulong 18446744073172680704 +; CHECK: %47 = OpTypeFunction %bool %ulong %uint +; CHECK: %_struct_58 = OpTypeStruct %_runtimearr_ulong +; CHECK: %60 = OpVariable %_ptr_StorageBuffer__struct_58 StorageBuffer +; CHECK: %90 = OpTypeFunction %void %uint %uint %uint %uint +; CHECK: %_struct_97 = OpTypeStruct %uint %_runtimearr_uint +; CHECK: %99 = OpVariable %_ptr_StorageBuffer__struct_97 StorageBuffer +; CHECK: %143 = OpConstantNull %Test_0 +)"; + + const std::string main = + R"( +%main = OpFunction %void None %3 +%5 = OpLabel +%37 = OpConvertUToPtr %_ptr_PhysicalStorageBuffer_TestBuffer %ulong_18446744073172680704 +%38 = OpAccessChain %_ptr_PhysicalStorageBuffer_Test_0 %37 %int_0 +%39 = OpLoad %Test_0 %38 Aligned 16 +; CHECK-NOT: %39 = OpLoad %Test_0 %38 Aligned 16 +; CHECK: %43 = OpConvertPtrToU %ulong %38 +; CHECK: %80 = OpFunctionCall %bool %45 %43 %uint_4 +; CHECK: OpSelectionMerge %81 None +; CHECK: OpBranchConditional %80 %82 %83 +; CHECK: %82 = OpLabel +; CHECK: %84 = OpLoad %Test_0 %38 Aligned 16 +; CHECK: OpBranch %81 +; CHECK: %83 = OpLabel +; CHECK: %85 = OpUConvert %uint %43 +; CHECK: %87 = OpShiftRightLogical %ulong %43 %uint_32 +; CHECK: %88 = OpUConvert %uint %87 +; CHECK: %142 = OpFunctionCall %void %89 %uint_37 %uint_2 %85 %88 +; CHECK: OpBranch %81 +; CHECK: %81 = OpLabel +; CHECK: %144 = OpPhi %Test_0 %84 %82 %143 %83 +%40 = OpCopyLogical %Test %39 +; CHECK-NOT: %40 = OpCopyLogical %Test %39 +; CHECK: %40 = OpCopyLogical %Test %144 +OpReturn +OpFunctionEnd +)"; + + const std::string output_funcs = + R"( +; CHECK: %45 = OpFunction %bool None %47 +; CHECK: %48 = OpFunctionParameter %ulong +; CHECK: %49 = OpFunctionParameter %uint +; CHECK: %50 = OpLabel +; CHECK: OpBranch %51 +; CHECK: %51 = OpLabel +; CHECK: %53 = OpPhi %uint %uint_1 %50 %54 %52 +; CHECK: OpLoopMerge %56 %52 None +; CHECK: OpBranch %52 +; CHECK: %52 = OpLabel +; CHECK: %54 = OpIAdd %uint %53 %uint_1 +; CHECK: %63 = OpAccessChain %_ptr_StorageBuffer_ulong %60 %uint_0 %54 +; CHECK: %64 = OpLoad %ulong %63 +; CHECK: %65 = OpUGreaterThan %bool %64 %48 +; CHECK: OpBranchConditional %65 %56 %51 +; CHECK: %56 = OpLabel +; CHECK: %66 = OpISub %uint %54 %uint_1 +; CHECK: %67 = OpAccessChain %_ptr_StorageBuffer_ulong %60 %uint_0 %66 +; CHECK: %68 = OpLoad %ulong %67 +; CHECK: %69 = OpISub %ulong %48 %68 +; CHECK: %70 = OpUConvert %ulong %49 +; CHECK: %71 = OpIAdd %ulong %69 %70 +; CHECK: %72 = OpAccessChain %_ptr_StorageBuffer_ulong %60 %uint_0 %uint_0 +; CHECK: %73 = OpLoad %ulong %72 +; CHECK: %74 = OpUConvert %uint %73 +; CHECK: %75 = OpISub %uint %66 %uint_1 +; CHECK: %76 = OpIAdd %uint %75 %74 +; CHECK: %77 = OpAccessChain %_ptr_StorageBuffer_ulong %60 %uint_0 %76 +; CHECK: %78 = OpLoad %ulong %77 +; CHECK: %79 = OpULessThanEqual %bool %71 %78 +; CHECK: OpReturnValue %79 +; CHECK: OpFunctionEnd +; CHECK: %89 = OpFunction %void None %90 +; CHECK: %91 = OpFunctionParameter %uint +; CHECK: %92 = OpFunctionParameter %uint +; CHECK: %93 = OpFunctionParameter %uint +; CHECK: %94 = OpFunctionParameter %uint +; CHECK: %95 = OpLabel +; CHECK: %101 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_0 +; CHECK: %103 = OpAtomicIAdd %uint %101 %uint_4 %uint_0 %uint_10 +; CHECK: %104 = OpIAdd %uint %103 %uint_10 +; CHECK: %105 = OpArrayLength %uint %99 1 +; CHECK: %106 = OpULessThanEqual %bool %104 %105 +; CHECK: OpSelectionMerge %107 None +; CHECK: OpBranchConditional %106 %108 %107 +; CHECK: %108 = OpLabel +; CHECK: %109 = OpIAdd %uint %103 %uint_0 +; CHECK: %110 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_1 %109 +; CHECK: OpStore %110 %uint_10 +; CHECK: %112 = OpIAdd %uint %103 %uint_1 +; CHECK: %113 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_1 %112 +; CHECK: OpStore %113 %uint_23 +; CHECK: %114 = OpIAdd %uint %103 %uint_2 +; CHECK: %115 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_1 %114 +; CHECK: OpStore %115 %91 +; CHECK: %117 = OpIAdd %uint %103 %uint_3 +; CHECK: %118 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_1 %117 +; CHECK: OpStore %118 %uint_4 +; CHECK: %122 = OpLoad %v4float %gl_FragCoord +; CHECK: %124 = OpBitcast %v4uint %122 +; CHECK: %125 = OpCompositeExtract %uint %124 0 +; CHECK: %126 = OpIAdd %uint %103 %uint_4 +; CHECK: %127 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_1 %126 +; CHECK: OpStore %127 %125 +; CHECK: %128 = OpCompositeExtract %uint %124 1 +; CHECK: %130 = OpIAdd %uint %103 %uint_5 +; CHECK: %131 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_1 %130 +; CHECK: OpStore %131 %128 +; CHECK: %133 = OpIAdd %uint %103 %uint_7 +; CHECK: %134 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_1 %133 +; CHECK: OpStore %134 %92 +; CHECK: %136 = OpIAdd %uint %103 %uint_8 +; CHECK: %137 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_1 %136 +; CHECK: OpStore %137 %93 +; CHECK: %139 = OpIAdd %uint %103 %uint_9 +; CHECK: %140 = OpAccessChain %_ptr_StorageBuffer_uint %99 %uint_1 %139 +; CHECK: OpStore %140 %94 +; CHECK: OpBranch %107 +; CHECK: %107 = OpLabel +; CHECK: OpReturn +; CHECK: OpFunctionEnd +)"; + + SetTargetEnv(SPV_ENV_VULKAN_1_2); + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndMatch<InstBuffAddrCheckPass>( + defs + decorates + globals + main + output_funcs, true); +} + } // namespace } // namespace opt } // namespace spvtools diff --git a/test/opt/ir_loader_test.cpp b/test/opt/ir_loader_test.cpp index 475dd235..ccdd032e 100644 --- a/test/opt/ir_loader_test.cpp +++ b/test/opt/ir_loader_test.cpp @@ -105,6 +105,22 @@ TEST(IrBuilder, RoundTripIncompleteFunction) { DoRoundTripCheck("%2 = OpFunction %1 None %3\n"); } +TEST(IrBuilder, RoundTripFunctionPointer) { + DoRoundTripCheck( + "OpCapability Linkage\n" + "OpCapability FunctionPointersINTEL\n" + "OpName %some_function \"some_function\"\n" + "OpName %ptr_to_function \"ptr_to_function\"\n" + "OpDecorate %some_function LinkageAttributes \"some_function\" Import\n" + "%float = OpTypeFloat 32\n" + "%4 = OpTypeFunction %float %float\n" + "%_ptr_Function_4 = OpTypePointer Function %4\n" + "%ptr_to_function = OpConstantFunctionPointerINTEL %_ptr_Function_4 " + "%some_function\n" + "%some_function = OpFunction %float Const %4\n" + "%6 = OpFunctionParameter %float\n" + "OpFunctionEnd\n"); +} TEST(IrBuilder, KeepLineDebugInfo) { // #version 310 es // void main() {} diff --git a/test/opt/local_single_block_elim.cpp b/test/opt/local_single_block_elim.cpp index 8e1cee61..28b8a07d 100644 --- a/test/opt/local_single_block_elim.cpp +++ b/test/opt/local_single_block_elim.cpp @@ -84,6 +84,56 @@ OpFunctionEnd predefs_before + before, predefs_before + after, true, true); } +TEST_F(LocalSingleBlockLoadStoreElimTest, LSBElimForLinkage) { + const std::string predefs_before = + R"(OpCapability Shader +OpCapability Linkage +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpSource HLSL 630 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %gl_FragColor "gl_FragColor" +OpDecorate %main LinkageAttributes "main" Export +%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 +OpStore %gl_FragColor %14 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck<LocalSingleBlockLoadStoreElimPass>( + predefs_before + before, predefs_before + after, true, true); +} + TEST_F(LocalSingleBlockLoadStoreElimTest, SimpleLoadLoadElim) { // #version 140 // diff --git a/test/opt/local_single_store_elim_test.cpp b/test/opt/local_single_store_elim_test.cpp index c94ff372..5d910c4e 100644 --- a/test/opt/local_single_store_elim_test.cpp +++ b/test/opt/local_single_store_elim_test.cpp @@ -126,6 +126,91 @@ OpFunctionEnd predefs + after, true, true); } +TEST_F(LocalSingleStoreElimTest, LSSElimForLinkage) { + const std::string predefs = + R"(OpCapability Shader +OpCapability Linkage +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpSource HLSL 630 +OpName %main "main" +OpName %v "v" +OpName %BaseColor "BaseColor" +OpName %f "f" +OpName %fi "fi" +OpName %gl_FragColor "gl_FragColor" +OpDecorate %main LinkageAttributes "main" Export +%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 +%27 = OpLoad %float %f +%28 = OpCompositeConstruct %v4float %27 %27 %27 %27 +%29 = OpFAdd %v4float %20 %28 +OpStore %gl_FragColor %29 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck<LocalSingleStoreElimPass>(predefs + before, + predefs + after, true, true); +} + TEST_F(LocalSingleStoreElimTest, ThreeStores) { // Three stores to multiple loads of v is not optimized. diff --git a/test/opt/local_ssa_elim_test.cpp b/test/opt/local_ssa_elim_test.cpp index ca9aba33..4b7542fe 100644 --- a/test/opt/local_ssa_elim_test.cpp +++ b/test/opt/local_ssa_elim_test.cpp @@ -2165,6 +2165,140 @@ OpFunctionEnd SinglePassRunAndMatch<SSARewritePass>(text, true); } +TEST_F(LocalSSAElimTest, ShaderDebugForLoop) { + const std::string text = R"( +; CHECK: [[f_name:%\w+]] = OpString "f" +; CHECK: [[i_name:%\w+]] = OpString "i" +; CHECK: [[dbg_f:%\w+]] = OpExtInst %void [[ext:%\d+]] DebugLocalVariable [[f_name]] +; CHECK: [[dbg_i:%\w+]] = OpExtInst %void [[ext]] DebugLocalVariable [[i_name]] + +; CHECK: OpStore %f %float_0 +; CHECK-NEXT: OpExtInst %void [[ext]] DebugValue [[dbg_f]] %float_0 +; CHECK-NEXT: OpStore %i %int_0 +; CHECK-NEXT: OpExtInst %void [[ext]] DebugValue [[dbg_i]] %int_0 + +; CHECK-NOT: DebugDeclare + +; CHECK: [[loop_head:%\w+]] = OpLabel +; CHECK: [[phi0:%\w+]] = OpPhi %float %float_0 +; CHECK: [[phi1:%\w+]] = OpPhi %int %int_0 +; CHECK-NEXT: OpExtInst %void [[ext]] DebugValue [[dbg_f]] [[phi0]] +; CHECK-NEXT: OpExtInst %void [[ext]] DebugValue [[dbg_i]] [[phi1]] +; CHECK: OpLoopMerge [[loop_merge:%\w+]] [[loop_cont:%\w+]] None +; CHECK-NEXT: OpBranch [[loop_body:%\w+]] + +; CHECK-NEXT: [[loop_body]] = OpLabel +; CHECK: OpBranchConditional {{%\w+}} [[bb:%\w+]] [[loop_merge]] + +; CHECK: [[bb]] = OpLabel +; CHECK: OpStore %f [[f_val:%\w+]] +; CHECK-NEXT: OpExtInst %void [[ext]] DebugValue [[dbg_f]] [[f_val]] +; CHECK-NEXT: OpBranch [[loop_cont]] + +; CHECK: [[loop_cont]] = OpLabel +; CHECK: OpStore %i [[i_val:%\w+]] +; CHECK-NEXT: OpExtInst %void [[ext]] DebugValue [[dbg_i]] [[i_val]] +; CHECK-NEXT: OpBranch [[loop_head]] + +; CHECK: [[loop_merge]] = OpLabel + +OpCapability Shader +OpExtension "SPV_KHR_non_semantic_info" +%1 = OpExtInstImport "GLSL.std.450" +%ext = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BC %fo +OpExecutionMode %main OriginUpperLeft +%file_name = OpString "test" +OpSource GLSL 140 +%float_name = OpString "float" +%main_name = OpString "main" +%f_name = OpString "f" +%i_name = OpString "i" +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 +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%uint_1 = OpConstant %uint 1 +%uint_3 = OpConstant %uint 3 +%uint_4 = OpConstant %uint 4 +%uint_5 = OpConstant %uint 5 +%uint_10 = OpConstant %uint 10 +%uint_32 = OpConstant %uint 32 +%_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 +%null_expr = OpExtInst %void %ext DebugExpression +%src = OpExtInst %void %ext DebugSource %file_name +%cu = OpExtInst %void %ext DebugCompilationUnit %uint_1 %uint_4 %src %uint_5 +%dbg_tf = OpExtInst %void %ext DebugTypeBasic %float_name %uint_32 %uint_3 %uint_0 +%dbg_v4f = OpExtInst %void %ext DebugTypeVector %dbg_tf %uint_4 +%main_ty = OpExtInst %void %ext DebugTypeFunction %uint_3 %dbg_v4f %dbg_v4f +%dbg_main = OpExtInst %void %ext DebugFunction %main_name %main_ty %src %uint_0 %uint_0 %cu %main_name %uint_3 %uint_10 +%dbg_f = OpExtInst %void %ext DebugLocalVariable %f_name %dbg_v4f %src %uint_0 %uint_0 %dbg_main %uint_4 +%dbg_i = OpExtInst %void %ext DebugLocalVariable %i_name %dbg_v4f %src %uint_0 %uint_0 %dbg_main %uint_4 +%main = OpFunction %void None %8 +%22 = OpLabel +%s0 = OpExtInst %void %ext DebugScope %dbg_main +%f = OpVariable %_ptr_Function_float Function +%i = OpVariable %_ptr_Function_int Function +OpStore %f %float_0 +OpStore %i %int_0 +%decl0 = OpExtInst %void %ext DebugDeclare %dbg_f %f %null_expr +%decl1 = OpExtInst %void %ext DebugDeclare %dbg_i %i %null_expr +OpBranch %23 +%23 = OpLabel +%s1 = OpExtInst %void %ext DebugScope %dbg_main +OpLoopMerge %24 %25 None +OpBranch %26 +%26 = OpLabel +%s2 = OpExtInst %void %ext DebugScope %dbg_main +%27 = OpLoad %int %i +%28 = OpSLessThan %bool %27 %int_4 +OpBranchConditional %28 %29 %24 +%29 = OpLabel +%s3 = OpExtInst %void %ext DebugScope %dbg_main +%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 +%s4 = OpExtInst %void %ext DebugScope %dbg_main +%35 = OpLoad %int %i +%36 = OpIAdd %int %35 %int_1 +OpStore %i %36 +OpBranch %23 +%24 = OpLabel +%s5 = OpExtInst %void %ext DebugScope %dbg_main +%37 = OpLoad %float %f +OpStore %fo %37 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch<SSARewritePass>(text, true); +} + TEST_F(LocalSSAElimTest, AddDebugValueForFunctionParameterWithPhi) { // Test the distribution of DebugValue for a parameter of an inlined function // and the visibility of Phi instruction. The ssa-rewrite pass must add @@ -4091,6 +4225,32 @@ TEST_F(LocalSSAElimTest, PointerVariables) { SinglePassRunAndMatch<SSARewritePass>(text, true); } +TEST_F(LocalSSAElimTest, FunctionDeclaration) { + // Make sure the pass works with a function declaration that is called. + const std::string text = R"(OpCapability Addresses +OpCapability Linkage +OpCapability Kernel +OpCapability Int8 +%1 = OpExtInstImport "OpenCL.std" +OpMemoryModel Physical64 OpenCL +OpEntryPoint Kernel %2 "_Z23julia__1166_kernel_77094Bool" +OpExecutionMode %2 ContractionOff +OpSource Unknown 0 +OpDecorate %3 LinkageAttributes "julia_error_7712" Import +%void = OpTypeVoid +%5 = OpTypeFunction %void +%3 = OpFunction %void None %5 +OpFunctionEnd +%2 = OpFunction %void None %5 +%6 = OpLabel +%7 = OpFunctionCall %void %3 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck<SSARewritePass>(text, text, false); +} + // TODO(greg-lunarg): Add tests to verify handling of these cases: // // No optimization in the presence of diff --git a/test/opt/loop_optimizations/unroll_assumptions.cpp b/test/opt/loop_optimizations/unroll_assumptions.cpp index 0f933021..159e4a14 100644 --- a/test/opt/loop_optimizations/unroll_assumptions.cpp +++ b/test/opt/loop_optimizations/unroll_assumptions.cpp @@ -42,6 +42,10 @@ class PartialUnrollerTestPass : public Pass { Status Process() override { bool changed = false; for (Function& f : *context()->module()) { + if (f.IsDeclaration()) { + continue; + } + LoopDescriptor& loop_descriptor = *context()->GetLoopDescriptor(&f); for (auto& loop : loop_descriptor) { LoopUtils loop_utils{context(), &loop}; @@ -1510,6 +1514,33 @@ OpFunctionEnd SinglePassRunAndCheck<PartialUnrollerTestPass<2>>(text, text, false); } +TEST_F(PassClassTest, FunctionDeclaration) { + // Make sure the pass works with a function declaration that is called. + const std::string text = R"(OpCapability Addresses +OpCapability Linkage +OpCapability Kernel +OpCapability Int8 +%1 = OpExtInstImport "OpenCL.std" +OpMemoryModel Physical64 OpenCL +OpEntryPoint Kernel %2 "_Z23julia__1166_kernel_77094Bool" +OpExecutionMode %2 ContractionOff +OpSource Unknown 0 +OpDecorate %3 LinkageAttributes "julia_error_7712" Import +%void = OpTypeVoid +%5 = OpTypeFunction %void +%3 = OpFunction %void None %5 +OpFunctionEnd +%2 = OpFunction %void None %5 +%6 = OpLabel +%7 = OpFunctionCall %void %3 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck<LoopUnroller>(text, text, false); + SinglePassRunAndCheck<PartialUnrollerTestPass<1>>(text, text, false); +} + } // namespace } // namespace opt } // namespace spvtools diff --git a/test/opt/loop_optimizations/unroll_simple.cpp b/test/opt/loop_optimizations/unroll_simple.cpp index 6a3cb6ee..ac0dfde7 100644 --- a/test/opt/loop_optimizations/unroll_simple.cpp +++ b/test/opt/loop_optimizations/unroll_simple.cpp @@ -378,6 +378,185 @@ OpFunctionEnd)"; SinglePassRunAndMatch<LoopUnroller>(text, true); } +TEST_F(PassClassTest, SimpleFullyUnrollWithShaderDebugInstructions) { + // We must preserve the debug information including + // NonSemantic.Shader.DebugInfo.100 instructions and DebugLine instructions. + const std::string text = R"( +OpCapability Shader +OpExtension "SPV_KHR_non_semantic_info" +%1 = OpExtInstImport "GLSL.std.450" +%ext = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" %3 +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 330 +%file_name = OpString "test" +%float_name = OpString "float" +%main_name = OpString "main" +%f_name = OpString "f" +%i_name = OpString "i" +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 +%uint_0 = OpConstant %14 0 +%uint_1 = OpConstant %14 1 +%uint_2 = OpConstant %14 2 +%uint_3 = OpConstant %14 3 +%uint_4 = OpConstant %14 4 +%uint_5 = OpConstant %14 5 +%uint_6 = OpConstant %14 6 +%uint_7 = OpConstant %14 7 +%uint_8 = OpConstant %14 8 +%uint_10 = OpConstant %14 10 +%uint_32 = OpConstant %14 32 +%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 +%null_expr = OpExtInst %6 %ext DebugExpression +%deref = OpExtInst %6 %ext DebugOperation %uint_0 +%deref_expr = OpExtInst %6 %ext DebugExpression %deref +%src = OpExtInst %6 %ext DebugSource %file_name +%cu = OpExtInst %6 %ext DebugCompilationUnit %uint_1 %uint_4 %src %uint_5 +%dbg_tf = OpExtInst %6 %ext DebugTypeBasic %float_name %uint_32 %uint_3 %uint_0 +%dbg_v4f = OpExtInst %6 %ext DebugTypeVector %dbg_tf %uint_4 +%main_ty = OpExtInst %6 %ext DebugTypeFunction %uint_3 %dbg_v4f %dbg_v4f +%dbg_main = OpExtInst %6 %ext DebugFunction %main_name %main_ty %src %uint_0 %uint_0 %cu %main_name %uint_3 %uint_10 +%bb = OpExtInst %6 %ext DebugLexicalBlock %src %uint_0 %uint_0 %dbg_main +%dbg_f = OpExtInst %6 %ext DebugLocalVariable %f_name %dbg_v4f %src %uint_0 %uint_0 %dbg_main %uint_4 +%dbg_i = OpExtInst %6 %ext DebugLocalVariable %i_name %dbg_v4f %src %uint_1 %uint_0 %bb %uint_4 + +; CHECK: [[f:%\w+]] = OpString "f" +; CHECK: [[i:%\w+]] = OpString "i" +; CHECK: [[int_0:%\w+]] = OpConstant {{%\w+}} 0 + +; CHECK: [[null_expr:%\w+]] = OpExtInst {{%\w+}} {{%\w+}} DebugExpression +; CHECK: [[deref:%\w+]] = OpExtInst {{%\w+}} {{%\w+}} DebugOperation %uint_0 +; CHECK: [[deref_expr:%\w+]] = OpExtInst {{%\w+}} {{%\w+}} DebugExpression [[deref]] +; CHECK: [[dbg_fn:%\w+]] = OpExtInst {{%\w+}} {{%\w+}} DebugFunction +; CHECK: [[dbg_bb:%\w+]] = OpExtInst {{%\w+}} {{%\w+}} DebugLexicalBlock +; CHECK: [[dbg_f:%\w+]] = OpExtInst {{%\w+}} {{%\w+}} DebugLocalVariable [[f]] {{%\w+}} {{%\w+}} %uint_0 %uint_0 [[dbg_fn]] +; CHECK: [[dbg_i:%\w+]] = OpExtInst {{%\w+}} {{%\w+}} DebugLocalVariable [[i]] {{%\w+}} {{%\w+}} %uint_1 %uint_0 [[dbg_bb]] + +%2 = OpFunction %6 None %7 +%23 = OpLabel + +; The first block has DebugDeclare and DebugValue with Deref +; +; CHECK: OpLabel +; CHECK: %x = OpVariable %_ptr_Function__arr_float_uint_4_0 Function +; CHECK: OpBranch +; CHECK: OpLabel +; CHECK: DebugScope [[dbg_fn]] +; CHECK: DebugValue [[dbg_f]] [[int_0]] [[null_expr]] +; CHECK: OpBranch +; CHECK: DebugScope [[dbg_fn]] +; CHECK: DebugLine {{%\w+}} %uint_1 %uint_1 %uint_1 %uint_1 +; CHECK: OpSLessThan +; CHECK: DebugLine {{%\w+}} %uint_2 %uint_2 %uint_0 %uint_0 +; CHECK: OpBranch +; CHECK: OpLabel +; CHECK: DebugScope [[dbg_bb]] +; CHECK: DebugDeclare [[dbg_f]] %x [[null_expr]] +; CHECK: DebugValue [[dbg_i]] %x [[deref_expr]] +; CHECK: DebugLine {{%\w+}} %uint_3 %uint_3 %uint_0 %uint_0 +; +; CHECK: DebugLine {{%\w+}} %uint_6 %uint_6 %uint_0 %uint_0 +; CHECK: [[add:%\w+]] = OpIAdd +; CHECK: DebugValue [[dbg_f]] [[add]] [[null_expr]] +; CHECK: DebugLine {{%\w+}} %uint_7 %uint_7 %uint_0 %uint_0 + +; Other blocks do not have DebugDeclare and DebugValue with Deref +; +; CHECK: DebugScope [[dbg_fn]] +; CHECK: DebugLine {{%\w+}} %uint_1 %uint_1 %uint_1 %uint_1 +; CHECK: OpSLessThan +; CHECK: DebugLine {{%\w+}} %uint_2 %uint_2 %uint_0 %uint_0 +; CHECK: OpBranch +; CHECK: OpLabel +; +; CHECK: DebugScope [[dbg_bb]] +; CHECK-NOT: DebugDeclare [[dbg_f]] %x [[null_expr]] +; CHECK-NOT: DebugValue [[dbg_i]] %x [[deref_expr]] +; CHECK: DebugLine {{%\w+}} %uint_3 %uint_3 %uint_0 %uint_0 +; +; CHECK: DebugLine {{%\w+}} %uint_6 %uint_6 %uint_0 %uint_0 +; CHECK: [[add:%\w+]] = OpIAdd +; CHECK: DebugValue [[dbg_f]] [[add]] [[null_expr]] +; CHECK: DebugLine {{%\w+}} %uint_7 %uint_7 %uint_0 %uint_0 +; +; CHECK-NOT: DebugDeclare [[dbg_f]] %x [[null_expr]] +; CHECK-NOT: DebugValue [[dbg_i]] %x [[deref_expr]] +; CHECK: DebugScope [[dbg_fn]] +; CHECK: DebugLine {{%\w+}} %uint_8 %uint_8 %uint_0 %uint_0 +; CHECK: OpReturn + +%5 = OpVariable %17 Function +OpBranch %24 +%24 = OpLabel +%35 = OpPhi %8 %10 %23 %34 %26 +%s1 = OpExtInst %6 %ext DebugScope %dbg_main +%d10 = OpExtInst %6 %ext DebugLine %file_name %uint_1 %uint_1 %uint_0 %uint_0 +%value0 = OpExtInst %6 %ext DebugValue %dbg_f %35 %null_expr +OpLoopMerge %25 %26 Unroll +OpBranch %27 +%27 = OpLabel +%s2 = OpExtInst %6 %ext DebugScope %dbg_main +%d1 = OpExtInst %6 %ext DebugLine %file_name %uint_1 %uint_1 %uint_1 %uint_1 +%29 = OpSLessThan %12 %35 %11 +%d2 = OpExtInst %6 %ext DebugLine %file_name %uint_2 %uint_2 %uint_0 %uint_0 +OpBranchConditional %29 %30 %25 +%30 = OpLabel +%s3 = OpExtInst %6 %ext DebugScope %bb +%decl0 = OpExtInst %6 %ext DebugDeclare %dbg_f %5 %null_expr +%decl1 = OpExtInst %6 %ext DebugValue %dbg_i %5 %deref_expr +%d3 = OpExtInst %6 %ext DebugLine %file_name %uint_3 %uint_3 %uint_0 %uint_0 +%32 = OpAccessChain %19 %5 %35 +%d4 = OpExtInst %6 %ext DebugLine %file_name %uint_4 %uint_4 %uint_0 %uint_0 +OpStore %32 %18 +%d5 = OpExtInst %6 %ext DebugLine %file_name %uint_5 %uint_5 %uint_0 %uint_0 +OpBranch %26 +%26 = OpLabel +%s4 = OpExtInst %6 %ext DebugScope %dbg_main +%d6 = OpExtInst %6 %ext DebugLine %file_name %uint_6 %uint_6 %uint_0 %uint_0 +%34 = OpIAdd %8 %35 %20 +%value1 = OpExtInst %6 %ext DebugValue %dbg_f %34 %null_expr +%d7 = OpExtInst %6 %ext DebugLine %file_name %uint_7 %uint_7 %uint_0 %uint_0 +OpBranch %24 +%25 = OpLabel +%s5 = OpExtInst %6 %ext DebugScope %dbg_main +%d8 = OpExtInst %6 %ext DebugLine %file_name %uint_8 %uint_8 %uint_0 %uint_0 +OpReturn +OpFunctionEnd)"; + + std::unique_ptr<IRContext> context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" + << text << std::endl; + + LoopUnroller loop_unroller; + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER | + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES); + SinglePassRunAndMatch<LoopUnroller>(text, true); +} + template <int factor> class PartialUnrollerTestPass : public Pass { public: diff --git a/test/opt/pass_merge_return_test.cpp b/test/opt/pass_merge_return_test.cpp index fd97efab..21960d17 100644 --- a/test/opt/pass_merge_return_test.cpp +++ b/test/opt/pass_merge_return_test.cpp @@ -2567,6 +2567,39 @@ TEST_F(MergeReturnPassTest, ChainedPointerUsedAfterLoop) { SinglePassRunAndMatch<MergeReturnPass>(before, true); } +TEST_F(MergeReturnPassTest, OverflowTest1) { + const std::string text = + R"( +; CHECK: OpReturn +; CHECK-NOT: OpReturn +; CHECK: OpFunctionEnd + OpCapability ClipDistance + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + %void = OpTypeVoid + %6 = OpTypeFunction %void + %2 = OpFunction %void None %6 + %4194303 = OpLabel + OpBranch %18 + %18 = OpLabel + OpLoopMerge %19 %20 None + OpBranch %21 + %21 = OpLabel + OpReturn + %20 = OpLabel + OpBranch %18 + %19 = OpLabel + OpUnreachable + OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + auto result = + SinglePassRunToBinary<MergeReturnPass>(text, /* skip_nop = */ true); + EXPECT_EQ(Pass::Status::Failure, std::get<1>(result)); +} + } // namespace } // namespace opt } // namespace spvtools diff --git a/test/opt/reduce_load_size_test.cpp b/test/opt/reduce_load_size_test.cpp index 7672e8f3..abb5cde6 100644 --- a/test/opt/reduce_load_size_test.cpp +++ b/test/opt/reduce_load_size_test.cpp @@ -17,6 +17,12 @@ #include "test/opt/pass_fixture.h" #include "test/opt/pass_utils.h" +namespace { + +const double kDefaultLoadReductionThreshold = 0.9; + +} // namespace + namespace spvtools { namespace opt { namespace { @@ -104,7 +110,8 @@ TEST_F(ReduceLoadSizeTest, cbuffer_load_extract) { 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<ReduceLoadSize>(test, false); + SinglePassRunAndMatch<ReduceLoadSize>(test, false, + kDefaultLoadReductionThreshold); } TEST_F(ReduceLoadSizeTest, cbuffer_load_extract_not_affected_by_debug_instr) { @@ -202,7 +209,8 @@ TEST_F(ReduceLoadSizeTest, cbuffer_load_extract_not_affected_by_debug_instr) { 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<ReduceLoadSize>(test, false); + SinglePassRunAndMatch<ReduceLoadSize>(test, false, + kDefaultLoadReductionThreshold); } TEST_F(ReduceLoadSizeTest, cbuffer_load_extract_vector) { @@ -280,7 +288,8 @@ 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); - SinglePassRunAndCheck<ReduceLoadSize>(test, test, true, false); + SinglePassRunAndCheck<ReduceLoadSize>(test, test, true, false, + kDefaultLoadReductionThreshold); } TEST_F(ReduceLoadSizeTest, cbuffer_load_5_extract) { @@ -351,7 +360,8 @@ 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); - SinglePassRunAndCheck<ReduceLoadSize>(test, test, true, false); + SinglePassRunAndCheck<ReduceLoadSize>(test, test, true, false, + kDefaultLoadReductionThreshold); } TEST_F(ReduceLoadSizeTest, cbuffer_load_fully_used) { @@ -416,7 +426,76 @@ 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); - SinglePassRunAndCheck<ReduceLoadSize>(test, test, true, false); + SinglePassRunAndCheck<ReduceLoadSize>(test, test, true, false, + kDefaultLoadReductionThreshold); +} + +TEST_F(ReduceLoadSizeTest, replace_cbuffer_load_fully_used) { + const std::string test = + R"( + OpCapability Shader + OpCapability SampledBuffer + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %out_var_SV_Target0 + OpExecutionMode %main OriginUpperLeft + OpSource HLSL 600 + OpName %type_MaterialInstancing_cbuffer "type.MaterialInstancing_cbuffer" + OpMemberName %type_MaterialInstancing_cbuffer 0 "MaterialInstancing_constants" + OpName %MaterialInstancing_Constants "MaterialInstancing_Constants" + OpMemberName %MaterialInstancing_Constants 0 "offset0" + OpMemberName %MaterialInstancing_Constants 1 "params" + OpName %InstancingParams_Constants "InstancingParams_Constants" + OpMemberName %InstancingParams_Constants 0 "offset1" + OpName %MaterialInstancing_cbuffer "MaterialInstancing_cbuffer" + OpName %out_var_SV_Target0 "out.var.SV_Target0" + OpName %main "main" + OpDecorate %out_var_SV_Target0 Location 0 + OpDecorate %MaterialInstancing_cbuffer DescriptorSet 6 + OpDecorate %MaterialInstancing_cbuffer Binding 0 + OpMemberDecorate %InstancingParams_Constants 0 Offset 0 + OpMemberDecorate %MaterialInstancing_Constants 0 Offset 0 + OpMemberDecorate %MaterialInstancing_Constants 1 Offset 16 + OpMemberDecorate %type_MaterialInstancing_cbuffer 0 Offset 0 + OpDecorate %type_MaterialInstancing_cbuffer Block + %int = OpTypeInt 32 1 + %int_0 = OpConstant %int 0 + %v4int = OpTypeVector %int 4 +%InstancingParams_Constants = OpTypeStruct %v4int +%MaterialInstancing_Constants = OpTypeStruct %v4int %InstancingParams_Constants +%type_MaterialInstancing_cbuffer = OpTypeStruct %MaterialInstancing_Constants +%_ptr_Uniform_type_MaterialInstancing_cbuffer = OpTypePointer Uniform %type_MaterialInstancing_cbuffer +%_ptr_Output_int = OpTypePointer Output %int + %void = OpTypeVoid + %60 = OpTypeFunction %void +%_ptr_Uniform_MaterialInstancing_Constants = OpTypePointer Uniform %MaterialInstancing_Constants +%MaterialInstancing_cbuffer = OpVariable %_ptr_Uniform_type_MaterialInstancing_cbuffer Uniform +%out_var_SV_Target0 = OpVariable %_ptr_Output_int Output + %main = OpFunction %void None %60 + %80 = OpLabel + %131 = OpAccessChain %_ptr_Uniform_MaterialInstancing_Constants %MaterialInstancing_cbuffer %int_0 + %132 = OpLoad %MaterialInstancing_Constants %131 +; CHECK: [[ac1:%\w+]] = OpAccessChain {{%\w+}} %MaterialInstancing_cbuffer %int_0 +; CHECK: [[ac2:%\w+]] = OpAccessChain {{%\w+}} [[ac1]] %uint_0 +; CHECK: OpLoad %v4int [[ac2]] + +; CHECK: [[ac3:%\w+]] = OpAccessChain {{%\w+}} [[ac1]] %uint_1 +; CHECK: [[ac4:%\w+]] = OpAccessChain {{%\w+}} [[ac3]] %uint_0 +; CHECK: OpLoad %v4int [[ac4]] + %134 = OpCompositeExtract %v4int %132 0 + %135 = OpCompositeExtract %InstancingParams_Constants %132 1 + %136 = OpCompositeExtract %v4int %135 0 + %149 = OpCompositeExtract %int %134 0 + %185 = OpCompositeExtract %int %136 0 + %156 = OpIAdd %int %149 %185 + OpStore %out_var_SV_Target0 %156 + 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<ReduceLoadSize>(test, false, 1.1); } } // namespace diff --git a/test/opt/redundancy_elimination_test.cpp b/test/opt/redundancy_elimination_test.cpp index 474f4661..28eda73e 100644 --- a/test/opt/redundancy_elimination_test.cpp +++ b/test/opt/redundancy_elimination_test.cpp @@ -335,6 +335,32 @@ TEST_F(RedundancyEliminationTest, OpenCLDebugInfo100) { SinglePassRunAndMatch<RedundancyEliminationPass>(text, false); } +TEST_F(RedundancyEliminationTest, FunctionDeclaration) { + // Make sure the pass works with a function declaration that is called. + const std::string text = R"(OpCapability Addresses +OpCapability Linkage +OpCapability Kernel +OpCapability Int8 +%1 = OpExtInstImport "OpenCL.std" +OpMemoryModel Physical64 OpenCL +OpEntryPoint Kernel %2 "_Z23julia__1166_kernel_77094Bool" +OpExecutionMode %2 ContractionOff +OpSource Unknown 0 +OpDecorate %3 LinkageAttributes "julia_error_7712" Import +%void = OpTypeVoid +%5 = OpTypeFunction %void +%3 = OpFunction %void None %5 +OpFunctionEnd +%2 = OpFunction %void None %5 +%6 = OpLabel +%7 = OpFunctionCall %void %3 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck<RedundancyEliminationPass>(text, text, false); +} + } // namespace } // namespace opt -} // namespace spvtools +} // namespace spvtools
\ No newline at end of file diff --git a/test/opt/relax_float_ops_test.cpp b/test/opt/relax_float_ops_test.cpp index 14cde0b9..b9cb0de0 100644 --- a/test/opt/relax_float_ops_test.cpp +++ b/test/opt/relax_float_ops_test.cpp @@ -137,6 +137,86 @@ OpFunctionEnd true); } +TEST_F(RelaxFloatOpsTest, RelaxFloatOpsForLinkage) { + const std::string defs0 = + R"(OpCapability Shader +OpCapability Linkage +OpCapability Sampled1D +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpSource HLSL 630 +OpName %main "main" +OpName %g_tTex1df4 "g_tTex1df4" +OpName %g_sSamp "g_sSamp" +OpName %i_Tex0 "i.Tex0" +OpName %i_Tex1 "i.Tex1" +OpName %_entryPointOutput_Color "@entryPointOutput.Color" +OpDecorate %main LinkageAttributes "main" Export +OpDecorate %g_tTex1df4 DescriptorSet 0 +OpDecorate %g_tTex1df4 Binding 0 +OpDecorate %g_sSamp DescriptorSet 0 +OpDecorate %g_sSamp Binding 0 +OpDecorate %i_Tex0 Location 0 +OpDecorate %i_Tex1 Location 1 +OpDecorate %_entryPointOutput_Color Location 0 +)"; + + const std::string defs1 = + R"(%void = OpTypeVoid +%3 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%17 = OpTypeImage %float 1D 0 0 0 1 Unknown +%_ptr_UniformConstant_17 = OpTypePointer UniformConstant %17 +%g_tTex1df4 = OpVariable %_ptr_UniformConstant_17 UniformConstant +%21 = OpTypeSampler +%_ptr_UniformConstant_21 = OpTypePointer UniformConstant %21 +%g_sSamp = OpVariable %_ptr_UniformConstant_21 UniformConstant +%25 = OpTypeSampledImage %17 +%_ptr_Input_float = OpTypePointer Input %float +%i_Tex0 = OpVariable %_ptr_Input_float Input +%i_Tex1 = OpVariable %_ptr_Input_float Input +%_ptr_Output_v4float = OpTypePointer Output %v4float +%_entryPointOutput_Color = OpVariable %_ptr_Output_v4float Output +%float_0_5 = OpConstant %float 0.5 +%116 = OpConstantComposite %v4float %float_0_5 %float_0_5 %float_0_5 %float_0_5 +)"; + + const std::string relax_decos = + R"(OpDecorate %60 RelaxedPrecision +OpDecorate %63 RelaxedPrecision +OpDecorate %82 RelaxedPrecision +OpDecorate %88 RelaxedPrecision +OpDecorate %91 RelaxedPrecision +OpDecorate %94 RelaxedPrecision +)"; + + const std::string func_orig = + R"(%main = OpFunction %void None %3 +%5 = OpLabel +%60 = OpLoad %float %i_Tex0 +%63 = OpLoad %float %i_Tex1 +%77 = OpLoad %17 %g_tTex1df4 +%78 = OpLoad %21 %g_sSamp +%79 = OpSampledImage %25 %77 %78 +%82 = OpImageSampleImplicitLod %v4float %79 %60 +%83 = OpLoad %17 %g_tTex1df4 +%84 = OpLoad %21 %g_sSamp +%85 = OpSampledImage %25 %83 %84 +%88 = OpImageSampleImplicitLod %v4float %85 %63 +%91 = OpFAdd %v4float %82 %88 +%94 = OpFMul %v4float %91 %116 +OpStore %_entryPointOutput_Color %94 +OpReturn +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck<RelaxFloatOpsPass>( + defs0 + defs1 + func_orig, defs0 + relax_decos + defs1 + func_orig, true, + true); +} + } // namespace } // namespace opt } // namespace spvtools diff --git a/test/opt/replace_desc_array_access_using_var_index_test.cpp b/test/opt/replace_desc_array_access_using_var_index_test.cpp new file mode 100644 index 00000000..ca625812 --- /dev/null +++ b/test/opt/replace_desc_array_access_using_var_index_test.cpp @@ -0,0 +1,411 @@ +// Copyright (c) 2021 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 <string> + +#include "gmock/gmock.h" +#include "test/opt/assembly_builder.h" +#include "test/opt/pass_fixture.h" +#include "test/opt/pass_utils.h" + +namespace spvtools { +namespace opt { +namespace { + +using ReplaceDescArrayAccessUsingVarIndexTest = PassTest<::testing::Test>; + +TEST_F(ReplaceDescArrayAccessUsingVarIndexTest, + ReplaceAccessChainToTextureArray) { + const std::string text = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %psmain "psmain" %gl_FragCoord %in_var_INSTANCEID %out_var_SV_TARGET + OpExecutionMode %psmain OriginUpperLeft + OpSource HLSL 600 + OpName %type_sampler "type.sampler" + OpName %Sampler0 "Sampler0" + OpName %type_2d_image "type.2d.image" + OpName %Tex0 "Tex0" + OpName %in_var_INSTANCEID "in.var.INSTANCEID" + OpName %out_var_SV_TARGET "out.var.SV_TARGET" + OpName %psmain "psmain" + OpName %type_sampled_image "type.sampled.image" + OpDecorate %gl_FragCoord BuiltIn FragCoord + OpDecorate %in_var_INSTANCEID Flat + OpDecorate %in_var_INSTANCEID Location 0 + OpDecorate %out_var_SV_TARGET Location 0 + OpDecorate %Sampler0 DescriptorSet 0 + OpDecorate %Sampler0 Binding 1 + OpDecorate %Tex0 DescriptorSet 0 + OpDecorate %Tex0 Binding 2 + %bool = OpTypeBool +%type_sampler = OpTypeSampler +%_ptr_UniformConstant_type_sampler = OpTypePointer UniformConstant %type_sampler + %uint = OpTypeInt 32 0 + %uint_3 = OpConstant %uint 3 + %float = OpTypeFloat 32 +%type_2d_image = OpTypeImage %float 2D 2 0 0 0 Unknown +%_arr_type_2d_image_uint_3 = OpTypeArray %type_2d_image %uint_3 +%_ptr_UniformConstant__arr_type_2d_image_uint_3 = OpTypePointer UniformConstant %_arr_type_2d_image_uint_3 + %v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%_ptr_Input_uint = OpTypePointer Input %uint +%_ptr_Output_v4float = OpTypePointer Output %v4float + %void = OpTypeVoid + %21 = OpTypeFunction %void +%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image + %v2float = OpTypeVector %float 2 + %v2uint = OpTypeVector %uint 2 + %uint_0 = OpConstant %uint 0 + %uint_1 = OpConstant %uint 1 + %27 = OpConstantComposite %v2uint %uint_0 %uint_1 +%type_sampled_image = OpTypeSampledImage %type_2d_image + %Sampler0 = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant + %Tex0 = OpVariable %_ptr_UniformConstant__arr_type_2d_image_uint_3 UniformConstant +%gl_FragCoord = OpVariable %_ptr_Input_v4float Input +%in_var_INSTANCEID = OpVariable %_ptr_Input_uint Input +%out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output + %uint_2 = OpConstant %uint 2 + %66 = OpConstantNull %v4float + +; CHECK: [[null_value:%\w+]] = OpConstantNull %v4float + + %psmain = OpFunction %void None %21 + %39 = OpLabel + %29 = OpLoad %v4float %gl_FragCoord + %30 = OpLoad %uint %in_var_INSTANCEID + %37 = OpIEqual %bool %30 %uint_2 + OpSelectionMerge %38 None + OpBranchConditional %37 %28 %40 + +; CHECK: [[var_index:%\w+]] = OpLoad %uint %in_var_INSTANCEID +; CHECK: OpSelectionMerge [[cond_branch_merge:%\w+]] None +; CHECK: OpBranchConditional {{%\w+}} {{%\w+}} [[bb_cond_br:%\w+]] + + %28 = OpLabel + %31 = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %30 + %32 = OpLoad %type_2d_image %31 + OpImageWrite %32 %27 %29 + +; CHECK: OpSelectionMerge [[merge:%\w+]] None +; CHECK: OpSwitch [[var_index]] [[default:%\w+]] 0 [[case0:%\w+]] 1 [[case1:%\w+]] 2 [[case2:%\w+]] +; CHECK: [[case0]] = OpLabel +; CHECK: OpAccessChain +; CHECK: OpLoad +; CHECK: OpImageWrite +; CHECK: OpBranch [[merge]] +; CHECK: [[case1]] = OpLabel +; CHECK: OpAccessChain +; CHECK: OpLoad +; CHECK: OpImageWrite +; CHECK: OpBranch [[merge]] +; CHECK: [[case2]] = OpLabel +; CHECK: OpAccessChain +; CHECK: OpLoad +; CHECK: OpImageWrite +; CHECK: OpBranch [[merge]] +; CHECK: [[default]] = OpLabel +; CHECK: OpBranch [[merge]] +; CHECK: [[merge]] = OpLabel + + %33 = OpLoad %type_sampler %Sampler0 + %34 = OpVectorShuffle %v2float %29 %29 0 1 + %35 = OpSampledImage %type_sampled_image %32 %33 + %36 = OpImageSampleImplicitLod %v4float %35 %34 None + +; CHECK: OpSelectionMerge [[merge:%\w+]] None +; CHECK: OpSwitch [[var_index]] [[default:%\w+]] 0 [[case0:%\w+]] 1 [[case1:%\w+]] 2 [[case2:%\w+]] +; CHECK: [[case0]] = OpLabel +; CHECK: [[ac:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %uint_0 +; CHECK: [[sam:%\w+]] = OpLoad %type_sampler %Sampler0 +; CHECK: [[img:%\w+]] = OpLoad %type_2d_image [[ac]] +; CHECK: [[sampledImg:%\w+]] = OpSampledImage %type_sampled_image [[img]] [[sam]] +; CHECK: [[value0:%\w+]] = OpImageSampleImplicitLod %v4float [[sampledImg]] +; CHECK: OpBranch [[merge]] +; CHECK: [[case1]] = OpLabel +; CHECK: [[ac:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %uint_1 +; CHECK: [[sam:%\w+]] = OpLoad %type_sampler %Sampler0 +; CHECK: [[img:%\w+]] = OpLoad %type_2d_image [[ac]] +; CHECK: [[sampledImg:%\w+]] = OpSampledImage %type_sampled_image [[img]] [[sam]] +; CHECK: [[value1:%\w+]] = OpImageSampleImplicitLod %v4float [[sampledImg]] +; CHECK: OpBranch [[merge]] +; CHECK: [[case2]] = OpLabel +; CHECK: [[ac:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %uint_2 +; CHECK: [[sam:%\w+]] = OpLoad %type_sampler %Sampler0 +; CHECK: [[img:%\w+]] = OpLoad %type_2d_image [[ac]] +; CHECK: [[sampledImg:%\w+]] = OpSampledImage %type_sampled_image [[img]] [[sam]] +; CHECK: [[value2:%\w+]] = OpImageSampleImplicitLod %v4float [[sampledImg]] +; CHECK: OpBranch [[merge]] +; CHECK: [[default]] = OpLabel +; CHECK: OpBranch [[merge]] +; CHECK: [[merge]] = OpLabel +; CHECK: [[phi0:%\w+]] = OpPhi %v4float [[value0]] [[case0]] [[value1]] [[case1]] [[value2]] [[case2]] [[null_value]] [[default]] + + OpBranch %38 + %40 = OpLabel + OpBranch %38 + %38 = OpLabel + %41 = OpPhi %v4float %36 %28 %29 %40 + +; CHECK: OpBranch [[cond_branch_merge]] +; CHECK: [[bb_cond_br]] = OpLabel +; CHECK: OpBranch [[cond_branch_merge]] +; CHECK: [[cond_branch_merge]] = OpLabel +; CHECK: [[phi1:%\w+]] = OpPhi %v4float [[phi0]] [[merge]] {{%\w+}} [[bb_cond_br]] +; CHECK: OpStore {{%\w+}} [[phi1]] + + OpStore %out_var_SV_TARGET %41 + OpReturn + OpFunctionEnd + )"; + + SinglePassRunAndMatch<ReplaceDescArrayAccessUsingVarIndex>(text, true); +} + +TEST_F(ReplaceDescArrayAccessUsingVarIndexTest, + ReplaceAccessChainToTextureArrayAndSamplerArray) { + const std::string text = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %psmain "psmain" %gl_FragCoord %in_var_INSTANCEID %out_var_SV_TARGET + OpExecutionMode %psmain OriginUpperLeft + OpSource HLSL 600 + OpName %type_sampler "type.sampler" + OpName %Sampler0 "Sampler0" + OpName %type_2d_image "type.2d.image" + OpName %Tex0 "Tex0" + OpName %in_var_INSTANCEID "in.var.INSTANCEID" + OpName %out_var_SV_TARGET "out.var.SV_TARGET" + OpName %psmain "psmain" + OpName %type_sampled_image "type.sampled.image" + OpDecorate %gl_FragCoord BuiltIn FragCoord + OpDecorate %in_var_INSTANCEID Flat + OpDecorate %in_var_INSTANCEID Location 0 + OpDecorate %out_var_SV_TARGET Location 0 + OpDecorate %Sampler0 DescriptorSet 0 + OpDecorate %Sampler0 Binding 1 + OpDecorate %Tex0 DescriptorSet 0 + OpDecorate %Tex0 Binding 2 +%type_sampler = OpTypeSampler + %uint = OpTypeInt 32 0 + %uint_2 = OpConstant %uint 2 +%_ptr_UniformConstant_type_sampler = OpTypePointer UniformConstant %type_sampler +%_arr_type_sampler_uint_2 = OpTypeArray %type_sampler %uint_2 +%_ptr_UniformConstant__arr_type_sampler_uint_2 = OpTypePointer UniformConstant %_arr_type_sampler_uint_2 + %float = OpTypeFloat 32 +%type_2d_image = OpTypeImage %float 2D 2 0 0 0 Unknown +%_arr_type_2d_image_uint_2 = OpTypeArray %type_2d_image %uint_2 +%_ptr_UniformConstant__arr_type_2d_image_uint_2 = OpTypePointer UniformConstant %_arr_type_2d_image_uint_2 + %v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%_ptr_Input_uint = OpTypePointer Input %uint +%_ptr_Output_v4float = OpTypePointer Output %v4float + %void = OpTypeVoid + %21 = OpTypeFunction %void +%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image + %v2float = OpTypeVector %float 2 + %v2uint = OpTypeVector %uint 2 + %uint_0 = OpConstant %uint 0 + %uint_1 = OpConstant %uint 1 + %27 = OpConstantComposite %v2uint %uint_0 %uint_1 +%type_sampled_image = OpTypeSampledImage %type_2d_image + %Sampler0 = OpVariable %_ptr_UniformConstant__arr_type_sampler_uint_2 UniformConstant + %Tex0 = OpVariable %_ptr_UniformConstant__arr_type_2d_image_uint_2 UniformConstant +%gl_FragCoord = OpVariable %_ptr_Input_v4float Input +%in_var_INSTANCEID = OpVariable %_ptr_Input_uint Input +%out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output + %66 = OpConstantNull %v4float + %psmain = OpFunction %void None %21 + %28 = OpLabel + %29 = OpLoad %v4float %gl_FragCoord + %30 = OpLoad %uint %in_var_INSTANCEID + %31 = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %30 + %32 = OpLoad %type_2d_image %31 + OpImageWrite %32 %27 %29 + +; CHECK: [[null_value:%\w+]] = OpConstantNull %v4float + +; CHECK: [[var_index:%\w+]] = OpLoad %uint %in_var_INSTANCEID +; CHECK: OpSelectionMerge [[merge:%\w+]] None +; CHECK: OpSwitch [[var_index]] [[default:%\w+]] 0 [[case0:%\w+]] 1 [[case1:%\w+]] +; CHECK: [[case0]] = OpLabel +; CHECK: OpAccessChain +; CHECK: OpLoad +; CHECK: OpImageWrite +; CHECK: OpBranch [[merge]] +; CHECK: [[case1]] = OpLabel +; CHECK: OpAccessChain +; CHECK: OpLoad +; CHECK: OpImageWrite +; CHECK: OpBranch [[merge]] +; CHECK: [[default]] = OpLabel +; CHECK: OpBranch [[merge]] +; CHECK: [[merge]] = OpLabel + + %33 = OpAccessChain %_ptr_UniformConstant_type_sampler %Sampler0 %30 + %37 = OpLoad %type_sampler %33 + %34 = OpVectorShuffle %v2float %29 %29 0 1 + %35 = OpSampledImage %type_sampled_image %32 %37 + %36 = OpImageSampleImplicitLod %v4float %35 %34 None + +; SPIR-V instructions to be replaced (will be killed by ADCE) +; CHECK: OpSelectionMerge +; CHECK: OpSwitch + +; CHECK: OpSelectionMerge [[merge_sampler:%\w+]] None +; CHECK: OpSwitch [[var_index]] [[default_sampler:%\w+]] 0 [[case_sampler0:%\w+]] 1 [[case_sampler1:%\w+]] + +; CHECK: [[case_sampler0]] = OpLabel +; CHECK: OpSelectionMerge [[merge_texture0:%\w+]] None +; CHECK: OpSwitch [[var_index]] [[default_texture:%\w+]] 0 [[case_texture0:%\w+]] 1 [[case_texture1:%\w+]] +; CHECK: [[case_texture0]] = OpLabel +; CHECK: [[pt0:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %uint_0 +; CHECK: [[ps0:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_sampler %Sampler0 %uint_0 +; CHECK: [[s0:%\w+]] = OpLoad %type_sampler [[ps0]] +; CHECK: [[t0:%\w+]] = OpLoad %type_2d_image [[pt0]] +; CHECK: [[sampledImg0:%\w+]] = OpSampledImage %type_sampled_image [[t0]] [[s0]] +; CHECK: [[value0:%\w+]] = OpImageSampleImplicitLod %v4float [[sampledImg0]] +; CHECK: OpBranch [[merge_texture0]] +; CHECK: [[case_texture1]] = OpLabel +; CHECK: [[pt1:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %uint_1 +; CHECK: [[ps0:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_sampler %Sampler0 %uint_0 +; CHECK: [[s0:%\w+]] = OpLoad %type_sampler [[ps0]] +; CHECK: [[t1:%\w+]] = OpLoad %type_2d_image [[pt1]] +; CHECK: [[sampledImg1:%\w+]] = OpSampledImage %type_sampled_image [[t1]] [[s0]] +; CHECK: [[value1:%\w+]] = OpImageSampleImplicitLod %v4float [[sampledImg1]] +; CHECK: OpBranch [[merge_texture0]] +; CHECK: [[default_texture]] = OpLabel +; CHECK: OpBranch [[merge_texture0]] +; CHECK: [[merge_texture0]] = OpLabel +; CHECK: [[phi0:%\w+]] = OpPhi %v4float [[value0]] [[case_texture0]] [[value1]] [[case_texture1]] [[null_value]] [[default_texture]] +; CHECK: OpBranch [[merge_sampler]] + +; CHECK: [[case_sampler1]] = OpLabel +; CHECK: OpSelectionMerge [[merge_texture1:%\w+]] None +; CHECK: OpSwitch [[var_index]] [[default_texture:%\w+]] 0 [[case_texture0:%\w+]] 1 [[case_texture1:%\w+]] +; CHECK: [[case_texture0]] = OpLabel +; CHECK: [[pt0:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %uint_0 +; CHECK: [[ps1:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_sampler %Sampler0 %uint_1 +; CHECK: [[s1:%\w+]] = OpLoad %type_sampler [[ps1]] +; CHECK: [[t0:%\w+]] = OpLoad %type_2d_image [[pt0]] +; CHECK: [[sampledImg0:%\w+]] = OpSampledImage %type_sampled_image [[t0]] [[s1]] +; CHECK: [[value0:%\w+]] = OpImageSampleImplicitLod %v4float [[sampledImg0]] +; CHECK: OpBranch [[merge_texture1]] +; CHECK: [[case_texture1]] = OpLabel +; CHECK: [[pt1:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %uint_1 +; CHECK: [[ps1:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_sampler %Sampler0 %uint_1 +; CHECK: [[s1:%\w+]] = OpLoad %type_sampler [[ps1]] +; CHECK: [[t1:%\w+]] = OpLoad %type_2d_image [[pt1]] +; CHECK: [[sampledImg1:%\w+]] = OpSampledImage %type_sampled_image [[t1]] [[s1]] +; CHECK: [[value1:%\w+]] = OpImageSampleImplicitLod %v4float [[sampledImg1]] +; CHECK: OpBranch [[merge_texture1]] +; CHECK: [[default_texture]] = OpLabel +; CHECK: OpBranch [[merge_texture1]] +; CHECK: [[merge_texture1]] = OpLabel +; CHECK: [[phi1:%\w+]] = OpPhi %v4float [[value0]] [[case_texture0]] [[value1]] [[case_texture1]] [[null_value]] [[default_texture]] + +; CHECK: [[default_sampler]] = OpLabel +; CHECK: OpBranch [[merge_sampler]] +; CHECK: [[merge_sampler]] = OpLabel +; CHECK: OpPhi %v4float [[phi0]] [[merge_texture0]] [[phi1]] [[merge_texture1]] [[null_value]] [[default_sampler]] +; CHECK: OpStore + + OpStore %out_var_SV_TARGET %36 + OpReturn + OpFunctionEnd + )"; + + SinglePassRunAndMatch<ReplaceDescArrayAccessUsingVarIndex>(text, true); +} + +TEST_F(ReplaceDescArrayAccessUsingVarIndexTest, + ReplaceAccessChainToTextureArrayWithSingleElement) { + const std::string text = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %psmain "psmain" %gl_FragCoord %in_var_INSTANCEID %out_var_SV_TARGET + OpExecutionMode %psmain OriginUpperLeft + OpSource HLSL 600 + OpName %type_sampler "type.sampler" + OpName %Sampler0 "Sampler0" + OpName %type_2d_image "type.2d.image" + OpName %Tex0 "Tex0" + OpName %in_var_INSTANCEID "in.var.INSTANCEID" + OpName %out_var_SV_TARGET "out.var.SV_TARGET" + OpName %psmain "psmain" + OpName %type_sampled_image "type.sampled.image" + OpDecorate %gl_FragCoord BuiltIn FragCoord + OpDecorate %in_var_INSTANCEID Flat + OpDecorate %in_var_INSTANCEID Location 0 + OpDecorate %out_var_SV_TARGET Location 0 + OpDecorate %Sampler0 DescriptorSet 0 + OpDecorate %Sampler0 Binding 1 + OpDecorate %Tex0 DescriptorSet 0 + OpDecorate %Tex0 Binding 2 +%type_sampler = OpTypeSampler +%_ptr_UniformConstant_type_sampler = OpTypePointer UniformConstant %type_sampler + %uint = OpTypeInt 32 0 + %uint_1 = OpConstant %uint 1 + %float = OpTypeFloat 32 +%type_2d_image = OpTypeImage %float 2D 2 0 0 0 Unknown +%_arr_type_2d_image_uint_1 = OpTypeArray %type_2d_image %uint_1 +%_ptr_UniformConstant__arr_type_2d_image_uint_1 = OpTypePointer UniformConstant %_arr_type_2d_image_uint_1 + %v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%_ptr_Input_uint = OpTypePointer Input %uint +%_ptr_Output_v4float = OpTypePointer Output %v4float + %void = OpTypeVoid + %21 = OpTypeFunction %void +%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image + %v2float = OpTypeVector %float 2 + %v2uint = OpTypeVector %uint 2 + %uint_0 = OpConstant %uint 0 + %27 = OpConstantComposite %v2uint %uint_0 %uint_1 +%type_sampled_image = OpTypeSampledImage %type_2d_image + %Sampler0 = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant + %Tex0 = OpVariable %_ptr_UniformConstant__arr_type_2d_image_uint_1 UniformConstant +%gl_FragCoord = OpVariable %_ptr_Input_v4float Input +%in_var_INSTANCEID = OpVariable %_ptr_Input_uint Input +%out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output + %uint_2 = OpConstant %uint 2 + %66 = OpConstantNull %v4float + %psmain = OpFunction %void None %21 + %28 = OpLabel + %29 = OpLoad %v4float %gl_FragCoord + %30 = OpLoad %uint %in_var_INSTANCEID + %31 = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %30 + %32 = OpLoad %type_2d_image %31 + OpImageWrite %32 %27 %29 + +; CHECK: [[ac:%\w+]] = OpAccessChain %_ptr_UniformConstant_type_2d_image %Tex0 %uint_0 +; CHECK-NOT: OpAccessChain +; CHECK-NOT: OpSwitch +; CHECK-NOT: OpPhi + + %33 = OpLoad %type_sampler %Sampler0 + %34 = OpVectorShuffle %v2float %29 %29 0 1 + %35 = OpSampledImage %type_sampled_image %32 %33 + %36 = OpImageSampleImplicitLod %v4float %35 %34 None + + OpStore %out_var_SV_TARGET %36 + OpReturn + OpFunctionEnd + )"; + + SinglePassRunAndMatch<ReplaceDescArrayAccessUsingVarIndex>(text, true); +} + +} // namespace +} // namespace opt +} // namespace spvtools diff --git a/test/opt/scalar_replacement_test.cpp b/test/opt/scalar_replacement_test.cpp index 8115f5fb..8cb888c9 100644 --- a/test/opt/scalar_replacement_test.cpp +++ b/test/opt/scalar_replacement_test.cpp @@ -1935,12 +1935,12 @@ OpName %6 "simple_struct" ; CHECK: [[dbg_local_var:%\w+]] = OpExtInst %void [[ext]] DebugLocalVariable ; CHECK: [[deref_expr:%\w+]] = OpExtInst %void [[ext]] DebugExpression [[deref]] ; CHECK: [[repl3:%\w+]] = OpVariable %_ptr_Function_uint Function -; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl3]] [[deref_expr]] %int_3 ; CHECK: [[repl2:%\w+]] = OpVariable %_ptr_Function_uint Function -; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl2]] [[deref_expr]] %int_2 ; CHECK: [[repl1:%\w+]] = OpVariable %_ptr_Function_uint Function -; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl1]] [[deref_expr]] %int_1 ; CHECK: [[repl0:%\w+]] = OpVariable %_ptr_Function_uint Function +; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl3]] [[deref_expr]] %int_3 +; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl2]] [[deref_expr]] %int_2 +; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl1]] [[deref_expr]] %int_1 ; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl0]] [[deref_expr]] %int_0 ; CHECK-NOT: DebugDeclare %decl = OpExtInst %1 %ext DebugDeclare %dbg_foo %14 %null_expr @@ -2058,10 +2058,10 @@ OpName %6 "simple_struct" ; CHECK: [[repl2:%\w+]] = OpVariable %_ptr_Function_float Function %float_1 ; CHECK: [[repl1:%\w+]] = OpVariable %_ptr_Function_uint Function %uint_32 ; CHECK: [[repl3:%\w+]] = OpVariable %_ptr_Function_float Function %float_1 +; CHECK: [[repl0:%\w+]] = OpVariable %_ptr_Function_uint Function %uint_32 ; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl3]] [[deref_expr]] %int_2 ; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl1]] [[deref_expr]] %int_1 %int_0 ; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl2]] [[deref_expr]] %int_1 %int_1 -; CHECK: [[repl0:%\w+]] = OpVariable %_ptr_Function_uint Function %uint_32 ; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl0]] [[deref_expr]] %int_0 ; CHECK-NOT: DebugDeclare %decl = OpExtInst %1 %ext DebugDeclare %dbg_foo %14 %null_expr @@ -2174,12 +2174,12 @@ OpName %6 "simple_struct" ; CHECK: [[dbg_local_var:%\w+]] = OpExtInst %void [[ext:%\w+]] DebugLocalVariable ; CHECK: [[repl3:%\w+]] = OpVariable %_ptr_Function_uint Function -; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl3]] [[deref_expr:%\w+]] %int_3 ; CHECK: [[repl2:%\w+]] = OpVariable %_ptr_Function_uint Function -; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl2]] [[deref_expr]] %int_2 ; CHECK: [[repl1:%\w+]] = OpVariable %_ptr_Function_uint Function -; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl1]] [[deref_expr]] %int_1 ; CHECK: [[repl0:%\w+]] = OpVariable %_ptr_Function_uint Function +; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl3]] [[deref_expr:%\w+]] %int_3 +; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl2]] [[deref_expr]] %int_2 +; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl1]] [[deref_expr]] %int_1 ; CHECK: OpExtInst %void [[ext]] DebugValue [[dbg_local_var]] [[repl0]] [[deref_expr]] %int_0 OpBranch %20 @@ -2237,6 +2237,32 @@ OpFunctionEnd SinglePassRunAndMatch<ScalarReplacementPass>(text, false); } +TEST_F(ScalarReplacementTest, FunctionDeclaration) { + // Make sure the pass works with a function declaration that is called. + const std::string text = R"(OpCapability Addresses +OpCapability Linkage +OpCapability Kernel +OpCapability Int8 +%1 = OpExtInstImport "OpenCL.std" +OpMemoryModel Physical64 OpenCL +OpEntryPoint Kernel %2 "_Z23julia__1166_kernel_77094Bool" +OpExecutionMode %2 ContractionOff +OpSource Unknown 0 +OpDecorate %3 LinkageAttributes "julia_error_7712" Import +%void = OpTypeVoid +%5 = OpTypeFunction %void +%3 = OpFunction %void None %5 +OpFunctionEnd +%2 = OpFunction %void None %5 +%6 = OpLabel +%7 = OpFunctionCall %void %3 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck<ScalarReplacementPass>(text, text, false); +} + } // namespace } // namespace opt } // namespace spvtools diff --git a/test/opt/set_spec_const_default_value_test.cpp b/test/opt/set_spec_const_default_value_test.cpp index 5e63862e..f1dd50ee 100644 --- a/test/opt/set_spec_const_default_value_test.cpp +++ b/test/opt/set_spec_const_default_value_test.cpp @@ -935,6 +935,98 @@ INSTANTIATE_TEST_SUITE_P( "%2 = OpSpecConstantTrue %bool\n" "%3 = OpSpecConstantTrue %bool\n", }, + // 19. 16-bit signed int type. + { + // code + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "OpDecorate %3 SpecId 102\n" + "%short = OpTypeInt 16 1\n" + "%1 = OpSpecConstant %short 10\n" + "%2 = OpSpecConstant %short 11\n" + "%3 = OpSpecConstant %short 11\n", + // default values + SpecIdToValueBitPatternMap{ + {100, {32767}}, {101, {0xffff}}, {102, {0xffffffd6}}}, + // expected. These are sign-extended + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "OpDecorate %3 SpecId 102\n" + "%short = OpTypeInt 16 1\n" + "%1 = OpSpecConstant %short 32767\n" + "%2 = OpSpecConstant %short -1\n" + "%3 = OpSpecConstant %short -42\n", + }, + // 20. 16-bit unsigned int type. + { + // code + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "OpDecorate %3 SpecId 102\n" + "%ushort = OpTypeInt 16 0\n" + "%1 = OpSpecConstant %ushort 10\n" + "%2 = OpSpecConstant %ushort 11\n" + "%3 = OpSpecConstant %ushort 11\n", + // default values + SpecIdToValueBitPatternMap{ + {100, {32767}}, {101, {0xffff}}, {102, {0xffffffd6}}}, + // expected. Upper bits are always zero. + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "OpDecorate %3 SpecId 102\n" + "%ushort = OpTypeInt 16 0\n" + "%1 = OpSpecConstant %ushort 32767\n" + "%2 = OpSpecConstant %ushort 65535\n" + "%3 = OpSpecConstant %ushort 65494\n", + }, + // 21. 8-bit signed int type. + { + // code + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "OpDecorate %3 SpecId 102\n" + "%char = OpTypeInt 8 1\n" + "%1 = OpSpecConstant %char 10\n" + "%2 = OpSpecConstant %char 11\n" + "%3 = OpSpecConstant %char 11\n", + // default values + SpecIdToValueBitPatternMap{ + {100, {127}}, {101, {128}}, {102, {0xd6}}}, + // expected. These are sign extended + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "OpDecorate %3 SpecId 102\n" + "%char = OpTypeInt 8 1\n" + "%1 = OpSpecConstant %char 127\n" + "%2 = OpSpecConstant %char -128\n" + "%3 = OpSpecConstant %char -42\n", + }, + // 22. 8-bit unsigned int type. + { + // code + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "OpDecorate %3 SpecId 102\n" + "OpDecorate %4 SpecId 103\n" + "%uchar = OpTypeInt 8 0\n" + "%1 = OpSpecConstant %uchar 10\n" + "%2 = OpSpecConstant %uchar 11\n" + "%3 = OpSpecConstant %uchar 11\n" + "%4 = OpSpecConstant %uchar 11\n", + // default values + SpecIdToValueBitPatternMap{ + {100, {127}}, {101, {128}}, {102, {256}}, {103, {0xffffffd6}}}, + // expected. Upper bits are always zero. + "OpDecorate %1 SpecId 100\n" + "OpDecorate %2 SpecId 101\n" + "OpDecorate %3 SpecId 102\n" + "OpDecorate %4 SpecId 103\n" + "%uchar = OpTypeInt 8 0\n" + "%1 = OpSpecConstant %uchar 127\n" + "%2 = OpSpecConstant %uchar 128\n" + "%3 = OpSpecConstant %uchar 0\n" + "%4 = OpSpecConstant %uchar 214\n", + }, })); INSTANTIATE_TEST_SUITE_P( diff --git a/test/opt/simplification_test.cpp b/test/opt/simplification_test.cpp index 7a9696ea..7727f567 100644 --- a/test/opt/simplification_test.cpp +++ b/test/opt/simplification_test.cpp @@ -360,6 +360,31 @@ OpFunctionEnd SinglePassRunAndMatch<SimplificationPass>(spirv, true); } +TEST_F(SimplificationTest, FunctionDeclaration) { + // Make sure the pass works with a function declaration that is called. + const std::string text = R"(OpCapability Addresses +OpCapability Linkage +OpCapability Kernel +OpCapability Int8 +%1 = OpExtInstImport "OpenCL.std" +OpMemoryModel Physical64 OpenCL +OpEntryPoint Kernel %2 "_Z23julia__1166_kernel_77094Bool" +OpExecutionMode %2 ContractionOff +OpSource Unknown 0 +OpDecorate %3 LinkageAttributes "julia_error_7712" Import +%void = OpTypeVoid +%5 = OpTypeFunction %void +%3 = OpFunction %void None %5 +OpFunctionEnd +%2 = OpFunction %void None %5 +%6 = OpLabel +%7 = OpFunctionCall %void %3 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck<SimplificationPass>(text, text, false); +} } // namespace } // namespace opt } // namespace spvtools diff --git a/test/opt/upgrade_memory_model_test.cpp b/test/opt/upgrade_memory_model_test.cpp index 7f64ffd7..2cd3c7df 100644 --- a/test/opt/upgrade_memory_model_test.cpp +++ b/test/opt/upgrade_memory_model_test.cpp @@ -404,7 +404,7 @@ OpCapability VariablePointers OpExtension "SPV_KHR_variable_pointers" OpMemoryModel Logical GLSL450 OpDecorate %param Coherent -OpDecorate %param ArrayStride 4 +OpDecorate %ptr_int_StorageBuffer ArrayStride 4 %void = OpTypeVoid %bool = OpTypeBool %int = OpTypeInt 32 0 diff --git a/test/opt/vector_dce_test.cpp b/test/opt/vector_dce_test.cpp index 9bdad375..b14e2256 100644 --- a/test/opt/vector_dce_test.cpp +++ b/test/opt/vector_dce_test.cpp @@ -1351,6 +1351,72 @@ OpFunctionEnd SinglePassRunAndMatch<VectorDCE>(text, true); } +TEST_F(VectorDCETest, OutOfBoundsExtract) { + // It tests that the vector DCE pass is able to handle an extract with an + // index that is out of bounds. + const std::string text = R"( +; CHECK: [[undef:%\w+]] = OpUndef %v4float +; CHECK: OpCompositeExtract %float [[undef]] 8 + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %OutColor + OpExecutionMode %main OriginUpperLeft + OpDecorate %OutColor Location 0 + %void = OpTypeVoid + %10 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 +%_ptr_Output_float = OpTypePointer Output %float + %OutColor = OpVariable %_ptr_Output_float Output + %null = OpConstantNull %v4float + %float_1 = OpConstant %float 1 + %main = OpFunction %void None %10 + %28 = OpLabel + %33 = OpCompositeInsert %v4float %float_1 %null 1 + %extract = OpCompositeExtract %float %33 8 + OpStore %OutColor %extract + OpReturn + OpFunctionEnd +)"; + + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER | + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES); + SinglePassRunAndMatch<VectorDCE>(text, false); +} + +TEST_F(VectorDCETest, OutOfBoundsShuffle) { + // It tests that the vector DCE pass is able to handle a shuffle with an + // index that is out of bounds. + const std::string text = R"( +; CHECK: [[undef:%\w+]] = OpUndef %v4float +; CHECK: OpVectorShuffle %v4float [[undef]] [[undef]] 9 10 11 12 + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %OutColor + OpExecutionMode %main OriginUpperLeft + OpDecorate %OutColor Location 0 + %void = OpTypeVoid + %10 = OpTypeFunction %void + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 +%_ptr_Output_v4float = OpTypePointer Output %v4float + %OutColor = OpVariable %_ptr_Output_v4float Output + %null = OpConstantNull %v4float + %float_1 = OpConstant %float 1 + %main = OpFunction %void None %10 + %28 = OpLabel + %33 = OpCompositeInsert %v4float %float_1 %null 1 + %shuffle = OpVectorShuffle %v4float %33 %33 9 10 11 12 + OpStore %OutColor %shuffle + OpReturn + OpFunctionEnd +)"; + + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER | + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES); + SinglePassRunAndMatch<VectorDCE>(text, false); +} + } // namespace } // namespace opt } // namespace spvtools diff --git a/test/text_to_binary_test.cpp b/test/text_to_binary_test.cpp index 57f0a6ce..99d9ed68 100644 --- a/test/text_to_binary_test.cpp +++ b/test/text_to_binary_test.cpp @@ -247,12 +247,6 @@ INSTANTIATE_TEST_SUITE_P( {"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()); diff --git a/test/val/CMakeLists.txt b/test/val/CMakeLists.txt index 39f9a098..64eba446 100644 --- a/test/val/CMakeLists.txt +++ b/test/val/CMakeLists.txt @@ -23,6 +23,7 @@ set(VAL_TEST_COMMON_SRCS add_spvtools_unittest(TARGET val_abcde SRCS val_adjacency_test.cpp + val_annotation_test.cpp val_arithmetics_test.cpp val_atomics_test.cpp val_barriers_test.cpp diff --git a/test/val/val_annotation_test.cpp b/test/val/val_annotation_test.cpp new file mode 100644 index 00000000..889c76ca --- /dev/null +++ b/test/val/val_annotation_test.cpp @@ -0,0 +1,951 @@ +// Copyright (c) 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Validation tests for decorations + +#include <string> +#include <vector> + +#include "gmock/gmock.h" +#include "test/test_fixture.h" +#include "test/unit_spirv.h" +#include "test/val/val_code_generator.h" +#include "test/val/val_fixtures.h" + +namespace spvtools { +namespace val { +namespace { + +using ::testing::Combine; +using ::testing::Eq; +using ::testing::HasSubstr; +using ::testing::Values; + +using DecorationTest = spvtest::ValidateBase<bool>; + +TEST_F(DecorationTest, WorkgroupSizeShader) { + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %ones BuiltIn WorkgroupSize +%int = OpTypeInt 32 0 +%int3 = OpTypeVector %int 3 +%int_1 = OpConstant %int 1 +%ones = OpConstantComposite %int3 %int_1 %int_1 %int_1 +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(DecorationTest, WorkgroupSizeKernel) { + const std::string text = R"( +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical OpenCL +OpDecorate %var BuiltIn WorkgroupSize +%int = OpTypeInt 32 0 +%int3 = OpTypeVector %int 3 +%ptr = OpTypePointer Input %int3 +%var = OpVariable %ptr Input +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +using MemberOnlyDecorations = spvtest::ValidateBase<std::string>; + +TEST_P(MemberOnlyDecorations, MemberDecoration) { + const auto deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpMemberDecorate %struct 0 )" + + deco + R"( +%float = OpTypeFloat 32 +%float2 = OpTypeVector %float 2 +%float2x2 = OpTypeMatrix %float2 2 +%struct = OpTypeStruct %float2x2 +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(MemberOnlyDecorations, Decoration) { + const auto deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %struct )" + deco + + R"( +%float = OpTypeFloat 32 +%float2 = OpTypeVector %float 2 +%float2x2 = OpTypeMatrix %float2 2 +%struct = OpTypeStruct %float2x2 +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("can only be applied to structure members")); +} + +INSTANTIATE_TEST_SUITE_P(ValidateMemberOnlyDecorations, MemberOnlyDecorations, + Values("RowMajor", "ColMajor", "MatrixStride 16" + // SPIR-V spec bug? + /*,"Offset 0"*/)); + +using NonMemberOnlyDecorations = spvtest::ValidateBase<std::string>; + +TEST_P(NonMemberOnlyDecorations, MemberDecoration) { + const auto deco = GetParam(); + const auto text = R"( +OpCapability Shader +OpCapability Kernel +OpCapability Linkage +OpCapability InputAttachment +OpCapability Addresses +OpCapability PhysicalStorageBufferAddresses +OpCapability ShaderNonUniform +OpExtension "SPV_KHR_no_integer_wrap_decoration" +OpExtension "SPV_KHR_physical_storage_buffer" +OpExtension "SPV_GOOGLE_hlsl_functionality1" +OpExtension "SPV_EXT_descriptor_indexing" +OpMemoryModel Logical GLSL450 +OpMemberDecorate %struct 0 )" + + deco + R"( +%float = OpTypeFloat 32 +%float2 = OpTypeVector %float 2 +%float2x2 = OpTypeMatrix %float2 2 +%struct = OpTypeStruct %float2x2 +)"; + + CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("cannot be applied to structure members")); +} + +INSTANTIATE_TEST_SUITE_P( + ValidateNonMemberOnlyDecorations, NonMemberOnlyDecorations, + Values("SpecId 1", "Block", "BufferBlock", "ArrayStride 4", "GLSLShared", + "GLSLPacked", "CPacked", + // TODO: https://github.com/KhronosGroup/glslang/issues/703: + // glslang applies Restrict to structure members. + //"Restrict", + "Aliased", "Constant", "Uniform", "SaturatedConversion", "Index 0", + "Binding 0", "DescriptorSet 0", "FuncParamAttr Zext", + "FPRoundingMode RTE", "FPFastMathMode None", + "LinkageAttributes \"ext\" Import", "NoContraction", + "InputAttachmentIndex 0", "Alignment 4", "MaxByteOffset 4", + "AlignmentId %float", "MaxByteOffsetId %float", "NoSignedWrap", + "NoUnsignedWrap", "NonUniform", "RestrictPointer", "AliasedPointer", + "CounterBuffer %float")); + +using StructDecorations = spvtest::ValidateBase<std::string>; + +TEST_P(StructDecorations, Struct) { + const std::string deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %struct )" + deco + + R"( +%struct = OpTypeStruct +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(StructDecorations, OtherType) { + const std::string deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %int )" + deco + R"( +%int = OpTypeInt 32 0 +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a structure type")); +} + +TEST_P(StructDecorations, Variable) { + const std::string deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %var )" + deco + R"( +%int = OpTypeInt 32 0 +%ptr = OpTypePointer Private %int +%var = OpVariable %ptr Private +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a structure type")); +} + +TEST_P(StructDecorations, FunctionParameter) { + const auto deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %func LinkageAttributes "import" Import +OpDecorate %param )" + deco + + R"( +%int = OpTypeInt 32 0 +%void = OpTypeVoid +%fn = OpTypeFunction %void %int +%func = OpFunction %void None %fn +%param = OpFunctionParameter %int +OpFunctionEnd +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a structure type")); +} + +TEST_P(StructDecorations, Constant) { + const std::string deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability Kernel +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %int_0 )" + deco + + R"( +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a structure type")); +} + +INSTANTIATE_TEST_SUITE_P(ValidateStructDecorations, StructDecorations, + Values("Block", "BufferBlock", "GLSLShared", + "GLSLPacked", "CPacked")); + +using ArrayDecorations = spvtest::ValidateBase<std::string>; + +TEST_P(ArrayDecorations, Array) { + const auto deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %array )" + deco + + R"( +%int = OpTypeInt 32 0 +%int_4 = OpConstant %int 4 +%array = OpTypeArray %int %int_4 +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ArrayDecorations, RuntimeArray) { + const auto deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %array )" + deco + + R"( +%int = OpTypeInt 32 0 +%array = OpTypeRuntimeArray %int +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ArrayDecorations, Pointer) { + const auto deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %ptr )" + deco + R"( +%int = OpTypeInt 32 0 +%ptr = OpTypePointer Workgroup %int +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(ArrayDecorations, Struct) { + const auto deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %struct )" + deco + + R"( +%int = OpTypeInt 32 0 +%struct = OpTypeStruct %int +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("must be an array or pointer type")); +} + +TEST_P(ArrayDecorations, Variable) { + const auto deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %var )" + deco + R"( +%int = OpTypeInt 32 0 +%ptr = OpTypePointer Private %int +%var = OpVariable %ptr Private +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("must be an array or pointer type")); +} + +TEST_P(ArrayDecorations, FunctionParameter) { + const auto deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %func LinkageAttributes "import" Import +OpDecorate %param )" + deco + + R"( +%int = OpTypeInt 32 0 +%void = OpTypeVoid +%fn = OpTypeFunction %void %int +%func = OpFunction %void None %fn +%param = OpFunctionParameter %int +OpFunctionEnd +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("must be an array or pointer type")); +} + +TEST_P(ArrayDecorations, Constant) { + const auto deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %null )" + deco + + R"( +%int = OpTypeInt 32 0 +%int_4 = OpConstant %int 4 +%array = OpTypeArray %int %int_4 +%null = OpConstantNull %array +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("must be an array or pointer type")); +} + +INSTANTIATE_TEST_SUITE_P(ValidateArrayDecorations, ArrayDecorations, + Values("ArrayStride 4")); + +using BuiltInDecorations = spvtest::ValidateBase<std::string>; + +TEST_P(BuiltInDecorations, Variable) { + const auto deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %var BuiltIn )" + + deco + R"( +%int = OpTypeInt 32 0 +%ptr = OpTypePointer Input %int +%var = OpVariable %ptr Input +)"; + + CompileSuccessfully(text); + if (deco != "WorkgroupSize") { + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); + } else { + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("must be a constant for WorkgroupSize")); + } +} + +TEST_P(BuiltInDecorations, IntegerType) { + const auto deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %int BuiltIn )" + + deco + R"( +%int = OpTypeInt 32 0 +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("BuiltIns can only target variables, structure members " + "or constants")); +} + +TEST_P(BuiltInDecorations, FunctionParameter) { + const auto deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %func LinkageAttributes "import" Import +OpDecorate %param BuiltIn )" + + deco + R"( +%int = OpTypeInt 32 0 +%void = OpTypeVoid +%fn = OpTypeFunction %void %int +%func = OpFunction %void None %fn +%param = OpFunctionParameter %int +OpFunctionEnd +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("BuiltIns can only target variables, structure members " + "or constants")); +} + +TEST_P(BuiltInDecorations, Constant) { + const auto deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %const BuiltIn )" + + deco + R"( +%int = OpTypeInt 32 0 +%int3 = OpTypeVector %int 3 +%int_1 = OpConstant %int 1 +%const = OpConstantComposite %int3 %int_1 %int_1 %int_1 +)"; + + CompileSuccessfully(text); + if (deco == "WorkgroupSize") { + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); + } else { + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a variable")); + } +} + +TEST_P(BuiltInDecorations, SpecConstant) { + const auto deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %const BuiltIn )" + + deco + R"( +%int = OpTypeInt 32 0 +%int3 = OpTypeVector %int 3 +%int_1 = OpConstant %int 1 +%const = OpSpecConstantComposite %int3 %int_1 %int_1 %int_1 +)"; + + CompileSuccessfully(text); + if (deco == "WorkgroupSize") { + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); + } else { + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a variable")); + } +} + +INSTANTIATE_TEST_SUITE_P(ValidateBuiltInDecorations, BuiltInDecorations, + Values("Position", "PointSize", "VertexId", + "InstanceId", "FragCoord", "FrontFacing", + "NumWorkgroups", "WorkgroupSize", + "LocalInvocationId", "GlobalInvocationId")); + +using MemoryObjectDecorations = spvtest::ValidateBase<std::string>; + +TEST_P(MemoryObjectDecorations, Variable) { + const auto deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpCapability SampleRateShading +OpCapability TransformFeedback +OpCapability GeometryStreams +OpCapability Tessellation +OpCapability PhysicalStorageBufferAddresses +OpExtension "SPV_KHR_physical_storage_buffer" +OpMemoryModel Logical GLSL450 +OpDecorate %var )" + deco + R"( +%float = OpTypeFloat 32 +%ptr = OpTypePointer Input %float +%var = OpVariable %ptr Input +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(MemoryObjectDecorations, FunctionParameterGood) { + const auto deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpCapability SampleRateShading +OpCapability TransformFeedback +OpCapability GeometryStreams +OpCapability Tessellation +OpCapability PhysicalStorageBufferAddresses +OpExtension "SPV_KHR_physical_storage_buffer" +OpMemoryModel Logical GLSL450 +OpDecorate %func LinkageAttributes "import" Import +OpDecorate %param )" + deco + + R"( +%float = OpTypeFloat 32 +%ptr = OpTypePointer Input %float +%void = OpTypeVoid +%fn = OpTypeFunction %void %ptr +%func = OpFunction %void None %fn +%param = OpFunctionParameter %ptr +OpFunctionEnd +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(MemoryObjectDecorations, FunctionParameterNotAPointer) { + const auto deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpCapability SampleRateShading +OpCapability TransformFeedback +OpCapability GeometryStreams +OpCapability Tessellation +OpCapability PhysicalStorageBufferAddresses +OpExtension "SPV_KHR_physical_storage_buffer" +OpMemoryModel Logical GLSL450 +OpDecorate %func LinkageAttributes "import" Import +OpDecorate %param )" + deco + + R"( +%float = OpTypeFloat 32 +%void = OpTypeVoid +%fn = OpTypeFunction %void %float +%func = OpFunction %void None %fn +%param = OpFunctionParameter %float +OpFunctionEnd +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a pointer type")); +} + +TEST_P(MemoryObjectDecorations, FloatType) { + const auto deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpCapability SampleRateShading +OpCapability TransformFeedback +OpCapability GeometryStreams +OpCapability Tessellation +OpCapability PhysicalStorageBufferAddresses +OpExtension "SPV_KHR_physical_storage_buffer" +OpMemoryModel Logical GLSL450 +OpDecorate %float )" + deco + + R"( +%float = OpTypeFloat 32 +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("must be a memory object declaration")); +} + +TEST_P(MemoryObjectDecorations, Constant) { + const auto deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability Linkage +OpCapability SampleRateShading +OpCapability TransformFeedback +OpCapability GeometryStreams +OpCapability Tessellation +OpCapability PhysicalStorageBufferAddresses +OpExtension "SPV_KHR_physical_storage_buffer" +OpMemoryModel Logical GLSL450 +OpDecorate %const )" + deco + + R"( +%float = OpTypeFloat 32 +%const = OpConstant %float 0 +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("must be a memory object declaration")); +} + +// NonWritable and NonReadable are covered by other tests. +INSTANTIATE_TEST_SUITE_P( + ValidateMemoryObjectDecorations, MemoryObjectDecorations, + Values("NoPerspective", "Flat", "Patch", "Centroid", "Component 0", + "Sample", "Restrict", "Aliased", "Volatile", "Coherent", "Stream 0", + "XfbBuffer 1", "XfbStride 1", "AliasedPointer", "RestrictPointer")); + +using VariableDecorations = spvtest::ValidateBase<std::string>; + +TEST_P(VariableDecorations, Variable) { + const auto deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability Kernel +OpCapability Linkage +OpCapability InputAttachment +OpMemoryModel Logical GLSL450 +OpDecorate %var )" + deco + R"( +%float = OpTypeFloat 32 +%ptr = OpTypePointer Input %float +%var = OpVariable %ptr Input +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_P(VariableDecorations, FunctionParameter) { + const auto deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability Kernel +OpCapability Linkage +OpCapability InputAttachment +OpMemoryModel Logical GLSL450 +OpDecorate %func LinkageAttributes "import" Import +OpDecorate %param )" + deco + + R"( +%float = OpTypeFloat 32 +%void = OpTypeVoid +%fn = OpTypeFunction %void %float +%func = OpFunction %void None %fn +%param = OpFunctionParameter %float +OpFunctionEnd +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a variable")); +} + +TEST_P(VariableDecorations, FloatType) { + const auto deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability Kernel +OpCapability Linkage +OpCapability InputAttachment +OpMemoryModel Logical GLSL450 +OpDecorate %float )" + deco + + R"( +%float = OpTypeFloat 32 +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a variable")); +} + +TEST_P(VariableDecorations, Constant) { + const auto deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability Kernel +OpCapability Linkage +OpCapability InputAttachment +OpMemoryModel Logical GLSL450 +OpDecorate %const )" + deco + + R"( +%float = OpTypeFloat 32 +%const = OpConstant %float 0 +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a variable")); +} + +INSTANTIATE_TEST_SUITE_P(ValidateVariableDecorations, VariableDecorations, + Values("Invariant", "Constant", "Location 0", + "Index 0", "Binding 0", "DescriptorSet 0")); + +using VulkanIOStorageClass = + spvtest::ValidateBase<std::tuple<std::string, std::string>>; + +TEST_P(VulkanIOStorageClass, Invalid) { + const auto deco = std::get<0>(GetParam()); + const auto sc = std::get<1>(GetParam()); + const std::string text = R"( +OpCapability Shader +OpExtension "SPV_KHR_storage_buffer_storage_class" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpDecorate %var )" + deco + R"( 0 +%void = OpTypeVoid +%float = OpTypeFloat 32 +%ptr = OpTypePointer )" + + sc + + R"( %float +%var = OpVariable %ptr )" + sc + + R"( +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(text, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("decoration must not be applied to this storage class")); +} + +INSTANTIATE_TEST_SUITE_P(ValidateVulkanIOStorageClass, VulkanIOStorageClass, + Combine(Values("Location", "Component"), + Values("StorageBuffer", "Uniform", + "UniformConstant", "Workgroup", + "Private"))); + +using VulkanResourceStorageClass = + spvtest::ValidateBase<std::tuple<std::string, std::string>>; + +TEST_P(VulkanResourceStorageClass, Invalid) { + const auto deco = std::get<0>(GetParam()); + const auto sc = std::get<1>(GetParam()); + const std::string text = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpDecorate %var )" + deco + R"( 0 +%void = OpTypeVoid +%float = OpTypeFloat 32 +%ptr = OpTypePointer )" + + sc + + R"( %float +%var = OpVariable %ptr )" + sc + + R"( +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(text, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("must be in the StorageBuffer, Uniform, or " + "UniformConstant storage class")); +} + +INSTANTIATE_TEST_SUITE_P(ValidateVulkanResourceStorageClass, + VulkanResourceStorageClass, + Combine(Values("DescriptorSet", "Binding"), + Values("Private", "Input", "Output", + "Workgroup"))); + +using VulkanInterpolationStorageClass = spvtest::ValidateBase<std::string>; + +TEST_P(VulkanInterpolationStorageClass, Input) { + const auto deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability SampleRateShading +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpDecorate %var )" + deco + R"( +%void = OpTypeVoid +%float = OpTypeFloat 32 +%void_fn = OpTypeFunction %void +%ptr = OpTypePointer Input %float +%var = OpVariable %ptr Input +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(text, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); +} + +TEST_P(VulkanInterpolationStorageClass, Output) { + const auto deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability SampleRateShading +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %main "main" +OpDecorate %var )" + deco + R"( +%void = OpTypeVoid +%float = OpTypeFloat 32 +%void_fn = OpTypeFunction %void +%ptr = OpTypePointer Output %float +%var = OpVariable %ptr Output +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(text, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); +} + +TEST_P(VulkanInterpolationStorageClass, Private) { + const auto deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability SampleRateShading +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpDecorate %var )" + deco + R"( +%void = OpTypeVoid +%float = OpTypeFloat 32 +%void_fn = OpTypeFunction %void +%ptr = OpTypePointer Private %float +%var = OpVariable %ptr Private +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(text, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("storage class must be Input or Output")); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("[VUID-StandaloneSpirv-Flat-04670")); +} + +TEST_P(VulkanInterpolationStorageClass, Uniform) { + const auto deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability SampleRateShading +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpDecorate %var )" + deco + R"( +OpDecorate %var Binding 0 +OpDecorate %var DescriptorSet 0 +%void = OpTypeVoid +%float = OpTypeFloat 32 +%void_fn = OpTypeFunction %void +%ptr = OpTypePointer Uniform %float +%var = OpVariable %ptr Uniform +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(text, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("storage class must be Input or Output")); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("[VUID-StandaloneSpirv-Flat-04670")); +} + +TEST_P(VulkanInterpolationStorageClass, StorageBuffer) { + const auto deco = GetParam(); + const std::string text = R"( +OpCapability Shader +OpCapability SampleRateShading +OpExtension "SPV_KHR_storage_buffer_storage_class" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpDecorate %var )" + deco + R"( +OpDecorate %var Binding 0 +OpDecorate %var DescriptorSet 0 +%void = OpTypeVoid +%float = OpTypeFloat 32 +%void_fn = OpTypeFunction %void +%ptr = OpTypePointer StorageBuffer %float +%var = OpVariable %ptr StorageBuffer +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(text, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("storage class must be Input or Output")); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("[VUID-StandaloneSpirv-Flat-04670")); +} + +INSTANTIATE_TEST_SUITE_P(ValidateVulkanInterpolationStorageClass, + VulkanInterpolationStorageClass, + Values("Flat", "NoPerspective", "Centroid", "Sample")); + +} // namespace +} // namespace val +} // namespace spvtools diff --git a/test/val/val_arithmetics_test.cpp b/test/val/val_arithmetics_test.cpp index b82fc97e..856ad02c 100644 --- a/test/val/val_arithmetics_test.cpp +++ b/test/val/val_arithmetics_test.cpp @@ -1309,6 +1309,57 @@ TEST_F(ValidateArithmetics, CoopMatDimFail) { HasSubstr("Cooperative matrix 'M' mismatch: CooperativeMatrixMulAddNV")); } +TEST_F(ValidateArithmetics, CoopMatComponentTypeNotScalarNumeric) { + const std::string types = R"( +%bad = OpTypeCooperativeMatrixNV %bool %subgroup %u32_8 %u32_8 +)"; + + CompileSuccessfully(GenerateCoopMatCode(types, "").c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("OpTypeCooperativeMatrixNV Component Type <id> " + "'4[%bool]' is not a scalar numerical type.")); +} + +TEST_F(ValidateArithmetics, CoopMatScopeNotConstantInt) { + const std::string types = R"( +%bad = OpTypeCooperativeMatrixNV %f16 %f32_1 %u32_8 %u32_8 +)"; + + CompileSuccessfully(GenerateCoopMatCode(types, "").c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpTypeCooperativeMatrixNV Scope <id> '17[%float_1]' is not a " + "constant instruction with scalar integer type.")); +} + +TEST_F(ValidateArithmetics, CoopMatRowsNotConstantInt) { + const std::string types = R"( +%bad = OpTypeCooperativeMatrixNV %f16 %subgroup %f32_1 %u32_8 +)"; + + CompileSuccessfully(GenerateCoopMatCode(types, "").c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpTypeCooperativeMatrixNV Rows <id> '17[%float_1]' is not a " + "constant instruction with scalar integer type.")); +} + +TEST_F(ValidateArithmetics, CoopMatColumnsNotConstantInt) { + const std::string types = R"( +%bad = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_8 %f32_1 +)"; + + CompileSuccessfully(GenerateCoopMatCode(types, "").c_str()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpTypeCooperativeMatrixNV Cols <id> '17[%float_1]' is not a " + "constant instruction with scalar integer type.")); +} + TEST_F(ValidateArithmetics, IAddCarrySuccess) { const std::string body = R"( %val1 = OpIAddCarry %struct_u32_u32 %u32_0 %u32_1 diff --git a/test/val/val_atomics_test.cpp b/test/val/val_atomics_test.cpp index b7f6948e..d1a030a8 100644 --- a/test/val/val_atomics_test.cpp +++ b/test/val/val_atomics_test.cpp @@ -2679,6 +2679,39 @@ OpFunctionEnd "CooperativeMatrixNV capability is present")); } +TEST_F(ValidateAtomics, IIncrementBadPointerDataType) { + const std::string spirv = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + %uint = OpTypeInt 32 0 +%_ptr_Input_uint = OpTypePointer Input %uint + %v3uint = OpTypeVector %uint 3 +%_ptr_Input_v3uint = OpTypePointer Input %v3uint + %void = OpTypeVoid + %16 = OpTypeFunction %void +%uint_538976288 = OpConstant %uint 538976288 + %int = OpTypeInt 32 1 +%_runtimearr_int = OpTypeRuntimeArray %int + %_struct_5 = OpTypeStruct %_runtimearr_int +%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5 + %3 = OpVariable %_ptr_Input_v3uint Input + %7 = OpVariable %_ptr_Uniform__struct_5 Uniform + %8224 = OpFunction %void None %16 + %65312 = OpLabel + %25 = OpAccessChain %_ptr_Input_uint %3 %uint_538976288 + %26 = OpLoad %uint %25 + %2097184 = OpAtomicIIncrement %int %7 %uint_538976288 %26 + OpUnreachable + OpFunctionEnd +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("AtomicIIncrement: expected Pointer to point to a " + "value of type Result Type")); +} + } // namespace } // namespace val } // namespace spvtools diff --git a/test/val/val_builtins_test.cpp b/test/val/val_builtins_test.cpp index 84952645..dff9adfe 100644 --- a/test/val/val_builtins_test.cpp +++ b/test/val/val_builtins_test.cpp @@ -2861,9 +2861,9 @@ OpDecorate %copy BuiltIn WorkgroupSize CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0); ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); - EXPECT_THAT( - getDiagnosticString(), - HasSubstr("BuiltIns can only target variables, structs or constants")); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("BuiltIns can only target variables, structure " + "members or constants")); } CodeGenerator GetWorkgroupSizeNotVectorGenerator() { @@ -3482,35 +3482,6 @@ OpDecorate %gl_ViewportIndex PerPrimitiveNV EXPECT_THAT(getDiagnosticString(), HasSubstr("is not an int scalar")); } -TEST_F(ValidateBuiltIns, GetUnderlyingTypeNoAssert) { - std::string spirv = R"( - OpCapability Shader - OpMemoryModel Logical GLSL450 - OpEntryPoint Fragment %4 "PSMa" %12 %17 - OpExecutionMode %4 OriginUpperLeft - OpDecorate %gl_PointCoord BuiltIn PointCoord - OpDecorate %12 Location 0 - OpDecorate %17 Location 0 - %void = OpTypeVoid - %3 = OpTypeFunction %void - %float = OpTypeFloat 32 - %v4float = OpTypeVector %float 4 - %gl_PointCoord = OpTypeStruct %v4float - %_ptr_Input_v4float = OpTypePointer Input %v4float - %_ptr_Output_v4float = OpTypePointer Output %v4float - %12 = OpVariable %_ptr_Input_v4float Input - %17 = OpVariable %_ptr_Output_v4float Output - %4 = OpFunction %void None %3 - %15 = OpLabel - OpReturn - OpFunctionEnd)"; - CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1); - ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_1)); - EXPECT_THAT(getDiagnosticString(), - HasSubstr("did not find an member index to get underlying data " - "type")); -} - TEST_P(ValidateVulkanSubgroupBuiltIns, InMain) { const char* const built_in = std::get<0>(GetParam()); const char* const execution_model = std::get<1>(GetParam()); @@ -3781,9 +3752,9 @@ OpDecorate %void BuiltIn Position CompileSuccessfully(text); EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); - EXPECT_THAT( - getDiagnosticString(), - HasSubstr("BuiltIns can only target variables, structs or constants")); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("BuiltIns can only target variables, structure members " + "or constants")); } TEST_F(ValidateBuiltIns, TargetIsVariable) { @@ -3801,47 +3772,6 @@ OpDecorate %wg_var BuiltIn Position EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); } -TEST_F(ValidateBuiltIns, TargetIsStruct) { - const std::string text = R"( -OpCapability Shader -OpCapability Linkage -OpMemoryModel Logical GLSL450 -OpDecorate %struct BuiltIn Position -%struct = OpTypeStruct -)"; - - CompileSuccessfully(text); - EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); -} - -TEST_F(ValidateBuiltIns, TargetIsConstant) { - const std::string text = R"( -OpCapability Shader -OpCapability Linkage -OpMemoryModel Logical GLSL450 -OpDecorate %int0 BuiltIn Position -%int = OpTypeInt 32 0 -%int0 = OpConstant %int 0 -)"; - - CompileSuccessfully(text); - EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); -} - -TEST_F(ValidateBuiltIns, TargetIsSpecConstant) { - const std::string text = R"( -OpCapability Shader -OpCapability Linkage -OpMemoryModel Logical GLSL450 -OpDecorate %int0 BuiltIn Position -%int = OpTypeInt 32 0 -%int0 = OpSpecConstant %int 0 -)"; - - CompileSuccessfully(text); - EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); -} - INSTANTIATE_TEST_SUITE_P( PrimitiveShadingRateOutputSuccess, ValidateVulkanCombineBuiltInExecutionModelDataTypeCapabilityExtensionResult, diff --git a/test/val/val_capability_test.cpp b/test/val/val_capability_test.cpp index 82f8d381..c432c3cf 100644 --- a/test/val/val_capability_test.cpp +++ b/test/val/val_capability_test.cpp @@ -1205,8 +1205,10 @@ INSTANTIATE_TEST_SUITE_P(Decoration, ValidateCapability, Values( std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" - "OpDecorate %intt RelaxedPrecision\n" - "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid), + "OpDecorate %var RelaxedPrecision\n" + "%intt = OpTypeInt 32 0\n" + "%ptr = OpTypePointer Private %intt\n" + "%var = OpVariable %ptr Private\n" + std::string(kVoidFVoid), ShaderDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + // Block applies to struct type. @@ -1224,93 +1226,125 @@ std::make_pair(std::string(kOpenCLMemoryModel) + ShaderDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" - "OpDecorate %intt RowMajor\n" - "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid), + "OpMemberDecorate %structt 0 RowMajor\n" + "%floatt = OpTypeFloat 32\n" + "%float2 = OpTypeVector %floatt 2\n" + "%mat2x2 = OpTypeMatrix %float2 2\n" + "%structt = OpTypeStruct %mat2x2\n" + std::string(kVoidFVoid), MatrixDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" - "OpDecorate %intt ColMajor\n" - "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid), + "OpMemberDecorate %structt 0 ColMajor\n" + "%floatt = OpTypeFloat 32\n" + "%float2 = OpTypeVector %floatt 2\n" + "%mat2x2 = OpTypeMatrix %float2 2\n" + "%structt = OpTypeStruct %mat2x2\n" + std::string(kVoidFVoid), MatrixDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" - "OpDecorate %intt ArrayStride 1\n" - "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid), + "OpDecorate %array ArrayStride 4\n" + "%intt = OpTypeInt 32 0\n" + "%array = OpTypeRuntimeArray %intt\n" + std::string(kVoidFVoid), ShaderDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" - "OpDecorate %intt MatrixStride 1\n" - "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid), + "OpMemberDecorate %structt 0 MatrixStride 8\n" + "%floatt = OpTypeFloat 32\n" + "%float2 = OpTypeVector %floatt 2\n" + "%mat2x2 = OpTypeMatrix %float2 2\n" + "%structt = OpTypeStruct %mat2x2\n" + std::string(kVoidFVoid), MatrixDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" - "OpDecorate %intt GLSLShared\n" - "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid), + "OpDecorate %struct GLSLShared\n" + "%struct = OpTypeStruct\n" + std::string(kVoidFVoid), ShaderDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" - "OpDecorate %intt GLSLPacked\n" - "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid), + "OpDecorate %struct GLSLPacked\n" + "%struct = OpTypeStruct\n" + std::string(kVoidFVoid), ShaderDependencies()), std::make_pair(std::string(kGLSL450MemoryModel) + "OpEntryPoint Vertex %func \"shader\" \n" - "OpDecorate %intt CPacked\n" - "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid), + "OpDecorate %struct CPacked\n" + "%struct = OpTypeStruct\n" + std::string(kVoidFVoid), KernelDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" - "OpDecorate %intt NoPerspective\n" - "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid), + "OpDecorate %var NoPerspective\n" + "%intt = OpTypeInt 32 0\n" + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), ShaderDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" - "OpDecorate %intt Flat\n" - "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid), + "OpDecorate %var Flat\n" + "%intt = OpTypeInt 32 0\n" + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), ShaderDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" - "OpDecorate %intt Patch\n" - "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid), + "OpDecorate %var Patch\n" + "%intt = OpTypeInt 32 0\n" + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), TessellationDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" - "OpDecorate %intt Centroid\n" - "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid), + "OpDecorate %var Centroid\n" + "%intt = OpTypeInt 32 0\n" + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), ShaderDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" - "OpDecorate %intt Sample\n" - "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid), + "OpDecorate %var Sample\n" + "%intt = OpTypeInt 32 0\n" + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), std::vector<std::string>{"SampleRateShading"}), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" - "OpDecorate %intt Invariant\n" - "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid), + "OpDecorate %var Invariant\n" + "%intt = OpTypeInt 32 0\n" + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), ShaderDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" - "OpDecorate %intt Restrict\n" - "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid), + "OpDecorate %var Restrict\n" + "%intt = OpTypeInt 32 0\n" + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), AllCapabilities()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" - "OpDecorate %intt Aliased\n" - "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid), + "OpDecorate %var Aliased\n" + "%intt = OpTypeInt 32 0\n" + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), AllCapabilities()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" - "OpDecorate %intt Volatile\n" - "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid), + "OpDecorate %var Volatile\n" + "%intt = OpTypeInt 32 0\n" + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), AllCapabilities()), std::make_pair(std::string(kGLSL450MemoryModel) + "OpEntryPoint Vertex %func \"shader\" \n" - "OpDecorate %intt Constant\n" - "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid), + "OpDecorate %var Constant\n" + "%intt = OpTypeInt 32 0\n" + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), KernelDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" - "OpDecorate %intt Coherent\n" - "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid), + "OpDecorate %var Coherent\n" + "%intt = OpTypeInt 32 0\n" + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), AllCapabilities()), std::make_pair(std::string(kOpenCLMemoryModel) + // NonWritable must target something valid, such as a storage image. @@ -1324,8 +1358,12 @@ std::make_pair(std::string(kOpenCLMemoryModel) + AllCapabilities()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" - "OpDecorate %intt NonReadable\n" - "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid), + "OpDecorate %var NonReadable " + "%float = OpTypeFloat 32 " + "%imstor = OpTypeImage %float 2D 0 0 0 2 Unknown " + "%ptr = OpTypePointer UniformConstant %imstor " + "%var = OpVariable %ptr UniformConstant " + + std::string(kVoidFVoid), AllCapabilities()), std::make_pair(std::string(kOpenCLMemoryModel) + // Uniform must target a non-void value. @@ -1342,8 +1380,10 @@ std::make_pair(std::string(kGLSL450MemoryModel) + KernelDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" - "OpDecorate %intt Stream 0\n" - "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid), + "OpDecorate %var Stream 0\n" + "%intt = OpTypeInt 32 0\n" + "%ptr = OpTypePointer Output %intt\n" + "%var = OpVariable %ptr Output\n" + std::string(kVoidFVoid), std::vector<std::string>{"GeometryStreams"}), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" @@ -1360,33 +1400,44 @@ std::make_pair(std::string(kOpenCLMemoryModel) + ShaderDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" - "OpDecorate %intt Index 0\n" - "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid), + "OpDecorate %var Index 0\n" + "%intt = OpTypeInt 32 0\n" + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), ShaderDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" - "OpDecorate %intt Binding 0\n" - "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid), + "OpDecorate %var Binding 0\n" + "%intt = OpTypeInt 32 0\n" + "%ptr = OpTypePointer Uniform %intt\n" + "%var = OpVariable %ptr Uniform\n" + std::string(kVoidFVoid), ShaderDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" - "OpDecorate %intt DescriptorSet 0\n" - "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid), + "OpDecorate %var DescriptorSet 0\n" + "%intt = OpTypeInt 32 0\n" + "%ptr = OpTypePointer Uniform %intt\n" + "%var = OpVariable %ptr Uniform\n" + std::string(kVoidFVoid), ShaderDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" - "OpDecorate %intt Offset 0\n" - "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid), + "OpMemberDecorate %structt 0 Offset 0\n" + "%intt = OpTypeInt 32 0\n" + "%structt = OpTypeStruct %intt\n" + std::string(kVoidFVoid), ShaderDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" - "OpDecorate %intt XfbBuffer 0\n" - "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid), + "OpDecorate %var XfbBuffer 0\n" + "%intt = OpTypeInt 32 0\n" + "%ptr = OpTypePointer Uniform %intt\n" + "%var = OpVariable %ptr Uniform\n" + std::string(kVoidFVoid), std::vector<std::string>{"TransformFeedback"}), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" - "OpDecorate %intt XfbStride 0\n" - "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid), + "OpDecorate %var XfbStride 0\n" + "%intt = OpTypeInt 32 0\n" + "%ptr = OpTypePointer Uniform %intt\n" + "%var = OpVariable %ptr Uniform\n" + std::string(kVoidFVoid), std::vector<std::string>{"TransformFeedback"}), std::make_pair(std::string(kGLSL450MemoryModel) + "OpEntryPoint Vertex %func \"shader\" \n" @@ -1410,8 +1461,10 @@ std::make_pair(std::string(kOpenCLMemoryModel) + ShaderDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" - "OpDecorate %intt InputAttachmentIndex 0\n" - "%intt = OpTypeInt 32 0\n" + std::string(kVoidFVoid), + "OpDecorate %var InputAttachmentIndex 0\n" + "%intt = OpTypeInt 32 0\n" + "%ptr = OpTypePointer UniformConstant %intt\n" + "%var = OpVariable %ptr UniformConstant\n" + std::string(kVoidFVoid), std::vector<std::string>{"InputAttachment"}), std::make_pair(std::string(kGLSL450MemoryModel) + "OpEntryPoint Vertex %func \"shader\" \n" @@ -1471,264 +1524,300 @@ INSTANTIATE_TEST_SUITE_P(BuiltIn, ValidateCapability, Values( std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn Position\n" + "OpDecorate %var BuiltIn Position\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::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 std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn PointSize\n" + "OpDecorate %var BuiltIn PointSize\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), AllCapabilities()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn ClipDistance\n" + "OpDecorate %var BuiltIn ClipDistance\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), AllCapabilities()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn CullDistance\n" + "OpDecorate %var BuiltIn CullDistance\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), AllCapabilities()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn VertexId\n" + "OpDecorate %var BuiltIn VertexId\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), ShaderDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn InstanceId\n" + "OpDecorate %var BuiltIn InstanceId\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), ShaderDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn PrimitiveId\n" + "OpDecorate %var BuiltIn PrimitiveId\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), GeometryTessellationDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn InvocationId\n" + "OpDecorate %var BuiltIn InvocationId\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), GeometryTessellationDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn Layer\n" + "OpDecorate %var BuiltIn Layer\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), GeometryDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn ViewportIndex\n" + "OpDecorate %var BuiltIn ViewportIndex\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), std::vector<std::string>{"MultiViewport"}), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn TessLevelOuter\n" + "OpDecorate %var BuiltIn TessLevelOuter\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), TessellationDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn TessLevelInner\n" + "OpDecorate %var BuiltIn TessLevelInner\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), TessellationDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn TessCoord\n" + "OpDecorate %var BuiltIn TessCoord\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), TessellationDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn PatchVertices\n" + "OpDecorate %var BuiltIn PatchVertices\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), TessellationDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn FragCoord\n" + "OpDecorate %var BuiltIn FragCoord\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), ShaderDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn PointCoord\n" + "OpDecorate %var BuiltIn PointCoord\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), ShaderDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn FrontFacing\n" + "OpDecorate %var BuiltIn FrontFacing\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), ShaderDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn SampleId\n" + "OpDecorate %var BuiltIn SampleId\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), std::vector<std::string>{"SampleRateShading"}), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn SamplePosition\n" + "OpDecorate %var BuiltIn SamplePosition\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), std::vector<std::string>{"SampleRateShading"}), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn SampleMask\n" + "OpDecorate %var BuiltIn SampleMask\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), ShaderDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn FragDepth\n" + "OpDecorate %var BuiltIn FragDepth\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), ShaderDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn HelperInvocation\n" + "OpDecorate %var BuiltIn HelperInvocation\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), ShaderDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn VertexIndex\n" + "OpDecorate %var BuiltIn VertexIndex\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), ShaderDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn InstanceIndex\n" + "OpDecorate %var BuiltIn InstanceIndex\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), ShaderDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn NumWorkgroups\n" + "OpDecorate %var BuiltIn NumWorkgroups\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), - AllCapabilities()), -std::make_pair(std::string(kOpenCLMemoryModel) + - "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn WorkgroupSize\n" - "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), AllCapabilities()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn WorkgroupId\n" + "OpDecorate %var BuiltIn WorkgroupId\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), AllCapabilities()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn LocalInvocationId\n" + "OpDecorate %var BuiltIn LocalInvocationId\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), AllCapabilities()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn GlobalInvocationId\n" + "OpDecorate %var BuiltIn GlobalInvocationId\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), AllCapabilities()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn LocalInvocationIndex\n" + "OpDecorate %var BuiltIn LocalInvocationIndex\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), AllCapabilities()), std::make_pair(std::string(kGLSL450MemoryModel) + "OpEntryPoint Vertex %func \"shader\" \n" + - "OpDecorate %int0 BuiltIn WorkDim\n" + "OpDecorate %var BuiltIn WorkDim\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), KernelDependencies()), std::make_pair(std::string(kGLSL450MemoryModel) + "OpEntryPoint Vertex %func \"shader\" \n" + - "OpDecorate %int0 BuiltIn GlobalSize\n" + "OpDecorate %var BuiltIn GlobalSize\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), KernelDependencies()), std::make_pair(std::string(kGLSL450MemoryModel) + "OpEntryPoint Vertex %func \"shader\" \n" + - "OpDecorate %int0 BuiltIn EnqueuedWorkgroupSize\n" + "OpDecorate %var BuiltIn EnqueuedWorkgroupSize\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), KernelDependencies()), std::make_pair(std::string(kGLSL450MemoryModel) + "OpEntryPoint Vertex %func \"shader\" \n" + - "OpDecorate %int0 BuiltIn GlobalOffset\n" + "OpDecorate %var BuiltIn GlobalOffset\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), KernelDependencies()), std::make_pair(std::string(kGLSL450MemoryModel) + "OpEntryPoint Vertex %func \"shader\" \n" + - "OpDecorate %int0 BuiltIn GlobalLinearId\n" + "OpDecorate %var BuiltIn GlobalLinearId\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), KernelDependencies()), std::make_pair(std::string(kGLSL450MemoryModel) + "OpEntryPoint Vertex %func \"shader\" \n" + - "OpDecorate %int0 BuiltIn SubgroupSize\n" + "OpDecorate %var BuiltIn SubgroupSize\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), KernelAndGroupNonUniformDependencies()), std::make_pair(std::string(kGLSL450MemoryModel) + "OpEntryPoint Vertex %func \"shader\" \n" + - "OpDecorate %int0 BuiltIn SubgroupMaxSize\n" + "OpDecorate %var BuiltIn SubgroupMaxSize\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), KernelDependencies()), std::make_pair(std::string(kGLSL450MemoryModel) + "OpEntryPoint Vertex %func \"shader\" \n" + - "OpDecorate %int0 BuiltIn NumSubgroups\n" + "OpDecorate %var BuiltIn NumSubgroups\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), KernelAndGroupNonUniformDependencies()), std::make_pair(std::string(kGLSL450MemoryModel) + "OpEntryPoint Vertex %func \"shader\" \n" + - "OpDecorate %int0 BuiltIn NumEnqueuedSubgroups\n" + "OpDecorate %var BuiltIn NumEnqueuedSubgroups\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), KernelDependencies()), std::make_pair(std::string(kGLSL450MemoryModel) + "OpEntryPoint Vertex %func \"shader\" \n" + - "OpDecorate %int0 BuiltIn SubgroupId\n" + "OpDecorate %var BuiltIn SubgroupId\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), KernelAndGroupNonUniformDependencies()), std::make_pair(std::string(kGLSL450MemoryModel) + "OpEntryPoint Vertex %func \"shader\" \n" + - "OpDecorate %int0 BuiltIn SubgroupLocalInvocationId\n" + "OpDecorate %var BuiltIn SubgroupLocalInvocationId\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), KernelAndGroupNonUniformDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn VertexIndex\n" + "OpDecorate %var BuiltIn VertexIndex\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), ShaderDependencies()), std::make_pair(std::string(kOpenCLMemoryModel) + "OpEntryPoint Kernel %func \"compute\" \n" + - "OpDecorate %int0 BuiltIn InstanceIndex\n" + "OpDecorate %var BuiltIn InstanceIndex\n" "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "%ptr = OpTypePointer Input %intt\n" + "%var = OpVariable %ptr Input\n" + std::string(kVoidFVoid), ShaderDependencies()) ))); @@ -1742,11 +1831,11 @@ INSTANTIATE_TEST_SUITE_P(BuiltIn, ValidateCapabilityVulkan10, ValuesIn(AllSpirV10Capabilities()), Values( std::make_pair(std::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" + std::string(kVoidFVoid), + "OpEntryPoint Vertex %func \"shader\" %var\n" + + "OpDecorate %var BuiltIn PointSize\n" + "%float = OpTypeFloat 32\n" + "%ptr_output_float = OpTypePointer Output %float\n" + "%var = OpVariable %ptr_output_float Output\n" + std::string(kVoidFVoid), // Capabilities which should succeed. AllVulkan10Capabilities()), std::make_pair(std::string(kGLSL450MemoryModel) + @@ -1775,22 +1864,31 @@ INSTANTIATE_TEST_SUITE_P(BuiltIn, ValidateCapabilityOpenGL40, ValuesIn(AllSpirV10Capabilities()), Values( std::make_pair(std::string(kGLSL450MemoryModel) + - "OpEntryPoint Vertex %func \"shader\" \n" + - "OpDecorate %int0 BuiltIn PointSize\n" - "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "OpEntryPoint Vertex %func \"shader\" %var\n" + + "OpDecorate %var BuiltIn PointSize\n" + "%float = OpTypeFloat 32\n" + "%ptr_output_float = OpTypePointer Output %float\n" + "%var = OpVariable %ptr_output_float Output\n" + std::string(kVoidFVoid), AllSpirV10Capabilities()), std::make_pair(std::string(kGLSL450MemoryModel) + - "OpEntryPoint Vertex %func \"shader\" \n" + - "OpDecorate %int0 BuiltIn ClipDistance\n" - "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "OpEntryPoint Vertex %func \"shader\" %var\n" + + "OpDecorate %var BuiltIn ClipDistance\n" + "%float = OpTypeFloat 32\n" + "%int = OpTypeInt 32 0\n" + "%int_1 = OpConstant %int 1\n" + "%array = OpTypeArray %float %int_1\n" + "%ptr = OpTypePointer Output %array\n" + "%var = OpVariable %ptr Output\n" + std::string(kVoidFVoid), AllSpirV10Capabilities()), std::make_pair(std::string(kGLSL450MemoryModel) + - "OpEntryPoint Vertex %func \"shader\" \n" + - "OpDecorate %int0 BuiltIn CullDistance\n" - "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "OpEntryPoint Vertex %func \"shader\" %var\n" + + "OpDecorate %var BuiltIn CullDistance\n" + "%float = OpTypeFloat 32\n" + "%int = OpTypeInt 32 0\n" + "%int_1 = OpConstant %int 1\n" + "%array = OpTypeArray %float %int_1\n" + "%ptr = OpTypePointer Output %array\n" + "%var = OpVariable %ptr Output\n" + std::string(kVoidFVoid), AllSpirV10Capabilities()) ))); @@ -1800,16 +1898,21 @@ INSTANTIATE_TEST_SUITE_P(Capabilities, ValidateCapabilityVulkan11, ValuesIn(AllCapabilities()), Values( std::make_pair(std::string(kGLSL450MemoryModel) + - "OpEntryPoint Vertex %func \"shader\" \n" + - "OpDecorate %int0 BuiltIn PointSize\n" - "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "OpEntryPoint Vertex %func \"shader\" %var\n" + + "OpDecorate %var BuiltIn PointSize\n" + "%float = OpTypeFloat 32\n" + "%ptr_output_float = OpTypePointer Output %float\n" + "%var = OpVariable %ptr_output_float Output\n" + std::string(kVoidFVoid), AllVulkan11Capabilities()), std::make_pair(std::string(kGLSL450MemoryModel) + - "OpEntryPoint Vertex %func \"shader\" \n" + - "OpDecorate %int0 BuiltIn CullDistance\n" - "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "OpEntryPoint Vertex %func \"shader\" %var\n" + + "OpDecorate %var BuiltIn CullDistance\n" + "%float = OpTypeFloat 32\n" + "%int = OpTypeInt 32 0\n" + "%int_1 = OpConstant %int 1\n" + "%array = OpTypeArray %float %int_1\n" + "%ptr = OpTypePointer Output %array\n" + "%var = OpVariable %ptr Output\n" + std::string(kVoidFVoid), AllVulkan11Capabilities()) ))); @@ -1819,16 +1922,21 @@ INSTANTIATE_TEST_SUITE_P(Capabilities, ValidateCapabilityVulkan12, ValuesIn(AllSpirV15Capabilities()), Values( std::make_pair(std::string(kGLSL450MemoryModel) + - "OpEntryPoint Vertex %func \"shader\" \n" + - "OpDecorate %int0 BuiltIn PointSize\n" - "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "OpEntryPoint Vertex %func \"shader\" %var\n" + + "OpDecorate %var BuiltIn PointSize\n" + "%float = OpTypeFloat 32\n" + "%ptr_output_float = OpTypePointer Output %float\n" + "%var = OpVariable %ptr_output_float Output\n" + std::string(kVoidFVoid), AllVulkan12Capabilities()), std::make_pair(std::string(kGLSL450MemoryModel) + - "OpEntryPoint Vertex %func \"shader\" \n" + - "OpDecorate %int0 BuiltIn CullDistance\n" - "%intt = OpTypeInt 32 0\n" - "%int0 = OpConstant %intt 0\n" + std::string(kVoidFVoid), + "OpEntryPoint Vertex %func \"shader\" %var\n" + + "OpDecorate %var BuiltIn CullDistance\n" + "%float = OpTypeFloat 32\n" + "%int = OpTypeInt 32 0\n" + "%int_1 = OpConstant %int 1\n" + "%array = OpTypeArray %float %int_1\n" + "%ptr = OpTypePointer Output %array\n" + "%var = OpVariable %ptr Output\n" + std::string(kVoidFVoid), AllVulkan12Capabilities()) ))); diff --git a/test/val/val_cfg_test.cpp b/test/val/val_cfg_test.cpp index 6ae2ee6d..311cfa77 100644 --- a/test/val/val_cfg_test.cpp +++ b/test/val/val_cfg_test.cpp @@ -4229,6 +4229,67 @@ OpFunctionEnd ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); } +TEST_F(ValidateCFG, StructuredSelections_RegisterBothTrueAndFalse) { + // In this test, we try to make a case where the false branches + // to %20 and %60 from blocks %10 and %50 must be registered + // during the validity check for sturctured selections. + // However, an error is caught earlier in the flow, that the + // branches from %100 to %20 and %60 violate dominance. + const std::string text = R"( + OpCapability Shader + OpMemoryModel Logical Simple + OpEntryPoint Fragment %main "main" + OpExecutionMode %main OriginUpperLeft + + %void = OpTypeVoid + %void_fn = OpTypeFunction %void + + %bool = OpTypeBool + %cond = OpUndef %bool + + %main = OpFunction %void None %void_fn + + %1 = OpLabel + OpSelectionMerge %999 None + OpBranchConditional %cond %10 %100 + + %10 = OpLabel + OpSelectionMerge %30 None ; force registration of %30 + OpBranchConditional %cond %30 %20 ; %20 should be registered too + + %20 = OpLabel + OpBranch %30 + + %30 = OpLabel ; merge for first if + OpBranch %50 + + + %50 = OpLabel + OpSelectionMerge %70 None ; force registration of %70 + OpBranchConditional %cond %70 %60 ; %60 should be registered + + %60 = OpLabel + OpBranch %70 + + %70 = OpLabel ; merge for second if + OpBranch %999 + + %100 = OpLabel + OpBranchConditional %cond %20 %60 ; should require a merge + + %999 = OpLabel + OpReturn + + OpFunctionEnd +)"; + + CompileSuccessfully(text); + EXPECT_NE(SPV_SUCCESS, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("The selection construct with the selection header " + "8[%8] does not dominate the merge block 10[%10]\n")); +} + TEST_F(ValidateCFG, UnreachableIsStaticallyReachable) { const std::string text = R"( OpCapability Shader @@ -4399,6 +4460,125 @@ OpFunctionEnd "1[%BAD], but not via a structured exit")); } +TEST_F(ValidateCFG, SwitchSelectorNotAnInt) { + const std::string spirv = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +%void = OpTypeVoid +%float = OpTypeFloat 32 +%float_1 = OpConstant %float 1 +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpSelectionMerge %default None +OpSwitch %float_1 %default +%default = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Selector type must be OpTypeInt")); +} + +TEST_F(ValidateCFG, SwitchDefaultNotALabel) { + const std::string spirv = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_1 = OpConstant %int 1 +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpSelectionMerge %default None +OpSwitch %int_1 %int_1 +%default = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Default must be an OpLabel instruction")); +} + +TEST_F(ValidateCFG, BlockDepthRecursion) { + const std::string text = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +%void = OpTypeVoid +%bool = OpTypeBool +%undef = OpUndef %bool +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%1 = OpLabel +OpBranch %2 +%2 = OpLabel +OpLoopMerge %3 %4 None +OpBranchConditional %undef %3 %4 +%4 = OpLabel +OpBranch %2 +%3 = OpLabel +OpBranch %5 +%5 = OpLabel +OpSelectionMerge %2 None +OpBranchConditional %undef %6 %7 +%6 = OpLabel +OpReturn +%7 = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); +} + +TEST_F(ValidateCFG, BadStructuredExitBackwardsMerge) { + const std::string spirv = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +%void = OpTypeVoid +%bool = OpTypeBool +%undef = OpUndef %bool +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%1 = OpLabel +OpBranch %2 +%2 = OpLabel +OpLoopMerge %4 %5 None +OpBranchConditional %undef %4 %6 +%6 = OpLabel +OpSelectionMerge %7 None +OpBranchConditional %undef %8 %9 +%7 = OpLabel +OpReturn +%8 = OpLabel +OpBranch %5 +%9 = OpLabel +OpSelectionMerge %6 None +OpBranchConditional %undef %5 %5 +%5 = OpLabel +OpBranch %2 +%4 = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions()); +} + } // namespace } // namespace val } // namespace spvtools diff --git a/test/val/val_decoration_test.cpp b/test/val/val_decoration_test.cpp index b9a413e3..f2953edc 100644 --- a/test/val/val_decoration_test.cpp +++ b/test/val/val_decoration_test.cpp @@ -687,8 +687,7 @@ TEST_F(ValidateDecorations, BlockDecoratingArrayBad) { CompileSuccessfully(spirv); EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState()); - EXPECT_THAT(getDiagnosticString(), - HasSubstr("Block decoration on a non-struct type")); + EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a structure type")); } TEST_F(ValidateDecorations, BlockDecoratingIntBad) { @@ -713,8 +712,7 @@ TEST_F(ValidateDecorations, BlockDecoratingIntBad) { CompileSuccessfully(spirv); EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState()); - EXPECT_THAT(getDiagnosticString(), - HasSubstr("Block decoration on a non-struct type")); + EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a structure type")); } TEST_F(ValidateDecorations, BlockMissingOffsetBad) { @@ -6129,9 +6127,7 @@ TEST_F(ValidateDecorations, NonWritableLabelTargetBad) { CompileSuccessfully(spirv); EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); EXPECT_THAT(getDiagnosticString(), - HasSubstr("Target of NonWritable decoration must be a " - "memory object declaration (a variable or a function " - "parameter)\n %label = OpLabel")); + HasSubstr("must be a memory object declaration")); } TEST_F(ValidateDecorations, NonWritableTypeTargetBad) { @@ -6140,9 +6136,7 @@ TEST_F(ValidateDecorations, NonWritableTypeTargetBad) { CompileSuccessfully(spirv); EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); EXPECT_THAT(getDiagnosticString(), - HasSubstr("Target of NonWritable decoration must be a " - "memory object declaration (a variable or a function " - "parameter)\n %void = OpTypeVoid")); + HasSubstr("must be a memory object declaration")); } TEST_F(ValidateDecorations, NonWritableValueTargetBad) { @@ -6151,9 +6145,7 @@ TEST_F(ValidateDecorations, NonWritableValueTargetBad) { CompileSuccessfully(spirv); EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); EXPECT_THAT(getDiagnosticString(), - HasSubstr("Target of NonWritable decoration must be a " - "memory object declaration (a variable or a function " - "parameter)\n %float_0 = OpConstant %float 0")); + HasSubstr("must be a memory object declaration")); } TEST_F(ValidateDecorations, NonWritableValueParamBad) { @@ -6161,10 +6153,7 @@ TEST_F(ValidateDecorations, NonWritableValueParamBad) { CompileSuccessfully(spirv); EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); - EXPECT_THAT(getDiagnosticString(), - HasSubstr("Target of NonWritable decoration is invalid: must " - "point to a storage image, uniform block, or storage " - "buffer\n %param_f = OpFunctionParameter %float")); + EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a pointer type")); } TEST_F(ValidateDecorations, NonWritablePointerParamButWrongTypeBad) { @@ -6467,8 +6456,7 @@ OpFunctionEnd CompileSuccessfully(spirv); EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState()); EXPECT_THAT(getDiagnosticString(), - HasSubstr("Target of Component decoration must be " - "a memory object declaration")); + HasSubstr("must be a memory object declaration")); } TEST_F(ValidateDecorations, ComponentDecorationBadStorageClass) { @@ -6767,8 +6755,8 @@ TEST_F(ValidateDecorations, ComponentDecorationFunctionParameter) { )"; CompileSuccessfully(spirv); - EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState()); - EXPECT_THAT(getDiagnosticString(), Eq("")); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState()); + EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a pointer type")); } TEST_F(ValidateDecorations, VulkanStorageBufferBlock) { @@ -7164,9 +7152,7 @@ OpDecorate %struct Location 0 CompileSuccessfully(spirv); EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); - EXPECT_THAT(getDiagnosticString(), - HasSubstr("Location decoration can only be applied to a variable " - "or member of a structure type")); + EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a variable")); } TEST_F(ValidateDecorations, LocationFloatBad) { @@ -7180,9 +7166,7 @@ OpDecorate %float Location 0 CompileSuccessfully(spirv); EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); - EXPECT_THAT(getDiagnosticString(), - HasSubstr("Location decoration can only be applied to a variable " - "or member of a structure type")); + EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a variable")); } TEST_F(ValidateDecorations, WorkgroupSingleBlockVariable) { @@ -7571,9 +7555,7 @@ TEST_F(ValidateDecorations, WorkgroupSingleBlockVariableNotAStruct) { CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4); EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState(SPV_ENV_UNIVERSAL_1_4)); - EXPECT_THAT( - getDiagnosticString(), - HasSubstr("Block decoration on a non-struct type")); + EXPECT_THAT(getDiagnosticString(), HasSubstr("must be a structure type")); } TEST_F(ValidateDecorations, WorkgroupSingleBlockVariableMissingLayout) { @@ -7791,6 +7773,70 @@ OpFunctionEnd "member 0 is a matrix with stride 3 not satisfying alignment to 4")); } +TEST_F(ValidateDecorations, MissingOffsetStructNestedInArray) { + const std::string spirv = R"( +OpCapability Shader +OpExtension "SPV_KHR_storage_buffer_storage_class" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %array ArrayStride 4 +OpDecorate %outer Block +OpMemberDecorate %outer 0 Offset 0 +OpDecorate %var DescriptorSet 0 +OpDecorate %var Binding 0 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_4 = OpConstant %int 4 +%inner = OpTypeStruct %int +%array = OpTypeArray %inner %int_4 +%outer = OpTypeStruct %array +%ptr_ssbo_outer = OpTypePointer StorageBuffer %outer +%var = OpVariable %ptr_ssbo_outer StorageBuffer +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Structure id 3 decorated as Block must be explicitly " + "laid out with Offset decorations")); +} + +TEST_F(ValidateDecorations, AllOnesOffset) { + const std::string spirv = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpDecorate %var DescriptorSet 0 +OpDecorate %var Binding 0 +OpDecorate %outer Block +OpMemberDecorate %outer 0 Offset 0 +OpMemberDecorate %struct 0 Offset 4294967295 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%struct = OpTypeStruct %int +%outer = OpTypeStruct %struct +%ptr = OpTypePointer Uniform %outer +%var = OpVariable %ptr Uniform +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("decorated as Block must be explicitly laid out with " + "Offset decorations")); +} + } // namespace } // namespace val } // namespace spvtools diff --git a/test/val/val_derivatives_test.cpp b/test/val/val_derivatives_test.cpp index 606abb93..0a846610 100644 --- a/test/val/val_derivatives_test.cpp +++ b/test/val/val_derivatives_test.cpp @@ -160,6 +160,31 @@ TEST_F(ValidateDerivatives, OpDPdxWrongExecutionModel) { "execution model: DPdx")); } +TEST_F(ValidateDerivatives, NoExecutionModeGLCompute) { + const std::string spirv = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +%void = OpTypeVoid +%float = OpTypeFloat 32 +%float4 = OpTypeVector %float 4 +%undef = OpUndef %float4 +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%derivative = OpDPdy %float4 %undef +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Derivative instructions require " + "DerivativeGroupQuadsNV or DerivativeGroupLinearNV " + "execution mode for GLCompute execution model")); +} + using ValidateHalfDerivatives = spvtest::ValidateBase<std::string>; TEST_P(ValidateHalfDerivatives, ScalarFailure) { diff --git a/test/val/val_ext_inst_debug_test.cpp b/test/val/val_ext_inst_debug_test.cpp index d3b9f038..307a8009 100644 --- a/test/val/val_ext_inst_debug_test.cpp +++ b/test/val/val_ext_inst_debug_test.cpp @@ -354,7 +354,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugSourceInFunction) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, "", "", dbg_inst, @@ -436,7 +436,7 @@ TEST_P(ValidateLocalDebugInfoOutOfFunction, VulkanDebugInfo100DebugScope) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; const std::string constants = R"( @@ -738,7 +738,7 @@ INSTANTIATE_TEST_SUITE_P(OpenCLAndVkDebugInfo100, ValidateXDebugInfo, )", R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )", })); @@ -798,7 +798,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugCompilationUnitFail) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; const std::string constants = R"( @@ -872,7 +872,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugTypeBasicFailName) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -941,7 +941,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugTypeBasicFailSize) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -1107,7 +1107,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugTypeQualifier) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -1141,7 +1141,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugTypeQualifierFail) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -1398,7 +1398,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugTypeArray) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -1436,7 +1436,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugTypeArrayWithVariableSize) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -1466,7 +1466,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugTypeArrayFailBaseType) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -1499,7 +1499,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugTypeArrayFailComponentCount) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -1534,7 +1534,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugTypeArrayFailComponentCountFloat) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -1569,7 +1569,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugTypeArrayFailComponentCountZero) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -1610,7 +1610,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugTypeArrayFailVariableSizeTypeFloat) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -1762,7 +1762,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugTypeVector) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -1792,7 +1792,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugTypeVectorFail) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -1825,7 +1825,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugTypeVectorFailComponentZero) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -1858,7 +1858,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugTypeVectorFailComponentFive) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -1967,7 +1967,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugTypedef) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -2001,7 +2001,7 @@ TEST_P(ValidateVulkan100DebugInfoDebugTypedef, Fail) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, constants, ss.str(), @@ -2152,7 +2152,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugTypeFunctionAndParams) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -2184,7 +2184,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugTypeFunctionFailReturn) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -2219,7 +2219,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugTypeFunctionFailParam) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -2349,7 +2349,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugTypeEnum) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -2383,7 +2383,7 @@ TEST_P(ValidateVulkan100DebugInfoDebugTypeEnum, Fail) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, constants, ss.str(), @@ -2705,7 +2705,7 @@ main() {} const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -2753,7 +2753,7 @@ main() {} const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, constants, ss.str(), @@ -2822,7 +2822,7 @@ main() {} const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, constants, ss.str(), @@ -3029,7 +3029,7 @@ main() {} const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -3069,7 +3069,7 @@ main() {} const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, constants, ss.str(), @@ -3133,7 +3133,7 @@ main() {} const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, constants, ss.str(), @@ -3290,7 +3290,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugLexicalBlock) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -3321,7 +3321,7 @@ TEST_P(ValidateVulkan100DebugInfoDebugLexicalBlock, Fail) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, constants, ss.str(), @@ -3363,7 +3363,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugScopeFailScope) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -3394,7 +3394,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugScopeFailInlinedAt) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -3505,7 +3505,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugLocalVariable) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -3540,7 +3540,7 @@ TEST_P(ValidateVulkan100DebugInfoDebugLocalVariable, Fail) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, constants, ss.str(), @@ -3736,7 +3736,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugDeclare) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; const std::string body = R"( @@ -3753,7 +3753,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugDeclareParam) { CompileSuccessfully(R"( OpCapability Shader OpExtension "SPV_KHR_non_semantic_info" - %1 = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" + %1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" OpMemoryModel Logical GLSL450 OpEntryPoint Vertex %main "main" %in_var_COLOR %4 = OpString "test.hlsl" @@ -3843,7 +3843,7 @@ TEST_P(ValidateVulkan100DebugInfoDebugDeclare, Fail) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; const auto& param = GetParam(); @@ -3913,7 +3913,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugExpression) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo("", "", dbg_inst_header, @@ -3929,7 +3929,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugExpressionFail) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo("", "", dbg_inst_header, @@ -4159,7 +4159,7 @@ main() {} const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -4198,7 +4198,7 @@ main() {} const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -4237,7 +4237,7 @@ main() {} const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -4274,7 +4274,7 @@ main() {} const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -4315,7 +4315,7 @@ main() {} const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -4522,7 +4522,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugGlobalVariable) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -4555,7 +4555,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugGlobalVariableStaticMember) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -4587,7 +4587,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugGlobalVariableDebugInfoNone) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -4618,7 +4618,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugGlobalVariableConst) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo( @@ -4652,7 +4652,7 @@ TEST_P(ValidateVulkan100DebugInfoDebugGlobalVariable, Fail) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; CompileSuccessfully(GenerateShaderCodeForDebugInfo(src, constants, ss.str(), @@ -4806,7 +4806,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugInlinedAt) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; const std::string body = R"( @@ -4844,7 +4844,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugInlinedAtFail) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; const std::string body = R"( @@ -4883,7 +4883,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugInlinedAtFail2) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; const std::string body = R"( @@ -5042,7 +5042,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugValue) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; const std::string body = R"( @@ -5084,7 +5084,7 @@ TEST_F(ValidateVulkan100DebugInfo, DebugValueWithVariableIndex) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; const std::string body = R"( @@ -5122,7 +5122,7 @@ TEST_P(ValidateVulkan100DebugInfoDebugValue, Fail) { const std::string extension = R"( OpExtension "SPV_KHR_non_semantic_info" -%DbgExt = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" +%DbgExt = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" )"; const auto& param = GetParam(); @@ -5153,7 +5153,7 @@ TEST_F(ValidateVulkan100DebugInfo, VulkanDebugInfoSample) { ss << R"( OpCapability Shader OpExtension "SPV_KHR_non_semantic_info" - %id_1 = OpExtInstImport "NonSemantic.Vulkan.DebugInfo.100" + %id_1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %id_MainPs "MainPs" %id_in_var_TEXCOORD2 %id_out_var_SV_Target0 OpExecutionMode %id_MainPs OriginUpperLeft diff --git a/test/val/val_ext_inst_test.cpp b/test/val/val_ext_inst_test.cpp index b014ad62..2b6df04d 100644 --- a/test/val/val_ext_inst_test.cpp +++ b/test/val/val_ext_inst_test.cpp @@ -1580,6 +1580,19 @@ TEST_F(ValidateExtInst, GlslStd450LdexpExpWrongSize) { "number as Result Type")); } +TEST_F(ValidateExtInst, GlslStd450LdexpExpNoType) { + const std::string body = R"( +%val1 = OpExtInst %f32 %extinst Ldexp %f32_1 %main_entry +)"; + + 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, GlslStd450FrexpStructSuccess) { const std::string body = R"( %val1 = OpExtInst %struct_f32_u32 %extinst FrexpStruct %f32_h diff --git a/test/val/val_id_test.cpp b/test/val/val_id_test.cpp index dd4c952c..ac057493 100644 --- a/test/val/val_id_test.cpp +++ b/test/val/val_id_test.cpp @@ -5524,9 +5524,9 @@ OpFunctionEnd CompileSuccessfully(spirv.c_str()); EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); EXPECT_THAT(getDiagnosticString(), - HasSubstr("OpDecorate SpecId decoration target <id> " - "'1[%uint_3]' is not a scalar specialization " - "constant.")); + HasSubstr("SpecId decoration on target <id> " + "'1[%uint_3]' must be a scalar specialization " + "constant")); } TEST_F(ValidateIdWithMessage, SpecIdTargetOpSpecConstantOpBad) { @@ -5546,8 +5546,8 @@ OpFunctionEnd CompileSuccessfully(spirv.c_str()); EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); EXPECT_THAT(getDiagnosticString(), - HasSubstr("OpDecorate SpecId decoration target <id> '1[%1]' is " - "not a scalar specialization constant.")); + HasSubstr("SpecId decoration on target <id> '1[%1]' " + "must be a scalar specialization constant")); } TEST_F(ValidateIdWithMessage, SpecIdTargetOpSpecConstantCompositeBad) { @@ -5566,8 +5566,8 @@ OpFunctionEnd CompileSuccessfully(spirv.c_str()); EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); EXPECT_THAT(getDiagnosticString(), - HasSubstr("OpDecorate SpecId decoration target <id> '1[%1]' is " - "not a scalar specialization constant.")); + HasSubstr("SpecId decoration on target <id> '1[%1]' " + "must be a scalar specialization constant")); } TEST_F(ValidateIdWithMessage, SpecIdTargetGood) { diff --git a/test/val/val_image_test.cpp b/test/val/val_image_test.cpp index 701e35e1..11b14fb0 100644 --- a/test/val/val_image_test.cpp +++ b/test/val/val_image_test.cpp @@ -1815,9 +1815,10 @@ TEST_F(ValidateImage, SampleImplicitLodMoreThanOneOffset) { 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")); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Image Operands Offset, ConstOffset, ConstOffsets, Offsets " + "cannot be used together")); } TEST_F(ValidateImage, SampleImplicitLodVulkanMoreThanOneOffset) { @@ -1833,9 +1834,10 @@ TEST_F(ValidateImage, SampleImplicitLodVulkanMoreThanOneOffset) { ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); EXPECT_THAT(getDiagnosticString(), AnyVUID("VUID-StandaloneSpirv-Offset-04662")); - EXPECT_THAT(getDiagnosticString(), - HasSubstr("Image Operands Offset, ConstOffset, ConstOffsets " - "cannot be used together")); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Image Operands Offset, ConstOffset, ConstOffsets, Offsets " + "cannot be used together")); } TEST_F(ValidateImage, SampleImplicitLodMinLodWrongType) { @@ -6061,6 +6063,42 @@ TEST_F(ValidateImage, ImageTexelPointerRgba16fVulkan) { "R32f, R32i, or R32ui for Vulkan environment")); } +TEST_F(ValidateImage, ImageExecutionModeLimitationNoMode) { + const std::string text = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %2 " " %4 +%void = OpTypeVoid +%8 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%12 = OpTypeImage %float 2D 0 0 0 1 Rgba8ui +%13 = OpTypeSampledImage %12 +%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13 +%5 = OpVariable %_ptr_UniformConstant_13 UniformConstant +%_ptr_Input_v4float = OpTypePointer Input %v4float +%4 = OpVariable %_ptr_Input_v4float Input +%v2float = OpTypeVector %float 2 +%float_1_35631564en19 = OpConstant %float 1.35631564e-19 +%2 = OpFunction %void None %8 +%8224 = OpLabel +%6 = OpLoad %13 %5 +%19 = OpLoad %v4float %4 +%20 = OpVectorShuffle %v2float %19 %19 0 1 +%21 = OpVectorTimesScalar %v2float %20 %float_1_35631564en19 +%65312 = OpImageSampleImplicitLod %v4float %6 %21 +OpUnreachable +OpFunctionEnd +)"; + + CompileSuccessfully(text); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ImplicitLod instructions require " + "DerivativeGroupQuadsNV or DerivativeGroupLinearNV " + "execution mode for GLCompute execution model")); +} + } // namespace } // namespace val } // namespace spvtools diff --git a/test/val/val_interfaces_test.cpp b/test/val/val_interfaces_test.cpp index be13cd71..a01fc19b 100644 --- a/test/val/val_interfaces_test.cpp +++ b/test/val/val_interfaces_test.cpp @@ -1267,10 +1267,9 @@ OpFunctionEnd )"; CompileSuccessfully(text, SPV_ENV_VULKAN_1_0); - EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); - EXPECT_THAT( - getDiagnosticString(), - HasSubstr("Index can only be applied to Fragment output variables")); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("must be in the Output storage class")); } TEST_F(ValidateInterfacesTest, VulkanLocationsArrayWithComponent) { @@ -1410,6 +1409,68 @@ OpFunctionEnd EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2)); } +TEST_F(ValidateInterfacesTest, VulkanLocationArrayWithComponent1) { + const std::string text = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %in +OpExecutionMode %main OriginUpperLeft +OpDecorate %struct Block +OpMemberDecorate %struct 0 Location 0 +OpMemberDecorate %struct 0 Component 0 +OpMemberDecorate %struct 1 Location 0 +OpMemberDecorate %struct 1 Component 1 +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%float = OpTypeFloat 32 +%int = OpTypeInt 32 0 +%int_2 = OpConstant %int 2 +%float_arr = OpTypeArray %float %int_2 +%struct = OpTypeStruct %float_arr %float_arr +%ptr = OpTypePointer Input %struct +%in = OpVariable %ptr Input +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(text, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); +} + +TEST_F(ValidateInterfacesTest, VulkanLocationArrayWithComponent2) { + const std::string text = R"( +OpCapability Shader +OpCapability Float64 +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %in +OpExecutionMode %main OriginUpperLeft +OpDecorate %struct Block +OpMemberDecorate %struct 0 Location 0 +OpMemberDecorate %struct 0 Component 0 +OpMemberDecorate %struct 1 Location 0 +OpMemberDecorate %struct 1 Component 1 +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%float = OpTypeFloat 32 +%double = OpTypeFloat 64 +%int = OpTypeInt 32 0 +%int_2 = OpConstant %int 2 +%double_arr = OpTypeArray %double %int_2 +%struct = OpTypeStruct %float %double_arr +%ptr = OpTypePointer Input %struct +%in = OpVariable %ptr Input +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(text, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); +} + TEST_F(ValidateInterfacesTest, DuplicateInterfaceVariableSuccess) { const std::string text = R"( OpCapability Shader diff --git a/test/val/val_layout_test.cpp b/test/val/val_layout_test.cpp index d34c97f9..7ebd7c00 100644 --- a/test/val/val_layout_test.cpp +++ b/test/val/val_layout_test.cpp @@ -538,7 +538,6 @@ TEST_F(ValidateLayout, ModuleProcessedInvalidIn10) { 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 )"; @@ -558,7 +557,6 @@ TEST_F(ValidateLayout, ModuleProcessedValidIn11) { 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 )"; diff --git a/test/val/val_memory_test.cpp b/test/val/val_memory_test.cpp index 616a88f6..2a884c4d 100644 --- a/test/val/val_memory_test.cpp +++ b/test/val/val_memory_test.cpp @@ -2344,11 +2344,12 @@ OpExtension "SPV_EXT_descriptor_indexing" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %func "func" OpExecutionMode %func OriginUpperLeft -OpDecorate %array_t Block +OpDecorate %struct Block %uint_t = OpTypeInt 32 0 %inner_array_t = OpTypeRuntimeArray %uint_t %array_t = OpTypeRuntimeArray %inner_array_t -%array_ptr = OpTypePointer StorageBuffer %array_t +%struct = OpTypeStruct %array_t +%array_ptr = OpTypePointer StorageBuffer %struct %2 = OpVariable %array_ptr StorageBuffer %void = OpTypeVoid %func_t = OpTypeFunction %void @@ -2504,13 +2505,14 @@ OpExtension "SPV_EXT_descriptor_indexing" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %func "func" OpExecutionMode %func OriginUpperLeft -OpDecorate %array_t Block +OpDecorate %struct Block %uint_t = OpTypeInt 32 0 %dim = OpConstant %uint_t 1 %sampler_t = OpTypeSampler %inner_array_t = OpTypeRuntimeArray %uint_t %array_t = OpTypeRuntimeArray %inner_array_t -%array_ptr = OpTypePointer StorageBuffer %array_t +%struct = OpTypeStruct %array_t +%array_ptr = OpTypePointer StorageBuffer %struct %2 = OpVariable %array_ptr StorageBuffer %void = OpTypeVoid %func_t = OpTypeFunction %void @@ -4372,6 +4374,29 @@ OpFunctionEnd HasSubstr("Cannot load a runtime-sized array")); } +TEST_F(ValidateMemory, Pre1p4WorkgroupMemoryBadLayoutOk) { + const std::string spirv = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpDecorate %struct Block +OpMemberDecorate %struct 0 Offset 0 +%void = OpTypeVoid +%bool = OpTypeBool +%struct = OpTypeStruct %bool +%ptr = OpTypePointer Workgroup %struct +%var = OpVariable %ptr Workgroup +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + } // namespace } // namespace val } // namespace spvtools diff --git a/test/val/val_modes_test.cpp b/test/val/val_modes_test.cpp index d060bb79..02a61327 100644 --- a/test/val/val_modes_test.cpp +++ b/test/val/val_modes_test.cpp @@ -63,12 +63,13 @@ OpEntryPoint GLCompute %main "main" CompileSuccessfully(spirv, env); EXPECT_THAT(SPV_ERROR_INVALID_DATA, ValidateInstructions(env)); EXPECT_THAT(getDiagnosticString(), - AnyVUID("VUID-StandaloneSpirv-LocalSize-04683")); + AnyVUID("VUID-StandaloneSpirv-LocalSize-06426")); EXPECT_THAT( getDiagnosticString(), - HasSubstr("In the Vulkan environment, GLCompute execution model entry " - "points require either the LocalSize execution mode or an " - "object decorated with WorkgroupSize must be specified.")); + HasSubstr( + "In the Vulkan environment, GLCompute execution model entry " + "points require either the LocalSize or LocalSizeId execution mode " + "or an object decorated with WorkgroupSize must be specified.")); } TEST_F(ValidateMode, GLComputeNoModeVulkanWorkgroupSize) { @@ -101,6 +102,40 @@ OpExecutionMode %main LocalSize 1 1 1 EXPECT_THAT(SPV_SUCCESS, ValidateInstructions(env)); } +TEST_F(ValidateMode, GLComputeVulkanLocalSizeIdBad) { + const std::string spirv = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionModeId %main LocalSizeId %int_1 %int_1 %int_1 +%int = OpTypeInt 32 0 +%int_1 = OpConstant %int 1 +)" + kVoidFunction; + + spv_target_env env = SPV_ENV_VULKAN_1_1; // need SPIR-V 1.2 + CompileSuccessfully(spirv, env); + EXPECT_THAT(SPV_ERROR_INVALID_DATA, ValidateInstructions(env)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("LocalSizeId mode is not allowed by the current environment.")); +} + +TEST_F(ValidateMode, GLComputeVulkanLocalSizeIdGood) { + const std::string spirv = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionModeId %main LocalSizeId %int_1 %int_1 %int_1 +%int = OpTypeInt 32 0 +%int_1 = OpConstant %int 1 +)" + kVoidFunction; + + spv_target_env env = SPV_ENV_VULKAN_1_1; // need SPIR-V 1.2 + CompileSuccessfully(spirv, env); + spvValidatorOptionsSetAllowLocalSizeId(getValidatorOptions(), true); + EXPECT_THAT(SPV_SUCCESS, ValidateInstructions(env)); +} + TEST_F(ValidateMode, FragmentOriginLowerLeftVulkan) { const std::string spirv = R"( OpCapability Shader diff --git a/test/wasm/test.js b/test/wasm/test.js new file mode 100644 index 00000000..7f0d8f3c --- /dev/null +++ b/test/wasm/test.js @@ -0,0 +1,64 @@ +// Copyright (c) 2020 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. + +const spirvTools = require("../../out/web/spirv-tools"); +const fs = require("fs"); +const util = require("util"); +const readFile = util.promisify(fs.readFile); + +const SPV_PATH = "./test/fuzzers/corpora/spv/simple.spv"; + +const test = async () => { + const spv = await spirvTools(); + + // disassemble from file + const buffer = await readFile(SPV_PATH); + const disFileResult = spv.dis( + buffer, + spv.SPV_ENV_UNIVERSAL_1_3, + spv.SPV_BINARY_TO_TEXT_OPTION_INDENT | + spv.SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES | + spv.SPV_BINARY_TO_TEXT_OPTION_COLOR + ); + console.log("dis from file:\n", disFileResult); + + // assemble + const source = ` + OpCapability Linkage + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpSource GLSL 450 + OpDecorate %spec SpecId 1 + %int = OpTypeInt 32 1 + %spec = OpSpecConstant %int 0 + %const = OpConstant %int 42`; + const asResult = spv.as( + source, + spv.SPV_ENV_UNIVERSAL_1_3, + spv.SPV_TEXT_TO_BINARY_OPTION_NONE + ); + console.log(`as returned ${asResult.byteLength} bytes`); + + // re-disassemble + const disResult = spv.dis( + asResult, + spv.SPV_ENV_UNIVERSAL_1_3, + spv.SPV_BINARY_TO_TEXT_OPTION_INDENT | + spv.SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES | + spv.SPV_BINARY_TO_TEXT_OPTION_COLOR + ); + console.log("dis:\n", disResult); +}; + +test(); diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp index 28770cb9..04f81b8c 100644 --- a/tools/opt/opt.cpp +++ b/tools/opt/opt.cpp @@ -163,6 +163,11 @@ Options (in lexicographical order):)", around known issues with some Vulkan drivers for initialize variables.)"); printf(R"( + --replace-desc-array-access-using-var-index + Replaces accesses to descriptor arrays based on a variable index + with a switch that has a case for every possible value of the + index.)"); + printf(R"( --descriptor-scalar-replacement Replaces every array variable |desc| that has a DescriptorSet and Binding decorations with a new variable for each element of @@ -387,9 +392,12 @@ Options (in lexicographical order):)", Change the scope of private variables that are used in a single function to that function.)"); printf(R"( - --reduce-load-size + --reduce-load-size[=<threshold>] Replaces loads of composite objects where not every component is - used by loads of just the elements that are used.)"); + used by loads of just the elements that are used. If the ratio + of the used components of the load is less than the <threshold>, + we replace the load. <threshold> is a double type number. If + it is bigger than 1.0, we always replaces the load.)"); printf(R"( --redundancy-elimination Looks for instructions in the same function that compute the diff --git a/tools/val/val.cpp b/tools/val/val.cpp index 21a7d8f4..55321dab 100644 --- a/tools/val/val.cpp +++ b/tools/val/val.cpp @@ -64,6 +64,8 @@ Options: --relax-struct-store Allow store from one struct type to a different type with compatible layout and members. + --allow-localsizeid Allow use of the LocalSizeId decoration where it would otherwise not + be allowed by the target environment. --before-hlsl-legalization Allows code patterns that are intended to be fixed by spirv-opt's legalization passes. --version Display validator version information. @@ -153,6 +155,8 @@ int main(int argc, char** argv) { options.SetWorkgroupScalarBlockLayout(true); } else if (0 == strcmp(cur_arg, "--skip-block-layout")) { options.SetSkipBlockLayout(true); + } else if (0 == strcmp(cur_arg, "--allow-localsizeid")) { + options.SetAllowLocalSizeId(true); } else if (0 == strcmp(cur_arg, "--relax-struct-store")) { options.SetRelaxStructStore(true); } else if (0 == cur_arg[1]) { diff --git a/utils/roll_deps.sh b/utils/roll_deps.sh index 7ecfdd3b..f61f2a31 100755 --- a/utils/roll_deps.sh +++ b/utils/roll_deps.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Copyright (c) 2019 Google Inc. +# Copyright (c) 2021 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,7 +15,10 @@ # Attempts to roll all entries in DEPS to tip-of-tree and create a commit. # -# Depends on roll-dep from depot_path being in PATH. +# Depends on roll-dep from depot_tools +# (https://chromium.googlesource.com/chromium/tools/depot_tools) being in PATH. + +set -eo pipefail effcee_dir="external/effcee/" effcee_trunk="origin/main" @@ -44,3 +47,4 @@ roll-dep --ignore-dirty-tree --roll-to="${re2_trunk}" "${re2_dir}" roll-dep --ignore-dirty-tree --roll-to="${spirv_headers_trunk}" "${spirv_headers_dir}" git rebase --interactive "${old_head}" + |